From bf532ba35d7d6b1e614c4000ddda5f0b3392ce3a Mon Sep 17 00:00:00 2001 From: echo Date: Mon, 24 Nov 2025 19:25:34 +0100 Subject: [PATCH] deepseek integrated succesfully --- apps/admin/.env.example | 3 + apps/admin/data/fitai.db | Bin 98304 -> 98304 bytes .../app/api/recommendations/generate/route.ts | 176 +++++++++++++----- apps/admin/src/app/recommendations/page.tsx | 27 ++- 4 files changed, 155 insertions(+), 51 deletions(-) create mode 100644 apps/admin/.env.example diff --git a/apps/admin/.env.example b/apps/admin/.env.example new file mode 100644 index 0000000..a30effd --- /dev/null +++ b/apps/admin/.env.example @@ -0,0 +1,3 @@ +# DeepSeek AI API Key +# Get your API key from https://platform.deepseek.com/ +DEEPSEEK_API_KEY=your_deepseek_api_key_here diff --git a/apps/admin/data/fitai.db b/apps/admin/data/fitai.db index 2f02afee6bccd73320dcc7c942d3174278770167..640dc8b0211bab254642dc652f3c7cc797420533 100644 GIT binary patch delta 1376 zcma)6-HIGV6rL7#34?S5MG2e039M-1bZ__U%rY6+$i|omx*!_8hzM3!pXshlbysgy z^^S8j~+dg}}>z{P|7yh6A6aVDg2XEo8pM>7F*ImD$$N3+j*E_iMS|{pkJnuwjXWM(f z>~}AJb$Gb-q$Wj9Z{f91A-KR#Fxw zS;;YlvbI_*tf07=kdpGs3g)VYq!uzIWQ}EJ^BxpTl$I`{(_+tIh9b)eq)Dihv4AD& zY#zcFSYVN$?m?v$DKf^1%QQ=CCjVakYAorim=#2l!h0!jl$dB{5lq2|$Fbm6X@Mq$ zPv&WJFheOx(fZAgy>}M`Hc=Wha&?S)9)4+=wj{ykL@()evg_9gJVckI&a+o zb5t{HdJalLRhyjPX=$jcWVghOs7%}kjcY=uL8>yyl}ru1A4l{d)sNm=ERo`k5blXm zn7maN4}r8TNL<4((ZWn$>T)@o^NZt*43^|6mC?3xoofYq2M6iOve;ZU@Ge3Ca|g^@=d{Gu!*UF z$%34t#&rV{!e@2K^D*3C|MQLa&PF_pvKuS*!CPPR9JO}SRWPfN3c+h$x6Rs8YLXoj zNkN@UxyYIcWh@oV$V(c$uHe2#oYJslwYIBgH@Bv6SCljXW4J?eOT*JJqN+OV|HNBl zq=0NwoGU3>7k2tYqJpDZl!{$6JdNZS?rKp^fqn=Q$r9)XyVO2J#;iUJudaW1oRkb=EzG?2pXuA3IV3hm|n#vZ;!|-O;eKgqy8|6 delta 86 zcmV-c0IC0gfChko29O&8*pVDV0obu%q%Q;s4v+w|3NT#`k)SKHkUn(b3JDIh01o~R s*$%X~5Fi164w0ZImya$1B)8Zu0W3iV0R`{?1@N;W5atE9@O1$Y3Jh%>WdHyG diff --git a/apps/admin/src/app/api/recommendations/generate/route.ts b/apps/admin/src/app/api/recommendations/generate/route.ts index bf209da..cbb6541 100644 --- a/apps/admin/src/app/api/recommendations/generate/route.ts +++ b/apps/admin/src/app/api/recommendations/generate/route.ts @@ -3,7 +3,7 @@ import { getDatabase } from "@/lib/database"; export async function POST(req: Request) { try { - const { userId } = await req.json(); + const { userId, useExternalModel } = await req.json(); if (!userId) { return NextResponse.json({ error: "User ID is required" }, { status: 400 }); @@ -21,7 +21,7 @@ export async function POST(req: Request) { ); } - // Construct prompt for Ollama + // Construct prompt const prompt = ` You are a professional fitness trainer and nutritionist. Generate a detailed daily recommendation for a user with the following profile: @@ -42,66 +42,144 @@ export async function POST(req: Request) { } `; - // Call Ollama - const ollamaResponse = await fetch("http://localhost:11434/api/generate", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: "gemma3:latest", // Make sure this model is pulled - prompt: prompt, - stream: false, - format: "json", - }), - }); - - if (!ollamaResponse.ok) { - console.error("Ollama API error:", await ollamaResponse.text()); - return NextResponse.json( - { error: "Failed to generate recommendation from AI service" }, - { status: 500 } - ); - } - - const aiData = await ollamaResponse.json(); - console.log("Raw AI Response:", aiData.response); - let parsedResponse; - try { - // Helper to clean up the response - let cleanResponse = aiData.response.trim(); - // Remove markdown code blocks if present - if (cleanResponse.startsWith("```json")) { - cleanResponse = cleanResponse.replace(/^```json\s*/, "").replace(/\s*```$/, ""); - } else if (cleanResponse.startsWith("```")) { - cleanResponse = cleanResponse.replace(/^```\s*/, "").replace(/\s*```$/, ""); + if (useExternalModel) { + // Use DeepSeek AI + const deepseekApiKey = process.env.DEEPSEEK_API_KEY; + + if (!deepseekApiKey) { + return NextResponse.json( + { error: "DeepSeek API key not configured" }, + { status: 500 } + ); } - // Find the first '{' and last '}' to extract the JSON object - const firstBrace = cleanResponse.indexOf("{"); - const lastBrace = cleanResponse.lastIndexOf("}"); + console.log("Using DeepSeek AI model..."); - if (firstBrace !== -1 && lastBrace !== -1) { - cleanResponse = cleanResponse.substring(firstBrace, lastBrace + 1); + const deepseekResponse = await fetch("https://api.deepseek.com/v1/chat/completions", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${deepseekApiKey}`, + }, + body: JSON.stringify({ + model: "deepseek-chat", + messages: [ + { + role: "system", + content: "You are a professional fitness trainer and nutritionist. Always respond with valid JSON only, no markdown or code blocks." + }, + { + role: "user", + content: prompt + } + ], + temperature: 0.7, + max_tokens: 1000, + }), + }); + + if (!deepseekResponse.ok) { + const errorText = await deepseekResponse.text(); + console.error("DeepSeek API error:", errorText); + return NextResponse.json( + { error: "Failed to generate recommendation from DeepSeek AI" }, + { status: 500 } + ); } - parsedResponse = JSON.parse(cleanResponse); - } catch (e) { - // Fallback if model doesn't return perfect JSON despite instruction - console.error("Failed to parse AI response:", aiData.response); - return NextResponse.json( - { error: "Invalid response format from AI model" }, - { status: 500 } - ); + const deepseekData = await deepseekResponse.json(); + console.log("Raw DeepSeek Response:", deepseekData); + + try { + const content = deepseekData.choices[0].message.content; + let cleanResponse = content.trim(); + + // Remove markdown code blocks if present + if (cleanResponse.startsWith("```json")) { + cleanResponse = cleanResponse.replace(/^```json\s*/, "").replace(/\s*```$/, ""); + } else if (cleanResponse.startsWith("```")) { + cleanResponse = cleanResponse.replace(/^```\s*/, "").replace(/\s*```$/, ""); + } + + // Find the first '{' and last '}' to extract the JSON object + const firstBrace = cleanResponse.indexOf("{"); + const lastBrace = cleanResponse.lastIndexOf("}"); + + if (firstBrace !== -1 && lastBrace !== -1) { + cleanResponse = cleanResponse.substring(firstBrace, lastBrace + 1); + } + + parsedResponse = JSON.parse(cleanResponse); + } catch (e) { + console.error("Failed to parse DeepSeek response:", deepseekData); + return NextResponse.json( + { error: "Invalid response format from DeepSeek AI" }, + { status: 500 } + ); + } + } else { + // Use local Ollama + console.log("Using local Ollama model..."); + + const ollamaResponse = await fetch("http://localhost:11434/api/generate", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: "gemma3:latest", + prompt: prompt, + stream: false, + format: "json", + }), + }); + + if (!ollamaResponse.ok) { + console.error("Ollama API error:", await ollamaResponse.text()); + return NextResponse.json( + { error: "Failed to generate recommendation from Ollama" }, + { status: 500 } + ); + } + + const aiData = await ollamaResponse.json(); + console.log("Raw Ollama Response:", aiData.response); + + try { + let cleanResponse = aiData.response.trim(); + + // Remove markdown code blocks if present + if (cleanResponse.startsWith("```json")) { + cleanResponse = cleanResponse.replace(/^```json\s*/, "").replace(/\s*```$/, ""); + } else if (cleanResponse.startsWith("```")) { + cleanResponse = cleanResponse.replace(/^```\s*/, "").replace(/\s*```$/, ""); + } + + // Find the first '{' and last '}' to extract the JSON object + const firstBrace = cleanResponse.indexOf("{"); + const lastBrace = cleanResponse.lastIndexOf("}"); + + if (firstBrace !== -1 && lastBrace !== -1) { + cleanResponse = cleanResponse.substring(firstBrace, lastBrace + 1); + } + + parsedResponse = JSON.parse(cleanResponse); + } catch (e) { + console.error("Failed to parse Ollama response:", aiData.response); + return NextResponse.json( + { error: "Invalid response format from Ollama" }, + { status: 500 } + ); + } } // Save to database const recommendation = await db.createRecommendation({ id: crypto.randomUUID(), userId, - fitnessProfileId: profile.userId, // Using userId as ID for now since it's 1:1 + fitnessProfileId: profile.userId, type: 'ai_plan', content: parsedResponse.recommendationText, activityPlan: parsedResponse.activityPlan, diff --git a/apps/admin/src/app/recommendations/page.tsx b/apps/admin/src/app/recommendations/page.tsx index 4cf3c91..dbdf00a 100644 --- a/apps/admin/src/app/recommendations/page.tsx +++ b/apps/admin/src/app/recommendations/page.tsx @@ -26,6 +26,7 @@ export default function RecommendationsPage() { const [pendingRecommendations, setPendingRecommendations] = useState([]); const [loading, setLoading] = useState(true); const [generating, setGenerating] = useState(null); + const [useExternalModel, setUseExternalModel] = useState(false); useEffect(() => { fetchData(); @@ -56,7 +57,7 @@ export default function RecommendationsPage() { const res = await fetch("/api/recommendations/generate", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ userId }), + body: JSON.stringify({ userId, useExternalModel }), }); if (!res.ok) { @@ -143,7 +144,29 @@ export default function RecommendationsPage() { return (
-

AI Recommendations

+
+

AI Recommendations

+ + {/* Model Selection Toggle */} +
+ + {useExternalModel ? "DeepSeek AI" : "Local Ollama"} + + + + {useExternalModel ? "External" : "Local"} + +
+
{/* Generate Section */}