v1.2.9 generate diet with meals on the server

This commit is contained in:
bossanyit 2023-06-14 18:55:06 +02:00
parent 8b0e6fe6c3
commit d2ef1415a1
17 changed files with 647 additions and 71 deletions

View File

@ -11,7 +11,7 @@ plugins {
} }
group = "com.aitrainer" group = "com.aitrainer"
version = "1.2.8" version = "1.2.9"
java.sourceCompatibility = JavaVersion.VERSION_17 java.sourceCompatibility = JavaVersion.VERSION_17
repositories { repositories {

7
data/db/update_1_2_9.sql Normal file
View File

@ -0,0 +1,7 @@
START TRANSACTION;
ALTER TABLE `diet` ADD COLUMN `generating` tinyint(1) NULL;
UPDATE configuration set config_value = "1.2.9", date_change=CURRENT_DATE WHERE config_key = "db_version";
COMMIT;

View File

@ -1,9 +1,12 @@
package com.aitrainer.api.controller package com.aitrainer.api.controller
import com.aallam.openai.api.BetaOpenAI import com.aallam.openai.api.BetaOpenAI
import com.aallam.openai.api.chat.ChatMessage
import com.aallam.openai.api.chat.ChatRole
import com.aitrainer.api.model.OpenAI import com.aitrainer.api.model.OpenAI
import com.aitrainer.api.model.OpenAIChat import com.aitrainer.api.model.OpenAIChat
import com.aitrainer.api.openai.OpenAIService import com.aitrainer.api.openai.OpenAIService
import com.google.gson.Gson
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
@ -72,5 +75,45 @@ class OpenAIController() {
} }
return result return result
} }
@OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class)
suspend fun chat(@Value("\${openai.key}") openaiKey: String, modelName: String, temperature: Double, userInput: String, systemInput: String = "") : String? {
val systemMsg = ChatMessage(
role = ChatRole.User,
content = systemInput
)
val userMsg = ChatMessage(
role = ChatRole.User,
content = userInput
)
val listMessages: MutableList<ChatMessage> = mutableListOf()
listMessages.add(systemMsg)
listMessages.add(userMsg)
val gson = Gson()
val messages = gson.toJson(listMessages)
val openai = OpenAIChat(
messages = messages,
modelName = modelName,
temperature = temperature
)
var response: String? = null
val openAIService = OpenAIService(openaiKey, openai.modelName, openai.temperature)
val deferred = GlobalScope.async {
println(openai.messages)
openAIService.chatCompletion(openai.messages)
}
runBlocking {
try {
response = deferred.await().toString()
} catch (exception: Exception) {
println("Timeout OpenAI chat")
}
}
return response
}
} }

View File

