mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-12-28 11:34:41 +03:00
Implemented fixing of Wasp file.
This commit is contained in:
parent
4c27df8324
commit
1d6277846b
@ -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?
|
||||
|
||||
|
@ -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},
|
||||
|
@ -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
|
||||
|]
|
||||
)
|
||||
|
96
waspc/src/Wasp/AI/GenerateNewProject/WaspFile.hs
Normal file
96
waspc/src/Wasp/AI/GenerateNewProject/WaspFile.hs
Normal 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
|
@ -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
|
||||
|
@ -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) ->
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user