fix: save uploaded files to recipe-images/ subdir and inject source URL
Build and Deploy / build-and-deploy (push) Successful in 1m49s

This commit is contained in:
2026-05-21 17:04:34 -04:00
parent 439f5780c9
commit 76fab5c716
+43 -14
View File
@@ -384,14 +384,15 @@ runFileImport recipeDir config originalName _mimeType b64Text = do
Html.ImportError $
"Could not decode uploaded file: " <> T.pack err
Right content -> do
-- Ensure recipe dir exists
createDirectoryIfMissing True recipeDir
-- Save the original file
let sourceFilename = map (\c -> if isAlphaNum c || c == '.' || c == '-' || c == '_' then c else '_') (T.unpack originalName)
sourcePath = recipeDir </> sourceFilename
-- Save the original file to recipe-images/ subdirectory
let imagesDir = recipeDir </> "recipe-images"
safeName = map (\c -> if isAlphaNum c || c == '.' || c == '-' || c == '_' then c else '_') (T.unpack originalName)
sourcePath = imagesDir </> safeName
sourceUrl = "/recipe-images/" <> T.pack safeName
createDirectoryIfMissing True imagesDir
BS.writeFile sourcePath content
putStrLn $ "[roux] saved uploaded file to " <> sourcePath
-- Call the LLM pipeline with the saved file
-- Call the LLM pipeline with the source URL
case rcAnthropic config of
Nothing ->
pure $
@@ -399,7 +400,7 @@ runFileImport recipeDir config originalName _mimeType b64Text = do
Html.ImportError $
"LLM import is not configured. Add an anthropic.api_key to the config file."
Just anthropicConfig -> do
result <- runLLMPipeline recipeDir anthropicConfig (Just sourcePath) ""
result <- runLLMPipeline recipeDir anthropicConfig (Just sourceUrl) (Just sourcePath) ""
case result of
Right filename -> pure $ Right filename
Left err -> pure $ Left err
@@ -418,7 +419,7 @@ runTextImport recipeDir config text = do
Html.ImportError $
"LLM import is not configured. Add an anthropic.api_key to the config file."
Just anthropicConfig ->
runLLMPipeline recipeDir anthropicConfig Nothing text
runLLMPipeline recipeDir anthropicConfig Nothing Nothing text
-- | Run the full LLM import pipeline: call convert-to-cooklang -> parse -> save.
runLLMPipeline ::
@@ -426,19 +427,21 @@ runLLMPipeline ::
FilePath ->
-- | API config
AnthropicConfig ->
-- | Just path = uploaded source file to keep
-- | Just url = source URL to inject into Cooklang front matter
Maybe Text ->
-- | Just path = file path for the script (file upload) / Nothing = use text
Maybe FilePath ->
-- | recipe text (or empty if file)
-- | recipe text (used when no file path)
Text ->
IO (Either Html.ImportError FilePath)
runLLMPipeline recipeDir anthropicConfig mSourceFile text = do
runLLMPipeline recipeDir anthropicConfig mSourceUrl mFilePath text = do
-- Step 1: locate the script
script <- findConvertScript
-- Step 2: write a temp config for the subprocess
let configPath = recipeDir </> ".convert-config.yaml"
writeConvertConfig configPath anthropicConfig
-- Step 3: build subprocess args
let args = case mSourceFile of
let args = case mFilePath of
Just fp -> ["--config", configPath, "--file", fp]
Nothing -> ["--config", configPath, "--text", T.unpack text]
putStrLn $ "[roux] running " <> script <> " for LLM import"
@@ -456,7 +459,10 @@ runLLMPipeline recipeDir anthropicConfig mSourceFile text = do
"Could not convert recipe: " <> errMsg
ExitSuccess -> do
-- Step 4: parse the Cooklang output to extract the title
let cooklangText = TE.decodeUtf8 (LB.toStrict stdoutBytes)
let rawCooklang = TE.decodeUtf8 (LB.toStrict stdoutBytes)
cooklangText = case mSourceUrl of
Just url -> injectSourceUrl rawCooklang url
Nothing -> rawCooklang
case parseRecipeTitle cooklangText of
Nothing -> do
putStrLn "[roux] could not extract title from Cooklang output"
@@ -469,7 +475,7 @@ runLLMPipeline recipeDir anthropicConfig mSourceFile text = do
filepath = recipeDir </> filename
-- Step 5: write the .cook file
putStrLn $ "[roux] writing imported recipe to " <> filepath
LB.writeFile filepath stdoutBytes
BS.writeFile filepath (TE.encodeUtf8 cooklangText)
putStrLn $ "[roux] successfully imported recipe as " <> filename
return $ Right filename
@@ -500,6 +506,29 @@ writeConvertConfig path ac = do
Nothing -> []
writeFile path (unlines pairs)
{- | Insert or update the 'source:' field in Cooklang YAML front matter.
If a source field already exists, its value is replaced. Otherwise a new
source line is added before the closing '---'.
-}
injectSourceUrl :: Text -> Text -> Text
injectSourceUrl cooklang url =
case T.breakOn "---" cooklang of
(before, rest) ->
let afterFirst = T.drop 3 rest
(frontMatter, afterSecond) = T.breakOn "---" afterFirst
updated =
let (beforeSource, afterSource) = T.breakOn "source:" frontMatter
in if T.null afterSource
-- No existing source field: append one
then
let trimmed = T.stripEnd frontMatter
in trimmed <> "\nsource: " <> url <> "\n"
-- Existing source field: replace its value
else
let restOfLine = T.drop 1 (T.dropWhile (/= '\n') afterSource)
in beforeSource <> "source: " <> url <> restOfLine
in before <> "---" <> updated <> "---" <> afterSecond
{- | Parse the recipe title from Cooklang front matter.
Looks for "title: ..." in the YAML front matter block.
-}