@ -1,17 +1,18 @@
package com.aitrainer.api.controller.diet package com.aitrainer.api.controller.diet
import com.aallam.openai.api.BetaOpenAI import com.aallam.openai.api.BetaOpenAI
import com.aallam.openai.api.chat.ChatMessage import com.aitrainer.api.controller.OpenAIController
import com.aallam.openai.api.chat.ChatRole import com.aitrainer.api.model.diet.*
import com.aitrainer.api.model.OpenAIChat
import com.aitrainer.api.model.diet.Diet
import com.aitrainer.api.openai.OpenAIService
import com.aitrainer.api.repository.diet.DietRepository import com.aitrainer.api.repository.diet.DietRepository
import com.aitrainer.api.repository.diet.MealRepository
import com.aitrainer.api.service.DietFunctions.getDateFromDayName
import com.aitrainer.api.service.DietFunctions.normalizeMealName
import com.aitrainer.api.service.Meals
import com.google.gson.Gson import com.google.gson.Gson
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.http.MediaType import org.springframework.http.MediaType
@ -23,8 +24,9 @@ import java.util.*
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
class DietController(private val dietRepository: DietRepository) { class DietController(private val dietRepository: DietRepository, private val mealRepository: MealRepository) {
private val logger = LoggerFactory.getLogger(javaClass) private val logger = LoggerFactory.getLogger(javaClass)
private var openAiKey: String = ""
@PostMapping("/diet", produces = [MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"]) @PostMapping("/diet", produces = [MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"])
fun insert(@RequestBody diet: Diet): ResponseEntity<*> { fun insert(@RequestBody diet: Diet): ResponseEntity<*> {
@ -73,8 +75,8 @@ class DietController(private val dietRepository: DietRepository) {
} }
@OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class) @OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class)
@PostMapping("/diet/generate_premium/{dietUserId}", produces = [MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"]) @PostMapping("/diet/generate_premium/{dietUserId}/{test}", produces = [MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"])
fun generatePremiumDiet(@PathVariable dietUserId: Long, @RequestBody input: String, @Value("\${openai.key}") openaiKey: String): ResponseEntity<*> { fun generatePremiumDiet(@PathVariable dietUserId: Long, @PathVariable test: Boolean = false, @RequestBody input: String, @Value("\${openai.key}") openaiKey: String): ResponseEntity<*> {
val now = LocalDateTime.now() val now = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
@ -85,56 +87,369 @@ class DietController(private val dietRepository: DietRepository) {
dietUserId = dietUserId, dietUserId = dietUserId,
dietText = "", dietText = "",
startDate = todayString, startDate = todayString,
premium = 0 premium = 0,
generating = 1
) )
diet = dietRepository.save(diet) diet = dietRepository.save(diet)
println("new diet: $diet.dietId")
} }
val systemMsg = ChatMessage(
role = ChatRole.System, val result: String?
content = "Te egy táplálkozási szakértő vagy. A feladat mindig egy étrend elkészítése ebben a 3 lépésben:\n" + this.openAiKey = openaiKey
if ( !test) {
val systemInput = "Te egy táplálkozási szakértő vagy. A feladat egy étrend elkészítése ebben a 3 lépésben:\n" +
"\n" + "\n" +
"1. Vizsgáld meg az input adatokat, és készítsd el az étrendet\n" + "1. Vizsgáld meg az input adatokat, és készítsd el az étrendet\n" +
"2. Ellenőrizd az ételeket, hogy tényleg létezik-e olyan, és megfelel az input adatok feltételeinek\n" + "2. Ellenőrizd az ételeket, hogy tényleg létezik-e olyan, és megfelel az input adatok feltételeinek\n" +
"3. Ellenőrizd a kimeneti formátumot. Csak a megadott kimeneti formátumban válaszolj" "3. Ellenőrizd a kimeneti formátumot. Csak a megadott kimeneti formátumban válaszolj"
)
val userMsg = ChatMessage(
role = ChatRole.User,
content = input
)
val listMessages: MutableList<ChatMessage> = mutableListOf()
listMessages.add(systemMsg)
listMessages.add(userMsg)
val gson = Gson()
val messages = gson.toJson(listMessages)
val openai = OpenAIChat(
messages = messages,
//modelName = "gpt-3.5-turbo",
modelName = "gpt-4",
temperature = 0.9
)
var result: String? = null
val openAIService = OpenAIService(openaiKey, openai.modelName, openai.temperature)
val deferred = GlobalScope.async { val deferred = GlobalScope.async {
println(openai.messages) OpenAIController().chat(
openAIService.chatCompletion(openai.messages) modelName = "gpt-4",
temperature = 0.9,
userInput = input,
systemInput = systemInput,
openaiKey = openaiKey
)
} }
runBlocking { runBlocking {
try {
result = deferred.await().toString() result = deferred.await().toString()
} catch (exception: Exception) {
println("Timeout for diet generation $dietUserId")
} }
} if (result == null) {
if ( result == null ) {
return ResponseEntity.badRequest().build<String>() return ResponseEntity.badRequest().build<String>()
} }
} else {
result = "{\n" +
" \"DIET\": [\n" +
" {\n" +
" \"nameDay\": \"monday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"zabkása\", \"mandulatej\", \"málna\", \"méz\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"monday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"grillezett csirkemell\", \"quinoa\", \"spenót\", \"cherry paradicsom\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"monday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"sárgarépa\", \"hummusz\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"monday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"csicseriborsó-saláta\", \"paprika\", \"paradicsom\", \"uborka\", \"olívaolaj\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"tuesday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"rántotta\", \"sonka\", \"paradicsom\", \"gluténmentes kenyér\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"tuesday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"grillezett lazac\", \"barnarizs\", \"zöldbab\", \"citrom\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"tuesday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"natúr joghurt (laktózmentes)\", \"méz\", \"dió\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"tuesday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"paradicsomos quinoa\", \"spárga\", \"tonhal (konzerv)\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"wednesday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"zabkása\", \"mandulatej\", \"eper\", \"magok (napraforgó, tökmag)\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"wednesday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"töltött paprika\", \"rizs\", \"sovány darált pulykahús\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"wednesday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"cékla\", \"avokádó\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"wednesday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"kókusztejes csirke\", \"spenót\", \"barnarizs\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"thursday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"natúr joghurt (laktózmentes)\", \"mogyoróvaj\", \"banán\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"thursday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"görög saláta\", \"olívaolaj\", \"feta sajt (laktózmentes)\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"thursday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"mandula\", \"szárított füge\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"thursday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"grillezett hal\", \"brokkoli\", \"kukoricalisztből készült polenta\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"friday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"gluténmentes müzli\", \"mandulatej\", \"áfonya\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"friday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"lecsó\", \"kolbász (gluténmentes)\", \"gluténmentes kenyér\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"friday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"yoghurt (lactose free)\", \"honey\", \"walnut\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"friday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"grillezett padlizsán\", \"couscous(gluten free)\", \"csicseriborsó\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"saturday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"omlette\", \"sonka\", \"spenót\", \"gluténmentes kenyér\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"saturday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"húsgombóc\", \"paradicsomos mártás\", \"cukkinispirál\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"saturday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"narancs\", \"keksz (gluténmentes)\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"saturday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"saláta\", \"paradicsom\", \"uborka\", \"pulykamell\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"sunday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"zabkása\", \"mandulatej\", \"mazsola\", \"mogyoró\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"sunday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"grillezett csirkemell\", \"háromszínű quinoa\", \"répa\", \"zöldborsó\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"sunday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"natúr joghurt (laktózmentes)\", \"zabpehely\", \"banán\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"sunday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"töltött gomba\", \"couscous(gluten free)\", \"paradicsomsalsa\"]\n" +
" }\n" +
" ]\n" +
"}"
}
diet.dietText = result!! val gson = Gson()
diet.dietText = result
diet.startDate = todayString diet.startDate = todayString
diet.premium = 1 diet.premium = 1
return ResponseEntity.ok().body(dietRepository.save(diet)) diet.generating = 0
val dietSaved = dietRepository.save(diet)
val dietJson = JSONObject(diet.dietText)
val mealPlansJson = dietJson.getJSONArray("DIET")
val dietCustoms = mutableListOf<DietCustom>()
for (i in 0 until mealPlansJson.length()) {
val dietCustom = gson.fromJson(mealPlansJson.get(i).toString(), DietCustom::class.java)
dietCustoms.add(dietCustom)
} }
dietCustoms.forEach { dietCustom ->
val dietCustomChanged = addTemporaryQuantity(dietCustom)
var index = 0
dietCustomChanged.meals.forEach {meal ->
val mealId: Long = getMealIdFromCustom(meal, dietCustomChanged, index, false, diet.dietUserId )
val dietMeal = DietMeal(
mealId = mealId,
meal = meal,
mealName = dietCustom.nameDay + "|" + dietCustom.mealTime,
mealDate = getDateFromDayName(dietCustom.nameDay, dietCustom.mealTime),
quantity = dietCustom.quantities[index],
)
dietMeal.diet = dietSaved
dietSaved.meals.add(dietMeal)
}
index++
}
val dietSavedWithMeals = dietRepository.save(dietSaved)
return ResponseEntity.ok().body(dietSavedWithMeals)
}
fun addTemporaryQuantity(dietCustom: DietCustom): DietCustom {
for (i in 0 until dietCustom.meals.size) {
var sum = 400
when (dietCustom.mealTime) {
Meals.breakfast.toString() -> sum = 400
Meals.elevenses.toString() -> sum = 100
Meals.lunch.toString() -> sum = 500
Meals.snack.toString() -> sum = 150
Meals.dinner.toString() -> sum = 350
Meals.eveningsnack.toString() -> sum = 100
}
if (i == 0) {
dietCustom.quantities.add(sum * 0.7)
} else {
dietCustom.quantities.add(sum * 0.3 / (dietCustom.meals.size - 1))
}
}
return dietCustom
}
fun getMealIdFromCustom(mealName: String, dietCustom: DietCustom, mealIndex: Int, onlyQuantity: Boolean, dietUserId: Long): Long {
var mealId: Long = 0
var foundMeal: Meal? = mealRepository.findByName(mealName)
var dietCustomVar = dietCustom
if (foundMeal == null) {
dietCustomVar = addTemporaryQuantity(dietCustomVar)
if (!onlyQuantity) {
foundMeal = getMealCaloriesFromAI(mealName, dietCustomVar.quantities[mealIndex])
}
if (foundMeal != null) {
mealId = foundMeal.id
}
} else {
println("FoundMeal cals: ${foundMeal.calMin}")
mealId = foundMeal.id
}
return mealId
}
@OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class)
fun getMealCaloriesFromAI(mealName: String, quantity: Double): Meal? {
var meal: Meal? = null
val systemInput = "Te egy táplálkozási szakértő vagy. Feladat az ételek kalóriaértékének meghatározása"
val input = """
Add meg ennek az ételnek a kalóriaértékét és tápanyagfelosztását: $mealName 100 gramm.
A választ ebben JSON formátumban írd, ne fűzz semmilyen megjegyzést hozzá: {
"name":<string>,
"quantity":<double>,
"quantityUnit":<string>,
"calMin":<double>,
"calMax":<double>,
"fatMin":<double>,
"fatMax":<double>,
"chMin":<double>,
"chMax":<double>,
"proteinMin":<double>,
"proteinMax":<double>,
"sugar":<double>,
"servingUnit":<enum(db, szelet, adag, ml, liter)>
"serving":<double, servingUnit in grams> }
"""
val response: String?
val openaiKey = this.openAiKey
val deferred = GlobalScope.async {
OpenAIController().chat(
modelName = "gpt-3.5-turbo",
temperature = 0.0,
userInput = input,
systemInput = systemInput,
openaiKey = openaiKey)
}
runBlocking {
response = deferred.await().toString()
}
try {
val newMeal = Gson().fromJson(response, Meal::class.java)
val newMealName = newMeal.name
val found: Meal? = mealRepository.findByName(newMealName)
if (found == null) {
val baseQuantity = 100.0
val rate = quantity / baseQuantity
newMeal.quantity = baseQuantity
newMeal.calMin /= rate
newMeal.calMax /= rate
newMeal.chMin /= rate
newMeal.chMax /= rate
newMeal.fatMin /= rate
newMeal.fatMax /= rate
newMeal.proteinMin /= rate
newMeal.proteinMax /= rate
newMeal.sugar /= rate
newMeal.normalizedName = normalizeMealName(newMeal.name)
newMeal.serving = newMeal.serving
newMeal.servingUnit = newMeal.servingUnit
val savedMeal = mealRepository.save(newMeal)
println("save new meal: ${newMeal.name}, ${newMeal.calMin} kCal")
//updateServingForMeal(savedMeal)
meal = savedMeal
} else {
meal = found
}
} catch (e: Exception) {
println( "Error in newMeal decode JSON: '$e'")
}
return meal
}
@OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class)
fun updateServingForMeal(meal: Meal) {
val input = """
Add meg, hány gramm egy adag "${meal.name}" átlagosan.
A választ pontosan ebben a formátumban kérem: {"meal": <string>, "serving":<double (average in gramms)>, "servingUnit": <enum (adag, szelet, db, ml, liter)>}.
The response MUST NOT contain other words just the JSON.
"""
val response: String?
val openaiKey = this.openAiKey
val deferred = GlobalScope.async {
OpenAIController().chat(modelName = "gpt-3.5-turbo", temperature = 0.0, userInput = input, openaiKey = openaiKey)
}
runBlocking {
response = deferred.await().toString()
}
val serving = Gson().fromJson(response, MealServing::class.java)
meal.serving = serving.serving
meal.servingUnit = serving.servingUnit
meal.normalizedName = normalizeMealName(meal.name) // placeholder for your function
mealRepository.save(meal)
}
} }

