v1.2.9 generate diet with meals on the server
This commit is contained in:
parent
8b0e6fe6c3
commit
d2ef1415a1
@ -11,7 +11,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = "com.aitrainer"
|
||||
version = "1.2.8"
|
||||
version = "1.2.9"
|
||||
java.sourceCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
repositories {
|
||||
|
7
data/db/update_1_2_9.sql
Normal file
7
data/db/update_1_2_9.sql
Normal 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;
|
@ -1,9 +1,12 @@
|
||||
package com.aitrainer.api.controller
|
||||
|
||||
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.OpenAIChat
|
||||
import com.aitrainer.api.openai.OpenAIService
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
@ -72,5 +75,45 @@ class OpenAIController() {
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,18 @@
|
||||
package com.aitrainer.api.controller.diet
|
||||
|
||||
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.OpenAIChat
|
||||
import com.aitrainer.api.model.diet.Diet
|
||||
import com.aitrainer.api.openai.OpenAIService
|
||||
import com.aitrainer.api.controller.OpenAIController
|
||||
import com.aitrainer.api.model.diet.*
|
||||
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 kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.json.JSONObject
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.http.MediaType
|
||||
@ -23,8 +24,9 @@ import java.util.*
|
||||
|
||||
@RestController
|
||||
@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 var openAiKey: String = ""
|
||||
|
||||
@PostMapping("/diet", produces = [MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"])
|
||||
fun insert(@RequestBody diet: Diet): ResponseEntity<*> {
|
||||
@ -73,8 +75,8 @@ class DietController(private val dietRepository: DietRepository) {
|
||||
}
|
||||
|
||||
@OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class)
|
||||
@PostMapping("/diet/generate_premium/{dietUserId}", produces = [MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"])
|
||||
fun generatePremiumDiet(@PathVariable dietUserId: Long, @RequestBody input: String, @Value("\${openai.key}") openaiKey: String): ResponseEntity<*> {
|
||||
@PostMapping("/diet/generate_premium/{dietUserId}/{test}", produces = [MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"])
|
||||
fun generatePremiumDiet(@PathVariable dietUserId: Long, @PathVariable test: Boolean = false, @RequestBody input: String, @Value("\${openai.key}") openaiKey: String): ResponseEntity<*> {
|
||||
|
||||
val now = LocalDateTime.now()
|
||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
@ -85,56 +87,369 @@ class DietController(private val dietRepository: DietRepository) {
|
||||
dietUserId = dietUserId,
|
||||
dietText = "",
|
||||
startDate = todayString,
|
||||
premium = 0
|
||||
premium = 0,
|
||||
generating = 1
|
||||
)
|
||||
diet = dietRepository.save(diet)
|
||||
println("new diet: $diet.dietId")
|
||||
}
|
||||
val systemMsg = ChatMessage(
|
||||
role = ChatRole.System,
|
||||
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" +
|
||||
|
||||
val result: String?
|
||||
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" +
|
||||
"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" +
|
||||
"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 {
|
||||
println(openai.messages)
|
||||
openAIService.chatCompletion(openai.messages)
|
||||
OpenAIController().chat(
|
||||
modelName = "gpt-4",
|
||||
temperature = 0.9,
|
||||
userInput = input,
|
||||
systemInput = systemInput,
|
||||
openaiKey = openaiKey
|
||||
)
|
||||
}
|
||||
runBlocking {
|
||||
try {
|
||||
result = deferred.await().toString()
|
||||
} catch (exception: Exception) {
|
||||
println("Timeout for diet generation $dietUserId")
|
||||
}
|
||||
|
||||
}
|
||||
if (result == null) {
|
||||
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.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)
|
||||
}
|
||||
|
||||
}
|
@ -13,6 +13,7 @@ data class Diet (
|
||||
@Expose @get: NotNull var dietText: String = "",
|
||||
@Expose @get: NotNull var startDate: String = "",
|
||||
@Expose @get: NotNull var premium: Int = 0,
|
||||
@Expose @get: NotNull var generating: Int = 0,
|
||||
) {
|
||||
@OneToMany(cascade = [(CascadeType.ALL)], fetch = FetchType.EAGER, mappedBy = "diet")
|
||||
@Fetch(value = FetchMode.SUBSELECT)
|
||||
|
17
src/main/kotlin/com/aitrainer/api/model/diet/DietCustom.kt
Normal file
17
src/main/kotlin/com/aitrainer/api/model/diet/DietCustom.kt
Normal 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()
|
||||
)
|
@ -9,18 +9,18 @@ data class Meal (
|
||||
|
||||
@Expose @get: NotNull val name: 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 var serving: Double = 0.0,
|
||||
@Expose @get: NotNull var servingUnit: String = "",
|
||||
@Expose @get: NotNull val description: String = "",
|
||||
@Expose @get: NotNull val calMin: Double = 0.0,
|
||||
@Expose @get: NotNull val calMax: Double = 0.0,
|
||||
@Expose @get: NotNull val proteinMin: Double = 0.0,
|
||||
@Expose @get: NotNull val proteinMax: Double = 0.0,
|
||||
@Expose @get: NotNull val fatMin: Double = 0.0,
|
||||
@Expose @get: NotNull val fatMax: Double = 0.0,
|
||||
@Expose @get: NotNull val chMin: Double = 0.0,
|
||||
@Expose @get: NotNull val chMax: Double = 0.0,
|
||||
@Expose @get: NotNull val sugar: Double = 0.0,
|
||||
@Expose @get: NotNull var calMin: Double = 0.0,
|
||||
@Expose @get: NotNull var calMax: Double = 0.0,
|
||||
@Expose @get: NotNull var proteinMin: Double = 0.0,
|
||||
@Expose @get: NotNull var proteinMax: Double = 0.0,
|
||||
@Expose @get: NotNull var fatMin: Double = 0.0,
|
||||
@Expose @get: NotNull var fatMax: Double = 0.0,
|
||||
@Expose @get: NotNull var chMin: Double = 0.0,
|
||||
@Expose @get: NotNull var chMax: Double = 0.0,
|
||||
@Expose @get: NotNull var sugar: Double = 0.0,
|
||||
)
|
13
src/main/kotlin/com/aitrainer/api/model/diet/MealServing.kt
Normal file
13
src/main/kotlin/com/aitrainer/api/model/diet/MealServing.kt
Normal 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 = "",
|
||||
)
|
@ -11,4 +11,7 @@ interface DietRepository : JpaRepository<Diet, Long> {
|
||||
|
||||
@Query(" FROM Diet WHERE dietId = :dietId")
|
||||
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?
|
||||
}
|
175
src/main/kotlin/com/aitrainer/api/service/DietFunctions.kt
Normal file
175
src/main/kotlin/com/aitrainer/api/service/DietFunctions.kt
Normal 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())
|
||||
}
|
||||
|
||||
}
|
@ -16,7 +16,7 @@ logging.config=classpath:logback-spring.xml
|
||||
logging.file=logs
|
||||
|
||||
# if the database structure has been changed, increment this version number
|
||||
application.version=1.2.8
|
||||
application.version=1.2.9
|
||||
|
||||
jwt.secret=aitrainer
|
||||
|
||||
|
@ -14,7 +14,7 @@ logging.config=classpath:logback-spring.xml
|
||||
logging.file=logs
|
||||
|
||||
# if the database structue has been changed, increment this version number
|
||||
application.version=1.2.8
|
||||
application.version=1.2.9
|
||||
|
||||
jwt.secret=aitrainer
|
||||
|
||||
|
@ -14,7 +14,7 @@ logging.config=classpath:logback-spring.xml
|
||||
logging.file=logs
|
||||
|
||||
# if the database structue has been changed, increment this version number
|
||||
application.version=1.2.8
|
||||
application.version=1.2.9
|
||||
|
||||
jwt.secret=aitrainer
|
||||
|
||||
|
@ -17,7 +17,7 @@ logging.config=classpath:logback-spring.xml
|
||||
logging.file=logs
|
||||
|
||||
# if the database structure has been changed, increment this version number
|
||||
application.version=1.2.8
|
||||
application.version=1.2.9
|
||||
|
||||
jwt.secret=aitrainer
|
||||
|
||||
|
@ -41,6 +41,7 @@ class DietTest {
|
||||
dietText = "Test diet text",
|
||||
startDate = "2023-02-02",
|
||||
premium = 0,
|
||||
generating = 0
|
||||
)
|
||||
|
||||
private var authToken: String? = ""
|
||||
@ -145,6 +146,7 @@ class DietTest {
|
||||
dietText = "Premium Test",
|
||||
startDate = "2023-05-02",
|
||||
premium = 1,
|
||||
generating = 0
|
||||
)
|
||||
|
||||
val mvcResult2: MvcResult = mockMvc.perform(
|
||||
@ -166,14 +168,14 @@ class DietTest {
|
||||
dietRepository.delete(newDiet2)
|
||||
}
|
||||
|
||||
@Test
|
||||
//@Test
|
||||
fun `find the last empty diet successfully`() {
|
||||
val diet: Diet? = dietRepository.findTheLastEmptyDiet(2)
|
||||
assertNotNull(diet)
|
||||
assertEquals(diet.premium, 1)
|
||||
}
|
||||
|
||||
//@Test
|
||||
@Test
|
||||
fun `generate premium diet successfully`() {
|
||||
|
||||
val input = "Készíts egy személyre szabott heti étrendet ezekkel a paraméterekkel:\n" +
|
||||
@ -198,7 +200,7 @@ class DietTest {
|
||||
"]}"
|
||||
|
||||
mockMvc.perform(
|
||||
MockMvcRequestBuilders.post("/api/diet/generate_premium/2")
|
||||
MockMvcRequestBuilders.post("/api/diet/generate_premium/2/true")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", "Bearer $authToken")
|
||||
.content(input)
|
||||
|
@ -138,11 +138,11 @@ class MealTest {
|
||||
.header("Authorization", "Bearer $authToken")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.[3].name").value("Tükörtojás"))
|
||||
.andExpect(jsonPath("$.[3].normalizedName").value("Tukortojas"))
|
||||
.andExpect(jsonPath("$.[4].name").value("Tigris buci"))
|
||||
.andExpect(jsonPath("$.[5].quantity").value(330.0))
|
||||
.andExpect(jsonPath("$.[5].name").value("Töltötttojás"))
|
||||
.andExpect(jsonPath("$.[73].name").value("Tükörtojás"))
|
||||
.andExpect(jsonPath("$.[73].normalizedName").value("Tukortojas"))
|
||||
.andExpect(jsonPath("$.[74].name").value("Tigris buci"))
|
||||
.andExpect(jsonPath("$.[75].quantity").value(330.0))
|
||||
.andExpect(jsonPath("$.[75].name").value("Töltötttojás"))
|
||||
|
||||
|
||||
// Act & Assert
|
||||
|
@ -54,7 +54,7 @@ class OpenAITest {
|
||||
}*/
|
||||
|
||||
@OptIn(BetaOpenAI::class)
|
||||
@Test
|
||||
//@Test
|
||||
fun `get a chat message completion`() {
|
||||
val systemMsg = ChatMessage(
|
||||
role = ChatRole.User,
|
||||
@ -86,7 +86,7 @@ class OpenAITest {
|
||||
}
|
||||
|
||||
@OptIn(BetaOpenAI::class)
|
||||
@Test
|
||||
// @Test
|
||||
fun `get a chat message completion no modelname`() {
|
||||
val systemMsg = ChatMessage(
|
||||
role = ChatRole.User,
|
||||
@ -115,7 +115,7 @@ class OpenAITest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
fun `get a question successfully with model name`() {
|
||||
val question = "Who the f. is Alice? Who sing that song?"
|
||||
val openai = OpenAI(
|
||||
@ -134,7 +134,7 @@ class OpenAITest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
fun `get models successfully`() {
|
||||
mockMvc.perform(
|
||||
MockMvcRequestBuilders.get("/api/openai/list_models")
|
||||
|
Loading…
Reference in New Issue
Block a user