Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
94.20% |
195 / 207 |
|
57.14% |
4 / 7 |
CRAP | |
0.00% |
0 / 1 |
RolePlayConversationsController | |
94.20% |
195 / 207 |
|
57.14% |
4 / 7 |
34.23 | |
0.00% |
0 / 1 |
start | |
100.00% |
32 / 32 |
|
100.00% |
1 / 1 |
1 | |||
process | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
end | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
recent | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
3 | |||
conversation | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
history | |
97.44% |
38 / 39 |
|
0.00% |
0 / 1 |
8 | |||
agents | |
98.89% |
89 / 90 |
|
0.00% |
0 / 1 |
13 |
1 | <?php |
2 | |
3 | namespace App\Http\Controllers\v2\RolePlay; |
4 | |
5 | use Illuminate\Http\Request; |
6 | use App\Traits\SubscriptionTrait; |
7 | use Illuminate\Http\JsonResponse; |
8 | use App\Http\Controllers\Controller; |
9 | use App\Http\Models\Parameter; |
10 | use App\Http\Models\RolePlayConversations; |
11 | use App\Http\Models\RolePlayProjects; |
12 | use App\Http\Models\UserAddOns; |
13 | use App\Http\Requests\v2\RolePlay\GetRolePlayAgentsRequest; |
14 | use App\Http\Requests\v2\RolePlay\RolePlaySessionEndRequest; |
15 | use App\Http\Requests\v2\RolePlay\RolePlaySessionHistoryRequest; |
16 | use App\Http\Requests\v2\RolePlay\RolePlaySessionProcessRequest; |
17 | use App\Http\Requests\v2\RolePlay\RolePlaySessionStartRequest; |
18 | use App\Jobs\ProcessRolePlaySessionAsyncJob; |
19 | use MongoDB\BSON\UTCDateTime; |
20 | |
21 | class RolePlayConversationsController extends Controller |
22 | { |
23 | use SubscriptionTrait; |
24 | |
25 | public function start(RolePlaySessionStartRequest $request): JsonResponse |
26 | { |
27 | $user = $request->user(); |
28 | $project_id = $request->input('project_id'); |
29 | $icp = $request->input('icp'); |
30 | $prompt = $request->input('prompt'); |
31 | $agent = $request->input('agent'); |
32 | |
33 | $conversation = RolePlayConversations::create([ |
34 | 'user_id' => $user->id, |
35 | 'project_id' => $project_id, |
36 | 'icp' => $icp, |
37 | 'prompt' => $prompt, |
38 | 'agent' => $agent, |
39 | 'duration' => 0, |
40 | 'status' => 'created', |
41 | ]); |
42 | |
43 | $rolePlayAddOn = UserAddOns::where('user_id', $user->id) |
44 | ->where('product', 'RolePlay') |
45 | ->where('status', 'active') |
46 | ->orderBy('created_at', 'desc') |
47 | ->first(); |
48 | |
49 | $rolePlayAddOn->load('addOn'); |
50 | |
51 | $monthly_total_time_credits = $rolePlayAddOn->addOn->features['monthly_total_time_credits'] ?? 0; |
52 | $usedRolePlayTimeCredits = RolePlayConversations::where('user_id', $user->id) |
53 | ->where('status', 'done') |
54 | ->where('created_at', '>=', new UTCDateTime(strtotime(now()->startOfMonth()->toDateString()) * 1000)) |
55 | ->sum('duration') ?? 0; |
56 | |
57 | return response()->json([ |
58 | 'status' => 'success', |
59 | 'data' => [ |
60 | 'session' => $conversation, |
61 | 'seconds_remaining' => max(0, $monthly_total_time_credits - $usedRolePlayTimeCredits) |
62 | ], |
63 | ]); |
64 | } |
65 | |
66 | public function process(RolePlaySessionProcessRequest $request, RolePlayConversations $conversation): JsonResponse |
67 | { |
68 | $project = RolePlayProjects::find($conversation->project_id); |
69 | |
70 | if (empty($conversation->feedback) || true) { |
71 | ProcessRolePlaySessionAsyncJob::dispatchSync($conversation, $conversation->id); |
72 | } else { |
73 | $project->calculateProgression($conversation->feedback); |
74 | } |
75 | |
76 | $conversation = $conversation->fresh(); |
77 | |
78 | $conversation->load('project'); |
79 | |
80 | return response()->json([ |
81 | 'status' => 'success', |
82 | 'data' => $conversation, |
83 | ]); |
84 | } |
85 | |
86 | public function end(RolePlaySessionEndRequest $request, RolePlayConversations $conversation): JsonResponse |
87 | { |
88 | $user = $request->user(); |
89 | |
90 | if ($conversation->user_id !== $user->id) { |
91 | return response()->json(['status' => 'error', 'message' => 'Unauthorized'], 403); |
92 | } |
93 | |
94 | $conversation->status = 'processing'; |
95 | $conversation->save(); |
96 | |
97 | ProcessRolePlaySessionAsyncJob::dispatch($conversation, $conversation->id); |
98 | |
99 | return response()->json([ |
100 | 'status' => 'success', |
101 | 'data' => $conversation, |
102 | ]); |
103 | } |
104 | |
105 | public function recent(Request $request): JsonResponse |
106 | { |
107 | $user = $request->user(); |
108 | |
109 | $conversations = RolePlayConversations::where('user_id', $user->id) |
110 | ->where('status', 'done') |
111 | ->latest() |
112 | ->take(5) |
113 | ->get() |
114 | ->load('project') |
115 | ->map(function ($conversation) { |
116 | $conversation->icp = is_string($conversation->icp) ? json_decode($conversation->icp, true) : (array) $conversation->icp; |
117 | $conversation->agent = is_string($conversation->agent) ? json_decode($conversation->agent, true) : (array) $conversation->agent; |
118 | return $conversation; |
119 | }); |
120 | |
121 | return response()->json([ |
122 | 'status' => 'success', |
123 | 'data' => $conversations, |
124 | ]); |
125 | } |
126 | |
127 | public function conversation(Request $request, RolePlayConversations $conversation): JsonResponse |
128 | { |
129 | $user = $request->user(); |
130 | |
131 | if ($conversation->user_id !== $user->id) { |
132 | return response()->json(['status' => 'error', 'message' => 'Unauthorized'], 403); |
133 | } |
134 | |
135 | $conversation->load('project'); |
136 | $conversation->icp = is_string($conversation->icp) ? json_decode($conversation->icp, true) : (array) $conversation->icp; |
137 | $conversation->agent = is_string($conversation->agent) ? json_decode($conversation->agent, true) : (array) $conversation->agent; |
138 | |
139 | return response()->json([ |
140 | 'status' => 'success', |
141 | 'data' => $conversation, |
142 | ]); |
143 | } |
144 | |
145 | public function history(RolePlaySessionHistoryRequest $request): JsonResponse |
146 | { |
147 | $user = $request->user(); |
148 | $period = $request->input('period', 'all_times'); |
149 | |
150 | $startDate = null; |
151 | $endDate = null; |
152 | |
153 | switch ($period) { |
154 | case 'this_week': |
155 | $startDate = now()->startOfWeek()->subMinute(); |
156 | $endDate = now()->endOfWeek()->addMinute(); |
157 | break; |
158 | case 'this_month': |
159 | $startDate = now()->startOfMonth()->subMinute(); |
160 | $endDate = now()->endOfMonth()->addMinute(); |
161 | break; |
162 | } |
163 | |
164 | $query = RolePlayConversations::where('user_id', $user->id); |
165 | |
166 | if ($startDate && $endDate) { |
167 | $query->whereBetween('created_at', [ |
168 | new UTCDateTime(strtotime($startDate->toDateString()) * 1000), |
169 | new UTCDateTime(strtotime($endDate->toDateString()) * 1000), |
170 | ]); |
171 | } elseif ($endDate) { |
172 | $query->where('created_at', '<', new UTCDateTime(strtotime($endDate->toDateString()) * 1000)); |
173 | } |
174 | |
175 | $conversations = $query->select([ |
176 | 'id', |
177 | 'project_id', |
178 | 'score', |
179 | 'status', |
180 | 'duration', |
181 | 'project', |
182 | 'icp', |
183 | 'agent', |
184 | 'created_at', |
185 | ])->with('project')->latest()->get()->load('project')->map(function ($conversation) { |
186 | $conversation->icp = is_string($conversation->icp) ? json_decode($conversation->icp, true) : (array) $conversation->icp; |
187 | $conversation->agent = is_string($conversation->agent) ? json_decode($conversation->agent, true) : (array) $conversation->agent; |
188 | return $conversation; |
189 | }); |
190 | |
191 | return response()->json([ |
192 | 'status' => 'success', |
193 | 'data' => $conversations, |
194 | ]); |
195 | } |
196 | |
197 | public function agents(GetRolePlayAgentsRequest $request, RolePlayProjects $persona): JsonResponse |
198 | { |
199 | $maleNamesParam = Parameter::where('name', 'role_play_male_names')->first(); |
200 | $maleImagesParam = Parameter::where('name', 'role_play_male_images')->first(); |
201 | $maleVoicesParam = Parameter::where('name', 'role_play_male_voices')->first(); |
202 | $femaleNamesParam = Parameter::where('name', 'role_play_female_names')->first(); |
203 | $femaleImagesParam = Parameter::where('name', 'role_play_female_images')->first(); |
204 | $femaleVoicesParam = Parameter::where('name', 'role_play_female_voices')->first(); |
205 | $personalitiesParam = Parameter::where('name', 'role_play_personalities')->first(); |
206 | $coldCallPromptParam = Parameter::where('name', 'role_play_cold_call_prompt')->first(); |
207 | $discoveryCallPromptParam = Parameter::where('name', 'role_play_discovery_call_prompt')->first(); |
208 | |
209 | $maleNames = $maleNamesParam->value ?? []; |
210 | $maleImages = $maleImagesParam->value ?? []; |
211 | $maleVoices = $maleVoicesParam->value ?? []; |
212 | $femaleNames = $femaleNamesParam->value ?? []; |
213 | $femaleImages = $femaleImagesParam->value ?? []; |
214 | $femaleVoices = $femaleVoicesParam->value ?? []; |
215 | $personalities = $personalitiesParam->value ?? []; |
216 | $icps = $persona->customer_profiles ?? []; |
217 | |
218 | $baseAgents = []; |
219 | shuffle($maleImages); |
220 | shuffle($femaleImages); |
221 | shuffle($personalities); |
222 | |
223 | $maleImageCount = count($maleImages); |
224 | $femaleImageCount = count($femaleImages); |
225 | $personalityCount = count($personalities); |
226 | $personalityIndex = 0; |
227 | |
228 | if ($maleImageCount > 0 && $personalityCount > 0 && !empty($maleVoices)) { |
229 | foreach ($maleNames as $index => $name) { |
230 | $baseAgents[] = [ |
231 | 'name' => $name, |
232 | 'gender' => 'male', |
233 | 'image' => $maleImages[$index % $maleImageCount], |
234 | 'voice' => $maleVoices[array_rand($maleVoices)], |
235 | 'personality' => $personalities[($personalityIndex++) % $personalityCount], |
236 | ]; |
237 | } |
238 | } |
239 | |
240 | if ($femaleImageCount > 0 && $personalityCount > 0 && !empty($femaleVoices)) { |
241 | foreach ($femaleNames as $index => $name) { |
242 | $baseAgents[] = [ |
243 | 'name' => $name, |
244 | 'gender' => 'female', |
245 | 'image' => $femaleImages[$index % $femaleImageCount], |
246 | 'voice' => $femaleVoices[array_rand($femaleVoices)], |
247 | 'personality' => $personalities[($personalityIndex++) % $personalityCount], |
248 | ]; |
249 | } |
250 | } |
251 | |
252 | shuffle($baseAgents); |
253 | |
254 | $responseData = []; |
255 | |
256 | $promptTemplate = ($persona->type === 'cold-call') |
257 | ? ($coldCallPromptParam->value ?? '') |
258 | : ($discoveryCallPromptParam->value ?? ''); |
259 | |
260 | foreach ($icps as $icp) { |
261 | $icpArray = is_array($icp) ? $icp : (array) $icp; |
262 | |
263 | $person_description = "\n• Company Name: {$icpArray['company_name']}"; |
264 | $person_description .= "\n• Company Size: {$icpArray['company_size']}"; |
265 | $person_description .= "\n• Current Pain Points: {$icpArray['pain_points']}"; |
266 | $person_description .= "\n• Decision-Making Authority: {$icpArray['decision_making']}"; |
267 | $person_description .= "\n• Current Solution: {$icpArray['current_solution']}"; |
268 | $person_description .= "\n• Budget: {$icpArray['budget']}"; |
269 | $person_description .= "\n• Urgency Level: {$icpArray['urgency_level']}"; |
270 | $person_description .= "\n• Openness to New Solutions: {$icpArray['openess_to_new_solutions']}"; |
271 | $person_description .= "\n• Communication Style: {$icpArray['communication_style']}"; |
272 | $person_description .= "\n• Personality: {$icpArray['personality']}\n"; |
273 | |
274 | $allObjections = array_map(function ($objection) { |
275 | $options = is_array($objection['options']) ? $objection['options'] : []; |
276 | $optionsString = implode("\n", array_map(fn($opt) => "- " . $opt, $options)); |
277 | return "{$objection['category']}:\n{$optionsString}"; |
278 | }, $persona->objections ?? []); |
279 | $allObjectionsString = implode("\n\n", $allObjections); |
280 | |
281 | $agentsWithPrompt = array_map(function ($agent) use ($persona, $person_description, $allObjectionsString, $promptTemplate) { |
282 | $placeholders = [ |
283 | '{name}', |
284 | '{targetIndustry}', |
285 | '{persona_prompt}', |
286 | '{all_objections}', |
287 | ]; |
288 | |
289 | $replacements = [ |
290 | $agent['name'], |
291 | $persona->industry, |
292 | trim($person_description), |
293 | $allObjectionsString, |
294 | ]; |
295 | |
296 | $agent['prompt'] = str_replace($placeholders, $replacements, $promptTemplate); |
297 | |
298 | return $agent; |
299 | }, $baseAgents); |
300 | |
301 | $responseData[] = [ |
302 | 'icp' => $icp, |
303 | 'agents' => $agentsWithPrompt, |
304 | ]; |
305 | } |
306 | |
307 | return response()->json([ |
308 | 'status' => 'success', |
309 | 'data' => $responseData, |
310 | ]); |
311 | } |
312 | } |