mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-12-28 11:34:41 +03:00
Command.AI.New is now in order.
This commit is contained in:
parent
1a8db2abf8
commit
a89f52b026
@ -15,8 +15,8 @@ where
|
||||
|
||||
import Control.Monad.IO.Class (MonadIO (liftIO))
|
||||
import Control.Monad.Reader (MonadReader, ReaderT (runReaderT), asks)
|
||||
import Control.Monad.State (MonadState, StateT (runStateT), gets)
|
||||
import Data.List (find)
|
||||
import Control.Monad.State (MonadState, StateT (runStateT), gets, modify)
|
||||
import qualified Data.HashMap.Strict as H
|
||||
import Data.Text (Text)
|
||||
import Wasp.OpenAI (OpenAIApiKey)
|
||||
import Wasp.OpenAI.ChatGPT (ChatGPTParams, ChatMessage)
|
||||
@ -31,11 +31,11 @@ data CodeAgentConfig = CodeAgentConfig
|
||||
_writeLog :: !(Text -> IO ())
|
||||
}
|
||||
|
||||
runCodeAgent :: CodeAgent a -> CodeAgentConfig -> IO a
|
||||
runCodeAgent codeAgent config =
|
||||
runCodeAgent :: CodeAgentConfig -> CodeAgent a -> IO a
|
||||
runCodeAgent config codeAgent =
|
||||
fst <$> (_unCodeAgent codeAgent `runReaderT` config) `runStateT` initialState
|
||||
where
|
||||
initialState = CodeAgentState {_files = []}
|
||||
initialState = CodeAgentState {_files = H.empty}
|
||||
|
||||
writeToLog :: Text -> CodeAgent ()
|
||||
writeToLog msg = asks _writeLog >>= \f -> liftIO $ f msg
|
||||
@ -44,16 +44,17 @@ writeToFile :: FilePath -> (Maybe Text -> Text) -> CodeAgent ()
|
||||
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 (path, content) =
|
||||
writeToFile path (maybe content $ error $ "file " <> path <> " shouldn't already exist")
|
||||
|
||||
getFile :: FilePath -> CodeAgent (Maybe Text)
|
||||
getFile path = (snd <$>) . find ((== path) . fst) <$> getAllFiles
|
||||
getFile path = gets $ H.lookup path . _files
|
||||
|
||||
getAllFiles :: CodeAgent [(FilePath, Text)]
|
||||
getAllFiles = gets _files
|
||||
getAllFiles = gets $ H.toList . _files
|
||||
|
||||
queryChatGPT :: ChatGPTParams -> [ChatMessage] -> CodeAgent Text
|
||||
queryChatGPT params messages = do
|
||||
@ -61,5 +62,5 @@ queryChatGPT params messages = do
|
||||
liftIO $ ChatGPT.queryChatGPT key params messages
|
||||
|
||||
data CodeAgentState = CodeAgentState
|
||||
{ _files :: ![(FilePath, Text)]
|
||||
{ _files :: H.HashMap FilePath Text
|
||||
}
|
||||
|
@ -7,22 +7,29 @@ where
|
||||
|
||||
-- TODO: Probably move this module out of here into general wasp lib.
|
||||
|
||||
import Control.Arrow (first)
|
||||
import Control.Monad (forM)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import NeatInterpolation (trimming)
|
||||
import qualified StrongPath as SP
|
||||
import Wasp.Cli.Command.AI.CodeAgent (CodeAgent, writeNewFile, writeToLog)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Plan (Plan)
|
||||
import qualified Wasp.Cli.Command.AI.GenerateNewProject.Plan as P
|
||||
import Wasp.Cli.Command.CreateNewProject (readCoreWaspProjectFiles)
|
||||
|
||||
data NewProjectDetails = NewProjectDetails
|
||||
{ _projectName :: !String,
|
||||
{ _projectAppName :: !String,
|
||||
_projectDescription :: !String,
|
||||
_projectAuth :: !AuthProvider
|
||||
}
|
||||
|
||||
-- TODO: Make these relative to WaspProjectDir?
|
||||
type File = (FilePath, Text)
|
||||
|
||||
data AuthProvider = Google
|
||||
-- TODO: Support more methods.
|
||||
data AuthProvider = UsernameAndPassword
|
||||
|
||||
-- TODO: Have generateNewProject accept Chan, to which it will stream its progress?
|
||||
-- It could just stream its output instead of printing it to stdout, so calling function
|
||||
@ -34,13 +41,13 @@ data AuthProvider = Google
|
||||
-- and also contain description of what happened (or maybe that is separate message).
|
||||
generateNewProject :: NewProjectDetails -> CodeAgent ()
|
||||
generateNewProject newProjectDetails = do
|
||||
coreFiles <- liftIO $ map (first SP.fromRelFile) <$> readCoreWaspProjectFiles
|
||||
mapM_ writeNewFile coreFiles
|
||||
let waspFile = generateBaseWaspFile newProjectDetails
|
||||
let waspFilePath = fst waspFile
|
||||
writeNewFile waspFile
|
||||
let dotEnvServerFile = generateDotEnvServerFile newProjectDetails
|
||||
writeNewFile dotEnvServerFile
|
||||
let otherNewProjectFiles = generateOtherNewProjectFiles newProjectDetails
|
||||
mapM_ writeNewFile otherNewProjectFiles
|
||||
writeToLog "Generated project skeleton."
|
||||
|
||||
writeToLog "Generating plan..."
|
||||
@ -76,18 +83,29 @@ generateNewProject newProjectDetails = do
|
||||
return page
|
||||
|
||||
-- TODO: what about having additional step here that goes through all the files once again and fixes any stuff in them (Wasp, JS files)? REPL?
|
||||
-- TODO: add some commented out lines to wasp file that showcase other features? jobs, api, serverSetup, sockets, ... .
|
||||
writeToLog "Done!"
|
||||
|
||||
-- TODO: OpenAI released ChatGPT 3.5-turbo with 16k context, should we use that one?
|
||||
-- What about "functions" feature that they released?
|
||||
|
||||
generateBaseWaspFile :: NewProjectDetails -> File
|
||||
generateBaseWaspFile = undefined
|
||||
|
||||
-- [ ChatMessage
|
||||
-- { role = System,
|
||||
-- content = "You are an expert Wasp developer, helping set up a new Wasp project."
|
||||
-- },
|
||||
-- ChatMessage
|
||||
-- { role = User,
|
||||
-- -- TODO: I should tell it to mark the type of each ext import: "page", "query", "action".
|
||||
-- content = "Hi"
|
||||
-- }
|
||||
-- ]
|
||||
|
||||
generateDotEnvServerFile :: NewProjectDetails -> File
|
||||
generateDotEnvServerFile = undefined
|
||||
|
||||
-- TODO: implement generateOtherNewProjectFiles based on existing CNP.createWaspProjectDir function.
|
||||
generateOtherNewProjectFiles :: NewProjectDetails -> [File]
|
||||
generateOtherNewProjectFiles = undefined -- Maybe add dotenvserver under this.
|
||||
|
||||
generatePlan :: NewProjectDetails -> CodeAgent Plan
|
||||
generatePlan = undefined
|
||||
|
||||
@ -140,3 +158,83 @@ data Page = Page
|
||||
_pageJsImpl :: String,
|
||||
_pagePlan :: P.Action
|
||||
}
|
||||
|
||||
waspFileExample =
|
||||
[trimming|
|
||||
Example main.wasp (comments are explanation for you):
|
||||
|
||||
```wasp
|
||||
app todoApp {
|
||||
wasp: { version: "^0.10.2" },
|
||||
title: "ToDo App",
|
||||
auth: {
|
||||
userEntity: User,
|
||||
// Define only if using social (google) auth.
|
||||
externalAuthEntity: SocialLogin,
|
||||
methods: {
|
||||
usernameAndPassword: {},
|
||||
google: {
|
||||
configFn: import { config } from "@server/auth/google.js",
|
||||
getUserFieldsFn: import { getUserFields } from "@server/auth/google.js"
|
||||
},
|
||||
},
|
||||
onAuthFailedRedirectTo: "/login",
|
||||
onAuthSucceededRedirectTo: "/"
|
||||
}
|
||||
}
|
||||
|
||||
// psl stands for Prisma Schema Language.
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
tasks Task[]
|
||||
externalAuthAssociations SocialLogin[] // Only if using social auth.
|
||||
psl=}
|
||||
|
||||
// Define only if using social auth (e.g. google).
|
||||
entity SocialLogin {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
provider String
|
||||
providerId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId Int
|
||||
createdAt DateTime @default(now())
|
||||
@@unique([provider, providerId, userId])
|
||||
psl=}
|
||||
|
||||
// Ommiting entity Task to keep the example short.
|
||||
|
||||
route SignupRoute { path: "/signup", to: SignupPage }
|
||||
page SignupPage {
|
||||
component: import Signup from "@client/pages/auth/Signup.jsx" // REQ.
|
||||
}
|
||||
|
||||
// Ommiting LoginRoute and LoginPage to keep the example short.
|
||||
|
||||
route HomeRoute { path: "/", to: MainPage }
|
||||
page MainPage {
|
||||
authRequired: true,
|
||||
component: import Main from "@client/pages/Main.jsx"
|
||||
}
|
||||
|
||||
// Queries are nodejs functions that do R in CRUD.
|
||||
query getTasks {
|
||||
fn: import { getTasks } from "@server/queries.js", // REQ
|
||||
entities: [Task]
|
||||
}
|
||||
|
||||
// Actions are like quries but do CUD in CRUD.
|
||||
action createTask {
|
||||
fn: import { createTask } from "@server/actions.js",
|
||||
entities: [Task]
|
||||
}
|
||||
```
|
||||
|]
|
||||
|
||||
basicWaspLangInfo =
|
||||
[trimming|
|
||||
Wasp is web app framework that uses React, NodeJS and Prisma.
|
||||
High-level is described in main.wasp file, details in JS/JSX files.
|
||||
Main Wasp features: frontend Routes and Pages, Queries and Actions (RPC), Entities.
|
||||
|]
|
||||
|
@ -3,239 +3,62 @@
|
||||
|
||||
module Wasp.Cli.Command.AI.New
|
||||
( new,
|
||||
queryChatGpt,
|
||||
sayHiToChatGpt,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Arrow ()
|
||||
import Control.Monad.Except (MonadIO (liftIO))
|
||||
import Data.Aeson ((.=))
|
||||
import qualified Data.Aeson as Aeson
|
||||
import Data.ByteString.UTF8 as BSU
|
||||
import Data.Text (Text)
|
||||
import GHC.Generics (Generic)
|
||||
import NeatInterpolation (trimming)
|
||||
import qualified Network.HTTP.Simple as HTTP
|
||||
import StrongPath (Abs, Dir, Path', fromAbsDir, fromRelFile, relfile, (</>))
|
||||
import Control.Monad.Except (MonadError (throwError), MonadIO (liftIO))
|
||||
import qualified Data.Text as T
|
||||
import StrongPath (fromAbsDir)
|
||||
import StrongPath.Operations ()
|
||||
import System.Directory (getFileSize, setCurrentDirectory)
|
||||
import qualified System.Environment as System.Environment
|
||||
import qualified Wasp.AppSpec.Valid as ASV
|
||||
import Wasp.Cli.Command (Command)
|
||||
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd, readWaspCompileInfo)
|
||||
import Wasp.Cli.Command.Compile (analyze)
|
||||
import Wasp.Cli.Command.CreateNewProject (createNewProject)
|
||||
import System.Directory (setCurrentDirectory)
|
||||
import System.Environment (lookupEnv)
|
||||
import Wasp.Cli.Command (Command, CommandError (CommandError))
|
||||
import qualified Wasp.Cli.Command.AI.CodeAgent as CA
|
||||
import qualified Wasp.Cli.Command.AI.GenerateNewProject as GNP
|
||||
import qualified Wasp.Cli.Command.CreateNewProject as CNP
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import Wasp.Cli.Command.Start.Db (getDbSystem)
|
||||
import qualified Wasp.Message as Msg
|
||||
import Wasp.Project (WaspProjectDir)
|
||||
import Wasp.Util.IO (removeFile)
|
||||
import qualified Wasp.Util.IO as IOUtil
|
||||
import qualified Wasp.Util.Terminal as Term
|
||||
|
||||
new :: Command ()
|
||||
new = do
|
||||
(webAppTitle, webAppDescription) <- liftIO $ do
|
||||
putStrLn "Describe the web app you want to create:"
|
||||
putStrLn "Title:"
|
||||
title <- getLine
|
||||
putStrLn "What it should work like:"
|
||||
desc <- getLine
|
||||
return (title, desc)
|
||||
openAIApiKey <-
|
||||
liftIO (lookupEnv "OPENAI_API_KEY")
|
||||
>>= maybe throwMissingOpenAIApiKeyEnvVarError pure
|
||||
|
||||
absWaspProjectDir <- createNewEmptyProject webAppTitle
|
||||
(webAppName, webAppDescription) <- liftIO $ do
|
||||
putStrLn "App name (e.g. MyFirstApp):"
|
||||
appName <- getLine
|
||||
putStrLn "Describe your app in a couple of sentences:"
|
||||
desc <- getLine
|
||||
return (appName, desc)
|
||||
|
||||
projectInfo <- CNP.parseProjectInfo webAppName
|
||||
|
||||
absWaspProjectDir <- CNP.createEmptyWaspProjectDir projectInfo
|
||||
liftIO $ setCurrentDirectory $ fromAbsDir absWaspProjectDir
|
||||
|
||||
waspFileContent <- aiWriteWaspFile absWaspProjectDir webAppTitle webAppDescription
|
||||
let codeAgentConfig =
|
||||
CA.CodeAgentConfig
|
||||
{ CA._openAIApiKey = openAIApiKey,
|
||||
-- TODO: Use more appropriate functions here.
|
||||
CA._writeFile = \fp c -> putStrLn $ "\nwriteFile:\n" <> show (fp, c) <> "\n",
|
||||
CA._writeLog = putStrLn . ("writeLog: " <>) . T.unpack
|
||||
}
|
||||
|
||||
aiWriteWaspPages absWaspProjectDir waspFileContent
|
||||
aiWriteWaspOperations absWaspProjectDir waspFileContent
|
||||
-- Maybe write something else also: setupFn or something.
|
||||
let newProjectDetails =
|
||||
GNP.NewProjectDetails
|
||||
{ GNP._projectAppName = webAppName,
|
||||
GNP._projectDescription = webAppDescription,
|
||||
GNP._projectAuth = GNP.UsernameAndPassword
|
||||
}
|
||||
|
||||
liftIO $
|
||||
CA.runCodeAgent codeAgentConfig $
|
||||
GNP.generateNewProject newProjectDetails
|
||||
|
||||
return ()
|
||||
where
|
||||
-- appSpec <- analyze waspDir
|
||||
-- let (appName, app) = ASV.getApp appSpec
|
||||
testAppDesc =
|
||||
[trimming|
|
||||
Simple app that enables inputing pokemons and then on request can produce a random fight between the two of them.
|
||||
It should have authentication, so each user has their own pokemon, but fights happen with random pokemon
|
||||
of another user.
|
||||
|]
|
||||
|
||||
createNewEmptyProject :: String -> Command (Path' Abs (Dir WaspProjectDir))
|
||||
createNewEmptyProject webAppTitle = do
|
||||
projectInfo <- CNP.parseProjectInfo webAppTitle
|
||||
CNP.createWaspProjectDir projectInfo
|
||||
absWaspProjectDir <- CNP.getAbsoluteWaspProjectDir projectInfo
|
||||
-- Delete existing source files that we generate in the new project.
|
||||
-- TODO: Instead of deleting files, I should instead have a function that generates
|
||||
-- the very basic skeleton for the Wasp app, and then the normal "new app" would
|
||||
-- just add files to it.
|
||||
liftIO $ do
|
||||
removeFile $ absWaspProjectDir </> [relfile|main.wasp|]
|
||||
removeFile $ absWaspProjectDir </> [relfile|src/client/Main.css|]
|
||||
removeFile $ absWaspProjectDir </> [relfile|src/client/MainPage.jsx|]
|
||||
removeFile $ absWaspProjectDir </> [relfile|src/client/waspLogo.png|]
|
||||
return absWaspProjectDir
|
||||
|
||||
-- Writes wasp file to disk, but also returns its content.
|
||||
-- TODO: Also check if it compiles and if not, send errors to GPT.
|
||||
aiWriteWaspFile :: Path' Abs (Dir WaspProjectDir) -> String -> Text -> Command Text
|
||||
aiWriteWaspFile absWaspProjectDir appTitle appDesc = do
|
||||
-- TODO: Tell GPT about Wasp in general, shortly.
|
||||
-- Also give it an example of a Wasp file that is pretty rich. It can even be done as a
|
||||
-- previous part of the conversation, so it has an example of what is good.
|
||||
-- Then, tell it to generate a Wasp file for the given prompt, while also adding comments in
|
||||
-- it for every ExtImport, where comments explain what that file is supposed to contain / do,
|
||||
-- so basically to serve as instrutioncs to itself.
|
||||
-- Or probably best to tell it to provide those instructions separately, in a JSON object
|
||||
-- where key is the name of each page or whatever.
|
||||
-- Once it does, let's feed that Wasp file to the Wasp analyzer and see if it returns any
|
||||
-- errors. If it does, send it back to chat GPT for repairs.
|
||||
-- Finally, write it to disk.
|
||||
-- In the example wasp file, we can put in a lot of comments explaining stuff, but then we can
|
||||
-- ask it to not produce those once it produces a wasp file, so we save some tokens.
|
||||
-- We should also make it clear which feature in wasp file is related to which part of the
|
||||
-- prompt, so it can know to skip them properly.
|
||||
-- TODO: What if it fails to repair it? Well we can just pretend all is ok, let user fix it.
|
||||
-- TODO: Ask chatGPT to compress our prompt for us.
|
||||
let chatMessages =
|
||||
[ ChatMessage
|
||||
{ role = System,
|
||||
content = "You are an expert Wasp developer, helping set up a new Wasp project."
|
||||
},
|
||||
ChatMessage
|
||||
{ role = User,
|
||||
-- TODO: I should tell it to mark the type of each ext import: "page", "query", "action".
|
||||
content =
|
||||
[trimming|
|
||||
Wasp is web app framework that uses React, NodeJS and Prisma.
|
||||
High-level is described in main.wasp file, details in JS/JSX files.
|
||||
Main Wasp features: frontend Routes and Pages, Queries and Actions (RPC), Entities.
|
||||
|
||||
Example main.wasp (comments are explanation for you):
|
||||
|
||||
```wasp
|
||||
app todoApp {
|
||||
wasp: { version: "^0.10.2" },
|
||||
title: "ToDo App",
|
||||
auth: {
|
||||
userEntity: User,
|
||||
// Define only if using social (google) auth.
|
||||
externalAuthEntity: SocialLogin,
|
||||
methods: {
|
||||
usernameAndPassword: {},
|
||||
google: {
|
||||
configFn: import { config } from "@server/auth/google.js",
|
||||
getUserFieldsFn: import { getUserFields } from "@server/auth/google.js"
|
||||
},
|
||||
},
|
||||
onAuthFailedRedirectTo: "/login",
|
||||
onAuthSucceededRedirectTo: "/"
|
||||
}
|
||||
}
|
||||
|
||||
// psl stands for Prisma Schema Language.
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
tasks Task[]
|
||||
externalAuthAssociations SocialLogin[] // Only if using social auth.
|
||||
psl=}
|
||||
|
||||
// Define only if using social auth (e.g. google).
|
||||
entity SocialLogin {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
provider String
|
||||
providerId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId Int
|
||||
createdAt DateTime @default(now())
|
||||
@@unique([provider, providerId, userId])
|
||||
psl=}
|
||||
|
||||
// TODO: implement entity Task.
|
||||
|
||||
route SignupRoute { path: "/signup", to: SignupPage }
|
||||
page SignupPage {
|
||||
component: import Signup from "@client/pages/auth/Signup.jsx" // REQ.
|
||||
}
|
||||
|
||||
// TODO: implement LoginPage, analogous to SignupPage.
|
||||
|
||||
route HomeRoute { path: "/", to: MainPage }
|
||||
page MainPage {
|
||||
authRequired: true,
|
||||
component: import Main from "@client/pages/Main.jsx"
|
||||
}
|
||||
|
||||
// Queries are nodejs functions that do R in CRUD.
|
||||
query getTasks {
|
||||
fn: import { getTasks } from "@server/queries.js", // REQ
|
||||
entities: [Task]
|
||||
}
|
||||
|
||||
// Actions are like quries but do CUD in CRUD.
|
||||
action createTask {
|
||||
fn: import { createTask } from "@server/actions.js",
|
||||
entities: [Task]
|
||||
}
|
||||
```
|
||||
|
||||
Now, I will describe a new Wasp app to you.
|
||||
|
||||
You will first respond with only the content of main.wasp file (no comments).
|
||||
|
||||
Then, you will print a line that goes like this: "---------------------------", to mark the ending of the wasp file content. This is very important, make sure to do this.
|
||||
|
||||
Finally, for every external import, provide instructions on how to implement the corresponding JS function/component.
|
||||
The instructions should be in a JSON format like this:
|
||||
[{ import: "import { createTask } from \"@server/tasks.js\"", in: "page", instruction: "..." }, ...].
|
||||
`in` field can be "page", "query" or "action".
|
||||
|
||||
Everything after this sentence is app description:
|
||||
|
||||
${appDesc}
|
||||
|]
|
||||
}
|
||||
]
|
||||
error "TODO"
|
||||
|
||||
aiWriteWaspPages :: Path' Abs (Dir WaspProjectDir) -> Text -> Command ()
|
||||
aiWriteWaspPages absWaspProjectDir waspFileContent = do
|
||||
-- TODO: Actually it should recieve AppSpec I think, right?
|
||||
-- So idea is here that for each page we give Wasp file to Chat GPT, also all the basic
|
||||
-- knowledge about Wasp, and then ask it to generate JS for that page. If page already exists,
|
||||
-- we also pass that and tell it to add stuff to it.
|
||||
--
|
||||
-- How do we get comments to it about it? Well, maybe it is smart enough to pick them up from
|
||||
-- the wasp file?
|
||||
-- Or, we give them separately, but then we need them in a good format. I think it will be
|
||||
-- able to pick them up on its own.
|
||||
-- Oh and we also need to give it info about the concept of the page itself! And example. Uff.
|
||||
-- Maybe that is too much and we can't give it all that. In that case we should drop the idea
|
||||
-- of passing whole Wasp file, and we need to give it only instructions for that page. We can
|
||||
-- have it write those instructions separately, for each page, in that case. Yeah probably
|
||||
-- that is the best.
|
||||
-- Hm and we also need initial prompt by user here.
|
||||
error "TODO"
|
||||
|
||||
aiWriteWaspOperations :: Path' Abs (Dir WaspProjectDir) -> Text -> Command ()
|
||||
aiWriteWaspOperations absWaspProjectDir waspFileContent = do
|
||||
-- Here we do everything analogous as we did for the pages, but it becomes extra important to be able to reuse the already written file, so we should make sure we have that going,
|
||||
-- because often it is normal to put multiple operations in the same file.
|
||||
error "TODO"
|
||||
|
||||
-- TODO: What about other ext imports? Make sure we cover all of them: jobs, setupFn (server, client), api, something else?
|
||||
|
||||
-- In general, gpt-3.5-turbo-0301 does not pay strong attention to the system message, and therefore important instructions are often better placed in a user message.
|
||||
|
||||
sayHiToChatGpt :: IO ()
|
||||
sayHiToChatGpt = do
|
||||
apiKey <- System.Environment.getEnv "OPENAI_API_KEY"
|
||||
answer <- queryChatGpt apiKey [ChatMessage {role = User, content = "What is 2 + 2?"}]
|
||||
print answer
|
||||
throwMissingOpenAIApiKeyEnvVarError =
|
||||
throwError $
|
||||
CommandError
|
||||
"Missing OPENAI_API_KEY env var"
|
||||
"You can obtain this key from your OpenAI profile."
|
||||
|
@ -5,16 +5,19 @@ module Wasp.Cli.Command.CreateNewProject
|
||||
parseProjectInfo,
|
||||
ProjectInfo (..),
|
||||
getAbsoluteWaspProjectDir,
|
||||
readCoreWaspProjectFiles,
|
||||
createEmptyWaspProjectDir,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad.Except (throwError)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Data.List (intercalate)
|
||||
import Data.Text (Text)
|
||||
import Path.IO (copyDirRecur, doesDirExist)
|
||||
import StrongPath (Abs, Dir, Path, Path', System, parseAbsDir, reldir, relfile, (</>))
|
||||
import StrongPath (Abs, Dir, File', Path, Path', Rel, System, fromAbsDir, parseAbsDir, reldir, relfile, (</>))
|
||||
import StrongPath.Path (toPathAbsDir)
|
||||
import System.Directory (getCurrentDirectory)
|
||||
import System.Directory (createDirectory, getCurrentDirectory)
|
||||
import qualified System.FilePath as FP
|
||||
import Text.Printf (printf)
|
||||
import Wasp.Analyzer.Parser (isValidWaspIdentifier)
|
||||
@ -22,6 +25,7 @@ import Wasp.Cli.Command (Command, CommandError (..))
|
||||
import qualified Wasp.Data as Data
|
||||
import Wasp.Project (WaspProjectDir)
|
||||
import Wasp.Util (indent, kebabToCamelCase)
|
||||
import Wasp.Util.IO (readFileStrict)
|
||||
import qualified Wasp.Util.IO as IOUtil
|
||||
import qualified Wasp.Util.Terminal as Term
|
||||
import qualified Wasp.Version as WV
|
||||
@ -63,6 +67,15 @@ parseProjectInfo name
|
||||
where
|
||||
appName = kebabToCamelCase name
|
||||
|
||||
createEmptyWaspProjectDir :: ProjectInfo -> Command (Path System Abs (Dir WaspProjectDir))
|
||||
createEmptyWaspProjectDir projectInfo = do
|
||||
absWaspProjectDir <- getAbsoluteWaspProjectDir projectInfo
|
||||
dirExists <- doesDirExist $ toPathAbsDir absWaspProjectDir
|
||||
if dirExists
|
||||
then throwProjectCreationError $ show absWaspProjectDir ++ " is an existing directory"
|
||||
else liftIO $ createDirectory $ fromAbsDir absWaspProjectDir
|
||||
return absWaspProjectDir
|
||||
|
||||
createWaspProjectDir :: ProjectInfo -> Command ()
|
||||
createWaspProjectDir projectInfo = do
|
||||
absWaspProjectDir <- getAbsoluteWaspProjectDir projectInfo
|
||||
@ -73,6 +86,28 @@ createWaspProjectDir projectInfo = do
|
||||
initializeProjectFromSkeleton absWaspProjectDir
|
||||
writeMainWaspFile absWaspProjectDir projectInfo
|
||||
|
||||
-- TODO: This module needs cleaning up now, after my changes, because there are multiple ways to do the same thing.
|
||||
-- Idea: maybe have two dirs, one called "core", another called "new" that only holds additional files.
|
||||
|
||||
-- TODO: This is now repeating what is in templates/new which is not great.
|
||||
coreWaspProjectFiles :: [Path System (Rel WaspProjectDir) File']
|
||||
coreWaspProjectFiles =
|
||||
[ [relfile|.gitignore|],
|
||||
[relfile|.wasproot|],
|
||||
[relfile|src/.waspignore|],
|
||||
[relfile|src/client/tsconfig.json|],
|
||||
[relfile|src/client/vite-env.d.ts|],
|
||||
[relfile|src/server/tsconfig.json|],
|
||||
[relfile|src/shared/tsconfig.json|]
|
||||
]
|
||||
|
||||
readCoreWaspProjectFiles :: IO [(Path System (Rel WaspProjectDir) File', Text)]
|
||||
readCoreWaspProjectFiles = do
|
||||
dataDir <- Data.getAbsDataDirPath
|
||||
let templatesNewDir = dataDir </> [reldir|Cli/templates/new|]
|
||||
contents <- mapM (readFileStrict . (templatesNewDir </>)) coreWaspProjectFiles
|
||||
return $ zip coreWaspProjectFiles contents
|
||||
|
||||
getAbsoluteWaspProjectDir :: ProjectInfo -> Command (Path System Abs (Dir WaspProjectDir))
|
||||
getAbsoluteWaspProjectDir (ProjectInfo projectName _) = do
|
||||
absCwd <- liftIO getCurrentDirectory
|
||||
|
@ -365,6 +365,7 @@ library cli-lib
|
||||
, waspc
|
||||
, waspls
|
||||
, neat-interpolation
|
||||
, unordered-containers
|
||||
other-modules: Paths_waspc
|
||||
exposed-modules:
|
||||
Wasp.Cli.Command
|
||||
|
Loading…
Reference in New Issue
Block a user