update
This commit is contained in:
parent
7e44093e8d
commit
80b772bc8b
@ -282,8 +282,8 @@ public function joinGuest(Request $request, $code)
|
|||||||
*/
|
*/
|
||||||
public function detectEmotion(Request $request, $code)
|
public function detectEmotion(Request $request, $code)
|
||||||
{
|
{
|
||||||
$speechText = $request->input('speech_text', '');
|
$speechText = (string)($request->input('speech_text') ?? '');
|
||||||
$imageFrame = $request->input('image_frame', null);
|
$imageFrame = $request->input('image_frame');
|
||||||
|
|
||||||
$result = $this->aiService->detectEmotion($speechText, $imageFrame);
|
$result = $this->aiService->detectEmotion($speechText, $imageFrame);
|
||||||
|
|
||||||
|
|||||||
@ -21,8 +21,13 @@ class AiClientCallService
|
|||||||
* @param string $clientName
|
* @param string $clientName
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function generateMoM(string $transcript, string $liveNotes = '', string $projectName = '', string $clientName = ''): array
|
public function generateMoM(?string $transcript = '', ?string $liveNotes = '', ?string $projectName = '', ?string $clientName = ''): array
|
||||||
{
|
{
|
||||||
|
$transcript = (string)($transcript ?? '');
|
||||||
|
$liveNotes = (string)($liveNotes ?? '');
|
||||||
|
$projectName = (string)($projectName ?? '');
|
||||||
|
$clientName = (string)($clientName ?? '');
|
||||||
|
|
||||||
$apiKey = env('GEMINI_API_KEY') ?: (env('GOOGLE_API_KEY') ?: (getenv('GEMINI_API_KEY') ?: getenv('GOOGLE_API_KEY')));
|
$apiKey = env('GEMINI_API_KEY') ?: (env('GOOGLE_API_KEY') ?: (getenv('GEMINI_API_KEY') ?: getenv('GOOGLE_API_KEY')));
|
||||||
|
|
||||||
$combinedContent = "Project: {$projectName}\nClient: {$clientName}\n\n";
|
$combinedContent = "Project: {$projectName}\nClient: {$clientName}\n\n";
|
||||||
@ -105,11 +110,11 @@ public function generateMoM(string $transcript, string $liveNotes = '', string $
|
|||||||
* @param string|null $imageFrameBase64
|
* @param string|null $imageFrameBase64
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function detectEmotion(string $speechText = '', ?string $imageFrameBase64 = null): array
|
public function detectEmotion(?string $speechText = '', ?string $imageFrameBase64 = null): array
|
||||||
{
|
{
|
||||||
$apiKey = env('GEMINI_API_KEY') ?: (env('GOOGLE_API_KEY') ?: (getenv('GEMINI_API_KEY') ?: getenv('GOOGLE_API_KEY')));
|
$apiKey = env('GEMINI_API_KEY') ?: (env('GOOGLE_API_KEY') ?: (getenv('GEMINI_API_KEY') ?: getenv('GOOGLE_API_KEY')));
|
||||||
|
|
||||||
$lowerText = strtolower(trim($speechText));
|
$lowerText = strtolower(trim($speechText ?? ''));
|
||||||
|
|
||||||
// 1. Check for negative polarity / negations that invert positive sentiment
|
// 1. Check for negative polarity / negations that invert positive sentiment
|
||||||
$negatedHappyPhrases = [
|
$negatedHappyPhrases = [
|
||||||
@ -121,7 +126,7 @@ public function detectEmotion(string $speechText = '', ?string $imageFrameBase64
|
|||||||
|
|
||||||
$foundNegations = [];
|
$foundNegations = [];
|
||||||
foreach ($negatedHappyPhrases as $negPhrase) {
|
foreach ($negatedHappyPhrases as $negPhrase) {
|
||||||
if (str_contains($lowerText, $negPhrase)) {
|
if (preg_match('/\b' . preg_quote($negPhrase, '/') . '\b/i', $lowerText)) {
|
||||||
$foundNegations[] = $negPhrase;
|
$foundNegations[] = $negPhrase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,7 +159,7 @@ public function detectEmotion(string $speechText = '', ?string $imageFrameBase64
|
|||||||
|
|
||||||
$detectedAngryWords = [];
|
$detectedAngryWords = [];
|
||||||
foreach ($angryTriggers as $word) {
|
foreach ($angryTriggers as $word) {
|
||||||
if (preg_match('/\b' . preg_quote($word, '/') . '\b/i', $lowerText) || str_contains($lowerText, $word)) {
|
if (preg_match('/\b' . preg_quote($word, '/') . '\b/i', $lowerText)) {
|
||||||
$detectedAngryWords[] = $word;
|
$detectedAngryWords[] = $word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,7 +169,7 @@ public function detectEmotion(string $speechText = '', ?string $imageFrameBase64
|
|||||||
|
|
||||||
$detectedSadWords = [];
|
$detectedSadWords = [];
|
||||||
foreach ($sadTriggers as $word) {
|
foreach ($sadTriggers as $word) {
|
||||||
if (preg_match('/\b' . preg_quote($word, '/') . '\b/i', $lowerText) || str_contains($lowerText, $word)) {
|
if (preg_match('/\b' . preg_quote($word, '/') . '\b/i', $lowerText)) {
|
||||||
$detectedSadWords[] = $word;
|
$detectedSadWords[] = $word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,7 +178,7 @@ public function detectEmotion(string $speechText = '', ?string $imageFrameBase64
|
|||||||
// Only consider happy words if there are no strong negations of happiness
|
// Only consider happy words if there are no strong negations of happiness
|
||||||
if (empty($foundNegations)) {
|
if (empty($foundNegations)) {
|
||||||
foreach ($happyTriggers as $word) {
|
foreach ($happyTriggers as $word) {
|
||||||
if (preg_match('/\b' . preg_quote($word, '/') . '\b/i', $lowerText) || str_contains($lowerText, $word)) {
|
if (preg_match('/\b' . preg_quote($word, '/') . '\b/i', $lowerText)) {
|
||||||
$detectedHappyWords[] = $word;
|
$detectedHappyWords[] = $word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -551,21 +556,21 @@ protected function generateFallbackMoM(string $combinedContent, string $transcri
|
|||||||
$lower = strtolower($combinedContent);
|
$lower = strtolower($combinedContent);
|
||||||
|
|
||||||
$negatedHappyPhrases = ['not happy', 'not satisfied', 'not good', 'not great', 'not working', 'not impressed', 'not pleased', 'no progress', 'displeased', 'unhappy', 'dissatisfied', 'cannot accept', 'unacceptable'];
|
$negatedHappyPhrases = ['not happy', 'not satisfied', 'not good', 'not great', 'not working', 'not impressed', 'not pleased', 'no progress', 'displeased', 'unhappy', 'dissatisfied', 'cannot accept', 'unacceptable'];
|
||||||
$foundNegations = array_values(array_filter($negatedHappyPhrases, fn($w) => str_contains($lower, $w)));
|
$foundNegations = array_values(array_filter($negatedHappyPhrases, fn($w) => (bool)preg_match('/\b' . preg_quote($w, '/') . '\b/i', $lower)));
|
||||||
|
|
||||||
$angryWords = ['angry', 'furious', 'unacceptable', 'ridiculous', 'terrible', 'waste of time', 'delay', 'delays', 'delayed', 'broken', 'disaster', 'unhappy', 'frustrated', 'mad', 'annoyed', 'horrible', 'refund', 'cancel', 'fail', 'incompetent', 'unprofessional', 'missed deadline'];
|
$angryWords = ['angry', 'furious', 'unacceptable', 'ridiculous', 'terrible', 'waste of time', 'delay', 'delays', 'delayed', 'broken', 'disaster', 'unhappy', 'frustrated', 'mad', 'annoyed', 'horrible', 'refund', 'cancel', 'fail', 'incompetent', 'unprofessional', 'missed deadline'];
|
||||||
$sadWords = ['sad', 'disappointed', 'worried', 'concerned', 'nervous', 'scared', 'afraid', 'regret', 'doubt', 'hesitant', 'uncertain', 'at risk'];
|
$sadWords = ['sad', 'disappointed', 'worried', 'concerned', 'nervous', 'scared', 'afraid', 'regret', 'doubt', 'hesitant', 'uncertain', 'at risk'];
|
||||||
$happyWords = ['great', 'excellent', 'fantastic', 'awesome', 'good job', 'love it', 'happy', 'pleased', 'perfect', 'superb', 'wonderful', 'thank you', 'appreciate', 'impressed', 'kudos', 'brilliant', 'delighted'];
|
$happyWords = ['great', 'excellent', 'fantastic', 'awesome', 'good job', 'love it', 'happy', 'pleased', 'perfect', 'superb', 'wonderful', 'thank you', 'appreciate', 'impressed', 'kudos', 'brilliant', 'delighted'];
|
||||||
|
|
||||||
$foundAngry = array_values(array_unique(array_merge(
|
$foundAngry = array_values(array_unique(array_merge(
|
||||||
array_filter($angryWords, fn($w) => str_contains($lower, $w)),
|
array_filter($angryWords, fn($w) => (bool)preg_match('/\b' . preg_quote($w, '/') . '\b/i', $lower)),
|
||||||
$foundNegations
|
$foundNegations
|
||||||
)));
|
)));
|
||||||
$foundSad = array_values(array_filter($sadWords, fn($w) => str_contains($lower, $w)));
|
$foundSad = array_values(array_filter($sadWords, fn($w) => (bool)preg_match('/\b' . preg_quote($w, '/') . '\b/i', $lower)));
|
||||||
|
|
||||||
$foundHappy = [];
|
$foundHappy = [];
|
||||||
if (empty($foundNegations)) {
|
if (empty($foundNegations)) {
|
||||||
$foundHappy = array_values(array_filter($happyWords, fn($w) => str_contains($lower, $w)));
|
$foundHappy = array_values(array_filter($happyWords, fn($w) => (bool)preg_match('/\b' . preg_quote($w, '/') . '\b/i', $lower)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$sentiment = 'neutral';
|
$sentiment = 'neutral';
|
||||||
|
|||||||
@ -782,4 +782,181 @@ public function test_client_hub_and_logs_pages_render_recording_actions_when_pre
|
|||||||
->assertSee('Play Video')
|
->assertSee('Play Video')
|
||||||
->assertSee('Download Video Recording', false);
|
->assertSee('Download Video Recording', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_ai_emotion_detection_all_scenarios(): void
|
||||||
|
{
|
||||||
|
$client = Client::create([
|
||||||
|
'client_name' => 'Emotion Test Client',
|
||||||
|
'client_email' => 'emotion@test.com',
|
||||||
|
'project_name' => 'Emotion AI Testing',
|
||||||
|
'responsible_user_id' => $this->pm->id,
|
||||||
|
'created_by' => $this->admin->id,
|
||||||
|
'status' => 'active',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$meeting = ClientMeeting::create([
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'title' => 'Emotion Scenarios Test',
|
||||||
|
'meeting_code' => 'meet-emotion-scenarios',
|
||||||
|
'host_user_id' => $this->pm->id,
|
||||||
|
'status' => 'in_progress',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Scenario 1: Frustrated speech -> Angry
|
||||||
|
$resAngry = $this->postJson(route('client-calls.detect-emotion', $meeting->meeting_code), [
|
||||||
|
'speech_text' => 'This continuous delay is unacceptable and we missed deadline.',
|
||||||
|
]);
|
||||||
|
$resAngry->assertStatus(200)->assertJson(['emotion' => 'angry']);
|
||||||
|
|
||||||
|
// Scenario 2: Negated happy phrase -> Angry
|
||||||
|
$resNegated = $this->postJson(route('client-calls.detect-emotion', $meeting->meeting_code), [
|
||||||
|
'speech_text' => 'We are not happy and not satisfied with the progress.',
|
||||||
|
]);
|
||||||
|
$resNegated->assertStatus(200)->assertJson(['emotion' => 'angry']);
|
||||||
|
|
||||||
|
// Scenario 3: Concern / hesitation -> Sad
|
||||||
|
$resSad = $this->postJson(route('client-calls.detect-emotion', $meeting->meeting_code), [
|
||||||
|
'speech_text' => 'We are worried and concerned about the upcoming launch date.',
|
||||||
|
]);
|
||||||
|
$resSad->assertStatus(200)->assertJson(['emotion' => 'sad']);
|
||||||
|
|
||||||
|
// Scenario 4: Praise and delight -> Happy
|
||||||
|
$resHappy = $this->postJson(route('client-calls.detect-emotion', $meeting->meeting_code), [
|
||||||
|
'speech_text' => 'The sprint demo looks fantastic and we love the new dashboard.',
|
||||||
|
]);
|
||||||
|
$resHappy->assertStatus(200)->assertJson(['emotion' => 'happy']);
|
||||||
|
|
||||||
|
// Scenario 5: Dialogue with words like "made" and "message" (formerly false positive triggers) -> Neutral
|
||||||
|
$resNeutral = $this->postJson(route('client-calls.detect-emotion', $meeting->meeting_code), [
|
||||||
|
'speech_text' => 'We made a roadmap plan and I will send you a message shortly.',
|
||||||
|
]);
|
||||||
|
$resNeutral->assertStatus(200)->assertJson(['emotion' => 'neutral']);
|
||||||
|
|
||||||
|
// Scenario 6: Empty speech -> Neutral
|
||||||
|
$resEmpty = $this->postJson(route('client-calls.detect-emotion', $meeting->meeting_code), [
|
||||||
|
'speech_text' => '',
|
||||||
|
]);
|
||||||
|
$resEmpty->assertStatus(200)->assertJson(['emotion' => 'neutral']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_ai_service_mom_generation_and_fallback_engine(): void
|
||||||
|
{
|
||||||
|
$aiService = app(\App\Services\AiClientCallService::class);
|
||||||
|
|
||||||
|
// Test happy conversation MoM
|
||||||
|
$happyMoM = $aiService->generateMoM(
|
||||||
|
'Client: The release looks great, wonderful job team!',
|
||||||
|
'Reviewed sprint deliverables demo.',
|
||||||
|
'Payment Gateway',
|
||||||
|
'Fintech Corp'
|
||||||
|
);
|
||||||
|
$this->assertEquals('happy', $happyMoM['detected_sentiment']);
|
||||||
|
$this->assertNotEmpty($happyMoM['summary']);
|
||||||
|
$this->assertNotEmpty($happyMoM['todo_items']);
|
||||||
|
$this->assertNotEmpty($happyMoM['emotion_details']);
|
||||||
|
|
||||||
|
// Test angry conversation MoM
|
||||||
|
$angryMoM = $aiService->generateMoM(
|
||||||
|
'Client: This broken feature and missed deadline is unacceptable!',
|
||||||
|
'Client escalated timeline delay.',
|
||||||
|
'Payment Gateway',
|
||||||
|
'Fintech Corp'
|
||||||
|
);
|
||||||
|
$this->assertEquals('angry', $angryMoM['detected_sentiment']);
|
||||||
|
$this->assertTrue($angryMoM['angry_analysis']['is_angry']);
|
||||||
|
$this->assertNotEmpty($angryMoM['angry_analysis']['trigger_words']);
|
||||||
|
|
||||||
|
// Test sad conversation MoM
|
||||||
|
$sadMoM = $aiService->generateMoM(
|
||||||
|
'Client: We are disappointed and worried about budget allocations.',
|
||||||
|
'Discussion on risk factors.',
|
||||||
|
'Payment Gateway',
|
||||||
|
'Fintech Corp'
|
||||||
|
);
|
||||||
|
$this->assertEquals('sad', $sadMoM['detected_sentiment']);
|
||||||
|
|
||||||
|
// Test neutral conversation MoM
|
||||||
|
$neutralMoM = $aiService->generateMoM(
|
||||||
|
'Client: We made notes and will review the API contracts next week.',
|
||||||
|
'Standard sync.',
|
||||||
|
'Payment Gateway',
|
||||||
|
'Fintech Corp'
|
||||||
|
);
|
||||||
|
$this->assertEquals('neutral', $neutralMoM['detected_sentiment']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_resend_mom_email_endpoint(): void
|
||||||
|
{
|
||||||
|
Mail::fake();
|
||||||
|
|
||||||
|
$client = Client::create([
|
||||||
|
'client_name' => 'Email Test Client',
|
||||||
|
'client_email' => 'emailclient@test.com',
|
||||||
|
'project_name' => 'Email Dispatches',
|
||||||
|
'responsible_user_id' => $this->pm->id,
|
||||||
|
'created_by' => $this->admin->id,
|
||||||
|
'status' => 'active',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$meeting = ClientMeeting::create([
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'title' => 'MoM Dispatch Sync',
|
||||||
|
'meeting_code' => 'meet-dispatch-789',
|
||||||
|
'host_user_id' => $this->pm->id,
|
||||||
|
'status' => 'completed',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$note = ClientCallNote::create([
|
||||||
|
'client_meeting_id' => $meeting->id,
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'created_by' => $this->pm->id,
|
||||||
|
'summary' => 'MoM dispatch testing',
|
||||||
|
'mom_content' => 'Full MoM details here.',
|
||||||
|
'detected_sentiment' => 'happy',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($this->pm)
|
||||||
|
->postJson(route('client-calls.resend-email', $note->id));
|
||||||
|
|
||||||
|
$response->assertStatus(200)
|
||||||
|
->assertJson([
|
||||||
|
'success' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Mail::assertSent(ClientCallMomMail::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_signaling_heartbeat_and_chat_messages(): void
|
||||||
|
{
|
||||||
|
$client = Client::create([
|
||||||
|
'client_name' => 'Signal Client',
|
||||||
|
'client_email' => 'signal@test.com',
|
||||||
|
'project_name' => 'Signal Project',
|
||||||
|
'responsible_user_id' => $this->pm->id,
|
||||||
|
'created_by' => $this->admin->id,
|
||||||
|
'status' => 'active',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$meeting = ClientMeeting::create([
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'title' => 'WebRTC Signal Test',
|
||||||
|
'meeting_code' => 'meet-signal-test-321',
|
||||||
|
'host_user_id' => $this->pm->id,
|
||||||
|
'status' => 'in_progress',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->postJson(route('client-calls.heartbeat', $meeting->meeting_code), [
|
||||||
|
'peer_id' => 'peer_test_1',
|
||||||
|
'peer_name' => 'Alice Tester',
|
||||||
|
'chat_message' => ['text' => 'Hello everyone!'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(200)
|
||||||
|
->assertJsonStructure(['active_peers', 'signals', 'chat_messages']);
|
||||||
|
|
||||||
|
$meeting->refresh();
|
||||||
|
$this->assertNotEmpty($meeting->active_peers);
|
||||||
|
$this->assertNotEmpty($meeting->chat_messages);
|
||||||
|
$this->assertEquals('Hello everyone!', $meeting->chat_messages[0]['text']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user