merubah UI dan login dan register dan profile

This commit is contained in:
dios.one
2026-05-04 17:32:06 +07:00
parent 9a9ad9c741
commit a5fcbb6c26
51 changed files with 4866 additions and 1238 deletions
+80 -71
View File
@@ -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',
};
}