Skip to content

Commit ce1ed54

Browse files
fix: Vibe coding on fixing the peroformance issues for gift suggestions (#697)
* Vibe coding on fixing the peroformance issues for gift suggestions * Clean: cleaned up the ai code some. * Feature: updated AI model * Fix: cleaned up more of the AI code * Fix: deleted interface as not needed. * Test: fixed failing tests --------- Co-authored-by: Alex Appleget <alexappleget2014@gmail.com>
1 parent ec8e025 commit ce1ed54

File tree

5 files changed

+130
-85
lines changed

5 files changed

+130
-85
lines changed

app/api/gift-exchanges/[id]/giftSuggestions/route.ts

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ export async function GET(
2727
throw new SupabaseError('User is not authenticated or exists', 500);
2828
}
2929

30-
// Get match with full profile info
31-
const { data: match, error: matchError } = await supabase
32-
.from('gift_exchange_members')
33-
.select(
34-
`
30+
const [matchResult, suggestionsResult] = await Promise.all([
31+
supabase
32+
.from('gift_exchange_members')
33+
.select(
34+
`
3535
id,
3636
recipient_id,
3737
recipient:profiles!gift_exchange_members_recipient_id_profiles_fkey (
@@ -48,40 +48,39 @@ export async function GET(
4848
avatar
4949
)
5050
`,
51-
)
52-
.eq('gift_exchange_id', id)
53-
.eq('user_id', user.id)
54-
.single();
51+
)
52+
.eq('gift_exchange_id', id)
53+
.eq('user_id', user.id)
54+
.single(),
55+
supabase
56+
.from('gift_suggestions')
57+
.select('*')
58+
.eq('gift_exchange_id', id)
59+
.eq('giver_id', user.id),
60+
]);
5561

56-
if (matchError) {
62+
if (matchResult.error) {
5763
throw new SupabaseError(
5864
'Failed to fetch match',
59-
matchError.code,
60-
matchError,
65+
matchResult.error.code,
66+
matchResult.error,
6167
);
6268
}
6369

64-
// Get suggestions
65-
const { data: suggestions, error: suggestionsError } = await supabase
66-
.from('gift_suggestions')
67-
.select('*')
68-
.eq('gift_exchange_id', id)
69-
.eq('giver_id', user.id);
70-
71-
if (suggestionsError) {
70+
if (suggestionsResult.error) {
7271
throw new SupabaseError(
7372
'Failed to fetch suggestions',
74-
suggestionsError.code,
75-
suggestionsError,
73+
suggestionsResult.error.code,
74+
suggestionsResult.error,
7675
);
7776
}
7877

7978
return NextResponse.json({
80-
match: match.recipient,
81-
suggestions: suggestions.map((s) => ({
82-
...s.suggestion,
83-
id: s.id,
84-
created_at: s.created_at,
79+
match: matchResult.data?.recipient,
80+
suggestions: (suggestionsResult.data || []).map((suggestion) => ({
81+
...suggestion.suggestion,
82+
id: suggestion.id,
83+
created_at: suggestion.created_at,
8584
})),
8685
});
8786
} catch (error) {

lib/drawGiftExchange.ts

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -69,40 +69,47 @@ export async function drawGiftExchange(
6969
// Shuffle members to assign
7070
const shuffledMembers = [...members].sort(() => Math.random() - 0.5);
7171

72-
// Update assignments
73-
for (let i = 0; i < shuffledMembers.length; i++) {
74-
const giver = shuffledMembers[i];
75-
// Last person gives to first person, closing the circle
76-
const recipient = shuffledMembers[(i + 1) % shuffledMembers.length];
77-
78-
const { error: updateError } = await supabase
79-
.from('gift_exchange_members')
80-
.update({
81-
recipient_id: recipient.user_id,
82-
has_drawn: true,
83-
})
84-
.eq('id', giver.id);
85-
86-
if (updateError) {
72+
const assignments = shuffledMembers.map((member, index) => ({
73+
giver: member,
74+
recipient: shuffledMembers[(index + 1) % shuffledMembers.length],
75+
}));
76+
77+
// Perform all member recipient assignments in parallel
78+
const assignmentResults = await Promise.allSettled(
79+
assignments.map((assignment) =>
80+
supabase
81+
.from('gift_exchange_members')
82+
.update({ recipient_id: assignment.recipient.user_id, has_drawn: true })
83+
.eq('id', assignment.giver.id),
84+
),
85+
);
86+
87+
for (const result of assignmentResults) {
88+
if (result.status === 'rejected') {
89+
throw new SupabaseError('Failed to assign recipients', 500);
90+
}
91+
if ('value' in result && result.value.error) {
8792
throw new SupabaseError(
8893
'Failed to assign recipients',
89-
updateError.code,
90-
updateError,
94+
result.value.error.code,
95+
result.value.error,
9196
);
9297
}
93-
94-
// Fire and forget suggestions with error handling
95-
// hacky way to avoid waiting for all suggestions to be generated
96-
// avoids timeout issues
97-
await generateAndStoreSuggestions(
98-
supabase,
99-
exchangeId,
100-
giver.user_id,
101-
recipient.user_id,
102-
exchange.budget,
103-
);
10498
}
10599

100+
// Generate suggestions for all members concurrently (do not block one another)
101+
await Promise.allSettled(
102+
assignments.map((assignment) =>
103+
generateAndStoreSuggestions(
104+
supabase,
105+
exchangeId,
106+
assignment.giver.user_id,
107+
assignment.recipient.user_id,
108+
exchange.budget,
109+
),
110+
),
111+
);
112+
106113
// Update exchange status to active
107114
const { error: statusError } = await supabase
108115
.from('gift_exchanges')

lib/generateAndStoreSuggestions.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ describe('generateAndStoreSuggestions', () => {
9696
expect(getAmazonImage).toHaveBeenCalledWith('Kindle Paperwhite');
9797
expect(mockFrom).toHaveBeenCalledWith('gift_suggestions');
9898
expect(mockInsert).toHaveBeenCalledWith(
99-
expect.objectContaining({
99+
expect.arrayContaining([
100+
expect.objectContaining({
100101
gift_exchange_id: 'Exchange1',
101102
giver_id: 'Giver1',
102103
recipient_id: 'Recipient1',
@@ -105,6 +106,7 @@ describe('generateAndStoreSuggestions', () => {
105106
imageUrl: 'https://amazon.com/kindle.jpg',
106107
}),
107108
}),
109+
])
108110
);
109111
});
110112

lib/generateAndStoreSuggestions.ts

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { SupabaseClient } from '@supabase/supabase-js';
55
import { openai } from '../app/api/openaiConfig/config';
66
import { getAmazonImage } from './getAmazonImage';
77
import { SupabaseError, OpenAiError } from './errors/CustomErrors';
8+
import {
9+
IGeneratedSuggestionNormalized,
10+
} from './interfaces/IGeneratedSuggestionRaw';
811

912
/**
1013
* Generates and store gift suggestions
@@ -69,7 +72,7 @@ export async function generateAndStoreSuggestions(
6972

7073
const completion = await openai.chat.completions.create({
7174
messages: [{ role: 'user', content: prompt }],
72-
model: 'gpt-3.5-turbo',
75+
model: 'gpt-4o-mini',
7376
temperature: 0.7,
7477
});
7578

@@ -99,39 +102,62 @@ export async function generateAndStoreSuggestions(
99102
}
100103
}
101104

102-
const parsedResponse = JSON.parse(jsonContent);
105+
const rawItems = JSON.parse(jsonContent) as Array<Record<string, unknown>>;
103106

104-
// Process each suggestion with Amazon data
105-
for (const suggestion of parsedResponse) {
106-
const amazonData = await getAmazonImage(suggestion.title);
107+
const parsedResponse = rawItems.map((item) => {
107108

108-
const cleanSuggestion = {
109-
title: String(suggestion.title),
110-
price: String(suggestion.price),
111-
description: String(suggestion.description),
112-
matchReasons: Array.isArray(suggestion.matchReasons)
113-
? suggestion.matchReasons.map(String)
109+
return {
110+
title: String(item.title),
111+
price: String(item.price),
112+
description: String(item.description),
113+
matchReasons: Array.isArray(item.matchReasons)
114+
? item.matchReasons.map(String)
114115
: [],
115-
matchScore: Number(suggestion.matchScore),
116-
imageUrl: amazonData.imageUrl || null,
116+
matchScore: Number(item.matchScore),
117117
};
118+
});
118119

119-
const { error: suggestionError } = await supabase
120-
.from('gift_suggestions')
121-
.insert({
122-
gift_exchange_id: exchangeId,
123-
giver_id: giverId,
124-
recipient_id: recipientId,
125-
suggestion: cleanSuggestion,
126-
});
127-
128-
if (suggestionError) {
129-
throw new SupabaseError(
130-
'Failed to store suggestion',
131-
suggestionError.code,
132-
suggestionError,
133-
);
134-
}
120+
const imageResults = await Promise.allSettled(
121+
parsedResponse.map((response) => getAmazonImage(String(response.title))),
122+
);
123+
124+
const rows = parsedResponse.map((suggestion, idx): {
125+
gift_exchange_id: string;
126+
giver_id: string;
127+
recipient_id: string;
128+
suggestion: IGeneratedSuggestionNormalized;
129+
} => {
130+
const imageResult = imageResults[idx];
131+
const imageUrl =
132+
imageResult.status === 'fulfilled' && imageResult.value.imageUrl
133+
? imageResult.value.imageUrl
134+
: null;
135+
136+
return {
137+
gift_exchange_id: exchangeId,
138+
giver_id: giverId,
139+
recipient_id: recipientId,
140+
suggestion: {
141+
title: suggestion.title,
142+
price: suggestion.price,
143+
description: suggestion.description,
144+
matchReasons: suggestion.matchReasons,
145+
matchScore: suggestion.matchScore,
146+
imageUrl,
147+
},
148+
};
149+
});
150+
151+
const { error: insertError } = await supabase
152+
.from('gift_suggestions')
153+
.insert(rows);
154+
155+
if (insertError) {
156+
throw new SupabaseError(
157+
'Failed to store suggestions',
158+
insertError.code,
159+
insertError,
160+
);
135161
}
136162
} catch (error) {
137163
throw error;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) Gridiron Survivor.
2+
// Licensed under the MIT License.
3+
4+
export interface IGeneratedSuggestionNormalized {
5+
title: string;
6+
price: string;
7+
description: string;
8+
matchReasons: string[];
9+
matchScore: number;
10+
imageUrl: string | null;
11+
}

0 commit comments

Comments
 (0)