View File

@ -13,6 +13,7 @@ data class Diet (
@Expose @get: NotNull var dietText: String = "", @Expose @get: NotNull var dietText: String = "",
@Expose @get: NotNull var startDate: String = "", @Expose @get: NotNull var startDate: String = "",
@Expose @get: NotNull var premium: Int = 0, @Expose @get: NotNull var premium: Int = 0,
@Expose @get: NotNull var generating: Int = 0,
) { ) {
@OneToMany(cascade = [(CascadeType.ALL)], fetch = FetchType.EAGER, mappedBy = "diet") @OneToMany(cascade = [(CascadeType.ALL)], fetch = FetchType.EAGER, mappedBy = "diet")
@Fetch(value = FetchMode.SUBSELECT) @Fetch(value = FetchMode.SUBSELECT)

View File

@ -0,0 +1,17 @@
package com.aitrainer.api.model.diet
import com.google.gson.annotations.Expose
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import org.jetbrains.annotations.NotNull
@Entity
data class DietCustom(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Expose val id: Int = 0,
@Expose @get: NotNull val nameDay: String = "",
@Expose @get: NotNull val mealTime: String = "",
@Expose @get: NotNull val meals: List<String> = listOf(),
@Expose @get: NotNull val quantities: MutableList<Double> = mutableListOf()
)

View File

@ -9,18 +9,18 @@ data class Meal (
@Expose @get: NotNull val name: String = "", @Expose @get: NotNull val name: String = "",
@Expose @get: NotNull var normalizedName: String = "", @Expose @get: NotNull var normalizedName: String = "",
@Expose @get: NotNull val quantity: Double = 0.0, @Expose @get: NotNull var quantity: Double = 0.0,
@Expose @get: NotNull val quantityUnit: String = "", @Expose @get: NotNull val quantityUnit: String = "",
@Expose @get: NotNull var serving: Double = 0.0, @Expose @get: NotNull var serving: Double = 0.0,
@Expose @get: NotNull var servingUnit: String = "", @Expose @get: NotNull var servingUnit: String = "",
@Expose @get: NotNull val description: String = "", @Expose @get: NotNull val description: String = "",
@Expose @get: NotNull val calMin: Double = 0.0, @Expose @get: NotNull var calMin: Double = 0.0,
@Expose @get: NotNull val calMax: Double = 0.0, @Expose @get: NotNull var calMax: Double = 0.0,
@Expose @get: NotNull val proteinMin: Double = 0.0, @Expose @get: NotNull var proteinMin: Double = 0.0,
@Expose @get: NotNull val proteinMax: Double = 0.0, @Expose @get: NotNull var proteinMax: Double = 0.0,
@Expose @get: NotNull val fatMin: Double = 0.0, @Expose @get: NotNull var fatMin: Double = 0.0,
@Expose @get: NotNull val fatMax: Double = 0.0, @Expose @get: NotNull var fatMax: Double = 0.0,
@Expose @get: NotNull val chMin: Double = 0.0, @Expose @get: NotNull var chMin: Double = 0.0,
@Expose @get: NotNull val chMax: Double = 0.0, @Expose @get: NotNull var chMax: Double = 0.0,
@Expose @get: NotNull val sugar: Double = 0.0, @Expose @get: NotNull var sugar: Double = 0.0,
) )

