192 lines
7.2 KiB
TypeScript
192 lines
7.2 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { getDatabase } from "@/lib/database";
|
|
import { buildAIContext } from "@/lib/ai/ai-context";
|
|
import { buildEnhancedPrompt, buildBasicPrompt } from "@/lib/ai/prompt-builder";
|
|
|
|
export async function POST(req: Request) {
|
|
try {
|
|
const { userId, useExternalModel } = await req.json();
|
|
|
|
if (!userId) {
|
|
return NextResponse.json({ error: "User ID is required" }, { status: 400 });
|
|
}
|
|
|
|
const db = await getDatabase();
|
|
|
|
// Fetch fitness profile
|
|
const profile = await db.getFitnessProfileByUserId(userId);
|
|
|
|
if (!profile) {
|
|
return NextResponse.json(
|
|
{ error: "Fitness profile not found for this user" },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
// Build AI context with goals and recommendations
|
|
let prompt: string;
|
|
try {
|
|
const context = await buildAIContext(userId);
|
|
prompt = buildEnhancedPrompt(context);
|
|
console.log('Using enhanced AI context with goals and history');
|
|
} catch (error) {
|
|
// Fallback to basic prompt if context building fails
|
|
console.warn('Failed to build AI context, using basic prompt:', error);
|
|
prompt = buildBasicPrompt(profile);
|
|
}
|
|
|
|
let parsedResponse;
|
|
|
|
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 }
|
|
);
|
|
}
|
|
|
|
console.log("Using DeepSeek AI model...");
|
|
|
|
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 }
|
|
);
|
|
}
|
|
|
|
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,
|
|
type: 'ai_plan',
|
|
content: parsedResponse.recommendationText,
|
|
activityPlan: parsedResponse.activityPlan,
|
|
dietPlan: parsedResponse.dietPlan,
|
|
status: 'pending'
|
|
});
|
|
|
|
return NextResponse.json(recommendation);
|
|
} catch (error) {
|
|
console.error("Error generating recommendation:", error);
|
|
return NextResponse.json(
|
|
{ error: "Internal server error" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|