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 Wasp.AI.GenerateNewProject.Plan (generatePlan)
import qualified Wasp.AI.GenerateNewProject.Plan as Plan import qualified Wasp.AI.GenerateNewProject.Plan as Plan
import Wasp.AI.GenerateNewProject.Skeleton (generateAndWriteProjectSkeletonAndPresetFiles) import Wasp.AI.GenerateNewProject.Skeleton (generateAndWriteProjectSkeletonAndPresetFiles)
import Wasp.AI.GenerateNewProject.WaspFile (fixWaspFile)
import Wasp.Project (WaspProjectDir) import Wasp.Project (WaspProjectDir)
generateNewProject :: generateNewProject ::
@ -56,6 +57,13 @@ generateNewProject newProjectDetails waspProjectSkeletonFiles = do
forM (Plan.pages plan) $ forM (Plan.pages plan) $
generateAndWritePage newProjectDetails waspFilePath (Plan.entities plan) queries actions 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 -- 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? -- fixes any stuff in them (Wasp, JS files)? REPL?

View File

@ -23,7 +23,7 @@ import Wasp.AI.GenerateNewProject.Common
defaultChatGPTParams, defaultChatGPTParams,
queryChatGPTForJSON, 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 qualified Wasp.AI.GenerateNewProject.Common.Prompts as Prompts
import Wasp.AI.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..)) import Wasp.AI.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..))
import qualified Wasp.Psl.Parser.Model as Psl.Parser 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. -- | Additional rule to follow while generating plan.
type PlanRule = String type PlanRule = String
generatePlan :: Wasp.AI.GenerateNewProject.Common.NewProjectDetails -> [PlanRule] -> CodeAgent Plan generatePlan :: NewProjectDetails -> [PlanRule] -> CodeAgent Plan
generatePlan newProjectDetails planRules = do generatePlan newProjectDetails planRules = do
queryChatGPTForJSON defaultChatGPTParams chatMessages queryChatGPTForJSON defaultChatGPTParams chatMessages
>>= fixPlanIfNeeded >>= fixPlanIfNeeded
@ -41,8 +41,7 @@ generatePlan newProjectDetails planRules = do
[ ChatMessage {role = System, content = Prompts.systemPrompt}, [ ChatMessage {role = System, content = Prompts.systemPrompt},
ChatMessage {role = User, content = planPrompt} ChatMessage {role = User, content = planPrompt}
] ]
appName = T.pack $ _projectAppName newProjectDetails appDescriptionBlockText = appDescriptionBlock newProjectDetails
appDesc = T.pack $ _projectDescription newProjectDetails
basicWaspLangInfoPrompt = Prompts.basicWaspLangInfo basicWaspLangInfoPrompt = Prompts.basicWaspLangInfo
waspFileExamplePrompt = Prompts.waspFileExample waspFileExamplePrompt = Prompts.waspFileExample
rulesText = T.pack . unlines $ "Instructions you must follow while generating plan:" : map (" - " ++) planRules 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. Please, respond ONLY with a valid JSON that is a plan.
There should be no other text in the response. There should be no other text in the response.
${appDescriptionStartMarkerLine} ${appDescriptionBlockText}
App name: ${appName}
${appDesc}
|] |]
fixPlanIfNeeded :: Plan -> CodeAgent Plan fixPlanIfNeeded :: Plan -> CodeAgent Plan
@ -107,7 +103,7 @@ generatePlan newProjectDetails planRules = do
if null issues if null issues
then return plan then return plan
else do else do
let issuesText = T.pack $ intercalate "\n" $ (<> " - ") <$> issues let issuesText = T.pack $ intercalate "\n" ((" - " <>) <$> issues)
queryChatGPTForJSON defaultChatGPTParams $ queryChatGPTForJSON defaultChatGPTParams $
chatMessages chatMessages
<> [ ChatMessage {role = Assistant, content = Util.Aeson.encodeToText plan}, <> [ 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.", [ "App uses username and password authentication.",
T.unpack T.unpack
[trimming| [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())` - `id Int @id @default(autoincrement())`
- `username String @unique` - `username String @unique`
- `password String` - `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."] planRules = authPlanRules <> ["Don't generate the Login or Signup page."]
@ -129,7 +129,7 @@ generateDotEnvServerFile :: File
generateDotEnvServerFile = generateDotEnvServerFile =
( ".env.server", ( ".env.server",
[trimming| [trimming|
// Here you can define env vars to pass to the server. # Here you can define env vars to pass to the server.
// MY_ENV_VAR=foobar # 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 let (chatResponse :: ChatResponse) = HTTP.getResponseBody response
when False $ when True $
pTrace pTrace
( "\n\n\n\n==================================\n\n" ( "\n\n\n\n==================================\n\n"
<> show requestMessages <> show requestMessages

View File

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

View File

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