View File

@ -0,0 +1,13 @@
package com.aitrainer.api.model.diet
import com.google.gson.annotations.Expose
import jakarta.persistence.*
import org.jetbrains.annotations.NotNull
@Entity
data class MealServing(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Expose val id: Int = 0,
@Expose @get: NotNull val meal: String = "",
@Expose @get: NotNull val serving: Double = 0.0,
@Expose @get: NotNull val servingUnit: String = "",
)

View File

@ -11,4 +11,7 @@ interface DietRepository : JpaRepository<Diet, Long> {
@Query(" FROM Diet WHERE dietId = :dietId") @Query(" FROM Diet WHERE dietId = :dietId")
fun findByDietId(dietId: Long): Diet? fun findByDietId(dietId: Long): Diet?
@Query(" FROM Diet WHERE ((LENGTH(dietText) = 0 AND premium = 1 ) OR (generating = 1)) AND dietUserId = :dietUserId ORDER BY dietId DESC LIMIT 1")
fun findTheLastEmptyDiet(dietUserId: Long): Diet?
} }

View File

@ -0,0 +1,175 @@
package com.aitrainer.api.service
import java.time.LocalDateTime
import java.util.*
enum class Meals {
breakfast,
elevenses,
lunch,
snack,
dinner,
eveningsnack;
companion object {
private fun Meals.enumToString(): String = this.toString().lowercase(Locale.getDefault())
fun Meals.equalsStringTo(mealName: String): Boolean = this.toString().lowercase(Locale.getDefault()) == mealName.lowercase(
Locale.getDefault()
)
fun getMealsByIndex(index: Int): String? {
for (meal in Meals.values()) {
if (index == meal.ordinal) {
return meal.enumToString()
}
}
return null
}
private fun Meals.getMealTimeHour(): Int {
return when (this) {
breakfast -> 7
elevenses -> 9
lunch -> 11
snack -> 14
dinner -> 17
eveningsnack -> 20
}
}
fun getHour(text: String): Int? {
for (meal in Meals.values()) {
if (text.lowercase(Locale.getDefault()) == meal.enumToString() || text.lowercase(Locale.getDefault())
.contains(meal.enumToString().lowercase(Locale.getDefault()))) {
return meal.getMealTimeHour()
}
}
return null
}
fun contains(text: String): Meals? {
for (meal in Meals.values()) {
if (text.lowercase(Locale.getDefault()).contains(meal.enumToString().lowercase(Locale.getDefault()))) {
return meal
}
}
return null
}
}
}
enum class WeekDays {
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday;
companion object WeekDaysExt {
private fun WeekDays.enumToString(): String {
return this.toString().toLowerCase()
}
fun WeekDays.equalsStringTo(dayName: String): Boolean {
return this.toString().equals(dayName, ignoreCase = true)
}
private fun WeekDays.getWeekdayNumber(): Int {
return when (this) {
WeekDays.monday -> 1
WeekDays.tuesday -> 2
WeekDays.wednesday -> 3
WeekDays.thursday -> 4
WeekDays.friday -> 5
WeekDays.saturday -> 6
WeekDays.sunday -> 7
else -> 1
}
}
fun contains(text: String): WeekDays? {
var actualDay: WeekDays? = null
for (day in WeekDays.values()) {
if (text.contains(day.enumToString())) {
actualDay = day
break
}
}
return actualDay
}
fun weekDay(text: String): Int? {
var actualDay: Int? = null
for (day in WeekDays.values()) {
val weekDayHu = day.enumToString()
if (day.name.equals(text, true) || weekDayHu.equals(text, true)) {
actualDay = day.getWeekdayNumber()
break
}
}
return actualDay
}
}
}
object DietFunctions {
@Throws(Exception::class)
fun getDateFromDayName(dayName: String, mealName: String): String {
var dayNameVar = dayName
if (dayNameVar == "evening_snack") {
dayNameVar = "eveningSnack"
}
val today = LocalDateTime.now()
val weekDay = WeekDays.weekDay(dayNameVar)
val hour = Meals.getHour(mealName)
weekDay ?: throw Exception("$dayNameVar is not a week day")
hour ?: throw Exception("$mealName is not a meal")
val difference = today.dayOfWeek.value - weekDay
val day = today.minusDays(difference.toLong())
return "${day.year}-${day.monthValue}-${day.dayOfMonth} $hour:00"
}
fun normalizeMealName(hungarianWord: String): String {
val diacriticals: Map<Char, Char> = mapOf(
'á' to 'a',
'é' to 'e',
'í' to 'i',
'ó' to 'o',
'ö' to 'o',
'ő' to 'o',
'ú' to 'u',
'ü' to 'u',
'ű' to 'u',
'Á' to 'A',
'É' to 'E',
'Í' to 'I',
'Ó' to 'O',
'Ö' to 'O',
'Ő' to 'O',
'Ú' to 'U',
'Ü' to 'U',
'Ű' to 'U',
'-' to ' ',
',' to ' ',
)
var englishWord = ""
var found = false
for (element in hungarianWord) {
englishWord += diacriticals[element] ?: element
if (diacriticals.containsKey(element)) {
found = true
}
}
return englishWord.lowercase(Locale.getDefault())
}
}

