mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-27 14:55:20 +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 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?
|
||||||
|
|
||||||
|
@ -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},
|
||||||
|
@ -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
|
||||||
|]
|
|]
|
||||||
)
|
)
|
||||||
|
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
|
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
|
||||||
|
@ -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) ->
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user