Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.20% covered (success)
94.20%
195 / 207
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
RolePlayConversationsController
94.20% covered (success)
94.20%
195 / 207
57.14% covered (warning)
57.14%
4 / 7
34.23
0.00% covered (danger)
0.00%
0 / 1
 start
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
1
 process
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 end
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 recent
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
3
 conversation
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 history
97.44% covered (success)
97.44%
38 / 39
0.00% covered (danger)
0.00%
0 / 1
8
 agents
98.89% covered (success)
98.89%
89 / 90
0.00% covered (danger)
0.00%
0 / 1
13
1<?php
2
3namespace App\Http\Controllers\v2\RolePlay;
4
5use Illuminate\Http\Request;
6use App\Traits\SubscriptionTrait;
7use Illuminate\Http\JsonResponse;
8use App\Http\Controllers\Controller;
9use App\Http\Models\Parameter;
10use App\Http\Models\RolePlayConversations;
11use App\Http\Models\RolePlayProjects;
12use App\Http\Models\UserAddOns;
13use App\Http\Requests\v2\RolePlay\GetRolePlayAgentsRequest;
14use App\Http\Requests\v2\RolePlay\RolePlaySessionEndRequest;
15use App\Http\Requests\v2\RolePlay\RolePlaySessionHistoryRequest;
16use App\Http\Requests\v2\RolePlay\RolePlaySessionProcessRequest;
17use App\Http\Requests\v2\RolePlay\RolePlaySessionStartRequest;
18use App\Jobs\ProcessRolePlaySessionAsyncJob;
19use MongoDB\BSON\UTCDateTime;
20
21class 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}