View File

@ -16,7 +16,7 @@ logging.config=classpath:logback-spring.xml
logging.file=logs logging.file=logs
# if the database structure has been changed, increment this version number # if the database structure has been changed, increment this version number
application.version=1.2.8 application.version=1.2.9
jwt.secret=aitrainer jwt.secret=aitrainer

View File

@ -14,7 +14,7 @@ logging.config=classpath:logback-spring.xml
logging.file=logs logging.file=logs
# if the database structue has been changed, increment this version number # if the database structue has been changed, increment this version number
application.version=1.2.8 application.version=1.2.9
jwt.secret=aitrainer jwt.secret=aitrainer

View File

@ -14,7 +14,7 @@ logging.config=classpath:logback-spring.xml
logging.file=logs logging.file=logs
# if the database structue has been changed, increment this version number # if the database structue has been changed, increment this version number
application.version=1.2.8 application.version=1.2.9
jwt.secret=aitrainer jwt.secret=aitrainer

View File

@ -17,7 +17,7 @@ logging.config=classpath:logback-spring.xml
logging.file=logs logging.file=logs
# if the database structure has been changed, increment this version number # if the database structure has been changed, increment this version number
application.version=1.2.8 application.version=1.2.9
jwt.secret=aitrainer jwt.secret=aitrainer

