Implemented fixing of Wasp file.

This commit is contained in:
Martin Sosic 2023-06-28 16:48:11 +02:00
parent 4c27df8324
commit 1d6277846b
7 changed files with 127 additions and 23 deletions

View File

@ -16,6 +16,7 @@ import Wasp.AI.GenerateNewProject.Page (generateAndWritePage)
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 Wasp.Project (WaspProjectDir)
generateNewProject ::
@ -56,6 +57,13 @@ generateNewProject newProjectDetails waspProjectSkeletonFiles = do
forM (Plan.pages plan) $
generateAndWritePage newProjectDetails waspFilePath (Plan.entities plan) queries actions
-- 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 any mistakes in Wasp file..."
fixWaspFile newProjectDetails waspFilePath
writeToLog "Wasp file fixed."
-- 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?

View File

@ -23,7 +23,7 @@ import Wasp.AI.GenerateNewProject.Common
defaultChatGPTParams,
queryChatGPTForJSON,
)
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionStartMarkerLine)
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock)
import qualified Wasp.AI.GenerateNewProject.Common.Prompts as Prompts
import Wasp.AI.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..))
import qualified Wasp.Psl.Parser.Model as Psl.Parser
@ -32,7 +32,7 @@ import qualified Wasp.Util.Aeson as Util.Aeson
-- | Additional rule to follow while generating plan.
type PlanRule = String
generatePlan :: Wasp.AI.GenerateNewProject.Common.NewProjectDetails -> [PlanRule] -> CodeAgent Plan
generatePlan :: NewProjectDetails -> [PlanRule] -> CodeAgent Plan
generatePlan newProjectDetails planRules = do
queryChatGPTForJSON defaultChatGPTParams chatMessages
>>= fixPlanIfNeeded
@ -41,8 +41,7 @@ generatePlan newProjectDetails planRules = do
[ ChatMessage {role = System, content = Prompts.systemPrompt},
ChatMessage {role = User, content = planPrompt}
]
appName = T.pack $ _projectAppName newProjectDetails
appDesc = T.pack $ _projectDescription newProjectDetails
appDescriptionBlockText = appDescriptionBlock newProjectDetails
basicWaspLangInfoPrompt = Prompts.basicWaspLangInfo
waspFileExamplePrompt = Prompts.waspFileExample
rulesText = T.pack . unlines $ "Instructions you must follow while generating plan:" : map (" - " ++) planRules
@ -91,10 +90,7 @@ generatePlan newProjectDetails planRules = do
Please, respond ONLY with a valid JSON that is a plan.
There should be no other text in the response.
${appDescriptionStartMarkerLine}
App name: ${appName}
${appDesc}
${appDescriptionBlockText}
|]
fixPlanIfNeeded :: Plan -> CodeAgent Plan
@ -107,7 +103,7 @@ generatePlan newProjectDetails planRules = do
if null issues
then return plan
else do
let issuesText = T.pack $ intercalate "\n" $ (<> " - ") <$> issues
let issuesText = T.pack $ intercalate "\n" ((" - " <>) <$> issues)
queryChatGPTForJSON defaultChatGPTParams $
chatMessages
<> [ ChatMessage {role = Assistant, content = Util.Aeson.encodeToText plan},

View File

@ -58,12 +58,12 @@ generateBaseWaspFile newProjectDetails = ((path, content), planRules)
[ "App uses username and password authentication.",
T.unpack
[trimming|
You MUST add a 'User' entity to plan, with following fields required:
App MUST have a 'User' entity, with following fields required:
- `id Int @id @default(autoincrement())`
- `username String @unique`
- `password String`
|],
"One of the pages you generate must have a route path \"/\"."
|],
"One of the pages in the app must have a route path \"/\"."
]
)
planRules = authPlanRules <> ["Don't generate the Login or Signup page."]
@ -129,7 +129,7 @@ generateDotEnvServerFile :: File
generateDotEnvServerFile =
( ".env.server",
[trimming|
// Here you can define env vars to pass to the server.
// MY_ENV_VAR=foobar
# Here you can define env vars to pass to the server.
# MY_ENV_VAR=foobar
|]
)

View File

