diff --git a/README.md b/README.md index e0cb9542d..7726197e9 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,10 @@ to install Wasp on OSX/Linux/WSL(Win). From there, just follow the instructions For more details, check out [the docs](https://wasp-lang.dev/docs). +# Wasp AI / Mage + +Wasp comes with experimental AI code generator to help you kickstart your next Wasp project -> you can use it via `wasp new` in the CLI (choose "AI" option) if you can provide your OpenAI keys or you can do it via our [Mage web app](https://usemage.ai) in which case our OpenAI keys are used in the background. + # This repository This is the main repo of the Wasp universe, containing core code (mostly `waspc` - Wasp compiler) and the supporting materials. diff --git a/wasp-ai/.gitignore b/mage/.gitignore similarity index 91% rename from wasp-ai/.gitignore rename to mage/.gitignore index ad2da72f4..be170ea39 100644 --- a/wasp-ai/.gitignore +++ b/mage/.gitignore @@ -9,3 +9,6 @@ # or modify/delete these two lines. *.env *.env.* + +# Emacs specific +.projectile \ No newline at end of file diff --git a/wasp-ai/.prettierrc b/mage/.prettierrc similarity index 100% rename from wasp-ai/.prettierrc rename to mage/.prettierrc diff --git a/wasp-ai/.wasproot b/mage/.wasproot similarity index 100% rename from wasp-ai/.wasproot rename to mage/.wasproot diff --git a/wasp-ai/Dockerfile b/mage/Dockerfile similarity index 99% rename from wasp-ai/Dockerfile rename to mage/Dockerfile index a86db7263..249d602f8 100644 --- a/wasp-ai/Dockerfile +++ b/mage/Dockerfile @@ -33,7 +33,7 @@ RUN cd server && PRISMA_CLIENT_OUTPUT_DIR=../server/node_modules/.prisma/client/ RUN cd server && npm run build FROM base AS server-production -RUN curl -sSL https://get.wasp-lang.dev/installer.sh | sh -s -- -v 0.11.4-wasp-ai-12 +RUN curl -sSL https://get.wasp-lang.dev/installer.sh | sh -s -- -v 0.12.0 ENV PATH "$PATH:/root/.local/bin" ENV NODE_ENV production WORKDIR /app diff --git a/mage/README.md b/mage/README.md new file mode 100644 index 000000000..37d617ada --- /dev/null +++ b/mage/README.md @@ -0,0 +1,44 @@ +# Mage + +This directory contains the source code of Mage (aka "GPT Web App Generator" aka "Wasp AI"): a Wasp app (so a full-stack web app) that allows you to create a new Wasp app (inception :)!) from just a short description. It uses ChatGPT in a smart way to accomplish this (so it would be clasified as an AI code agent). + +Mage is hosted at https://usemage.ai and you can use it there for free. + +You can learn more about it [here](https://wasp-lang.dev/blog/2023/07/10/gpt-web-app-generator) and [here](https://wasp-lang.dev/blog/2023/07/17/how-we-built-gpt-web-app-generator). + +## Running locally + +Mage is really just a client / UI for calling "Wasp AI", which is AI logic that does all the heavy lifting, and is integrated into Wasp's CLI: `wasp`. +So, if you want to generate Wasp apps via AI locally, on your machine, with your OpenAI keys and your choice of models/parameters, we recommend NOT running the Mage app locally, because it is not so easy, instead we recommend you to do it directly via `wasp` CLI, with `wasp new` or `wasp new:ai` commands. Check our docs on how to install `wasp` CLI: https://wasp-lang.dev/docs/quick-start#installation . + +If you still want to run Mage web app locally for some specific reason, most likely because you want to contribute, you will need to do the following: + +1. Copy `.env.server.example` into `.env.server` and fill in the missing values. You will basically need to provide Github and Google OAuth creds (and first create OAuth apps on both Github and Google if you don't have them yet - if you are a member of Wasp team ask for dev creds, if not you will have to create your own OAuth apps). +2. Run `wasp db start` and then `wasp start`! It might ask you to do `wasp db migrate-dev`, do that if needed. +3. When running Mage locally, it will be looking for `wasp-cli` binary on your machine to use. To satisfy this requirement, you can go to `waspc/` dir (just next to this one) and run `./run install` there. You will want to check though if that Wasp version matches the version Mage expects (check its Dockerfile to see which version of Wasp it expects). + If building `waspc/` is too complex for you (you don't have Haskell toolchain set up, taking too long, ...), you can go into the code of Mage, find where it calls `wasp-cli` and modify that temporarily to call `wasp` instead. + +## Developing + +### Updating Wasp version in Dockerfile + +Keep in mind that Mage, when deployed, will install the version of Wasp specified in its Dockerfile. +So, make sure to update that version to be in sync with the version of Wasp that it was developed against. +Most often that should be the current version of Wasp on `main`, even if not released yet. + +## Deployment + +Mage is currently deployed on Wasp's Fly.io cloud. + +Same as the rest of Wasp (blog/docs, CLI, ...), the latest deployed version is tracked on `release` branch. + +So if you want to deploy new version of Mage, you should get it in wanted state on `release` branch, and then deploy from there. + +Also, before deploying, check that version of `wasp` in `Dockerfile` makes sense. + +To deploy it, just run `wasp deploy fly deploy`. You might want to add `--org wasp` if needed. + +## FAQ + +Q: What is the difference between Wasp AI and Mage? Are those the same thing? +A: When we say "Wasp AI" we refer to logic implemented in `wasp` CLI, while when we say "Mage" we refer to the Mage web app that really serves as a client for "Wasp AI" (calls it in the background). That said, we sometimes use these interchangeably. diff --git a/wasp-ai/fly-client.toml b/mage/fly-client.toml similarity index 100% rename from wasp-ai/fly-client.toml rename to mage/fly-client.toml diff --git a/wasp-ai/fly-server.toml b/mage/fly-server.toml similarity index 100% rename from wasp-ai/fly-server.toml rename to mage/fly-server.toml diff --git a/wasp-ai/main.wasp b/mage/main.wasp similarity index 100% rename from wasp-ai/main.wasp rename to mage/main.wasp diff --git a/wasp-ai/migrations/20230627092555_initial/migration.sql b/mage/migrations/20230627092555_initial/migration.sql similarity index 100% rename from wasp-ai/migrations/20230627092555_initial/migration.sql rename to mage/migrations/20230627092555_initial/migration.sql diff --git a/wasp-ai/migrations/20230627093239_add_index_on_files/migration.sql b/mage/migrations/20230627093239_add_index_on_files/migration.sql similarity index 100% rename from wasp-ai/migrations/20230627093239_add_index_on_files/migration.sql rename to mage/migrations/20230627093239_add_index_on_files/migration.sql diff --git a/wasp-ai/migrations/20230627142306_log/migration.sql b/mage/migrations/20230627142306_log/migration.sql similarity index 100% rename from wasp-ai/migrations/20230627142306_log/migration.sql rename to mage/migrations/20230627142306_log/migration.sql diff --git a/wasp-ai/migrations/20230629140423_add_status/migration.sql b/mage/migrations/20230629140423_add_status/migration.sql similarity index 100% rename from wasp-ai/migrations/20230629140423_add_status/migration.sql rename to mage/migrations/20230629140423_add_status/migration.sql diff --git a/wasp-ai/migrations/20230630142741_add_color/migration.sql b/mage/migrations/20230630142741_add_color/migration.sql similarity index 100% rename from wasp-ai/migrations/20230630142741_add_color/migration.sql rename to mage/migrations/20230630142741_add_color/migration.sql diff --git a/wasp-ai/migrations/20230630143420_add_auth_method/migration.sql b/mage/migrations/20230630143420_add_auth_method/migration.sql similarity index 100% rename from wasp-ai/migrations/20230630143420_add_auth_method/migration.sql rename to mage/migrations/20230630143420_add_auth_method/migration.sql diff --git a/wasp-ai/migrations/20230703150042_add_user/migration.sql b/mage/migrations/20230703150042_add_user/migration.sql similarity index 100% rename from wasp-ai/migrations/20230703150042_add_user/migration.sql rename to mage/migrations/20230703150042_add_user/migration.sql diff --git a/wasp-ai/migrations/20230703151150_add_email_to_user/migration.sql b/mage/migrations/20230703151150_add_email_to_user/migration.sql similarity index 100% rename from wasp-ai/migrations/20230703151150_add_email_to_user/migration.sql rename to mage/migrations/20230703151150_add_email_to_user/migration.sql diff --git a/wasp-ai/migrations/20230704092326_change_default_state/migration.sql b/mage/migrations/20230704092326_change_default_state/migration.sql similarity index 100% rename from wasp-ai/migrations/20230704092326_change_default_state/migration.sql rename to mage/migrations/20230704092326_change_default_state/migration.sql diff --git a/wasp-ai/migrations/20230704104226_add_optional_user_on_project/migration.sql b/mage/migrations/20230704104226_add_optional_user_on_project/migration.sql similarity index 100% rename from wasp-ai/migrations/20230704104226_add_optional_user_on_project/migration.sql rename to mage/migrations/20230704104226_add_optional_user_on_project/migration.sql diff --git a/wasp-ai/migrations/20230712122224_creativity_level/migration.sql b/mage/migrations/20230712122224_creativity_level/migration.sql similarity index 100% rename from wasp-ai/migrations/20230712122224_creativity_level/migration.sql rename to mage/migrations/20230712122224_creativity_level/migration.sql diff --git a/wasp-ai/migrations/20230713120447_asdf/migration.sql b/mage/migrations/20230713120447_asdf/migration.sql similarity index 100% rename from wasp-ai/migrations/20230713120447_asdf/migration.sql rename to mage/migrations/20230713120447_asdf/migration.sql diff --git a/wasp-ai/migrations/20230713123032_zip/migration.sql b/mage/migrations/20230713123032_zip/migration.sql similarity index 100% rename from wasp-ai/migrations/20230713123032_zip/migration.sql rename to mage/migrations/20230713123032_zip/migration.sql diff --git a/wasp-ai/migrations/20230715114712_added_feedback_entity/migration.sql b/mage/migrations/20230715114712_added_feedback_entity/migration.sql similarity index 100% rename from wasp-ai/migrations/20230715114712_added_feedback_entity/migration.sql rename to mage/migrations/20230715114712_added_feedback_entity/migration.sql diff --git a/wasp-ai/migrations/20230715131955_added_created_at_to_feedback_entity/migration.sql b/mage/migrations/20230715131955_added_created_at_to_feedback_entity/migration.sql similarity index 100% rename from wasp-ai/migrations/20230715131955_added_created_at_to_feedback_entity/migration.sql rename to mage/migrations/20230715131955_added_created_at_to_feedback_entity/migration.sql diff --git a/wasp-ai/migrations/20231002090520_add_github_login/migration.sql b/mage/migrations/20231002090520_add_github_login/migration.sql similarity index 100% rename from wasp-ai/migrations/20231002090520_add_github_login/migration.sql rename to mage/migrations/20231002090520_add_github_login/migration.sql diff --git a/wasp-ai/migrations/20231026125258_username/migration.sql b/mage/migrations/20231026125258_username/migration.sql similarity index 100% rename from wasp-ai/migrations/20231026125258_username/migration.sql rename to mage/migrations/20231026125258_username/migration.sql diff --git a/wasp-ai/migrations/20231117102034_delete_user/migration.sql b/mage/migrations/20231117102034_delete_user/migration.sql similarity index 100% rename from wasp-ai/migrations/20231117102034_delete_user/migration.sql rename to mage/migrations/20231117102034_delete_user/migration.sql diff --git a/wasp-ai/migrations/migration_lock.toml b/mage/migrations/migration_lock.toml similarity index 100% rename from wasp-ai/migrations/migration_lock.toml rename to mage/migrations/migration_lock.toml diff --git a/wasp-ai/postcss.config.cjs b/mage/postcss.config.cjs similarity index 100% rename from wasp-ai/postcss.config.cjs rename to mage/postcss.config.cjs diff --git a/wasp-ai/src/.waspignore b/mage/src/.waspignore similarity index 100% rename from wasp-ai/src/.waspignore rename to mage/src/.waspignore diff --git a/wasp-ai/src/client/Main.css b/mage/src/client/Main.css similarity index 100% rename from wasp-ai/src/client/Main.css rename to mage/src/client/Main.css diff --git a/wasp-ai/src/client/RootComponent.jsx b/mage/src/client/RootComponent.jsx similarity index 97% rename from wasp-ai/src/client/RootComponent.jsx rename to mage/src/client/RootComponent.jsx index 358ee4157..cf6763aaa 100644 --- a/wasp-ai/src/client/RootComponent.jsx +++ b/mage/src/client/RootComponent.jsx @@ -50,7 +50,7 @@ export function RootComponent({ children }) { cursor-pointer flex-row space-x-3 text-white bg-gradient-to-r from-pink-400 to-amber-400" - onClick={() => window.open("https://github.com/wasp-lang/wasp/tree/wasp-ai")} + onClick={() => window.open("https://github.com/wasp-lang/wasp")} >
This whole app is open-source, you can find the code{" "}
- With GPT4 increasing its availability and with LLMs improving in general, the quality of generated code will only get better! + With LLMs improving in general, the quality of generated code will only get better!

}, { @@ -80,26 +80,28 @@ const faqs = [

- However, in the future, when GPT4 becomes cheaper / more available, it would make sense to switch to it completely, since it does generate better code! + However, in the future, when GPT4 becomes cheaper / faster, it would make sense to switch to it completely, since it does generate better code!

}, { - question: '[Advanced] Can I use GPT4 for the whole app?', + question: '[Advanced] Can I use GPT4 for the whole app? / Can I run Mage locally?', answer:

As mentioned above, we use GPT4 + GPT3.5 for practical reasons, even though using GPT4 exclusively does give better results.

- However, if you have access yourself to the OpenAI API, you can use GPT4 for the whole app, or play with adjusting the temperature, by running the Wasp GPT code agent locally!
+ However, if you have access yourself to the OpenAI API, you can use GPT4 for the whole app, or play with adjusting the temperature, by running the Wasp GPT code agent locally! So same thing like Mage, but via CLI.
Note: generating an app usually consumes from 20k to 50k tokens, which is then approximately $1 to $2 per app with the current GPT4 pricing (Jul 11th 2023).

- You will need to install special version of Wasp:
+ To run Wasp AI (Mage) locally, make sure you have wasp {'>='}v0.12 installed and just run:
- curl -sSL https://get.wasp-lang.dev/installer.sh | sh -s -- -v 0.11.1-wasp-ai-11 + wasp new
+ When asked, choose AI generation, answer some questions, and your app will start generating!

- Now you can run app generation locally via:
+ There is also a command for running the same thing programmatically, without interactive questions:
- wasp new-ai:disk MyAwesomeApp "Description of my awesome app." {'"{ \\"defaultGptModel\\": \\"gpt-4\\" }"'} - + wasp new:ai +
+ Run it with no arguments (as above) to see its usage instructions.

}, ] diff --git a/wasp-ai/src/client/components/FileTree.css b/mage/src/client/components/FileTree.css similarity index 100% rename from wasp-ai/src/client/components/FileTree.css rename to mage/src/client/components/FileTree.css diff --git a/wasp-ai/src/client/components/FileTree.jsx b/mage/src/client/components/FileTree.jsx similarity index 100% rename from wasp-ai/src/client/components/FileTree.jsx rename to mage/src/client/components/FileTree.jsx diff --git a/wasp-ai/src/client/components/Header.jsx b/mage/src/client/components/Header.jsx similarity index 100% rename from wasp-ai/src/client/components/Header.jsx rename to mage/src/client/components/Header.jsx diff --git a/wasp-ai/src/client/components/Loader.css b/mage/src/client/components/Loader.css similarity index 100% rename from wasp-ai/src/client/components/Loader.css rename to mage/src/client/components/Loader.css diff --git a/wasp-ai/src/client/components/Loader.jsx b/mage/src/client/components/Loader.jsx similarity index 100% rename from wasp-ai/src/client/components/Loader.jsx rename to mage/src/client/components/Loader.jsx diff --git a/wasp-ai/src/client/components/Logs.jsx b/mage/src/client/components/Logs.jsx similarity index 100% rename from wasp-ai/src/client/components/Logs.jsx rename to mage/src/client/components/Logs.jsx diff --git a/wasp-ai/src/client/components/Radio.jsx b/mage/src/client/components/Radio.jsx similarity index 100% rename from wasp-ai/src/client/components/Radio.jsx rename to mage/src/client/components/Radio.jsx diff --git a/wasp-ai/src/client/components/StatusPill.jsx b/mage/src/client/components/StatusPill.jsx similarity index 100% rename from wasp-ai/src/client/components/StatusPill.jsx rename to mage/src/client/components/StatusPill.jsx diff --git a/wasp-ai/src/client/components/Title.jsx b/mage/src/client/components/Title.jsx similarity index 100% rename from wasp-ai/src/client/components/Title.jsx rename to mage/src/client/components/Title.jsx diff --git a/wasp-ai/src/client/components/WaitingRoomContent.jsx b/mage/src/client/components/WaitingRoomContent.jsx similarity index 100% rename from wasp-ai/src/client/components/WaitingRoomContent.jsx rename to mage/src/client/components/WaitingRoomContent.jsx diff --git a/wasp-ai/src/client/components/WaspIcon.jsx b/mage/src/client/components/WaspIcon.jsx similarity index 100% rename from wasp-ai/src/client/components/WaspIcon.jsx rename to mage/src/client/components/WaspIcon.jsx diff --git a/wasp-ai/src/client/css/prismjs-github-theme.css b/mage/src/client/css/prismjs-github-theme.css similarity index 100% rename from wasp-ai/src/client/css/prismjs-github-theme.css rename to mage/src/client/css/prismjs-github-theme.css diff --git a/wasp-ai/src/client/examples.ts b/mage/src/client/examples.ts similarity index 100% rename from wasp-ai/src/client/examples.ts rename to mage/src/client/examples.ts diff --git a/wasp-ai/src/client/magic-app-gen-logo.png b/mage/src/client/magic-app-gen-logo.png similarity index 100% rename from wasp-ai/src/client/magic-app-gen-logo.png rename to mage/src/client/magic-app-gen-logo.png diff --git a/wasp-ai/src/client/pages/FeedbackPage.jsx b/mage/src/client/pages/FeedbackPage.jsx similarity index 100% rename from wasp-ai/src/client/pages/FeedbackPage.jsx rename to mage/src/client/pages/FeedbackPage.jsx diff --git a/wasp-ai/src/client/pages/LoginPage.jsx b/mage/src/client/pages/LoginPage.jsx similarity index 100% rename from wasp-ai/src/client/pages/LoginPage.jsx rename to mage/src/client/pages/LoginPage.jsx diff --git a/wasp-ai/src/client/pages/MainPage.jsx b/mage/src/client/pages/MainPage.jsx similarity index 100% rename from wasp-ai/src/client/pages/MainPage.jsx rename to mage/src/client/pages/MainPage.jsx diff --git a/wasp-ai/src/client/pages/ResultPage.jsx b/mage/src/client/pages/ResultPage.jsx similarity index 99% rename from wasp-ai/src/client/pages/ResultPage.jsx rename to mage/src/client/pages/ResultPage.jsx index f3cdf8b14..741b75c65 100644 --- a/wasp-ai/src/client/pages/ResultPage.jsx +++ b/mage/src/client/pages/ResultPage.jsx @@ -278,7 +278,7 @@ export const ResultPage = () => { > window.open("https://github.com/wasp-lang/wasp/tree/wasp-ai")} + onClick={() => window.open("https://github.com/wasp-lang/wasp")} > 🔮 This is a Wasp powered project. If you like it,{" "} diff --git a/wasp-ai/src/client/pages/StatsPage.jsx b/mage/src/client/pages/StatsPage.jsx similarity index 100% rename from wasp-ai/src/client/pages/StatsPage.jsx rename to mage/src/client/pages/StatsPage.jsx diff --git a/wasp-ai/src/client/pages/UserPage.jsx b/mage/src/client/pages/UserPage.jsx similarity index 100% rename from wasp-ai/src/client/pages/UserPage.jsx rename to mage/src/client/pages/UserPage.jsx diff --git a/wasp-ai/src/client/prism/prisma.js b/mage/src/client/prism/prisma.js similarity index 100% rename from wasp-ai/src/client/prism/prisma.js rename to mage/src/client/prism/prisma.js diff --git a/wasp-ai/src/client/prism/wasp.js b/mage/src/client/prism/wasp.js similarity index 100% rename from wasp-ai/src/client/prism/wasp.js rename to mage/src/client/prism/wasp.js diff --git a/wasp-ai/src/client/project/utils.js b/mage/src/client/project/utils.js similarity index 100% rename from wasp-ai/src/client/project/utils.js rename to mage/src/client/project/utils.js diff --git a/wasp-ai/src/client/public/cover.png b/mage/src/client/public/cover.png similarity index 100% rename from wasp-ai/src/client/public/cover.png rename to mage/src/client/public/cover.png diff --git a/wasp-ai/src/client/public/favicon.ico b/mage/src/client/public/favicon.ico similarity index 100% rename from wasp-ai/src/client/public/favicon.ico rename to mage/src/client/public/favicon.ico diff --git a/wasp-ai/src/client/public/twitter.png b/mage/src/client/public/twitter.png similarity index 100% rename from wasp-ai/src/client/public/twitter.png rename to mage/src/client/public/twitter.png diff --git a/wasp-ai/src/client/storage.ts b/mage/src/client/storage.ts similarity index 100% rename from wasp-ai/src/client/storage.ts rename to mage/src/client/storage.ts diff --git a/wasp-ai/src/client/tsconfig.json b/mage/src/client/tsconfig.json similarity index 100% rename from wasp-ai/src/client/tsconfig.json rename to mage/src/client/tsconfig.json diff --git a/wasp-ai/src/client/vite-env.d.ts b/mage/src/client/vite-env.d.ts similarity index 100% rename from wasp-ai/src/client/vite-env.d.ts rename to mage/src/client/vite-env.d.ts diff --git a/wasp-ai/src/client/waspLogo.png b/mage/src/client/waspLogo.png similarity index 100% rename from wasp-ai/src/client/waspLogo.png rename to mage/src/client/waspLogo.png diff --git a/wasp-ai/src/client/zip/zipHelpers.js b/mage/src/client/zip/zipHelpers.js similarity index 100% rename from wasp-ai/src/client/zip/zipHelpers.js rename to mage/src/client/zip/zipHelpers.js diff --git a/wasp-ai/src/server/auth.ts b/mage/src/server/auth.ts similarity index 100% rename from wasp-ai/src/server/auth.ts rename to mage/src/server/auth.ts diff --git a/wasp-ai/src/server/jobs/checkForPendingApps.ts b/mage/src/server/jobs/checkForPendingApps.ts similarity index 100% rename from wasp-ai/src/server/jobs/checkForPendingApps.ts rename to mage/src/server/jobs/checkForPendingApps.ts diff --git a/wasp-ai/src/server/jobs/failStaleGenerations.ts b/mage/src/server/jobs/failStaleGenerations.ts similarity index 100% rename from wasp-ai/src/server/jobs/failStaleGenerations.ts rename to mage/src/server/jobs/failStaleGenerations.ts diff --git a/wasp-ai/src/server/jobs/generateApp.ts b/mage/src/server/jobs/generateApp.ts similarity index 99% rename from wasp-ai/src/server/jobs/generateApp.ts rename to mage/src/server/jobs/generateApp.ts index b2109adf6..a4d4797a6 100644 --- a/wasp-ai/src/server/jobs/generateApp.ts +++ b/mage/src/server/jobs/generateApp.ts @@ -62,7 +62,8 @@ export const generateApp: GenerateAppJob< const stdoutMutex = new Mutex(); let waspCliProcess = null; const waspCliProcessArgs = [ - "new-ai", + "new:ai", + "--stdout", project.name, project.description, JSON.stringify(projectConfig), diff --git a/wasp-ai/src/server/jobs/utils.ts b/mage/src/server/jobs/utils.ts similarity index 100% rename from wasp-ai/src/server/jobs/utils.ts rename to mage/src/server/jobs/utils.ts diff --git a/wasp-ai/src/server/operations.ts b/mage/src/server/operations.ts similarity index 100% rename from wasp-ai/src/server/operations.ts rename to mage/src/server/operations.ts diff --git a/wasp-ai/src/server/stats.ts b/mage/src/server/stats.ts similarity index 100% rename from wasp-ai/src/server/stats.ts rename to mage/src/server/stats.ts diff --git a/wasp-ai/src/server/tsconfig.json b/mage/src/server/tsconfig.json similarity index 100% rename from wasp-ai/src/server/tsconfig.json rename to mage/src/server/tsconfig.json diff --git a/wasp-ai/src/server/utils.ts b/mage/src/server/utils.ts similarity index 100% rename from wasp-ai/src/server/utils.ts rename to mage/src/server/utils.ts diff --git a/wasp-ai/src/shared/tsconfig.json b/mage/src/shared/tsconfig.json similarity index 100% rename from wasp-ai/src/shared/tsconfig.json rename to mage/src/shared/tsconfig.json diff --git a/wasp-ai/tailwind.config.cjs b/mage/tailwind.config.cjs similarity index 100% rename from wasp-ai/tailwind.config.cjs rename to mage/tailwind.config.cjs diff --git a/waspc/ChangeLog.md b/waspc/ChangeLog.md index e003ebaf6..1e2491e5b 100644 --- a/waspc/ChangeLog.md +++ b/waspc/ChangeLog.md @@ -1,6 +1,6 @@ # Changelog -## 0.12.0 +## [WIP] 0.12.0 ### ⚠️ Breaking changes diff --git a/waspc/README.md b/waspc/README.md index edc1df684..d4fd2cf42 100644 --- a/waspc/README.md +++ b/waspc/README.md @@ -326,23 +326,24 @@ NOTE: When you run it for the first time it might take a while (~10 minutes) for We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.2/) convention when creating commits. ## Branching and merging strategy -This repo contains both the source code that makes up a Wasp release (under `waspc`), as well as our website containing documentation and blog posts (under `web`). In order to facilitate the development of Wasp code while still allowing for website updates or hotfixes of the current release, we have decided on the following minimal branching strategy. +This repo contains both the source code that makes up a Wasp release (under `waspc`), as well as our website containing documentation and blog posts (under `web`), and also Mage web app (under `mage`). In order to facilitate the development of Wasp code while still allowing for website / Mage updates or hotfixes of the current release, we have decided on the following minimal branching strategy. All Wasp development should be done on feature branches. They form the basis of PRs that will target one of the two following branches: - `main`: this branch contains all the actively developed new features and corresponding documentation updates. Some of these things may not yet be released, but anything merged into `main` should be in a release-ready state. - This is the default branch to target for any Wasp feature branches. -- `release`: this branch contains the source code of current/latest Wasp release, as well as the documentation and blog posts currently published and therefore visible on the website. +- `release`: this branch contains the source code of current/latest Wasp release, as well as the documentation and blog posts currently published and therefore visible on the website, and also currently published version of Mage. - When doing a full release, which means making a new release based on what we have currently on `main`, we do the following: 1. Update `main` branch by merging `release` into it. There might be conflicts but they shouldn't be too hard to fix. Once `main` is updated, you can create a new waspc release from it, as well as deploy the website from it. 2. Update `release` branch to this new `main` by merging `main` into it. There will be no conflicts since we already resolved all of them in the previous step. How do I know where I want to target my PR, to `release` or `main`? - - If you have a change that you want to publish right now or very soon, certainly earlier than waiting till `main` is ready for publishing, then you want to target `release`. This could be website content update, new blog post, documentation (hot)fix, compiler hotfix that we need to release quickly via a new patch version, ... . + - If you have a change that you want to publish right now or very soon, certainly earlier than waiting till `main` is ready for publishing, then you want to target `release`. This could be website content update, new blog post, documentation (hot)fix, compiler hotfix that we need to release quickly via a new patch version, update for Mage that needs to go out now, ... . - If you have a change that is not urgent and can wait until the next "normal" Wasp release is published, then target `main`. These are new features, refactorings, docs accompanying new features, ... . + - Stuff published on `release` (docs, Mage) uses/references version of `wasp` that was last released (so one that is also on `release`). - TLDR; - - `release` is for changes to the already published stuff (the present). - - `main` is for changes to the to-be-published stuff (the future). + - `release` represents the present, and is for changes to the already published stuff. + - `main` represents near future, and is for changes to the to-be-published stuff. ## Deployment / CI We use Github Actions for CI. @@ -375,6 +376,7 @@ If it happens just once every so it is probably nothing to worry about. If it ha - Publish the draft release when ready. - Merge `release` back into `main` (`git merge release` while on the `main` branch), if needed. - Publish new [docs](/web#deployment) from the `release` branch as well. +- Publish new [Mage](/mage#deployment) from the `release` branch as well, if needed. - Announce new release in Discord. #### Determining next version @@ -393,7 +395,10 @@ If doing this, steps are the following: ## Documentation -External documentation, for users of Wasp, is hosted at https://wasp-lang.dev/docs, and its source is available at [web/docs](/web/docs), next to the website and blog. +External documentation, for users of Wasp, is hosted at https://wasp-lang.dev/docs, and its source is available at [web/docs](/web/docs), next to the website and blog. + +## Mage +Wasp's magic GPT web app generator aka Wasp AI aka Mage is hosted at https://usemage.ai and its source is available at [mage](/mage). Make sure to update it when changes modify how Wasp works. diff --git a/waspc/cli/exe/Main.hs b/waspc/cli/exe/Main.hs index d2aeec88f..e9f709b6d 100644 --- a/waspc/cli/exe/Main.hs +++ b/waspc/cli/exe/Main.hs @@ -46,13 +46,7 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do args <- getArgs let commandCall = case args of ("new" : newArgs) -> Command.Call.New newArgs - -- new-ai / new-ai:stdout is meant to be called and consumed programatically (e.g. by our Wasp AI - -- web app), while new-ai:disk is useful for us for testing. - [newAiCmd, projectName, appDescription, projectConfigJson] - | newAiCmd `elem` ["new-ai", "new-ai:stdout"] -> - Command.Call.NewAiToStdout projectName appDescription projectConfigJson - | newAiCmd == "new-ai:disk" -> - Command.Call.NewAiToDisk projectName appDescription projectConfigJson + ("new:ai" : newAiArgs) -> Command.Call.NewAi newAiArgs ["start"] -> Command.Call.Start ["start", "db"] -> Command.Call.StartDb ["clean"] -> Command.Call.Clean @@ -88,10 +82,20 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do case commandCall of Command.Call.New newArgs -> runCommand $ createNewProject newArgs - Command.Call.NewAiToStdout projectName appDescription projectConfigJson -> - runCommand $ Command.CreateNewProject.AI.createNewProjectNonInteractiveToStdout projectName appDescription projectConfigJson - Command.Call.NewAiToDisk projectName appDescription projectConfigJson -> - runCommand $ Command.CreateNewProject.AI.createNewProjectNonInteractiveOnDisk projectName appDescription projectConfigJson + Command.Call.NewAi newAiArgs -> case newAiArgs of + ["--stdout", projectName, appDescription, projectConfigJson] -> + runCommand $ + Command.CreateNewProject.AI.createNewProjectNonInteractiveToStdout + projectName + appDescription + projectConfigJson + [projectName, appDescription, projectConfigJson] -> + runCommand $ + Command.CreateNewProject.AI.createNewProjectNonInteractiveOnDisk + projectName + appDescription + projectConfigJson + _ -> printWaspNewAiUsage Command.Call.Start -> runCommand start Command.Call.StartDb -> runCommand Command.Start.Db.start Command.Call.Clean -> runCommand clean @@ -141,6 +145,11 @@ printUsage = " -t|--template ", " Check out the templates list here: https://github.com/wasp-lang/starters", "", + cmd " new:ai []", + " Uses AI to create a new Wasp project just based on the app name and the description.", + " You can do the same thing with `wasp new` interactively.", + " Run `wasp new:ai` for more info.", + "", cmd " version Prints current version of CLI.", cmd " waspls Run Wasp Language Server. Add --help to get more info.", cmd " completion Prints help on bash completion.", @@ -230,6 +239,30 @@ printDbUsage = ] {- ORMOLU_ENABLE -} +{- ORMOLU_DISABLE -} +printWaspNewAiUsage :: IO () +printWaspNewAiUsage = + putStrLn $ + unlines + [ title "USAGE", + " wasp new:ai ", + "", + " Config JSON:", + " It is used to provide additional configuration to Wasp AI.", + " Following fields are supported:", + " {", + " \"defaultGptTemperature\"?: number (from 0 to 2)", + " \"planningGptModel\"?: string (OpenAI model name)", + " \"codingGptModel\"?: string (OpenAI model name)", + " \"primaryColor\"?: string (Tailwind color name)", + " }", + "", + title "EXAMPLES", + " wasp new:ai ButtonApp \"One page with button\" \"{}\"", + " wasp new:ai ButtonApp \"One page with button\" \"{ \\\"defaultGptTemperature\\\": 0.5, \\\"codingGptModel\\\": \\\"gpt-4-1106-preview\\\" }\"" + ] +{- ORMOLU_ENABLE -} + cmd :: String -> String cmd = mapFirstWord (Term.applyStyles [Term.Yellow, Term.Bold]) diff --git a/waspc/cli/src/Wasp/Cli/Command/Call.hs b/waspc/cli/src/Wasp/Cli/Command/Call.hs index c7f375267..beb665a5b 100644 --- a/waspc/cli/src/Wasp/Cli/Command/Call.hs +++ b/waspc/cli/src/Wasp/Cli/Command/Call.hs @@ -2,8 +2,7 @@ module Wasp.Cli.Command.Call where data Call = New Arguments - | NewAiToStdout String String String -- projectName, appDescription, projectConfigJson - | NewAiToDisk String String String -- projectName, appDescription, projectConfigJson + | NewAi Arguments | Start | StartDb | Clean diff --git a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject.hs b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject.hs index 30ca115df..920eb222c 100644 --- a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject.hs +++ b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject.hs @@ -5,13 +5,11 @@ where import Control.Monad.IO.Class (liftIO) import Data.Function ((&)) -import StrongPath (Abs, Dir, Path') -import qualified StrongPath as SP import Wasp.Cli.Command (Command) import Wasp.Cli.Command.Call (Arguments) -import Wasp.Cli.Command.CreateNewProject.AI (createNewProjectInteractiveOnDisk) +import qualified Wasp.Cli.Command.CreateNewProject.AI as AI import Wasp.Cli.Command.CreateNewProject.ArgumentsParser (parseNewProjectArgs) -import Wasp.Cli.Command.CreateNewProject.Common (throwProjectCreationError) +import Wasp.Cli.Command.CreateNewProject.Common (printGettingStartedInstructions, throwProjectCreationError) import Wasp.Cli.Command.CreateNewProject.ProjectDescription ( NewProjectDescription (..), obtainNewProjectDescription, @@ -24,9 +22,7 @@ import Wasp.Cli.Command.CreateNewProject.StarterTemplates import Wasp.Cli.Command.CreateNewProject.StarterTemplates.Local (createProjectOnDiskFromLocalTemplate) import Wasp.Cli.Command.CreateNewProject.StarterTemplates.Remote (createProjectOnDiskFromRemoteTemplate) import Wasp.Cli.Command.Message (cliSendMessageC) -import Wasp.Cli.Common (WaspProjectDir) import qualified Wasp.Message as Msg -import qualified Wasp.Util.Terminal as Term -- | It receives all of the arguments that were passed to the `wasp new` command. createNewProject :: Arguments -> Command () @@ -38,19 +34,6 @@ createNewProject args = do createProjectOnDisk newProjectDescription liftIO $ printGettingStartedInstructions $ _absWaspProjectDir newProjectDescription - where - -- This function assumes that the project dir is created inside the current working directory when it - -- prints the instructions. - printGettingStartedInstructions :: Path' Abs (Dir WaspProjectDir) -> IO () - printGettingStartedInstructions absProjectDir = do - let projectFolder = init . SP.toFilePath . SP.basename $ absProjectDir -{- ORMOLU_DISABLE -} - putStrLn $ Term.applyStyles [Term.Green] $ "Created new Wasp app in ./" ++ projectFolder ++ " directory!" - putStrLn "To run it, do:" - putStrLn "" - putStrLn $ Term.applyStyles [Term.Bold] $ " cd " ++ projectFolder - putStrLn $ Term.applyStyles [Term.Bold] " wasp start" -{- ORMOLU_ENABLE -} createProjectOnDisk :: NewProjectDescription -> Command () createProjectOnDisk @@ -67,4 +50,4 @@ createProjectOnDisk LocalStarterTemplate metadata -> liftIO $ createProjectOnDiskFromLocalTemplate absWaspProjectDir projectName appName $ _path metadata AiGeneratedStarterTemplate -> - createNewProjectInteractiveOnDisk absWaspProjectDir appName + AI.createNewProjectInteractiveOnDisk absWaspProjectDir appName diff --git a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/AI.hs b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/AI.hs index 91a8b5930..f4527ab77 100644 --- a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/AI.hs +++ b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/AI.hs @@ -6,32 +6,110 @@ module Wasp.Cli.Command.CreateNewProject.AI where import Control.Arrow () +import Control.Monad (unless) import Control.Monad.Except (MonadError (throwError), MonadIO (liftIO)) import Data.Function ((&)) +import Data.Functor ((<&>)) +import Data.List (intercalate) +import qualified Data.List.NonEmpty as NE import qualified Data.Text as T import qualified Data.Text.IO as T.IO -import StrongPath (Abs, Dir, Path', fromAbsDir) +import StrongPath (Abs, Dir, Path', basename, fromAbsDir, fromRelDir) import StrongPath.Operations () import System.Directory (createDirectory, createDirectoryIfMissing, setCurrentDirectory) import System.Environment (lookupEnv) import System.FilePath (takeDirectory) +import qualified System.FilePath as FP import System.IO (hFlush, stdout) import qualified Wasp.AI.CodeAgent as CA import qualified Wasp.AI.GenerateNewProject as GNP -import Wasp.AI.GenerateNewProject.Common (NewProjectConfig, NewProjectDetails (..), emptyNewProjectConfig) +import Wasp.AI.GenerateNewProject.Common + ( NewProjectConfig, + NewProjectDetails (..), + emptyNewProjectConfig, + ) +import qualified Wasp.AI.GenerateNewProject.Common as GNP.C +import qualified Wasp.AI.GenerateNewProject.LogMsg as GNP.L import Wasp.AI.OpenAI (OpenAIApiKey) +import qualified Wasp.AI.OpenAI.ChatGPT as ChatGPT import Wasp.Cli.Command (Command, CommandError (CommandError)) -import Wasp.Cli.Command.CreateNewProject.ProjectDescription (NewProjectAppName (..), obtainAvailableProjectDirPath, parseWaspProjectNameIntoAppName) +import Wasp.Cli.Command.CreateNewProject.ProjectDescription + ( NewProjectAppName (..), + obtainAvailableProjectDirPath, + parseWaspProjectNameIntoAppName, + ) import Wasp.Cli.Command.CreateNewProject.StarterTemplates (readWaspProjectSkeletonFiles) import Wasp.Cli.Common (WaspProjectDir) import qualified Wasp.Cli.Interactive as Interactive +import qualified Wasp.Util as U import qualified Wasp.Util.Aeson as Utils.Aeson +import qualified Wasp.Util.Terminal as T createNewProjectInteractiveOnDisk :: Path' Abs (Dir WaspProjectDir) -> NewProjectAppName -> Command () createNewProjectInteractiveOnDisk waspProjectDir appName = do openAIApiKey <- getOpenAIApiKey appDescription <- liftIO $ Interactive.askForRequiredInput "Describe your app in a couple of sentences" - liftIO $ createNewProjectOnDisk openAIApiKey waspProjectDir appName appDescription emptyNewProjectConfig + (planningGptModel, codingGptModel) <- + liftIO $ + Interactive.askToChoose' + "Choose GPT model(s) you want to use:" + $ NE.fromList + [ Interactive.Option + "gpt-4 (planning) + gpt-3.5-turbo (coding)" + (Just "Ok results. Cheap and fast. Best cost/benefit ratio.") + (ChatGPT.GPT_4, ChatGPT.GPT_3_5_turbo), + Interactive.Option + "gpt-4 (planning) + gpt-4-1106-preview (coding)" + (Just "Possibly better results, but somewhat slower and somewhat more expensive (~2-3x).") + (ChatGPT.GPT_4, ChatGPT.GPT_4_1106_Preview), + Interactive.Option + "gpt-4 (planning + coding)" + (Just "Best results, but quite slower and quite more expensive (~5x).") + (ChatGPT.GPT_4, ChatGPT.GPT_4) + ] + temperature <- + liftIO $ + Interactive.askToChoose' + "Choose the creativity level (temperature):" + $ NE.fromList + [ Interactive.Option + "Balanced (0.7)" + (Just "Optimal trade-off between creativity and possible mistakes.") + 0.7, + Interactive.Option + "Conventional (0.4)" + (Just "Generates sensible code with minimal amount of mistakes.") + 0.4, + Interactive.Option + "Creative (1.0)" + (Just "Generates more creative code, but mistakes are more likely.") + 1.0 + ] + let projectConfig = + emptyNewProjectConfig + { GNP.C.projectPlanningGptModel = Just planningGptModel, + GNP.C.projectCodingGptModel = Just codingGptModel, + GNP.C.projectDefaultGptTemperature = Just temperature + } + + liftIO $ createNewProjectOnDisk openAIApiKey waspProjectDir appName appDescription projectConfig + + liftIO $ do + putStrLn $ + unlines + [ "", + "========", + "", + "⚠️ Experimental tech", + "Since this is a GPT generated app, it will likely contain some mistakes, proportional to how", + "complex the app is. If there are some in your app, check out Wasp docs for help while", + "fixing them, and also feel free to reach out to us on Discord! You can also try", + "generating the app again to get different results (try playing with the creativity level).", + " - Wasp docs: https://wasp-lang.dev/docs", + " - Our Discord: https://discord.gg/rzdnErX", + "", + "========" + ] createNewProjectNonInteractiveOnDisk :: String -> String -> String -> Command () createNewProjectNonInteractiveOnDisk projectName appDescription projectConfigJson = do @@ -64,14 +142,16 @@ createNewProjectOnDisk openAIApiKey waspProjectDir appName appDescription projec CA._writeLog = forwardLogToStdout } + writeFileToDisk :: FilePath -> T.Text -> IO () writeFileToDisk path content = do createDirectoryIfMissing True (takeDirectory path) T.IO.writeFile path content - putStrLn $ "> Wrote file at " <> path + putStrLn $ T.applyStyles [T.Yellow] $ "> Wrote to file: " <> fromRelDir (basename waspProjectDir) FP. path hFlush stdout + forwardLogToStdout :: GNP.L.LogMsg -> IO () forwardLogToStdout msg = do - putStrLn . T.unpack $ msg + putStrLn $ GNP.L.toTermString msg hFlush stdout -- | Instead of writing files to disk, it will write files (and logs) to the stdout, @@ -97,25 +177,32 @@ createNewProjectNonInteractiveToStdout projectName appDescription projectConfigJ liftIO $ generateNewProject codeAgentConfig appName appDescription projectConfig where + writeFileToStdoutWithDelimiters :: FilePath -> T.Text -> IO () writeFileToStdoutWithDelimiters path content = - writeToStdoutWithDelimiters "WRITE FILE" [T.pack path, content] + writeToStdoutWithDelimiters "WRITE FILE" [path, T.unpack content] + writeLogToStdoutWithDelimiters :: GNP.L.LogMsg -> IO () writeLogToStdoutWithDelimiters msg = - writeToStdoutWithDelimiters "LOG" [msg] + unless (null msg') $ + writeToStdoutWithDelimiters "LOG" [msg'] + where + msg' = U.trim $ GNP.L.toPlainString msg + writeToStdoutWithDelimiters :: String -> [String] -> IO () writeToStdoutWithDelimiters delimiterTitle paragraphs = do - T.IO.putStrLn . ("\n" <>) $ withDelimiter delimiterTitle $ T.intercalate "\n" paragraphs + putStrLn . ("\n" <>) $ withDelimiter delimiterTitle $ intercalate "\n" paragraphs hFlush stdout + withDelimiter :: String -> String -> String withDelimiter title content = - T.intercalate + intercalate "\n" [ "==== WASP AI: " <> title <> " ====", content, "===/ WASP AI: " <> title <> " ====" ] -generateNewProject :: CA.CodeAgentConfig -> NewProjectAppName -> String -> NewProjectConfig -> IO () +generateNewProject :: CA.CodeAgentConfig GNP.L.LogMsg -> NewProjectAppName -> String -> NewProjectConfig -> IO () generateNewProject codeAgentConfig (NewProjectAppName appName) appDescription projectConfig = do waspProjectSkeletonFiles <- readWaspProjectSkeletonFiles CA.runCodeAgent codeAgentConfig $ do @@ -123,9 +210,12 @@ generateNewProject codeAgentConfig (NewProjectAppName appName) appDescription pr getOpenAIApiKey :: Command OpenAIApiKey getOpenAIApiKey = - liftIO (lookupEnv "OPENAI_API_KEY") + liftIO (lookupEnv "OPENAI_API_KEY" <&> (>>= validateKey)) >>= maybe throwMissingOpenAIApiKeyEnvVarError pure where + validateKey "" = Nothing + validateKey k = Just k + throwMissingOpenAIApiKeyEnvVarError = throwError $ CommandError @@ -133,9 +223,13 @@ getOpenAIApiKey = $ unlines [ "Wasp AI uses ChatGPT to generate your project, and therefore requires you to provide it with an OpenAI API key.", "You can obtain this key via your OpenAI account's user settings (https://platform.openai.com/account/api-keys).", - "Then, add", + "Then, set OPENAI_API_KEY env var to it and wasp CLI will read from it.", + "", + "To persist the OPENAI_API_KEY env var, add", " export OPENAI_API_KEY=", - "to .bash_profile or .profile, restart your shell, and you should be good to go." + "to your .bash_profile (or .profile or .zprofile or whatever your machine is using), restart your shell, and you should be good to go.", + "", + "Alternatively, you can go to our Mage web app at https://usemage.ai and generate new Wasp app there for free, with no OpenAI API keys needed." ] newProjectDetails :: NewProjectConfig -> String -> String -> NewProjectDetails diff --git a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/Common.hs b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/Common.hs index a72493e7d..acb065743 100644 --- a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/Common.hs +++ b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/Common.hs @@ -1,15 +1,42 @@ -module Wasp.Cli.Command.CreateNewProject.Common where +module Wasp.Cli.Command.CreateNewProject.Common + ( throwProjectCreationError, + throwInvalidTemplateNameUsedError, + defaultWaspVersionBounds, + printGettingStartedInstructions, + ) +where import Control.Monad.Except (throwError) +import StrongPath (Abs, Dir, Path') +import qualified StrongPath as SP import Wasp.Cli.Command (Command, CommandError (..)) +import Wasp.Cli.Common (WaspProjectDir) import qualified Wasp.SemanticVersion as SV +import qualified Wasp.Util.Terminal as Term import qualified Wasp.Version as WV throwProjectCreationError :: String -> Command a throwProjectCreationError = throwError . CommandError "Project creation failed" throwInvalidTemplateNameUsedError :: Command a -throwInvalidTemplateNameUsedError = throwProjectCreationError "Are you sure that the template exists? 🤔 Check the list of templates here: https://github.com/wasp-lang/starters" +throwInvalidTemplateNameUsedError = + throwProjectCreationError $ + "Are you sure that the template exists?" + <> " 🤔 Check the list of templates here: https://github.com/wasp-lang/starters" defaultWaspVersionBounds :: String defaultWaspVersionBounds = show (SV.backwardsCompatibleWith WV.waspVersion) + +-- | This function assumes that the project dir is created inside the current working directory +-- when it prints the instructions. +printGettingStartedInstructions :: Path' Abs (Dir WaspProjectDir) -> IO () +printGettingStartedInstructions absProjectDir = do + let projectFolder = init . SP.toFilePath . SP.basename $ absProjectDir +{- ORMOLU_DISABLE -} + putStrLn $ Term.applyStyles [Term.Green] $ "Created new Wasp app in ./" ++ projectFolder ++ " directory!" + putStrLn "To run it, do:" + putStrLn "" + putStrLn $ Term.applyStyles [Term.Bold] $ " cd " ++ projectFolder + putStrLn $ Term.applyStyles [Term.Bold] " wasp start" + putStrLn "" +{- ORMOLU_ENABLE -} diff --git a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates.hs b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates.hs index a617b9e3f..d11360c14 100644 --- a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates.hs +++ b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates.hs @@ -12,6 +12,7 @@ where import Data.Either (fromRight) import Data.Foldable (find) +import Data.List (isPrefixOf) import Data.Text (Text) import StrongPath (File', Path, Rel, System, reldir, ()) import qualified Wasp.Cli.Command.CreateNewProject.StarterTemplates.Remote.Github as Github @@ -38,12 +39,12 @@ instance Show StarterTemplate where show (LocalStarterTemplate metadata) = _name metadata show AiGeneratedStarterTemplate = "ai-generated" -instance Interactive.Option StarterTemplate where +instance Interactive.IsOption StarterTemplate where showOption = show showOptionDescription (RemoteStarterTemplate metadata) = Just $ _description metadata showOptionDescription (LocalStarterTemplate metadata) = Just $ _description metadata showOptionDescription AiGeneratedStarterTemplate = - Just "[experimental] Describe an app in a couple of sentences and have ChatGPT generate initial code for you." + Just "🤖 Describe an app in a couple of sentences and have Wasp AI generate initial code for you. (experimental)" getStarterTemplates :: IO [StarterTemplate] getStarterTemplates = do diff --git a/waspc/cli/src/Wasp/Cli/Interactive.hs b/waspc/cli/src/Wasp/Cli/Interactive.hs index e16aad30a..df152d9fe 100644 --- a/waspc/cli/src/Wasp/Cli/Interactive.hs +++ b/waspc/cli/src/Wasp/Cli/Interactive.hs @@ -3,7 +3,9 @@ module Wasp.Cli.Interactive ( askForInput, askToChoose, + askToChoose', askForRequiredInput, + IsOption (..), Option (..), ) where @@ -11,7 +13,7 @@ where import Control.Applicative ((<|>)) import Data.Foldable (find) import Data.Function ((&)) -import Data.List (intercalate) +import Data.List (intercalate, isPrefixOf) import Data.List.NonEmpty (NonEmpty ((:|))) import qualified Data.List.NonEmpty as NE import qualified Data.Text as T @@ -37,22 +39,35 @@ import qualified Wasp.Util.Terminal as Term We want to avoid this so users can type the name of the option when answering without having to type the quotes as well. - We introduced the Option class to get different "show" behavior for Strings and other - types. If we are using something other then String, an instance of Option needs to be defined, + We introduced the IsOption class to get different "show" behavior for Strings and other + types. If we are using something other then String, an instance of IsOption needs to be defined, but for Strings it just returns the String itself. -} -class Option o where +class IsOption o where showOption :: o -> String showOptionDescription :: o -> Maybe String -instance Option [Char] where +instance IsOption [Char] where showOption = id showOptionDescription = const Nothing +data Option o = Option + { oDisplayName :: !String, + oDescription :: !(Maybe String), + oValue :: !o + } + +instance IsOption (Option o) where + showOption = oDisplayName + showOptionDescription = oDescription + askForRequiredInput :: String -> IO String askForRequiredInput = repeatIfNull . askForInput -askToChoose :: forall o. Option o => String -> NonEmpty o -> IO o +askToChoose' :: String -> NonEmpty (Option o) -> IO o +askToChoose' question options = oValue <$> askToChoose question options + +askToChoose :: forall o. IsOption o => String -> NonEmpty o -> IO o askToChoose _ (singleOption :| []) = return singleOption askToChoose question options = do putStrLn $ Term.applyStyles [Term.Bold] question @@ -83,18 +98,26 @@ askToChoose question options = do showIndexedOptions = intercalate "\n" $ showIndexedOption <$> zip [1 ..] (NE.toList options) where showIndexedOption (idx, option) = - Term.applyStyles [Term.Yellow] indexPrefix - <> Term.applyStyles [Term.Bold] (showOption option) - <> (if isDefaultOption option then " (default)" else "") - <> showDescription option (length indexPrefix) + concat + [ indexPrefix, + optionName, + tags, + optionDescription + ] where - indexPrefix = showIndex idx <> " " + indexPrefix = Term.applyStyles [Term.Yellow] (showIndex idx) <> " " + optionName = Term.applyStyles [Term.Bold] (showOption option) + tags = whenDefault (Term.applyStyles [Term.Yellow] " (default)") + optionDescription = showDescription (idx, option) + whenDefault xs = if isDefaultOption option then xs else mempty - showIndex i = "[" ++ show (i :: Int) ++ "]" + showIndex idx = "[" ++ show (idx :: Int) ++ "]" - showDescription option indentLength = case showOptionDescription option of + showDescription (idx, option) = case showOptionDescription option of Just description -> "\n" <> replicate indentLength ' ' <> description Nothing -> "" + where + indentLength = length (showIndex idx) + 1 defaultOption :: o defaultOption = NE.head options diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/main.wasp b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/main.wasp index 1a6fc0fe8..0b2cb7916 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/main.wasp +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/main.wasp @@ -1,7 +1,7 @@ app waspBuild { db: { system: PostgreSQL }, wasp: { - version: "^0.11.8" + version: "^0.12.0" }, title: "waspBuild" } diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/main.wasp b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/main.wasp index a0d1b97ec..793b47c20 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/main.wasp +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/main.wasp @@ -1,6 +1,6 @@ app waspCompile { wasp: { - version: "^0.11.8" + version: "^0.12.0" }, title: "waspCompile" } diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/main.wasp b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/main.wasp index da568a34d..5fab63309 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/main.wasp +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/main.wasp @@ -1,7 +1,7 @@ app waspComplexTest { db: { system: PostgreSQL }, wasp: { - version: "^0.11.8" + version: "^0.12.0" }, auth: { userEntity: User, diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/main.wasp b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/main.wasp index 4487e48c1..2e5bcb97e 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/main.wasp +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/main.wasp @@ -1,7 +1,7 @@ app waspJob { db: { system: PostgreSQL }, wasp: { - version: "^0.11.8" + version: "^0.12.0" }, title: "waspJob" } diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/main.wasp b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/main.wasp index 0649213bb..cc45a9633 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/main.wasp +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/main.wasp @@ -1,6 +1,6 @@ app waspMigrate { wasp: { - version: "^0.11.8" + version: "^0.12.0" }, title: "waspMigrate" } diff --git a/waspc/e2e-test/test-outputs/waspNew-golden/waspNew/main.wasp b/waspc/e2e-test/test-outputs/waspNew-golden/waspNew/main.wasp index c66d28d68..db5c502f0 100644 --- a/waspc/e2e-test/test-outputs/waspNew-golden/waspNew/main.wasp +++ b/waspc/e2e-test/test-outputs/waspNew-golden/waspNew/main.wasp @@ -1,6 +1,6 @@ app waspNew { wasp: { - version: "^0.11.8" + version: "^0.12.0" }, title: "waspNew" } diff --git a/waspc/examples/todoApp/todoApp.wasp b/waspc/examples/todoApp/todoApp.wasp index 65a5c1f71..d61d2c2de 100644 --- a/waspc/examples/todoApp/todoApp.wasp +++ b/waspc/examples/todoApp/todoApp.wasp @@ -1,6 +1,6 @@ app todoApp { wasp: { - version: "^0.11.0" + version: "^0.12.0" }, title: "ToDo App", // head: [], diff --git a/waspc/headless-test/examples/todoApp/todoApp.wasp b/waspc/headless-test/examples/todoApp/todoApp.wasp index 50d7b7a42..4becd46b7 100644 --- a/waspc/headless-test/examples/todoApp/todoApp.wasp +++ b/waspc/headless-test/examples/todoApp/todoApp.wasp @@ -1,6 +1,6 @@ app todoApp { wasp: { - version: "^0.11.0" + version: "^0.12.0" }, title: "ToDo App", dependencies: [ diff --git a/waspc/packages/prisma/package-lock.json b/waspc/packages/prisma/package-lock.json index c15ee2d64..47206b293 100644 --- a/waspc/packages/prisma/package-lock.json +++ b/waspc/packages/prisma/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "json5": "^2.2.3", - "prisma": "^4.12.0", + "prisma": "^4.16.2", "strip-ansi": "^6.0.0", "tmp-promise": "3.0.3" }, diff --git a/waspc/src/Wasp/AI/CodeAgent.hs b/waspc/src/Wasp/AI/CodeAgent.hs index 35a6a35e4..d67a50a00 100644 --- a/waspc/src/Wasp/AI/CodeAgent.hs +++ b/waspc/src/Wasp/AI/CodeAgent.hs @@ -21,8 +21,8 @@ import Control.Monad.IO.Class (MonadIO (liftIO)) import Control.Monad.Reader (MonadReader, ReaderT (runReaderT), asks) import Control.Monad.State (MonadState, StateT (runStateT), gets, modify) import qualified Data.HashMap.Strict as H +import Data.String (IsString (fromString)) import Data.Text (Text) -import qualified Data.Text as T import qualified Network.HTTP.Simple as HTTP import System.IO (hPutStrLn, stderr) import UnliftIO (Handler (Handler), catches, throwIO) @@ -35,33 +35,33 @@ import qualified Wasp.Util.IO.Retry as R import Wasp.Util.Network.HTTP (catchRetryableHttpException) import qualified Wasp.Util.Network.HTTP as Utils.HTTP -newtype CodeAgent a = CodeAgent {_unCodeAgent :: ReaderT CodeAgentConfig (StateT CodeAgentState IO) a} - deriving (Monad, Applicative, Functor, MonadIO, MonadReader CodeAgentConfig, MonadState CodeAgentState) +newtype CodeAgent logMsg a = CodeAgent {_unCodeAgent :: ReaderT (CodeAgentConfig logMsg) (StateT CodeAgentState IO) a} + deriving (Monad, Applicative, Functor, MonadIO, MonadReader (CodeAgentConfig logMsg), MonadState CodeAgentState) -data CodeAgentConfig = CodeAgentConfig +data CodeAgentConfig logMsg = CodeAgentConfig { _openAIApiKey :: !OpenAIApiKey, _writeFile :: !(FilePath -> Text -> IO ()), -- TODO: Use StrongPath? Not clear which kind of path is it, rel, abs, ... . - _writeLog :: !(Text -> IO ()) + _writeLog :: !(logMsg -> IO ()) } -instance MonadRetry CodeAgent where +instance MonadRetry (CodeAgent logMsg) where rThreadDelay = liftIO . threadDelay -runCodeAgent :: CodeAgentConfig -> CodeAgent a -> IO a +runCodeAgent :: (IsString logMsg) => CodeAgentConfig logMsg -> CodeAgent logMsg a -> IO a runCodeAgent config codeAgent = (fst <$> (_unCodeAgent codeAgent `runReaderT` config) `runStateT` initialState) `catches` [ Handler ( \(e :: HTTP.HttpException) -> do let errorInfo = maybe (showShortException e) show $ Utils.HTTP.getHttpExceptionStatusCode e - logMsg = T.pack $ "Code agent failed with the http error: " <> errorInfo + logMsg = fromString $ "Code agent failed with the http error: " <> errorInfo _writeLog config logMsg throwIO e ), Handler ( \(e :: SomeException) -> do _writeLog config $ - "Code agent failed with the following error: " <> T.pack (showShortException e) + fromString $ "Code agent failed with the following error: " <> showShortException e throwIO e ) ] @@ -78,26 +78,26 @@ runCodeAgent config codeAgent = showShortException :: forall e. Exception e => e -> String showShortException = shortenWithEllipsisTo 30 . displayException -writeToLog :: Text -> CodeAgent () +writeToLog :: IsString logMsg => logMsg -> CodeAgent logMsg () writeToLog msg = asks _writeLog >>= \f -> liftIO $ f msg -writeToFile :: FilePath -> (Maybe Text -> Text) -> CodeAgent () +writeToFile :: FilePath -> (Maybe Text -> Text) -> CodeAgent logMsg () writeToFile path updateContentFn = do content <- updateContentFn <$> getFile path asks _writeFile >>= \f -> liftIO $ f path content modify $ \s -> s {_files = H.insert path content (_files s)} -writeNewFile :: (FilePath, Text) -> CodeAgent () +writeNewFile :: (FilePath, Text) -> CodeAgent logMsg () writeNewFile (path, content) = writeToFile path (maybe content $ error $ "file " <> path <> " shouldn't already exist") -getFile :: FilePath -> CodeAgent (Maybe Text) +getFile :: FilePath -> CodeAgent logMsg (Maybe Text) getFile path = gets $ H.lookup path . _files -getAllFiles :: CodeAgent [(FilePath, Text)] +getAllFiles :: CodeAgent logMsg [(FilePath, Text)] getAllFiles = gets $ H.toList . _files -queryChatGPT :: ChatGPTParams -> [ChatMessage] -> CodeAgent Text +queryChatGPT :: ChatGPTParams -> [ChatMessage] -> CodeAgent logMsg Text queryChatGPT params messages = do key <- asks _openAIApiKey chatResponse <- queryChatGPTWithRetry key params messages @@ -105,7 +105,7 @@ queryChatGPT params messages = do return $ ChatGPT.getChatResponseContent chatResponse where {- ORMOLU_DISABLE -} - queryChatGPTWithRetry :: OpenAIApiKey -> ChatGPTParams -> [ChatMessage] -> CodeAgent ChatResponse + queryChatGPTWithRetry :: OpenAIApiKey -> ChatGPTParams -> [ChatMessage] -> CodeAgent logMsg ChatResponse queryChatGPTWithRetry key params' messages' = do R.retry @@ -123,13 +123,13 @@ queryChatGPT params messages = do >>= either throwIO pure {- ORMOLU_ENABLE -} -getOpenAIApiKey :: CodeAgent OpenAIApiKey +getOpenAIApiKey :: CodeAgent logMsg OpenAIApiKey getOpenAIApiKey = asks _openAIApiKey type NumTokens = Int -- | Returns total tokens usage: (, ). -getTotalTokensUsage :: CodeAgent (NumTokens, NumTokens) +getTotalTokensUsage :: CodeAgent logMsg (NumTokens, NumTokens) getTotalTokensUsage = do usage <- gets _usage let numPromptTokens = sum $ ChatGPT.prompt_tokens <$> usage diff --git a/waspc/src/Wasp/AI/GenerateNewProject.hs b/waspc/src/Wasp/AI/GenerateNewProject.hs index 07da112c9..1ded44866 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject.hs @@ -4,15 +4,26 @@ module Wasp.AI.GenerateNewProject where import Control.Monad (forM, forM_) +import Data.Function ((&)) import Data.List (nub) +import Data.String (fromString) import Data.Text (Text) -import qualified Data.Text as T import StrongPath (File', Path, Rel, System) import Text.Printf (printf) -import Wasp.AI.CodeAgent (CodeAgent, getTotalTokensUsage, writeToLog) -import Wasp.AI.GenerateNewProject.Common (NewProjectDetails (..)) +import Wasp.AI.CodeAgent (getTotalTokensUsage, writeToLog) +import Wasp.AI.GenerateNewProject.Common + ( CodeAgent, + NewProjectDetails (..), + codingChatGPTParams, + planningChatGPTParams, + ) import Wasp.AI.GenerateNewProject.Entity (writeEntitiesToWaspFile) -import Wasp.AI.GenerateNewProject.Operation (OperationType (..), generateAndWriteOperation, getOperationJsFilePath) +import qualified Wasp.AI.GenerateNewProject.LogMsg as L +import Wasp.AI.GenerateNewProject.Operation + ( OperationType (..), + generateAndWriteOperation, + getOperationJsFilePath, + ) import Wasp.AI.GenerateNewProject.OperationsJsFile (fixOperationsJsFile) import Wasp.AI.GenerateNewProject.Page (generateAndWritePage, getPageComponentPath) import Wasp.AI.GenerateNewProject.PageComponentFile (fixImportsInPageComponentFile, fixPageComponent) @@ -20,6 +31,7 @@ import Wasp.AI.GenerateNewProject.Plan (generatePlan) import qualified Wasp.AI.GenerateNewProject.Plan as Plan import Wasp.AI.GenerateNewProject.Skeleton (generateAndWriteProjectSkeletonAndPresetFiles) import Wasp.AI.GenerateNewProject.WaspFile (fixWaspFile) +import qualified Wasp.AI.OpenAI.ChatGPT as ChatGPT import Wasp.Project (WaspProjectDir) generateNewProject :: @@ -30,30 +42,40 @@ generateNewProject :: [(Path System (Rel WaspProjectDir) File', Text)] -> CodeAgent () generateNewProject newProjectDetails waspProjectSkeletonFiles = do - writeToLog . T.pack $ - "Generating a new Wasp project named " <> _projectAppName newProjectDetails <> "!" + writeToLog $ + "\nGenerating a new Wasp project named " <> L.styled L.Important (fromString $ _projectAppName newProjectDetails) <> "!" - writeToLog "Generating project skeleton..." + let showParams chatGPTParams = + L.styled L.Important (fromString $ show $ ChatGPT._model chatGPTParams) + <> (ChatGPT._temperature chatGPTParams & maybe "" (\t -> " (temp " <> fromString (show t) <> ")")) + in writeToLog $ + "\n" + <> "Using " + <> showParams (planningChatGPTParams newProjectDetails) + <> " for planning and " + <> showParams (codingChatGPTParams newProjectDetails) + <> " for coding." + + writeToLogGenerating "project skeleton..." (waspFilePath, planRules) <- generateAndWriteProjectSkeletonAndPresetFiles newProjectDetails waspProjectSkeletonFiles writeToLog "Generated project skeleton." plan <- generatePlan newProjectDetails planRules - writeEntitiesToWaspFile waspFilePath (Plan.entities plan) writeToLog "Updated the Wasp file with entities." - writeToLog "Generating actions..." + writeToLogGenerating "actions..." actions <- forM (Plan.actions plan) $ generateAndWriteOperation Action newProjectDetails waspFilePath plan - writeToLog "Generating queries..." + writeToLogGenerating "queries..." queries <- forM (Plan.queries plan) $ generateAndWriteOperation Query newProjectDetails waspFilePath plan - writeToLog "Generating pages..." + writeToLogGenerating "pages..." pages <- forM (Plan.pages plan) $ generateAndWritePage newProjectDetails waspFilePath (Plan.entities plan) queries actions @@ -61,32 +83,36 @@ generateNewProject newProjectDetails waspProjectSkeletonFiles = do -- TODO: Pass plan rules into fixWaspFile, as extra guidance what to keep an eye on? We can't just -- do it blindly though, some of them are relevant only to plan (e.g. not generating login / -- signup page), we would have to do some adapting. - writeToLog "Fixing mistakes in the Wasp file..." + writeToLogFixing "mistakes in the Wasp file..." fixWaspFile newProjectDetails waspFilePath plan writeToLog "Wasp file fixed." - writeToLog "Fixing mistakes in NodeJS operation files..." + writeToLogFixing "mistakes in NodeJS operation files..." forM_ (nub $ getOperationJsFilePath <$> (queries <> actions)) $ \opFp -> do fixOperationsJsFile newProjectDetails waspFilePath opFp - writeToLog $ T.pack $ "Fixed NodeJS operation file '" <> opFp <> "'." + writeToLog $ "Fixed NodeJS operation file '" <> fromString opFp <> "'." writeToLog "NodeJS operation files fixed." - writeToLog "Fixing common mistakes in pages..." + writeToLogFixing "common mistakes in pages..." forM_ (getPageComponentPath <$> pages) $ \pageFp -> do fixPageComponent newProjectDetails waspFilePath pageFp - writeToLog $ T.pack $ "Fixed '" <> pageFp <> "' page." + writeToLog $ "Fixed '" <> fromString pageFp <> "' page." writeToLog "Pages fixed." - writeToLog "Fixing import mistakes in pages..." + writeToLogFixing "import mistakes in pages..." forM_ (getPageComponentPath <$> pages) $ \pageFp -> do fixImportsInPageComponentFile pageFp queries actions - writeToLog $ T.pack $ "Fixed '" <> pageFp <> "' page." + writeToLog $ "Fixed '" <> fromString pageFp <> "' page." writeToLog "Imports in pages fixed." (promptTokensUsed, completionTokensUsed) <- getTotalTokensUsage writeToLog $ - T.pack $ - printf "Total tokens usage: ~%.1fk" $ - fromIntegral (promptTokensUsed + completionTokensUsed) / (1000 :: Double) + "\nTotal tokens usage: " + <> ( L.styled L.Important . fromString . printf "~%.1fk" $ + fromIntegral (promptTokensUsed + completionTokensUsed) / (1000 :: Double) + ) - writeToLog "Done!" + writeToLog $ L.styled L.Important "\nDone!" + where + writeToLogFixing msg = writeToLog $ "\n" <> L.styled L.Fixing "Fixing " <> msg + writeToLogGenerating msg = writeToLog $ "\n" <> L.styled L.Generating "Generating " <> msg diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Common.hs b/waspc/src/Wasp/AI/GenerateNewProject/Common.hs index 9cad527ab..4f123d90d 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Common.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Common.hs @@ -11,23 +11,29 @@ module Wasp.AI.GenerateNewProject.Common planningChatGPTParams, codingChatGPTParams, fixingChatGPTParams, + CodeAgent, ) where import Data.Aeson (FromJSON, withObject, withText, (.:?)) import qualified Data.Aeson as Aeson import Data.Maybe (fromMaybe) +import Data.String (fromString) import Data.Text (Text) import qualified Data.Text as T -import Wasp.AI.CodeAgent (CodeAgent, queryChatGPT, writeToFile, writeToLog) +import qualified Wasp.AI.CodeAgent as CA +import Wasp.AI.GenerateNewProject.LogMsg (LogMsg) +import qualified Wasp.AI.GenerateNewProject.LogMsg as L import Wasp.AI.OpenAI.ChatGPT (ChatGPTParams, ChatMessage) import qualified Wasp.AI.OpenAI.ChatGPT as GPT import Wasp.Util (naiveTrimJSON, textToLazyBS) +type CodeAgent a = CA.CodeAgent LogMsg a + data NewProjectDetails = NewProjectDetails { _projectAppName :: !String, _projectDescription :: !String, - _projectConfig :: NewProjectConfig + _projectConfig :: !NewProjectConfig } data NewProjectConfig = NewProjectConfig @@ -101,7 +107,7 @@ queryChatGPTForJSON chatGPTParams initChatMsgs = doQueryForJSON 0 0 initChatMsgs -- `maxNumFailedRunsBeforeGivingUpCompletely` * `maxNumFailuresPerRunBeforeGivingUpOnARun`. doQueryForJSON :: (FromJSON a) => Int -> Int -> [ChatMessage] -> CodeAgent a doQueryForJSON numPrevFailedRuns numPrevFailuresPerCurrentRun chatMsgs = do - response <- queryChatGPT chatGPTParams chatMsgs + response <- CA.queryChatGPT chatGPTParams chatMsgs case Aeson.eitherDecode . textToLazyBS . naiveTrimJSON $ response of Right result -> return result Left errMsg -> @@ -111,13 +117,13 @@ queryChatGPTForJSON chatGPTParams initChatMsgs = doQueryForJSON 0 0 initChatMsgs let numFailedRuns = numPrevFailedRuns + 1 in if numFailedRuns >= maxNumFailedRunsBeforeGivingUpCompletely then do - writeToLog givingUpMessage - error $ T.unpack givingUpMessage <> " Error:" <> errMsg + CA.writeToLog $ L.styled L.Error $ fromString givingUpMessage + error $ givingUpMessage <> " Error:" <> errMsg else do - writeToLog retryingMessage + CA.writeToLog $ L.styled L.Error $ fromString retryingMessage doQueryForJSON numFailedRuns 0 initChatMsgs else do - writeToLog retryingMessage + CA.writeToLog $ L.styled L.Error $ fromString retryingMessage doQueryForJSON numPrevFailedRuns numFailuresPerCurrentRun $ initChatMsgs ++ [ GPT.ChatMessage {GPT.role = GPT.Assistant, GPT.content = response}, @@ -132,7 +138,9 @@ queryChatGPTForJSON chatGPTParams initChatMsgs = doQueryForJSON 0 0 initChatMsgs } ] where + givingUpMessage :: String givingUpMessage = "Repeatedly failed to parse ChatGPT response as JSON, giving up." + retryingMessage :: String retryingMessage = "Failed to parse ChatGPT response as JSON, trying again." maxNumFailuresPerRunBeforeGivingUpOnARun = 2 @@ -161,5 +169,5 @@ fixingChatGPTParams params = params {GPT._temperature = subtract 0.2 <$> GPT._te writeToWaspFileEnd :: FilePath -> Text -> CodeAgent () writeToWaspFileEnd waspFilePath text = do - writeToFile waspFilePath $ + CA.writeToFile waspFilePath $ (<> "\n" <> text) . fromMaybe (error "wasp file shouldn't be empty") diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Entity.hs b/waspc/src/Wasp/AI/GenerateNewProject/Entity.hs index 3567b96a3..9b83bbced 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Entity.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Entity.hs @@ -7,8 +7,7 @@ where import Data.Text (Text) import qualified Data.Text as T import NeatInterpolation (trimming) -import Wasp.AI.CodeAgent (CodeAgent) -import Wasp.AI.GenerateNewProject.Common (writeToWaspFileEnd) +import Wasp.AI.GenerateNewProject.Common (CodeAgent, writeToWaspFileEnd) import qualified Wasp.AI.GenerateNewProject.Plan as Plan writeEntitiesToWaspFile :: FilePath -> [Plan.Entity] -> CodeAgent () diff --git a/waspc/src/Wasp/AI/GenerateNewProject/LogMsg.hs b/waspc/src/Wasp/AI/GenerateNewProject/LogMsg.hs new file mode 100644 index 000000000..9c76e2667 --- /dev/null +++ b/waspc/src/Wasp/AI/GenerateNewProject/LogMsg.hs @@ -0,0 +1,55 @@ +module Wasp.AI.GenerateNewProject.LogMsg + ( LogMsg, + styled, + toPlainString, + toTermString, + fromText, + Style (..), + ) +where + +import Data.String (IsString (fromString)) +import qualified Data.Text as T +import qualified Wasp.Util.Terminal as Term + +data LogMsg = Plain String | Concat [LogMsg] | Styled Style LogMsg + deriving (Show, Eq) + +data Style = Important | Generating | Fixing | Error | Custom [Term.Style] + deriving (Show, Eq) + +instance IsString LogMsg where + fromString = Plain + +fromText :: T.Text -> LogMsg +fromText = fromString . T.unpack + +instance Semigroup LogMsg where + Concat ms1 <> Concat ms2 = Concat $ ms1 <> ms2 + Concat ms1 <> ms2 = Concat $ ms1 <> [ms2] + ms1 <> Concat ms2 = Concat $ ms1 : ms2 + m1 <> m2 = Concat [m1, m2] + +instance Monoid LogMsg where + mempty = Plain "" + +styled :: Style -> LogMsg -> LogMsg +styled s m = Styled s m + +toPlainString :: LogMsg -> String +toPlainString (Plain m) = m +toPlainString (Styled _ m) = toPlainString m +toPlainString (Concat ms) = concat $ toPlainString <$> ms + +toTermString :: LogMsg -> String +toTermString (Plain m) = m +toTermString (Styled s m) = + Term.applyStyles (styleToTermStyles s) $ toTermString m + where + styleToTermStyles = \case + Important -> [Term.Bold, Term.Magenta] + Generating -> [Term.Cyan] + Fixing -> [Term.Green] + Error -> [Term.Red] + Custom styles -> styles +toTermString (Concat ms) = concat $ toTermString <$> ms diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Operation.hs b/waspc/src/Wasp/AI/GenerateNewProject/Operation.hs index de7de7ee0..d135e2c0d 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Operation.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Operation.hs @@ -17,13 +17,15 @@ where import Data.Aeson (FromJSON) import Data.Aeson.Types (ToJSON) import Data.List (find, intercalate, isInfixOf, isPrefixOf, nub) +import Data.String (fromString) import Data.Text (Text) import qualified Data.Text as T import GHC.Generics (Generic) import NeatInterpolation (trimming) -import Wasp.AI.CodeAgent (CodeAgent, writeToFile, writeToLog) +import Wasp.AI.CodeAgent (writeToFile, writeToLog) import Wasp.AI.GenerateNewProject.Common - ( NewProjectDetails (..), + ( CodeAgent, + NewProjectDetails (..), codingChatGPTParams, fixingChatGPTParams, queryChatGPTForJSON, @@ -43,7 +45,7 @@ generateAndWriteOperation operationType newProjectDetails waspFilePath plan oper operation <- generateOperation operationType newProjectDetails (Plan.entities plan) operationPlan writeOperationToJsFile operation writeOperationToWaspFile waspFilePath operation - writeToLog $ T.pack $ "Generated " <> show operationType <> ": " <> Plan.opName operationPlan + writeToLog $ "Generated " <> fromString (show operationType) <> ": " <> fromString (Plan.opName operationPlan) return operation generateOperation :: OperationType -> NewProjectDetails -> [Plan.Entity] -> Plan.Operation -> CodeAgent Operation diff --git a/waspc/src/Wasp/AI/GenerateNewProject/OperationsJsFile.hs b/waspc/src/Wasp/AI/GenerateNewProject/OperationsJsFile.hs index 7f5e48884..05d5541f4 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/OperationsJsFile.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/OperationsJsFile.hs @@ -12,9 +12,10 @@ import Data.Text (Text) import qualified Data.Text as T import GHC.Generics (Generic) import NeatInterpolation (trimming) -import Wasp.AI.CodeAgent (CodeAgent, getFile, writeToFile) +import Wasp.AI.CodeAgent (getFile, writeToFile) import Wasp.AI.GenerateNewProject.Common - ( NewProjectDetails, + ( CodeAgent, + NewProjectDetails, codingChatGPTParams, fixingChatGPTParams, queryChatGPTForJSON, diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Page.hs b/waspc/src/Wasp/AI/GenerateNewProject/Page.hs index 77baf8a99..e171602e3 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Page.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Page.hs @@ -12,13 +12,15 @@ where import Data.Aeson (FromJSON) import Data.List (isPrefixOf) import Data.Maybe (fromMaybe) +import Data.String (fromString) import Data.Text (Text) import qualified Data.Text as T import GHC.Generics (Generic) import NeatInterpolation (trimming) -import Wasp.AI.CodeAgent (CodeAgent, writeToFile, writeToLog) +import Wasp.AI.CodeAgent (writeToFile, writeToLog) import Wasp.AI.GenerateNewProject.Common - ( NewProjectDetails (..), + ( CodeAgent, + NewProjectDetails (..), codingChatGPTParams, queryChatGPTForJSON, writeToWaspFileEnd, @@ -35,7 +37,7 @@ generateAndWritePage newProjectDetails waspFilePath entityPlans queries actions page <- generatePage newProjectDetails entityPlans queries actions pPlan writePageToJsFile page writePageToWaspFile waspFilePath page - writeToLog $ T.pack $ "Generated page: " <> Plan.pageName pPlan + writeToLog $ "Generated page: " <> fromString (Plan.pageName pPlan) return page generatePage :: NewProjectDetails -> [Plan.Entity] -> [Operation] -> [Operation] -> Plan.Page -> CodeAgent Page diff --git a/waspc/src/Wasp/AI/GenerateNewProject/PageComponentFile.hs b/waspc/src/Wasp/AI/GenerateNewProject/PageComponentFile.hs index 8e74a0a7a..0c24e68b9 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/PageComponentFile.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/PageComponentFile.hs @@ -23,9 +23,10 @@ import qualified Data.Text as T import GHC.Generics (Generic) import NeatInterpolation (trimming) import Text.Printf (printf) -import Wasp.AI.CodeAgent (CodeAgent, getFile, writeToFile) +import Wasp.AI.CodeAgent (getFile, writeToFile) import Wasp.AI.GenerateNewProject.Common - ( NewProjectDetails (..), + ( CodeAgent, + NewProjectDetails (..), codingChatGPTParams, fixingChatGPTParams, queryChatGPTForJSON, diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Plan.hs b/waspc/src/Wasp/AI/GenerateNewProject/Plan.hs index 4c5f22aed..c829d9567 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Plan.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Plan.hs @@ -22,31 +22,37 @@ import GHC.Generics (Generic) import NeatInterpolation (trimming) import Numeric.Natural (Natural) import qualified Text.Parsec as Parsec -import Wasp.AI.CodeAgent (CodeAgent, writeToLog) +import Wasp.AI.CodeAgent (writeToLog) import Wasp.AI.GenerateNewProject.Common - ( NewProjectDetails (..), + ( CodeAgent, + NewProjectDetails (..), fixingChatGPTParams, planningChatGPTParams, queryChatGPTForJSON, ) import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock) import qualified Wasp.AI.GenerateNewProject.Common.Prompts as Prompts +import qualified Wasp.AI.GenerateNewProject.LogMsg as L import Wasp.AI.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..)) import qualified Wasp.Psl.Format as Prisma import qualified Wasp.Psl.Parser.Model as Psl.Parser import qualified Wasp.Util.Aeson as Util.Aeson +import qualified Wasp.Util.Terminal as Term -- | Additional rule to follow while generating plan. type PlanRule = String generatePlan :: NewProjectDetails -> [PlanRule] -> CodeAgent Plan generatePlan newProjectDetails planRules = do - writeToLog "Generating plan (slowest step, usually takes 30 to 90 seconds)..." + writeToLog $ + "\n" <> L.styled L.Generating "Generating" <> " plan (slowest step, usually takes 30 to 90 seconds)" + <> L.styled (L.Custom [Term.Blink]) "..." initialPlan <- queryChatGPTForJSON (planningChatGPTParams newProjectDetails) chatMessages - writeToLog $ "Initial plan generated!\n" <> summarizePlan initialPlan - writeToLog "Fixing initial plan..." + writeToLog $ "Initial plan generated!\n" <> L.fromText (summarizePlan initialPlan) + writeToLog $ L.styled L.Fixing "Fixing" <> " initial plan..." fixedPlan <- fixPlanRepeatedly 3 initialPlan writeToLog "Plan fixed!" + return fixedPlan where chatMessages = diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Skeleton.hs b/waspc/src/Wasp/AI/GenerateNewProject/Skeleton.hs index 117f071cf..1c30bfa23 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Skeleton.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Skeleton.hs @@ -10,8 +10,15 @@ import NeatInterpolation (trimming) import StrongPath (File', Path, Rel) import qualified StrongPath as SP import StrongPath.Types (System) -import Wasp.AI.CodeAgent (CodeAgent, writeNewFile) -import Wasp.AI.GenerateNewProject.Common (AuthProvider (..), File, NewProjectDetails (..), getProjectAuth, getProjectPrimaryColor) +import Wasp.AI.CodeAgent (writeNewFile) +import Wasp.AI.GenerateNewProject.Common + ( AuthProvider (..), + CodeAgent, + File, + NewProjectDetails (..), + getProjectAuth, + getProjectPrimaryColor, + ) import Wasp.AI.GenerateNewProject.Plan (PlanRule) import Wasp.Project (WaspProjectDir) import qualified Wasp.SemanticVersion as SV diff --git a/waspc/src/Wasp/AI/GenerateNewProject/WaspFile.hs b/waspc/src/Wasp/AI/GenerateNewProject/WaspFile.hs index 2089ad07d..f4d4e98bf 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/WaspFile.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/WaspFile.hs @@ -15,9 +15,10 @@ import Data.Text (Text) import qualified Data.Text as T import GHC.Generics (Generic) import NeatInterpolation (trimming) -import Wasp.AI.CodeAgent (CodeAgent, getFile, writeToFile) +import Wasp.AI.CodeAgent (getFile, writeToFile) import Wasp.AI.GenerateNewProject.Common - ( NewProjectDetails, + ( CodeAgent, + NewProjectDetails, codingChatGPTParams, fixingChatGPTParams, queryChatGPTForJSON, diff --git a/waspc/src/Wasp/AI/OpenAI/ChatGPT.hs b/waspc/src/Wasp/AI/OpenAI/ChatGPT.hs index 5a0981b5f..7431a1c5e 100644 --- a/waspc/src/Wasp/AI/OpenAI/ChatGPT.hs +++ b/waspc/src/Wasp/AI/OpenAI/ChatGPT.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} +{-# OPTIONS_GHC -Wno-deprecations #-} module Wasp.AI.OpenAI.ChatGPT ( queryChatGPT, @@ -15,7 +16,6 @@ module Wasp.AI.OpenAI.ChatGPT ) where -import Control.Monad (when) import Data.Aeson (FromJSON, ToJSON, (.=)) import qualified Data.Aeson as Aeson import Data.ByteString.UTF8 as BSU @@ -24,12 +24,12 @@ import Data.Functor ((<&>)) import Data.List (find) import Data.Text (Text) import qualified Data.Text as T -import Debug.Pretty.Simple (pTrace) import GHC.Generics (Generic) import qualified Network.HTTP.Conduit as HTTP.C import qualified Network.HTTP.Simple as HTTP import Wasp.AI.OpenAI (OpenAIApiKey) import qualified Wasp.Util as Util +import Wasp.Util.Debug (debugTrace) import qualified Wasp.Util.Network.HTTP as Utils.HTTP -- | Might throw an HttpException. @@ -53,18 +53,17 @@ queryChatGPT apiKey params requestMessages = do Utils.HTTP.httpJSONThatThrowsIfNot2xx request <&> either (error . ("Failed to parse ChatGPT response body as JSON: " <>)) Prelude.id - when True $ - pTrace - ( "\n\n\n\n==================================\n" - <> "\n===== GPT PARAMS ======\n" - <> show params - <> "\n====== REQUEST =======\n" - <> show requestMessages - <> "\n====== RESPONSE ======\n" - <> show chatResponse - <> "\n==================================\n\n\n\n" - ) - $ return () + debugTrace + ( "\n\n\n\n==================================\n" + <> "\n===== GPT PARAMS ======\n" + <> show params + <> "\n====== REQUEST =======\n" + <> show requestMessages + <> "\n====== RESPONSE ======\n" + <> show chatResponse + <> "\n==================================\n\n\n\n" + ) + $ return () return chatResponse diff --git a/waspc/src/Wasp/Util/Debug.hs b/waspc/src/Wasp/Util/Debug.hs new file mode 100644 index 000000000..b525a6cf1 --- /dev/null +++ b/waspc/src/Wasp/Util/Debug.hs @@ -0,0 +1,23 @@ +{-# OPTIONS_GHC -Wno-deprecations #-} + +module Wasp.Util.Debug + ( inDebugMode, + debugTrace, + ) +where + +import Debug.Pretty.Simple (pTrace) +import System.Environment (lookupEnv) +import System.IO.Unsafe (unsafePerformIO) + +inDebugMode :: Bool +inDebugMode = + case unsafePerformIO $ lookupEnv "DEBUG" of + Nothing -> False + _ -> True + +debugTrace :: String -> a -> a +debugTrace output a = + if inDebugMode + then pTrace output a + else a diff --git a/waspc/src/Wasp/Util/Terminal.hs b/waspc/src/Wasp/Util/Terminal.hs index 8373d522d..c9a17a629 100644 --- a/waspc/src/Wasp/Util/Terminal.hs +++ b/waspc/src/Wasp/Util/Terminal.hs @@ -7,6 +7,8 @@ module Wasp.Util.Terminal ) where +import Data.List (foldl') + data Style = Black | Red @@ -18,13 +20,15 @@ data Style | White | Bold | Underline + | Blink + deriving (Show, Eq) -- | Given a string, returns decorated string that when printed in terminal -- will have same content as original string but will also exibit specified styles. applyStyles :: [Style] -> String -> String applyStyles [] str = str applyStyles _ "" = "" -applyStyles styles str = foldl applyStyle str styles ++ escapeCode ++ resetCode +applyStyles styles str = foldl' applyStyle str styles ++ escapeCode ++ resetCode where applyStyle s style = escapeCode ++ styleCode style ++ s @@ -39,6 +43,7 @@ styleCode Cyan = "[36m" styleCode White = "[37m" styleCode Bold = "[1m" styleCode Underline = "[4m" +styleCode Blink = "[5m" -- Blink does not work in all terminal emulators (e.g. on mac in iTerm2). escapeCode :: String escapeCode = "\ESC" diff --git a/waspc/test/AI/GenerateNewProject/LogMsgTest.hs b/waspc/test/AI/GenerateNewProject/LogMsgTest.hs new file mode 100644 index 000000000..6468c45fc --- /dev/null +++ b/waspc/test/AI/GenerateNewProject/LogMsgTest.hs @@ -0,0 +1,21 @@ +module AI.GenerateNewProject.LogMsgTest where + +import Test.Tasty.Hspec +import Wasp.AI.GenerateNewProject.LogMsg +import qualified Wasp.Util.Terminal as Term + +spec_Wasp_AI_GenerateNewProject_LogMsg :: Spec +spec_Wasp_AI_GenerateNewProject_LogMsg = do + it "Concatenation" $ do + toPlainString ("foo" <> "bar") `shouldBe` toPlainString "foobar" + toPlainString ("foo" <> "bar") <> "buzz" `shouldBe` toPlainString "foobarbuzz" + toPlainString "foo " <> (" bar" <> " buzz") `shouldBe` toPlainString "foo bar buzz" + + it "Styling" $ do + let styledMsg = styled Important "foo" <> " bar" <> styled Error "buzz" + toPlainString styledMsg `shouldBe` "foo barbuzz" + toTermString styledMsg + `shouldBe` ( Term.applyStyles [Term.Bold, Term.Magenta] "foo" + <> " bar" + <> Term.applyStyles [Term.Red] "buzz" + ) diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 0a4ebf4f9..aabce8f15 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -6,7 +6,7 @@ cabal-version: 2.4 -- Consider using hpack, or maybe even hpack-dhall. name: waspc -version: 0.11.8 +version: 0.12.0 description: Please see the README on GitHub at homepage: https://github.com/wasp-lang/wasp/waspc#readme bug-reports: https://github.com/wasp-lang/wasp/issues @@ -154,6 +154,7 @@ library Wasp.AI.GenerateNewProject.Common Wasp.AI.GenerateNewProject.Common.Prompts Wasp.AI.GenerateNewProject.Entity + Wasp.AI.GenerateNewProject.LogMsg Wasp.AI.GenerateNewProject.Operation Wasp.AI.GenerateNewProject.OperationsJsFile Wasp.AI.GenerateNewProject.Page @@ -361,6 +362,7 @@ library Wasp.Util Wasp.Util.Aeson Wasp.Util.Control.Monad + Wasp.Util.Debug Wasp.Util.Fib Wasp.Util.FilePath Wasp.Util.IO @@ -562,6 +564,7 @@ test-suite waspc-test , tasty-golden ^>= 2.3.5 other-modules: AI.GenerateNewProject.PageComponentFileTest + AI.GenerateNewProject.LogMsgTest Analyzer.Evaluation.EvaluationErrorTest Analyzer.EvaluatorTest Analyzer.Parser.ConcreteParserTest diff --git a/web/blog/2023-07-10-gpt-web-app-generator.md b/web/blog/2023-07-10-gpt-web-app-generator.md index c57179314..bf6e9f686 100644 --- a/web/blog/2023-07-10-gpt-web-app-generator.md +++ b/web/blog/2023-07-10-gpt-web-app-generator.md @@ -2,7 +2,7 @@ title: 'GPT Web App Generator - Let AI create a full-stack React & Node.js codebase based on your description 🤖🤯' authors: [martinsos] image: /img/gpt-wasp/gpt-wasp-thumbnail.png -tags: [wasp-ai, GPT] +tags: [wasp-ai, mage, GPT] --- import Link from '@docusaurus/Link'; @@ -44,7 +44,7 @@ Besides React & Node.js, GPT Web App Generator uses [Prisma](https://www.prisma. [Wasp](https://github.com/wasp-lang/wasp) is a batteries-included, full-stack framework for React & Node.js. It takes care of everything from front-end to back-end and database along with authentication, sending emails, async jobs, deployment, and more. -Additionaly, all the code behind GPT Web App Generator is completely open-source: [web app](https://github.com/wasp-lang/wasp/tree/wasp-ai/wasp-ai), [GPT code agent](https://github.com/wasp-lang/wasp/tree/wasp-ai/waspc/src/Wasp/AI). +Additionaly, all the code behind GPT Web App Generator is completely open-source: [web app](https://github.com/wasp-lang/wasp/tree/main/mage), [GPT code agent](https://github.com/wasp-lang/wasp/tree/main/waspc/src/Wasp/AI). ## What kind of apps can I build with it? :::caution diff --git a/web/blog/2023-07-17-how-we-built-gpt-web-app-generator.md b/web/blog/2023-07-17-how-we-built-gpt-web-app-generator.md index 5107854a2..fe82e11ec 100644 --- a/web/blog/2023-07-17-how-we-built-gpt-web-app-generator.md +++ b/web/blog/2023-07-17-how-we-built-gpt-web-app-generator.md @@ -2,7 +2,7 @@ title: 'How we built a GPT code agent that generates full-stack web apps in React & Node.js, explained simply' authors: [martinsos] image: /img/how-we-built-gpt-wasp/generator-logs.png -tags: [wasp-ai, GPT] +tags: [mage, wasp-ai, GPT] --- import Link from '@docusaurus/Link'; diff --git a/web/docs/general/cli.md b/web/docs/general/cli.md index b478487a7..cf0c94ed2 100644 --- a/web/docs/general/cli.md +++ b/web/docs/general/cli.md @@ -20,6 +20,10 @@ COMMANDS -t|--template Check out the templates list here: https://github.com/wasp-lang/starters + new:ai [] + Uses AI to create a new Wasp project just based on the app name and the description. + You can do the same thing with `wasp new` interactively. + version Prints current version of CLI. waspls Run Wasp Language Server. Add --help to get more info. completion Prints help on bash completion. @@ -35,8 +39,9 @@ COMMANDS telemetry Prints telemetry status. deps Prints the dependencies that Wasp uses in your project. dockerfile Prints the contents of the Wasp generated Dockerfile. - info Prints basic information about current Wasp project. + info Prints basic information about the current Wasp project. test Executes tests in your project. + studio (experimental) GUI for inspecting your Wasp app. EXAMPLES wasp new MyApp diff --git a/web/docs/introduction/getting-started.md b/web/docs/introduction/getting-started.md index a0138cfb6..f70f8c33a 100644 --- a/web/docs/introduction/getting-started.md +++ b/web/docs/introduction/getting-started.md @@ -50,7 +50,7 @@ Check [More Details](#more-details) section below if anything went wrong, or if ------ -## More details +## More details ### Requirements