View File

@ -41,6 +41,7 @@ class DietTest {
dietText = "Test diet text", dietText = "Test diet text",
startDate = "2023-02-02", startDate = "2023-02-02",
premium = 0, premium = 0,
generating = 0
) )
private var authToken: String? = "" private var authToken: String? = ""
@ -145,6 +146,7 @@ class DietTest {
dietText = "Premium Test", dietText = "Premium Test",
startDate = "2023-05-02", startDate = "2023-05-02",
premium = 1, premium = 1,
generating = 0
) )
val mvcResult2: MvcResult = mockMvc.perform( val mvcResult2: MvcResult = mockMvc.perform(
@ -166,14 +168,14 @@ class DietTest {
dietRepository.delete(newDiet2) dietRepository.delete(newDiet2)
} }
@Test //@Test
fun `find the last empty diet successfully`() { fun `find the last empty diet successfully`() {
val diet: Diet? = dietRepository.findTheLastEmptyDiet(2) val diet: Diet? = dietRepository.findTheLastEmptyDiet(2)
assertNotNull(diet) assertNotNull(diet)
assertEquals(diet.premium, 1) assertEquals(diet.premium, 1)
} }
//@Test @Test
fun `generate premium diet successfully`() { fun `generate premium diet successfully`() {
val input = "Készíts egy személyre szabott heti étrendet ezekkel a paraméterekkel:\n" + val input = "Készíts egy személyre szabott heti étrendet ezekkel a paraméterekkel:\n" +
@ -198,7 +200,7 @@ class DietTest {
"]}" "]}"
mockMvc.perform( mockMvc.perform(
MockMvcRequestBuilders.post("/api/diet/generate_premium/2") MockMvcRequestBuilders.post("/api/diet/generate_premium/2/true")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer $authToken") .header("Authorization", "Bearer $authToken")
.content(input) .content(input)

View File

@ -138,11 +138,11 @@ class MealTest {
.header("Authorization", "Bearer $authToken") .header("Authorization", "Bearer $authToken")
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk) .andExpect(status().isOk)
.andExpect(jsonPath("$.[3].name").value("Tükörtojás")) .andExpect(jsonPath("$.[73].name").value("Tükörtojás"))
.andExpect(jsonPath("$.[3].normalizedName").value("Tukortojas")) .andExpect(jsonPath("$.[73].normalizedName").value("Tukortojas"))
.andExpect(jsonPath("$.[4].name").value("Tigris buci")) .andExpect(jsonPath("$.[74].name").value("Tigris buci"))
.andExpect(jsonPath("$.[5].quantity").value(330.0)) .andExpect(jsonPath("$.[75].quantity").value(330.0))
.andExpect(jsonPath("$.[5].name").value("Töltötttojás")) .andExpect(jsonPath("$.[75].name").value("Töltötttojás"))
// Act & Assert // Act & Assert

View File

@ -54,7 +54,7 @@ class OpenAITest {
}*/ }*/
@OptIn(BetaOpenAI::class) @OptIn(BetaOpenAI::class)
@Test //@Test
fun `get a chat message completion`() { fun `get a chat message completion`() {
val systemMsg = ChatMessage( val systemMsg = ChatMessage(
role = ChatRole.User, role = ChatRole.User,
@ -86,7 +86,7 @@ class OpenAITest {
} }
@OptIn(BetaOpenAI::class) @OptIn(BetaOpenAI::class)
@Test // @Test
fun `get a chat message completion no modelname`() { fun `get a chat message completion no modelname`() {
val systemMsg = ChatMessage( val systemMsg = ChatMessage(
role = ChatRole.User, role = ChatRole.User,
@ -115,7 +115,7 @@ class OpenAITest {
} }
@Test // @Test
fun `get a question successfully with model name`() { fun `get a question successfully with model name`() {
val question = "Who the f. is Alice? Who sing that song?" val question = "Who the f. is Alice? Who sing that song?"
val openai = OpenAI( val openai = OpenAI(
@ -134,7 +134,7 @@ class OpenAITest {
} }
@Test // @Test
fun `get models successfully`() { fun `get models successfully`() {
mockMvc.perform( mockMvc.perform(
MockMvcRequestBuilders.get("/api/openai/list_models") MockMvcRequestBuilders.get("/api/openai/list_models")