@ -0,0 +1,96 @@
{-# LANGUAGE DeriveGeneric #-}
module Wasp.AI.GenerateNewProject.WaspFile
( fixWaspFile,
)
where
import Control.Monad.IO.Class (liftIO)
import Data.Aeson (FromJSON)
import Data.Aeson.Types (ToJSON)
import Data.Functor ((<&>))
import Data.List (intercalate)
import Data.Maybe (fromMaybe)
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.GenerateNewProject.Common
( NewProjectDetails,
defaultChatGPTParams,
queryChatGPTForJSON,
)
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock)
import qualified Wasp.AI.GenerateNewProject.Common.Prompts as Prompts
import Wasp.AI.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..))
import Wasp.Analyzer.Parser.Ctx (Ctx (..))
import Wasp.Project.Analyze (analyzeWaspFileContent)
fixWaspFile :: NewProjectDetails -> FilePath -> CodeAgent ()
fixWaspFile newProjectDetails waspFilePath = do
currentWaspFileContent <- fromMaybe (error "couldn't find wasp file to fix") <$> getFile waspFilePath
compileErrors <-
liftIO (analyzeWaspFileContent $ T.unpack currentWaspFileContent)
<&> either (pure . showCompileError) (const [])
fixedWaspFile <-
queryChatGPTForJSON
defaultChatGPTParams
[ ChatMessage {role = System, content = Prompts.systemPrompt},
ChatMessage {role = User, content = fixWaspFilePrompt currentWaspFileContent compileErrors}
]
writeToFile waspFilePath (const $ waspFileContent fixedWaspFile)
where
fixWaspFilePrompt currentWaspFileContent compileErrors =
let compileErrorsText =
T.pack $
if null compileErrors
then ""
else
"Compile errors we detected:\n"
<> intercalate "\n" ((" - " <>) <$> compileErrors)
in [trimming|
${basicWaspLangInfoPrompt}
${waspFileExamplePrompt}
We are together building a new Wasp app (description at the end of prompt).
Here is a wasp file that we generated together so far:
```wasp
${currentWaspFileContent}
```
This file likely has some mistakes: let's fix it!
${compileErrorsText}
Some common mistakes to look for:
- Missing ',' between dictionary entries, for example before `entities` field in action/query.
Fix these by adding missing ','.
- "TODO" comments or "..." that should be replaced with actual implementation.
Fix these by replacing them with actual implementation.
- Value of `fn:` field in `query` or `action` not having correct import syntax,
for example it might have invalid syntax, e.g. `fn: @server/actions.js`.
Fix these by replacing it with correct syntax, e.g. `fn: import { actionName } from "@server/actions.js"`.
With this in mind, generate a new, fixed wasp file.
Do actual fixes, don't leave comments with "TODO"!
Please respond ONLY with a valid JSON of the format { waspFileContent: string }.
There should be no other text in your response. Don't wrap content with the "```" code delimiters.
${appDescriptionBlockText}
|]
appDescriptionBlockText = appDescriptionBlock newProjectDetails
basicWaspLangInfoPrompt = Prompts.basicWaspLangInfo
waspFileExamplePrompt = Prompts.waspFileExample
showCompileError (errMsg, Ctx {ctxSourceRegion = loc}) = show loc <> ": " <> errMsg
data WaspFile = WaspFile
{ waspFileContent :: Text
}
deriving (Generic, Show)
instance FromJSON WaspFile
instance ToJSON WaspFile

View File

@ -49,7 +49,7 @@ queryChatGPT apiKey params requestMessages = do
let (chatResponse :: ChatResponse) = HTTP.getResponseBody response
when False $
when True $
pTrace
( "\n\n\n\n==================================\n\n"
<> show requestMessages

View File

@ -1,5 +1,7 @@
module Wasp.Project.Analyze
( analyzeWaspProject,
analyzeWaspFile,
analyzeWaspFileContent,
)
where
@ -9,6 +11,7 @@ import Data.List (find, isSuffixOf)
import StrongPath (Abs, Dir, File', Path', toFilePath, (</>))
import qualified Wasp.Analyzer as Analyzer
import Wasp.Analyzer.AnalyzeError (getErrorMessageAndCtx)
import Wasp.Analyzer.Parser.Ctx (Ctx)
import qualified Wasp.AppSpec as AS
import Wasp.AppSpec.Valid (validateAppSpec)
import Wasp.CompileOptions (CompileOptions)
@ -31,17 +34,17 @@ analyzeWaspProject ::
IO (Either [CompileError] AS.AppSpec)
analyzeWaspProject waspDir options = runExceptT $ do
waspFilePath <- ExceptT $ Control.Arrow.left pure <$> findWaspFile waspDir
declarations <- ExceptT $ Control.Arrow.left pure <$> analyzeWaspFileContent waspFilePath
declarations <- ExceptT $ Control.Arrow.left pure <$> analyzeWaspFile waspFilePath
ExceptT $ constructAppSpec waspDir options declarations
analyzeWaspFileContent :: Path' Abs File' -> IO (Either CompileError [AS.Decl])
analyzeWaspFileContent waspFilePath = do
analyzeWaspFile :: Path' Abs File' -> IO (Either CompileError [AS.Decl])
analyzeWaspFile waspFilePath = do
waspFileContent <- IOUtil.readFile waspFilePath
let declsOrAnalyzeError = Analyzer.analyze waspFileContent
return $
Control.Arrow.left
(showCompilerErrorForTerminal (waspFilePath, waspFileContent) . getErrorMessageAndCtx)
declsOrAnalyzeError
left (showCompilerErrorForTerminal (waspFilePath, waspFileContent))
<$> analyzeWaspFileContent waspFileContent
analyzeWaspFileContent :: String -> IO (Either (String, Ctx) [AS.Decl])
analyzeWaspFileContent = return . left getErrorMessageAndCtx . Analyzer.analyze
constructAppSpec ::
Path' Abs (Dir WaspProjectDir) ->

View File

@ -147,6 +147,7 @@ library
Wasp.AI.GenerateNewProject.Page
Wasp.AI.GenerateNewProject.Plan
Wasp.AI.GenerateNewProject.Skeleton
Wasp.AI.GenerateNewProject.WaspFile
Wasp.AI.OpenAI
Wasp.AI.OpenAI.ChatGPT
Wasp.Analyzer