merubah UI dan login dan register dan profile
This commit is contained in:
+80
-71
@@ -1,9 +1,6 @@
|
||||
import safeParseAI from '../utils/safeParseAI';
|
||||
import { getSuggestionsForIdentity } from '../utils/helpers';
|
||||
import { GEMINI_API_KEY } from '../config/keys';
|
||||
|
||||
const GEMINI_URL =
|
||||
'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent';
|
||||
import { AI_SERVICE_URL, AI_MODEL } from '../config/keys';
|
||||
|
||||
function buildPrompt(story) {
|
||||
return `You are a thoughtful and emotionally intelligent personal growth coach.
|
||||
@@ -15,6 +12,15 @@ Your job is to deeply understand the person and reflect their intention back in
|
||||
|
||||
---
|
||||
|
||||
LANGUAGE RULE (CRITICAL):
|
||||
Detect the language of the user's story.
|
||||
Your ENTIRE output (identity_title, identity_summary, ALL habits) MUST be in the SAME language as the user's story.
|
||||
If the story is in Indonesian, respond in Indonesian.
|
||||
If the story is in English, respond in English.
|
||||
If the story is in any other language, respond in that language.
|
||||
|
||||
---
|
||||
|
||||
INSTRUCTIONS:
|
||||
|
||||
1. Read the user's story carefully.
|
||||
@@ -28,7 +34,6 @@ INSTRUCTIONS:
|
||||
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
|
||||
@@ -36,25 +41,25 @@ B. Identity Summary
|
||||
- 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:
|
||||
C. Suggested Habits — generate exactly 20 habit objects
|
||||
- The FIRST 5 are TOP PRIORITY (most important, most aligned with story)
|
||||
- The remaining 15 are additional recommendations
|
||||
- All 20 must be simple, actionable, and doable daily
|
||||
- Habits should feel like "small proof of identity"
|
||||
- Not tasks, but expressions of who they want to become
|
||||
- Make each habit unique — no duplicates
|
||||
|
||||
Each habit MUST be an object with these fields:
|
||||
- "title": short habit name (max 8 words)
|
||||
- "best_time": recommended time of day to do it (e.g. "06:00", "07:30", "12:00", "18:00", "21:00", "05:30"). Use 24-hour format. Pick a realistic time that fits the activity.
|
||||
- "duration": how long per session in minutes as a number (e.g. 5, 10, 15, 20, 30, 45, 60). Must be a number, not a string.
|
||||
- "frequency": how often (e.g. "Every day", "3x per week", "Every morning", "Every evening", "Weekdays only")
|
||||
- "category": one of: "Mind", "Body", "Emotion", "Social", "Skill", "Discipline", "Health", "Creative"
|
||||
- "difficulty": one of: "Easy", "Medium", "Hard"
|
||||
- "why": 1 short sentence explaining why this habit matters for their identity
|
||||
|
||||
D. Tone Style:
|
||||
- Warm
|
||||
- Supportive
|
||||
- Slightly motivational
|
||||
- Never robotic
|
||||
- Never overly formal
|
||||
- Warm, supportive, slightly motivational
|
||||
- Never robotic or overly formal
|
||||
- Avoid buzzwords like "optimize", "maximize"
|
||||
|
||||
---
|
||||
@@ -65,86 +70,78 @@ Return ONLY valid JSON. No explanation, no markdown, no extra text.
|
||||
{
|
||||
"identity_title": "",
|
||||
"identity_summary": "",
|
||||
"priority_habits": [
|
||||
{
|
||||
"title": "",
|
||||
"best_time": "06:00",
|
||||
"duration": 15,
|
||||
"frequency": "",
|
||||
"category": "",
|
||||
"difficulty": "",
|
||||
"why": ""
|
||||
}
|
||||
],
|
||||
"suggested_habits": [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
{
|
||||
"title": "",
|
||||
"best_time": "07:00",
|
||||
"duration": 10,
|
||||
"frequency": "",
|
||||
"category": "",
|
||||
"difficulty": "",
|
||||
"why": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
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}
|
||||
"""`;
|
||||
}
|
||||
|
||||
const MAX_RETRIES = 2;
|
||||
const RETRY_DELAY_MS = 4000;
|
||||
|
||||
async function callGemini(story, attempt = 1) {
|
||||
const response = await fetch(`${GEMINI_URL}?key=${GEMINI_API_KEY}`, {
|
||||
async function callAI(prompt) {
|
||||
const response = await fetch(`${AI_SERVICE_URL}/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
contents: [{ parts: [{ text: buildPrompt(story) }] }],
|
||||
generationConfig: {
|
||||
temperature: 0.7,
|
||||
topP: 0.9,
|
||||
maxOutputTokens: 1024,
|
||||
},
|
||||
model: AI_MODEL,
|
||||
max_tokens: 2048,
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
}),
|
||||
});
|
||||
|
||||
// Rate limited — retry after delay
|
||||
if (response.status === 429 && attempt <= MAX_RETRIES) {
|
||||
console.warn(`Gemini rate limited, retrying in ${RETRY_DELAY_MS}ms (attempt ${attempt})...`);
|
||||
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
||||
return callGemini(story, attempt + 1);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errText = await response.text();
|
||||
console.warn('Gemini API error:', response.status, errText);
|
||||
console.warn('AI API error:', response.status, errText);
|
||||
throw new Error(`AI request failed (${response.status})`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
const json = await response.json();
|
||||
return json?.choices?.[0]?.message?.content || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate identity + habits from user's personal story using Gemini AI.
|
||||
* Retries on rate limit. Falls back to local generation if AI fails.
|
||||
* Generate identity + habits from user's personal story using AI.
|
||||
* Falls back to local generation if AI fails.
|
||||
*/
|
||||
export async function generateFromStory(story) {
|
||||
try {
|
||||
const json = await callGemini(story);
|
||||
const rawText = json?.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
||||
|
||||
const rawText = await callAI(buildPrompt(story));
|
||||
const parsed = safeParseAI(rawText);
|
||||
|
||||
if (
|
||||
parsed.identity_title &&
|
||||
parsed.identity_summary &&
|
||||
parsed.suggested_habits.length > 0
|
||||
(parsed.priority_habits?.length > 0 || parsed.suggested_habits?.length > 0)
|
||||
) {
|
||||
return {
|
||||
...parsed,
|
||||
suggested_habits: parsed.suggested_habits.slice(0, 8),
|
||||
identity_title: parsed.identity_title,
|
||||
identity_summary: parsed.identity_summary,
|
||||
priority_habits: (parsed.priority_habits || []).slice(0, 5),
|
||||
suggested_habits: (parsed.suggested_habits || []).slice(0, 15),
|
||||
source: 'ai',
|
||||
};
|
||||
}
|
||||
@@ -189,18 +186,30 @@ function generateFallback(story) {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate varied habits from the story text
|
||||
let habits = getSuggestionsForIdentity(words);
|
||||
if (habits.length < 3) {
|
||||
// Also try with the extracted title
|
||||
// Generate varied habits from the story text and convert to rich objects
|
||||
let habitTitles = getSuggestionsForIdentity(words);
|
||||
if (habitTitles.length < 8) {
|
||||
const titleHabits = getSuggestionsForIdentity(title);
|
||||
titleHabits.forEach((h) => { if (!habits.includes(h)) habits.push(h); });
|
||||
titleHabits.forEach((h) => { if (!habitTitles.includes(h)) habitTitles.push(h); });
|
||||
}
|
||||
|
||||
const times = ['06:00', '06:30', '07:00', '07:30', '08:00', '12:00', '17:00', '18:00', '19:00', '20:00', '21:00', '21:30'];
|
||||
const durations = [5, 10, 15, 20, 30, 10, 15, 20, 5, 10];
|
||||
const toRichHabit = (t, i) => ({
|
||||
title: t,
|
||||
best_time: times[i % times.length],
|
||||
duration: durations[i % durations.length],
|
||||
frequency: i < 5 ? 'Every day' : ['Every day', '3x per week', 'Every morning', 'Every evening', 'Weekdays only'][i % 5],
|
||||
category: ['Discipline', 'Body', 'Mind', 'Emotion', 'Health', 'Skill', 'Social', 'Creative'][i % 8],
|
||||
difficulty: i < 3 ? 'Easy' : i < 7 ? 'Medium' : 'Hard',
|
||||
why: '',
|
||||
});
|
||||
|
||||
return {
|
||||
identity_title: title,
|
||||
identity_summary: `Based on your story, this journey is about becoming ${title.toLowerCase()}. Every day is a step closer to the person you described.`,
|
||||
suggested_habits: habits.slice(0, 8),
|
||||
priority_habits: habitTitles.slice(0, 5).map(toRichHabit),
|
||||
suggested_habits: habitTitles.slice(5, 20).map((t, i) => toRichHabit(t, i + 5)),
|
||||
source: 'fallback',
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user