Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -646,12 +646,25 @@ components:
allOf:
- $ref: "#/components/schemas/RecipeInput"
- type: object
required: [id]
required: [id, createdAt, editedAt]
properties:
id:
type: integer
format: int64
minimum: 1
createdAt:
type: string
format: date-time
description: When the recipe was saved (UTC)
editedAt:
type: string
format: date-time
description: When the recipe was last edited (UTC)
openedAt:
type: string
format: date-time
nullable: true
description: When the recipe was last opened (UTC)

RecipeRequest:
type: object
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion services/py-help-service/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ langchain-google-genai==2.1.12
langchain-core==1.4.0
pydantic==2.7.4
python-dotenv==1.0.1
attrs
attrs
python-dateutil

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion services/py-recipe-service/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ langchain-core==1.4.0
pydantic==2.7.4
python-dotenv==1.0.1
attrs
httpx
httpx
python-dateutil
1 change: 0 additions & 1 deletion services/spring-api/.openapi-generator/FILES

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package org.openapitools
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan
import org.springframework.scheduling.annotation.EnableScheduling

@SpringBootApplication
@EnableScheduling
@ComponentScan(basePackages = ["org.openapitools", "org.openapitools.api", "org.openapitools.model"])
class Application

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.openapitools.model.RecipeRequest
import org.openapitools.model.UserPreferences
import org.openapitools.model.UserProfile
import org.openapitools.repository.UserRepository
import org.slf4j.LoggerFactory
import org.springframework.core.ParameterizedTypeReference
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
Expand All @@ -30,42 +31,57 @@ class AIApiController(
private val userRepository: UserRepository,
private val objectMapper: ObjectMapper,
) : AIApi {
private val log = LoggerFactory.getLogger(javaClass)

// Cap how long we wait on the GenAI service before returning an error
private val aiTimeout = Duration.ofSeconds(60)

override fun aiHelpPost(
@Valid helpRequest: HelpRequest,
): ResponseEntity<HelpResponse> {
val user = userRepository.findByUsername(currentUsername()).orElseThrow()
val username = currentUsername()
log.info("Help request [user={}, promptLength={}]", username, helpRequest.prompt.length)
val user = userRepository.findByUsername(username).orElseThrow()
val response =
aiHelpWebClient
.post()
.uri("/ai/help")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(mapOf("profile" to user.toProfile(), "recipe" to helpRequest.recipe, "prompt" to helpRequest.prompt))
.retrieve()
.bodyValue(
objectMapper.writeValueAsString(
mapOf(
"profile" to user.toProfile(),
"recipe" to helpRequest.recipe,
"prompt" to helpRequest.prompt,
),
),
).retrieve()
.bodyToMono(HelpResponse::class.java)
.timeout(aiTimeout)
.onErrorMap(TimeoutException::class.java) { GatewayTimeoutException("GenAI service timed out") }
.block() ?: throw BadGatewayException("GenAI service unavailable or returned an unparseable response")
log.info("Help response delivered [user={}]", username)
return ResponseEntity.ok(response)
}

override fun aiRecipesPost(
@Valid recipeRequest: RecipeRequest,
): ResponseEntity<List<RecipeInput>> {
val user = userRepository.findByUsername(currentUsername()).orElseThrow()
val username = currentUsername()
log.info("Recipe generation request [user={}, promptLength={}]", username, recipeRequest.prompt.length)
val user = userRepository.findByUsername(username).orElseThrow()
val recipes =
aiRecipeWebClient
.post()
.uri("/ai/recipes")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(mapOf("profile" to user.toProfile(), "prompt" to recipeRequest.prompt))
.bodyValue(objectMapper.writeValueAsString(mapOf("profile" to user.toProfile(), "prompt" to recipeRequest.prompt)))
.retrieve()
.bodyToMono(object : ParameterizedTypeReference<List<RecipeInput>>() {})
.timeout(aiTimeout)
.onErrorMap(TimeoutException::class.java) { GatewayTimeoutException("GenAI service timed out") }
.block() ?: throw BadGatewayException("GenAI service unavailable or returned an unparseable response")
log.info("Recipe generation complete [user={}, count={}]", username, recipes.size)
return ResponseEntity.ok(recipes)
}

Expand Down
Loading
Loading