Files
dios.one 23dc306f12 init
2026-04-21 18:00:30 +07:00

197 lines
5.3 KiB
TypeScript

// Supabase Edge Function: generate-habits
// Deploy: supabase functions deploy generate-habits --no-verify-jwt
// Set secret: supabase secrets set GEMINI_API_KEY=your-key-here
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
const GEMINI_URL =
'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent';
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
function buildPrompt(story: string): string {
return `You are a thoughtful and emotionally intelligent personal growth coach.
A user has written a personal story about their current life and who they want to become.
Your job is NOT just to generate habits.
Your job is to deeply understand the person and reflect their intention back in a meaningful way.
---
INSTRUCTIONS:
1. Read the user's story carefully.
2. Understand:
- their struggles
- their desires
- their emotional tone
3. Then generate:
A. Identity Title
- A short, powerful sentence
- Feels personal and motivating
- Example: "I am someone who shows up even when it's hard"
B. Identity Summary
- 2-3 sentences
- Reflect their story in a human tone
- Make it feel like someone truly understands them
- Avoid generic phrases
C. Suggested Habits (5-8 items)
Rules:
- Very simple and actionable
- Can be done daily
- No complexity
- Each habit max 1 sentence
- Feels aligned with their story (not random)
IMPORTANT:
- Habits should feel like "small proof of identity"
- Not tasks, but expressions of who they want to become
D. Tone Style:
- Warm
- Supportive
- Slightly motivational
- Never robotic
- Never overly formal
- Avoid buzzwords like "optimize", "maximize"
---
OUTPUT FORMAT (JSON ONLY):
Return ONLY valid JSON. No explanation, no markdown, no extra text.
{
"identity_title": "",
"identity_summary": "",
"suggested_habits": [
"",
"",
"",
"",
""
]
}
---
EXAMPLE:
User story: "I feel like I procrastinate a lot and I want to be more focused and disciplined."
Bad output: "Improve productivity with structured routines"
Good output: "I am someone who takes action even when I don't feel ready"
Make the user feel understood, not analyzed.
---
User story:
"""
${story}
"""`;
}
serve(async (req) => {
// Handle CORS preflight
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}
try {
const { story } = await req.json();
if (!story || typeof story !== 'string' || story.trim().length < 10) {
return new Response(
JSON.stringify({ error: 'Please write at least a few sentences about yourself.' }),
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
const apiKey = Deno.env.get('GEMINI_API_KEY');
if (!apiKey) {
return new Response(
JSON.stringify({ error: 'AI service not configured. Please set GEMINI_API_KEY.' }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
// Call Gemini API
const geminiResponse = await fetch(`${GEMINI_URL}?key=${apiKey}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: buildPrompt(story.trim()) }] }],
generationConfig: {
temperature: 0.7,
topP: 0.9,
maxOutputTokens: 1024,
},
}),
});
if (!geminiResponse.ok) {
const errText = await geminiResponse.text();
console.error('Gemini API error:', errText);
return new Response(
JSON.stringify({ error: 'AI generation failed. Please try again.' }),
{ status: 502, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
const geminiData = await geminiResponse.json();
// Extract text from Gemini response
const rawText =
geminiData?.candidates?.[0]?.content?.parts?.[0]?.text || '';
// Parse JSON from response (handle potential markdown wrapping)
let cleaned = rawText.trim();
// Remove markdown code blocks if present
cleaned = cleaned.replace(/```json\s*/gi, '').replace(/```\s*/gi, '');
cleaned = cleaned.trim();
let result;
try {
result = JSON.parse(cleaned);
} catch {
console.error('Failed to parse Gemini output:', cleaned);
return new Response(
JSON.stringify({ error: 'AI returned invalid data. Please try again.' }),
{ status: 502, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
// Validate structure
if (
!result.identity_title ||
!result.identity_summary ||
!Array.isArray(result.suggested_habits) ||
result.suggested_habits.length === 0
) {
return new Response(
JSON.stringify({ error: 'AI returned incomplete data. Please try again.' }),
{ status: 502, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
return new Response(JSON.stringify(result), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
});
} catch (err) {
console.error('Function error:', err);
return new Response(
JSON.stringify({ error: 'Something went wrong. Please try again.' }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
});