← back to ajthal__noomi-bodi

Function bodies 299 total

All specs Real LLM only Function bodies
classifyIntent function · typescript · L247-L253 (7 LOC)
src/services/claude.ts
export function classifyIntent(message: string, hasImage: boolean): ToolIntent {
  if (hasImage) return 'meal_log';
  if (MEAL_LOG_PATTERNS.test(message)) return 'meal_log';
  if (DATA_QUERY_PATTERNS.test(message)) return 'data_query';
  if (SUGGESTION_PATTERNS.test(message)) return 'meal_suggestion';
  return 'general';
}
buildSystemBlocks function · typescript · L267-L270 (4 LOC)
src/services/claude.ts
export function buildSystemBlocks(
  staticPrompt: string,
  dynamicContext: string,
): { type: 'text'; text: string; cache_control?: { type: string } }[] {
sendMessageToClaude function · typescript · L287-L495 (209 LOC)
src/services/claude.ts
export async function sendMessageToClaude(
  messages: ChatMessage[],
  apiKey?: string | null,
  systemPrompt?: string | null,
  /** Pass separate static/dynamic system prompt blocks for prompt caching. */
  systemBlocks?: { type: 'text'; text: string; cache_control?: { type: string } }[] | null,
): Promise<string> {
  if (!apiKey) {
    throw new Error(
      'No Claude API key provided. Please add your API key in the Profile screen.',
    );
  }

  if (!apiKey.startsWith('sk-ant-')) {
    throw new Error(
      'Invalid Claude API key format. Keys should start with "sk-ant-". Please check your API key in the Profile screen.',
    );
  }

  const startTime = Date.now();
  const hasImage = messages.some(m => m.imageBase64);
  let totalInputTokens = 0;
  let totalOutputTokens = 0;
  let totalCacheReadTokens = 0;
  let totalCacheCreationTokens = 0;
  const allToolsUsed: string[] = [];

  // Classify intent from the last user message for dynamic tool selection
  const lastUserMsg = [...m
parseMealData function · typescript · L500-L527 (28 LOC)
src/services/claude.ts
export function parseMealData(text: string): MealData | null {
  const start = text.indexOf(MEAL_START);
  const end = text.indexOf(MEAL_END);
  if (start === -1 || end === -1 || end <= start) return null;

  const jsonStr = text.slice(start + MEAL_START.length, end).trim();
  try {
    const data = JSON.parse(jsonStr);
    if (
      typeof data.name === 'string' &&
      typeof data.calories === 'number' &&
      typeof data.protein === 'number' &&
      typeof data.carbs === 'number' &&
      typeof data.fat === 'number'
    ) {
      return {
        name: data.name,
        calories: Math.round(data.calories),
        protein: Math.round(data.protein),
        carbs: Math.round(data.carbs),
        fat: Math.round(data.fat),
      };
    }
  } catch {
    console.warn('Failed to parse meal data JSON:', jsonStr);
  }
  return null;
}
parseAllMealData function · typescript · L530-L559 (30 LOC)
src/services/claude.ts
export function parseAllMealData(text: string): MealData[] {
  const results: MealData[] = [];
  let searchFrom = 0;
  while (true) {
    const start = text.indexOf(MEAL_START, searchFrom);
    const end = text.indexOf(MEAL_END, start + 1);
    if (start === -1 || end === -1 || end <= start) break;
    const jsonStr = text.slice(start + MEAL_START.length, end).trim();
    try {
      const data = JSON.parse(jsonStr);
      if (
        typeof data.name === 'string' &&
        typeof data.calories === 'number' &&
        typeof data.protein === 'number' &&
        typeof data.carbs === 'number' &&
        typeof data.fat === 'number'
      ) {
        results.push({
          name: data.name,
          calories: Math.round(data.calories),
          protein: Math.round(data.protein),
          carbs: Math.round(data.carbs),
          fat: Math.round(data.fat),
        });
      }
    } catch { /* skip malformed */ }
    searchFrom = end + MEAL_END.length;
  }
  return results;
}
stripMealMarkers function · typescript · L562-L564 (3 LOC)
src/services/claude.ts
export function stripMealMarkers(text: string): string {
  return text.replace(/\[MEAL_DATA\][\s\S]*?\[\/MEAL_DATA\]/g, '').trim();
}
parseSaveMealSuggestion function · typescript · L567-L594 (28 LOC)
src/services/claude.ts
export function parseSaveMealSuggestion(text: string): MealData | null {
  const start = text.indexOf(SAVE_MEAL_START);
  const end = text.indexOf(SAVE_MEAL_END);
  if (start === -1 || end === -1 || end <= start) return null;

  const jsonStr = text.slice(start + SAVE_MEAL_START.length, end).trim();
  try {
    const data = JSON.parse(jsonStr);
    if (
      typeof data.name === 'string' &&
      typeof data.calories === 'number' &&
      typeof data.protein === 'number' &&
      typeof data.carbs === 'number' &&
      typeof data.fat === 'number'
    ) {
      return {
        name: data.name,
        calories: Math.round(data.calories),
        protein: Math.round(data.protein),
        carbs: Math.round(data.carbs),
        fat: Math.round(data.fat),
      };
    }
  } catch {
    console.warn('Failed to parse save-meal suggestion JSON:', jsonStr);
  }
  return null;
}
About: code-quality intelligence by Repobility · https://repobility.com
parseAllSaveMealSuggestions function · typescript · L597-L626 (30 LOC)
src/services/claude.ts
export function parseAllSaveMealSuggestions(text: string): MealData[] {
  const results: MealData[] = [];
  let searchFrom = 0;
  while (true) {
    const start = text.indexOf(SAVE_MEAL_START, searchFrom);
    const end = text.indexOf(SAVE_MEAL_END, start + 1);
    if (start === -1 || end === -1 || end <= start) break;
    const jsonStr = text.slice(start + SAVE_MEAL_START.length, end).trim();
    try {
      const data = JSON.parse(jsonStr);
      if (
        typeof data.name === 'string' &&
        typeof data.calories === 'number' &&
        typeof data.protein === 'number' &&
        typeof data.carbs === 'number' &&
        typeof data.fat === 'number'
      ) {
        results.push({
          name: data.name,
          calories: Math.round(data.calories),
          protein: Math.round(data.protein),
          carbs: Math.round(data.carbs),
          fat: Math.round(data.fat),
        });
      }
    } catch { /* skip malformed */ }
    searchFrom = end + SAVE_MEAL_END.length;
 
stripSaveMealMarkers function · typescript · L629-L631 (3 LOC)
src/services/claude.ts
export function stripSaveMealMarkers(text: string): string {
  return text.replace(/\[SAVE_MEAL\][\s\S]*?\[\/SAVE_MEAL\]/g, '').trim();
}
parsePlanText function · typescript · L634-L639 (6 LOC)
src/services/claude.ts
export function parsePlanText(text: string): string | null {
  const start = text.indexOf(PLAN_START);
  const end = text.indexOf(PLAN_END);
  if (start === -1 || end === -1 || end <= start) return null;
  return text.slice(start + PLAN_START.length, end).trim() || null;
}
stripPlanMarkers function · typescript · L642-L648 (7 LOC)
src/services/claude.ts
export function stripPlanMarkers(text: string): string {
  const start = text.indexOf(PLAN_START);
  const end = text.indexOf(PLAN_END);
  if (start === -1 || end === -1 || end <= start) return text;
  const planText = text.slice(start + PLAN_START.length, end).trim();
  return (text.slice(0, start) + planText + text.slice(end + PLAN_END.length)).trim();
}
buildStaticSystemPrompt function · typescript · L659-L722 (64 LOC)
src/services/claude.ts
export function buildStaticSystemPrompt(): string {
  return [
    "You are the same NoomiBodi coach who created this user's plan. You have the following context about them.",
    '',
    'When the user asks to change their plan, update it based on this context and their request.',
    'When you output a NEW or REVISED plan, wrap ONLY the exact plan text between [PLAN_START] and [PLAN_END] markers on their own lines, so the app can save it automatically.',
    '',
    '**Meal logging instructions**',
    'Whenever you estimate or describe the nutritional content of a specific meal (whether from an image or conversation), include a JSON block wrapped in markers so the app can offer to log it:',
    '[MEAL_DATA]{"name":"Meal Name","calories":000,"protein":00,"carbs":00,"fat":00}[/MEAL_DATA]',
    'Always include this block when you give a nutritional breakdown for a meal. The values should be your best estimates in whole numbers (calories in kcal, macros in grams).',
    'IMPORTANT: When
buildDynamicContext function · typescript · L726-L796 (71 LOC)
src/services/claude.ts
export function buildDynamicContext(
  profile: UserProfile | null,
  daily?: DailyContext | null,
): string {
  if (!profile) return '';

  const heightImperial = cmToFeetInchesStr(profile.heightCm);
  const weightLbs = Math.round(kgToLbs(profile.weightKg));
  const targetLbs = profile.targetWeightKg
    ? Math.round(kgToLbs(profile.targetWeightKg))
    : null;

  const now = new Date();
  const dateStr = now.toLocaleDateString(undefined, {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  });
  const timeStr = now.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });

  const goals = estimateDailyGoals(profile);

  const lines = [
    `**Current date & time**: ${dateStr}, ${timeStr}`,
    'CRITICAL: The date and time above are authoritative and always accurate. NEVER infer the current time or date from conversation history. Each turn uses fresh, real-time data from the app.',
    '',
    `TODAY_DATE = ${toLocalDateString(now)} (this is the u
buildChatSystemPrompt function · typescript · L800-L808 (9 LOC)
src/services/claude.ts
export function buildChatSystemPrompt(
  profile: UserProfile | null,
  daily?: DailyContext | null,
): string {
  if (!profile) return '';
  const staticPart = buildStaticSystemPrompt();
  const dynamicPart = buildDynamicContext(profile, daily);
  return staticPart + '\n\n' + dynamicPart;
}
generatePlanWithClaude function · typescript · L811-L855 (45 LOC)
src/services/claude.ts
export async function generatePlanWithClaude(
  profile: UserProfile,
  apiKey?: string | null,
  extraDetails?: string,
): Promise<string> {
  const { gender, age, heightCm, weightKg, goal, targetWeightKg, activityLevel } =
    profile;

  const heightImperial = cmToFeetInchesStr(heightCm);
  const weightLbs = Math.round(kgToLbs(weightKg));
  const targetWeightLbs = targetWeightKg
    ? Math.round(kgToLbs(targetWeightKg))
    : null;

  const userContent = [
    'You are a friendly nutrition and fitness coach.',
    'Create a concise, easy-to-follow 7-day plan for this person.',
    '',
    `Gender: ${gender}`,
    `Age: ${age}`,
    `Height: ${heightImperial} (feet/inches)`,
    `Current weight: ${weightLbs} lb`,
    `Goal: ${goal} weight`,
    targetWeightLbs ? `Target weight: ${targetWeightLbs} lb` : '',
    `Activity level: ${activityLevel}`,
    extraDetails
      ? ['', 'Additional context from the user (use this to personalise the plan):', extraDetails].join('\n')
      : '',
 
Open data scored by Repobility · https://repobility.com
sendSimpleMessage function · typescript · L858-L908 (51 LOC)
src/services/claude.ts
async function sendSimpleMessage(
  messages: ChatMessage[],
  apiKey: string,
): Promise<string> {
  const startTime = Date.now();
  const apiMessages = messages.map(msg => ({
    role: msg.role,
    content: msg.content,
  }));

  try {
    const response = await withRetry(() => axios.post(
      API_URL,
      { model: CLAUDE_MODEL, max_tokens: 1024, messages: apiMessages },
      {
        headers: {
          'x-api-key': apiKey,
          ...ANTHROPIC_HEADERS,
        },
        timeout: 30000,
      },
    ));

    const { usage } = response.data;
    logAiUsage({
      model: CLAUDE_MODEL,
      tokensInput: usage?.input_tokens ?? 0,
      tokensOutput: usage?.output_tokens ?? 0,
      latencyMs: Date.now() - startTime,
      success: true,
    });

    return response.data.content[0].text;
  } catch (error: any) {
    const apiError = error?.response?.data;
    const apiErrorDetail = apiError?.error?.message || apiError?.error?.type;
    const errorMsg = apiErrorDetail
      ?
getUserId function · typescript · L40-L43 (4 LOC)
src/services/feedback.ts
async function getUserId(): Promise<string | null> {
  const { data: { user } } = await supabase.auth.getUser();
  return user?.id ?? null;
}
base64ToArrayBuffer function · typescript · L45-L53 (9 LOC)
src/services/feedback.ts
function base64ToArrayBuffer(base64: string): ArrayBuffer {
  const binaryString = atob(base64);
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes.buffer;
}
getDeviceContext function · typescript · L57-L68 (12 LOC)
src/services/feedback.ts
export function getDeviceContext(): DeviceContext {
  const { width, height } = Dimensions.get('window');
  return {
    os: Platform.OS,
    osVersion: Platform.Version,
    model: DeviceInfo.getModel(),
    appVersion: DeviceInfo.getVersion(),
    buildNumber: DeviceInfo.getBuildNumber(),
    screenWidth: Math.round(width),
    screenHeight: Math.round(height),
  };
}
uploadFeedbackScreenshot function · typescript · L72-L91 (20 LOC)
src/services/feedback.ts
export async function uploadFeedbackScreenshot(base64Data: string): Promise<string> {
  const userId = await getUserId();
  if (!userId) throw new Error('Not authenticated');

  const timestamp = Date.now();
  const filePath = `${userId}/${timestamp}.jpg`;
  const arrayBuffer = base64ToArrayBuffer(base64Data);

  const { error: uploadError } = await supabase.storage
    .from('feedback-screenshots')
    .upload(filePath, arrayBuffer, { contentType: 'image/jpeg', upsert: true });

  if (uploadError) throw uploadError;

  const { data: urlData } = supabase.storage
    .from('feedback-screenshots')
    .getPublicUrl(filePath);

  return urlData.publicUrl;
}
submitFeedback function · typescript · L95-L118 (24 LOC)
src/services/feedback.ts
export async function submitFeedback(data: {
  category: FeedbackCategory;
  title: string;
  description?: string;
  screenshotUrls?: string[];
  currentScreen?: string;
}): Promise<void> {
  const userId = await getUserId();
  if (!userId) throw new Error('Not authenticated');

  const deviceInfo = getDeviceContext();

  const { error } = await supabase.from('feedback').insert({
    user_id: userId,
    category: data.category,
    title: data.title,
    description: data.description ?? null,
    screenshot_urls: data.screenshotUrls ?? [],
    device_info: deviceInfo,
    current_screen: data.currentScreen ?? null,
  });

  if (error) throw error;
}
getAdminFeedback function · typescript · L122-L166 (45 LOC)
src/services/feedback.ts
export async function getAdminFeedback(): Promise<FeedbackItem[]> {
  const { data, error } = await supabase
    .from('feedback')
    .select('*')
    .order('created_at', { ascending: false });

  if (error) throw error;

  const rows = data ?? [];
  if (rows.length === 0) return [];

  const userIds = [...new Set(rows.map((r: any) => r.user_id))];
  const { data: profiles } = await supabase
    .from('public_profiles')
    .select('id, username, display_name')
    .in('id', userIds);

  const profileMap = new Map<string, { username: string | null; displayName: string | null }>();
  for (const p of profiles ?? []) {
    profileMap.set(p.id, {
      username: p.username || null,
      displayName: p.display_name || null,
    });
  }

  return rows.map((r: any) => {
    const profile = profileMap.get(r.user_id);
    return {
      id: r.id,
      userId: r.user_id,
      category: r.category,
      title: r.title,
      description: r.description,
      screenshotUrls: r.screenshot_url
updateFeedbackStatus function · typescript · L168-L187 (20 LOC)
src/services/feedback.ts
export async function updateFeedbackStatus(
  id: string,
  status: FeedbackStatus,
  adminNotes?: string,
): Promise<void> {
  const updates: Record<string, any> = {
    status,
    updated_at: new Date().toISOString(),
  };
  if (adminNotes !== undefined) {
    updates.admin_notes = adminNotes;
  }

  const { error } = await supabase
    .from('feedback')
    .update(updates)
    .eq('id', id);

  if (error) throw error;
}
Powered by Repobility — scan your code at https://repobility.com
getUserId function · typescript · L24-L27 (4 LOC)
src/services/friendships.ts
async function getUserId(): Promise<string | null> {
  const { data: { user } } = await supabase.auth.getUser();
  return user?.id ?? null;
}
rowToFriendship function · typescript · L29-L38 (10 LOC)
src/services/friendships.ts
function rowToFriendship(row: any): Friendship {
  return {
    id: row.id,
    followerId: row.follower_id,
    followingId: row.following_id,
    status: row.status,
    createdAt: row.created_at,
    acceptedAt: row.accepted_at,
  };
}
sendFriendRequest function · typescript · L42-L54 (13 LOC)
src/services/friendships.ts
export async function sendFriendRequest(followingId: string): Promise<Friendship> {
  const userId = await getUserId();
  if (!userId) throw new Error('Not authenticated');

  const { data, error } = await supabase
    .from('friendships')
    .insert({ follower_id: userId, following_id: followingId, status: 'pending' })
    .select()
    .single();

  if (error) throw error;
  return rowToFriendship(data);
}
acceptFriendRequest function · typescript · L56-L67 (12 LOC)
src/services/friendships.ts
export async function acceptFriendRequest(friendshipId: string): Promise<void> {
  const userId = await getUserId();
  if (!userId) throw new Error('Not authenticated');

  const { error } = await supabase
    .from('friendships')
    .update({ status: 'accepted', accepted_at: new Date().toISOString() })
    .eq('id', friendshipId)
    .eq('following_id', userId);

  if (error) throw error;
}
declineFriendRequest function · typescript · L69-L80 (12 LOC)
src/services/friendships.ts
export async function declineFriendRequest(friendshipId: string): Promise<void> {
  const userId = await getUserId();
  if (!userId) throw new Error('Not authenticated');

  const { error } = await supabase
    .from('friendships')
    .delete()
    .eq('id', friendshipId)
    .eq('following_id', userId);

  if (error) throw error;
}
removeFriend function · typescript · L82-L89 (8 LOC)
src/services/friendships.ts
export async function removeFriend(friendshipId: string): Promise<void> {
  const { error } = await supabase
    .from('friendships')
    .delete()
    .eq('id', friendshipId);

  if (error) throw error;
}
getAcceptedFriends function · typescript · L93-L141 (49 LOC)
src/services/friendships.ts
export async function getAcceptedFriends(): Promise<FriendWithProfile[]> {
  const userId = await getUserId();
  if (!userId) return [];

  try {
    const { data: rows, error } = await supabase
      .from('friendships')
      .select('id, follower_id, following_id, status')
      .or(`follower_id.eq.${userId},following_id.eq.${userId}`);

    if (error) throw error;

    const accepted = (rows ?? []).filter(r => r.status === 'accepted');
    if (accepted.length === 0) return [];

    const friendIds = accepted.map(r =>
      r.follower_id === userId ? r.following_id : r.follower_id,
    );

    const { data: profiles, error: profErr } = await supabase
      .from('public_profiles')
      .select('id, username, display_name, profile_picture_url, bio, is_private')
      .in('id', friendIds);

    if (profErr) throw profErr;

    const profileMap = new Map((profiles ?? []).map(p => [p.id, p]));

    return accepted
      .map(row => {
        const friendId = row.follower_id === userId 
getFriendshipStatus function · typescript · L243-L245 (3 LOC)
src/services/friendships.ts
export async function getFriendshipStatus(
  otherUserId: string,
): Promise<{ status: RelationshipStatus; friendshipId: string | null }> {
Source: Repobility analyzer · https://repobility.com
getUserId function · typescript · L28-L31 (4 LOC)
src/services/insightGenerator.ts
async function getUserId(): Promise<string | null> {
  const { data: { user } } = await supabase.auth.getUser();
  return user?.id ?? null;
}
toLocalDateString function · typescript · L33-L38 (6 LOC)
src/services/insightGenerator.ts
function toLocalDateString(date: Date): string {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, '0');
  const d = String(date.getDate()).padStart(2, '0');
  return `${y}-${m}-${d}`;
}
rowToInsight function · typescript · L40-L51 (12 LOC)
src/services/insightGenerator.ts
function rowToInsight(row: any): Insight {
  return {
    id: row.id,
    type: row.insight_type as InsightType,
    title: row.title,
    description: row.description,
    dataContext: row.data_context,
    priority: row.priority ?? 0,
    isDismissed: row.is_dismissed ?? false,
    createdAt: new Date(row.created_at).getTime(),
  };
}
gatherDataForInsights function · typescript · L55-L95 (41 LOC)
src/services/insightGenerator.ts
async function gatherDataForInsights(): Promise<string> {
  const today = toLocalDateString(new Date());
  const sections: string[] = [`Data snapshot taken on ${today}:\n`];

  const [weekSummary, monthSummary, weightAll, savedMeals, adherence7, adherence30] =
    await Promise.all([
      executeTool('get_period_summary', { days: 7 }),
      executeTool('get_period_summary', { days: 30 }),
      executeTool('get_weight_trend', { days: 0 }),
      executeTool('search_saved_meals', { query: '' }),
      executeTool('calculate_adherence_rate', { days: 7, calorie_goal: 0 }),
      executeTool('calculate_adherence_rate', { days: 30, calorie_goal: 0 }),
    ]);

  sections.push('## Last 7 days nutrition:\n' + weekSummary);
  sections.push('\n## Last 30 days nutrition:\n' + monthSummary);
  sections.push('\n## Weight history (all time):\n' + weightAll);
  sections.push('\n## Saved meals library:\n' + savedMeals);

  const profile = await loadUserProfile();
  if (profile) {
    const goals = 
callClaudeForInsights function · typescript · L130-L178 (49 LOC)
src/services/insightGenerator.ts
async function callClaudeForInsights(dataContext: string, apiKey: string): Promise<Insight[]> {
  const response = await axios.post(
    API_URL,
    {
      model: CLAUDE_MODEL,
      max_tokens: 1024,
      system: INSIGHT_SYSTEM_PROMPT,
      messages: [{ role: 'user', content: `Analyze this user's data and generate insights:\n\n${dataContext}` }],
    },
    {
      headers: {
        'x-api-key': apiKey,
        'anthropic-version': '2023-06-01',
        'anthropic-beta': 'prompt-caching-2024-07-31',
        'Content-Type': 'application/json',
      },
    },
  );

  const text: string = response.data.content[0].text;

  // Extract JSON array from response (Claude may wrap it in backticks)
  const jsonMatch = text.match(/\[[\s\S]*\]/);
  if (!jsonMatch) {
    console.warn('Insight generation: no JSON array found in response');
    return [];
  }

  const parsed = JSON.parse(jsonMatch[0]);
  if (!Array.isArray(parsed)) return [];

  return parsed
    .filter(
      (item: any) =>
 
saveInsightsToDb function · typescript · L182-L216 (35 LOC)
src/services/insightGenerator.ts
async function saveInsightsToDb(insights: Insight[]): Promise<Insight[]> {
  const userId = await getUserId();
  if (!userId) return insights;

  // Clear old undismissed insights for this user
  await supabase
    .from('user_insights')
    .delete()
    .eq('user_id', userId)
    .eq('is_dismissed', false);

  const rows = insights.map(ins => ({
    user_id: userId,
    insight_type: ins.type,
    title: ins.title,
    description: ins.description,
    data_context: ins.dataContext,
    priority: ins.priority,
    is_dismissed: false,
    valid_from: new Date().toISOString(),
    valid_until: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
  }));

  const { data: savedRows, error } = await supabase
    .from('user_insights')
    .insert(rows)
    .select();

  if (error || !savedRows) {
    console.error('Failed to save insights:', error);
    return insights;
  }

  return savedRows.map(rowToInsight);
}
loadCachedInsights function · typescript · L218-L232 (15 LOC)
src/services/insightGenerator.ts
export async function loadCachedInsights(): Promise<Insight[]> {
  const userId = await getUserId();
  if (!userId) return [];

  const { data: rows, error } = await supabase
    .from('user_insights')
    .select('*')
    .eq('user_id', userId)
    .eq('is_dismissed', false)
    .gte('valid_until', new Date().toISOString())
    .order('priority', { ascending: false });

  if (error || !rows) return [];
  return rows.map(rowToInsight);
}
dismissInsight function · typescript · L256-L261 (6 LOC)
src/services/insightGenerator.ts
export async function dismissInsight(id: string): Promise<void> {
  await supabase
    .from('user_insights')
    .update({ is_dismissed: true })
    .eq('id', id);
}
About: code-quality intelligence by Repobility · https://repobility.com
generateInsights function · typescript · L265-L279 (15 LOC)
src/services/insightGenerator.ts
export async function generateInsights(forceRefresh = false): Promise<Insight[]> {
  if (!forceRefresh) {
    const cached = await loadCachedInsights();
    if (cached.length > 0) return cached;
  }

  const apiKey = await getApiKey();
  if (!apiKey) return [];

  const dataContext = await gatherDataForInsights();
  const insights = await callClaudeForInsights(dataContext, apiKey);
  if (insights.length === 0) return [];

  return saveInsightsToDb(insights);
}
getUserId function · typescript · L26-L29 (4 LOC)
src/services/leaderboard.ts
async function getUserId(): Promise<string | null> {
  const { data: { user } } = await supabase.auth.getUser();
  return user?.id ?? null;
}
getCurrentWeekRange function · typescript · L31-L48 (18 LOC)
src/services/leaderboard.ts
export function getCurrentWeekRange(): WeekRange {
  const now = new Date();
  const day = now.getDay();
  const diffToMonday = day === 0 ? -6 : 1 - day;

  const start = new Date(now);
  start.setDate(now.getDate() + diffToMonday);
  start.setHours(0, 0, 0, 0);

  const end = new Date(start);
  end.setDate(start.getDate() + 6);
  end.setHours(23, 59, 59, 999);

  const fmt = (d: Date) =>
    d.toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' });

  return { start, end, label: `${fmt(start)} - ${fmt(end)}` };
}
toLocalDateString function · typescript · L50-L55 (6 LOC)
src/services/leaderboard.ts
function toLocalDateString(date: Date): string {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, '0');
  const d = String(date.getDate()).padStart(2, '0');
  return `${y}-${m}-${d}`;
}
getUserAdherence function · typescript · L59-L63 (5 LOC)
src/services/leaderboard.ts
async function getUserAdherence(
  userId: string,
  weekStart: Date,
  weekEnd: Date,
): Promise<{ daysHit: number; daysTotal: number }> {
getWeeklyLeaderboard function · typescript · L101-L104 (4 LOC)
src/services/leaderboard.ts
export async function getWeeklyLeaderboard(): Promise<{
  entries: LeaderboardEntry[];
  weekRange: WeekRange;
}> {
getUserId function · typescript · L35-L38 (4 LOC)
src/services/mealLog.ts
async function getUserId(): Promise<string | null> {
  const { data: { user } } = await supabase.auth.getUser();
  return user?.id ?? null;
}
rowToMealEntry function · typescript · L40-L63 (24 LOC)
src/services/mealLog.ts
function rowToMealEntry(row: any): MealEntry {
  const imageUrl: string | null = row.image_url;
  let imageBase64: string | undefined;
  let imageUri: string | undefined;

  if (imageUrl?.startsWith('data:')) {
    imageBase64 = imageUrl.replace(/^data:image\/\w+;base64,/, '');
    imageUri = imageUrl;
  } else if (imageUrl) {
    imageUri = imageUrl;
  }

  return {
    id: row.id,
    name: row.meal_name,
    calories: row.calories || 0,
    protein: Number(row.protein_g) || 0,
    carbs: Number(row.carbs_g) || 0,
    fat: Number(row.fat_g) || 0,
    timestamp: new Date(row.logged_at).getTime(),
    imageUri,
    imageBase64,
  };
}
Open data scored by Repobility · https://repobility.com
logMeal function · typescript · L67-L113 (47 LOC)
src/services/mealLog.ts
export async function logMeal(
  data: MealData,
  imageUri?: string,
  imageBase64?: string,
): Promise<MealEntry> {
  const userId = await getUserId();
  if (!userId) throw new Error('Not authenticated');

  const imageUrl = imageBase64
    ? `data:image/jpeg;base64,${imageBase64}`
    : data.imageUrl || null;

  try {
    const { data: row, error } = await supabase
      .from('daily_logs')
      .insert({
        user_id: userId,
        meal_name: data.name,
        calories: data.calories,
        protein_g: data.protein,
        carbs_g: data.carbs,
        fat_g: data.fat,
        image_url: imageUrl,
      })
      .select()
      .single();

    if (error) throw error;
    if (!row) throw new Error('Failed to log meal');

    const entry = rowToMealEntry(row);
    if (imageUri) entry.imageUri = imageUri;

    // Check for streak milestones in the background
    checkStreakMilestone().catch(() => {});

    return entry;
  } catch (err) {
    if (isNetworkError(err)) {
      co
getTodaysMeals function · typescript · L115-L148 (34 LOC)
src/services/mealLog.ts
export async function getTodaysMeals(): Promise<MealEntry[]> {
  const pendingMeals = (await getPendingMealsForToday()).map(m => ({
    ...m,
    pending: true,
  }));

  try {
    const userId = await getUserId();
    if (!userId) return pendingMeals;

    const startOfDay = new Date();
    startOfDay.setHours(0, 0, 0, 0);

    const { data: rows, error } = await supabase
      .from('daily_logs')
      .select('*')
      .eq('user_id', userId)
      .gte('logged_at', startOfDay.toISOString())
      .order('logged_at', { ascending: false });

    if (error) throw error;

    const remoteMeals = (rows ?? []).map(rowToMealEntry);
    await cacheTodaysMeals(remoteMeals);
    return [...remoteMeals, ...pendingMeals];
  } catch (err) {
    if (isNetworkError(err)) {
      const cached = await getCachedTodaysMeals();
      return [...(cached ?? []), ...pendingMeals];
    }
    console.error('Error loading meals:', err);
    return pendingMeals;
  }
}
getMealsForDate function · typescript · L150-L169 (20 LOC)
src/services/mealLog.ts
export async function getMealsForDate(date: Date): Promise<MealEntry[]> {
  const userId = await getUserId();
  if (!userId) return [];

  const start = new Date(date);
  start.setHours(0, 0, 0, 0);
  const end = new Date(date);
  end.setHours(23, 59, 59, 999);

  const { data: rows, error } = await supabase
    .from('daily_logs')
    .select('*')
    .eq('user_id', userId)
    .gte('logged_at', start.toISOString())
    .lte('logged_at', end.toISOString())
    .order('logged_at', { ascending: false });

  if (error || !rows) return [];
  return rows.map(rowToMealEntry);
}
‹ prevpage 4 / 6next ›