Skip to content

Conversation

@Devnil434
Copy link

@Devnil434 Devnil434 commented Jan 10, 2026

📝 Description

This PR introduces post-debate personality analysis for debate participants (human and AI), providing structured, AI-generated insights into how participants argue rather than what they argue.

The feature analyzes completed debate transcripts, generates personality profiles per participant, and displays the results in the UI to support reflection and learning.

🎯 Motivation

DebateAI focuses on improving debating and critical-thinking skills. While current debates capture arguments and outcomes, users lack post-debate feedback on their argumentation style, tone, clarity, and responsiveness.

This change adds a reflection layer by generating Debate Personality Profiles after debate completion, aligned with DebateAI’s educational and AI-assisted goals.

⚙️ Proposed Changes

Backend

➕ New Files

  • personality.go

  • Defines PersonalityProfile model:

  • ID: primitive.ObjectID

  • DebateID: primitive.ObjectID (linked to saved_debate_transcripts)

  • ParticipantID: string (user email or bot name)

  • ArgumentStyle: string

  • Tone: string

  • EvidenceUsage: int (0–10)

  • Clarity: int (0–10)

  • Responsiveness: int (0–10)

  • Summary: string

  • CreatedAt: time.Time

  • personality_service.go

  • Implements GeneratePersonalityProfiles(debateID string):

  • Fetches debate transcript from saved_debate_transcripts

  • Ensures debate Result is not "pending"

  • Groups messages by sender (User / Bot / Opponent)

  • Constructs participant-specific transcript

  • Calls AI with a structured prompt

  • Parses JSON response into PersonalityProfile

  • Persists results to personality_profiles collection

personality_controller.go

  • API handler for generating personality profiles

Handles:

  • Debate not found
  • Debate not completed
  • AI processing errors

✏️ Modified Files

debate.go

Adds route:

POST /api/debates/:id/personality

  • Frontend
    ➕ New Components

  • PersonalityProfile.tsx

  • Renders participant personality traits

  • Uses radar chart or bar indicators

  • Displays AI-generated disclaimer clearly

✏️ Modified Components

ViewDebate.tsx

  • Fetches personality profiles for completed debates
  • Displays PersonalityProfile component for each participant

🧪 Verification Plan

Automated Testing

  • Unit tests for personality service:
  • go test ./services/personality_service_test.go
  • Verify API response structure using curl or Postman

Manual Testing

Complete a debate (User vs User or User vs Bot)

Trigger personality analysis

Open View Debate

Verify:

  • Personality profiles appear correctly
  • Scores and summaries are rendered properly
  • AI disclaimer is visible
  • UI styling remains consistent

📦 Scope & Design Notes

  • Analysis is post-debate only (no real-time inference)
  • One personality profile per participant per debate
  • AI output is presented as assistive feedback, not absolute judgment

Screenshot:

Screenshot 2026-01-10 123836 Screenshot 2026-01-10 124147

🚀 Future Extensions (Not Included)

  • Long-term personality trends per user
  • Comparative analysis across debates
  • Personalized debate improvement tips

🤝 Contribution Note

This PR is intended as a minimal, reviewable MVP. I’m happy to iterate on:

  • Trait definitions
  • Prompt design
  • UI visualization
  • Data modeling

Feedback is very welcome.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added AI-powered personality analysis for debate participants, generating profiles with metrics including argument style, tone, evidence usage, clarity, and responsiveness
    • Personality profiles automatically display in debate views and saved transcript history with radar chart visualizations
    • Enhanced transcript metadata to include participant email and role information

closes #184
✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 10, 2026

📝 Walkthrough

Walkthrough

This PR adds personality analysis capability for debate transcripts, introducing backend services and frontend components to generate and display personality profiles from participant arguments. It also enriches transcript metadata with email and role information, removes sensitive configuration examples, and centralizes password validation in authentication forms.

Changes

Cohort / File(s) Summary
Environment & Configuration Cleanup
.env.example, backend/config/config.prod.sample.yml
Removed all sensitive secret placeholders (API keys, JWT secrets, SMTP credentials, OAuth IDs) and server configuration examples from sample/example files
Backend Personality Analysis Service
backend/models/personality.go, backend/services/personality_service.go, backend/controllers/personality_controller.go, backend/routes/personality.go
Introduces new personality analysis feature: model with profile fields (ArgumentStyle, Tone, EvidenceUsage, Clarity, Responsiveness, Summary); service with GPT-4 integration to analyze participant arguments and persist profiles; controller handlers for generating and retrieving profiles; route registration at /api/debates/:id/personality
Backend Server Integration
backend/cmd/server/main.go
Registers personality service initialization and routes during server startup
Transcript Data Model Enhancement
backend/models/transcript.go
Introduces TranscriptEntry struct with text, email, and role fields; updates SavedDebateTranscript.Transcripts from map[string]string to map[string]TranscriptEntry to store richer metadata per transcript entry
Transcript Service & Controller Updates
backend/services/transcriptservice.go, backend/controllers/transcript_controller.go, backend/controllers/debatevsbot_controller.go, backend/controllers/post_controller.go
Propagates transcript model changes through service signature and controller usage; updates SaveDebateTranscript to accept TranscriptEntry maps; adjusts transcript persistence and preview logic to handle structured entries
Frontend Personality API Integration
frontend/src/services/transcriptService.ts
Adds PersonalityProfileData interface and two new methods: getPersonalityProfiles() for fetching existing profiles and generatePersonalityProfiles() for generating new ones; updates SavedDebateTranscript transcript type to include email and role
Frontend Personality Visualization Component
frontend/src/components/PersonalityProfile.tsx
New React component rendering radar charts for each personality profile with three metrics (Evidence, Clarity, Responsiveness), participant style/tone display, and analysis summary
Frontend Personality View Integration
frontend/src/Pages/ViewDebate.tsx, frontend/src/components/JudgementPopup.tsx, frontend/src/components/SavedTranscripts.tsx
Integrates personality analysis sections into debate view, judgment popup, and saved transcripts components; each manages personality profile state, loading, and error handling; fetches profiles on component mount or when debate ID changes
Frontend Form Validation Improvement
frontend/src/Pages/Authentication/forms.tsx
Introduces MIN_PASSWORD_LENGTH = 6 constant and replaces hard-coded password length checks across LoginForm, SignUpForm, and ResetPasswordForm
Frontend UI Cleanup
frontend/src/Pages/DebateRoom.tsx
Formatting adjustments and passes debateId prop to JudgmentPopup component; no semantic logic changes

Sequence Diagram(s)

sequenceDiagram
    participant Client as Frontend Client
    participant Service as Personality Service
    participant Mongo as MongoDB
    participant GPT as GPT-4 API

    Client->>Service: GeneratePersonalityProfiles(debateID)
    Service->>Mongo: Load SavedDebateTranscript by debateID
    Mongo-->>Service: Debate transcript data
    Service->>Service: Validate debate status & aggregate<br/>transcripts by participant
    
    loop For each participant
        Service->>GPT: Analyze participant arguments<br/>(structured JSON request)
        GPT-->>Service: Personality profile JSON<br/>(tone, style, evidence, etc.)
        Service->>Service: Parse & enrich profile<br/>(add DebateID, ParticipantID, CreatedAt)
        Service->>Mongo: Save PersonalityProfile
        Mongo-->>Service: Confirm saved
    end
    
    Service-->>Client: Return PersonalityProfile[] array
    Client->>Client: Display personality analysis UI
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The changes introduce substantial new functionality (personality analysis service with GPT integration and complex transcript aggregation logic), propagate structural changes across multiple backend layers (model, service, controller), and integrate the feature across diverse frontend components with state management and API handling. The heterogeneous nature—spanning new service logic, data model changes, and multi-component frontend integration—combined with logic density in transcript processing and error handling requires careful review across multiple areas.

Possibly Related PRs

Poem

🐰 With whiskers twitching, ears held high,
We trace the arguments, oh my!
Each thought and tone in profiles bright,
From GPT's gaze and MongoDB's might—
Personalities bloom where debaters dance,
A feature hopped into existence! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly matches the main changeset: it adds personality profile generation and display for debate participants (human and AI), which is the primary feature across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @backend/controllers/auth.go:
- Line 191: Remove the insecure logging of verification codes: delete the
log.Printf("DEMO MODE: Verification code for %s is %s", request.Email,
verificationCode) statement (or ensure it is only ever executed in a hermetic
test environment behind a strict dev-only feature flag), so that neither
request.Email nor verificationCode are written to logs; if demo support is
required, route codes to a mock email service or a secure, ephemeral test store
accessible only via authenticated admin tooling instead.

In @backend/services/personality_service.go:
- Around line 56-70: The participant-mapping for user_vs_user in the block
iterating debate.Transcripts is incorrect: instead of defaulting to "Opponent"
and guessing by phase substring, use explicit participant identifiers from the
transcript data or the debate object; update the logic in the function handling
SavedDebateTranscript (the loop over debate.Transcripts and the participants
map) to check each transcript entry for a Role, Email, or UserID field (e.g.,
Transcript.Role or Transcript.Email) and assign text to "User" when it matches
debate.Email (or the current user's identifier) and to "Opponent" otherwise; if
the SavedDebateTranscript model currently only contains phase keys, first extend
the model to include participant metadata for each phase (or maintain a separate
phase->participant mapping derived from debate setup) and then use that metadata
here to correctly populate participants["User"] and participants["Opponent"].
- Around line 90-96: The participant mapping sets profile.ParticipantID from
message sender but only handles "User" and "Bot"/"Opponent", leaving "Judge" and
other allowed Message sender values unmapped; update the mapping in the
personality analysis code that assigns profile.DebateID and
profile.ParticipantID so it either (a) explicitly handles "Judge" (e.g., map
"Judge" to a canonical debate participant value or debate.Judge/other field if
your domain model has one) or (b) filters out non-debate-participant senders
(like "Judge") before creating profiles/analysis; modify the block that assigns
profile.ParticipantID (where name is checked against "User" and "Bot") to cover
"Judge" and any other allowed sender values consistently.

In @backend/structs/auth.go:
- Line 5: The Password field's validation tag was loosened to min=6 on the auth
struct (Password), which weakens backend password policy; revert or strengthen
it by updating the binding tag on the Password field(s) to a more secure rule
(e.g., min=8 or a regex requiring mixed case/digits/symbols) and apply the same
change to the other Password validation tags present in this diff; if you need
shorter minimums for UX, instead enforce additional entropy rules (regex) or
keep min-length higher and implement compensating controls (rate
limiting/account lockouts) elsewhere.
🧹 Nitpick comments (9)
frontend/src/Pages/ViewDebate.tsx (1)

80-90: Redundant error handling pattern.

The .catch(() => []) on line 83 swallows errors and returns an empty array, so the outer try-catch block (lines 85-87) will never execute. Either remove the inner .catch() to let the outer block handle errors, or remove the outer try-catch since the inner handler already provides a fallback.

♻️ Suggested simplification
 const fetchPersonalityProfiles = async (id: string) => {
-  try {
-    setLoadingPersonality(true);
-    const profiles = await transcriptService.getPersonalityProfiles(id).catch(() => []);
-    setPersonalityProfiles(profiles);
-  } catch (err) {
-    console.error("Error fetching personality profiles:", err);
-  } finally {
-    setLoadingPersonality(false);
-  }
+  setLoadingPersonality(true);
+  try {
+    const profiles = await transcriptService.getPersonalityProfiles(id);
+    setPersonalityProfiles(profiles);
+  } catch (err) {
+    console.error("Error fetching personality profiles:", err);
+    setPersonalityProfiles([]);
+  } finally {
+    setLoadingPersonality(false);
+  }
 };
backend/controllers/personality_controller.go (1)

29-34: Consider sanitizing error messages returned to clients.

Line 32 returns err.Error() directly to the client. Depending on the error source (e.g., database connection issues, AI service failures), this could expose internal implementation details. Consider returning a generic error message while logging the detailed error server-side.

♻️ Suggested improvement
 // Generate new profiles
 profiles, err := services.GeneratePersonalityProfiles(debateID)
 if err != nil {
-  c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+  // Log detailed error for debugging
+  log.Printf("Failed to generate personality profiles for debate %s: %v", debateID, err)
+  c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate personality profiles"})
   return
 }
frontend/src/components/JudgementPopup.tsx (1)

145-159: Cascading GET→POST logic with redundant error handling.

The fetch pattern (GET first, then POST if empty) is sound for auto-generating profiles when none exist. However, the error handling has the same issue as in ViewDebate.tsx: the inner .catch(() => []) handlers prevent the outer try-catch from ever triggering.

Additionally, this fetch logic is duplicated across JudgementPopup.tsx and SavedTranscripts.tsx. Consider extracting this into a shared utility or hook.

♻️ Suggested refactor
 const fetchPersonalityProfiles = async (id: string) => {
+  setLoadingPersonality(true);
   try {
-    setLoadingPersonality(true);
-    // Try to GET first, then POST if none found
-    let profiles = await transcriptService.getPersonalityProfiles(id).catch(() => []);
+    let profiles = await transcriptService.getPersonalityProfiles(id);
     if (profiles.length === 0) {
-      profiles = await transcriptService.generatePersonalityProfiles(id).catch(() => []);
+      profiles = await transcriptService.generatePersonalityProfiles(id);
     }
     setPersonalityProfiles(profiles);
   } catch (err) {
     console.error('Error fetching personality profiles:', err);
+    setPersonalityProfiles([]);
   } finally {
     setLoadingPersonality(false);
   }
 };
frontend/src/components/SavedTranscripts.tsx (1)

102-120: Consistent fetch pattern but duplicated across components.

This implementation properly propagates errors to the outer try-catch block (unlike the similar code in ViewDebate.tsx and JudgementPopup.tsx). The GET→POST cascade is appropriate for auto-generating profiles on view.

Consider extracting this shared logic into a custom hook (e.g., usePersonalityProfiles) to reduce duplication and ensure consistent behavior across ViewDebate, JudgementPopup, and SavedTranscripts.

frontend/src/components/PersonalityProfile.tsx (1)

13-25: Type definition could be aligned with service interface.

The PersonalityTrait interface is similar to but not identical to PersonalityProfileData from transcriptService.ts (which includes id and debateId fields). Since this component receives data from the service, consider reusing PersonalityProfileData directly or using Pick/Omit to derive the type, ensuring type consistency.

♻️ Suggested alignment
+import { PersonalityProfileData } from '@/services/transcriptService';
+
-interface PersonalityTrait {
-    participantId: string;
-    argument_style: string;
-    tone: string;
-    evidence_usage: number;
-    clarity: number;
-    responsiveness: number;
-    summary: string;
-}
-
 interface PersonalityProfileProps {
-    profiles: PersonalityTrait[];
+    profiles: PersonalityProfileData[];
 }
frontend/src/services/transcriptService.ts (1)

23-33: Consider using camelCase for consistency.

The field names in PersonalityProfileData use snake_case (argument_style, evidence_usage, clarity, responsiveness), which is inconsistent with TypeScript conventions and the existing interfaces in this file (e.g., debateType, createdAt, updatedAt in SavedDebateTranscript).

♻️ Proposed refactor to align with TypeScript conventions
 export interface PersonalityProfileData {
   id: string;
   debateId: string;
   participantId: string;
-  argument_style: string;
+  argumentStyle: string;
   tone: string;
-  evidence_usage: number;
+  evidenceUsage: number;
   clarity: number;
   responsiveness: number;
   summary: string;
 }

Note: This change would require corresponding updates in the backend response serialization or a mapping layer in the frontend.

backend/services/personality_service.go (3)

85-88: Silent error handling may hide analysis failures.

When analyzeParticipantPersonality fails for a participant, the error is silently skipped with continue. This means:

  • Partial results may be returned without indication that some participants were not analyzed
  • Debugging failures becomes difficult as errors are not logged or surfaced
  • Callers cannot distinguish between "no participants found" and "analysis failed for all participants"
♻️ Proposed improvement

Consider at minimum logging the error:

 		profile, err := analyzeParticipantPersonality(name, transcript)
 		if err != nil {
+			fmt.Printf("Warning: Failed to analyze participant %s: %v\n", name, err)
 			continue // Skip failed analysis for one participant
 		}

Or collect errors and return them alongside successful profiles for better observability.


109-157: Unused parameter in function signature.

The name parameter at line 109 is never used within the function body. While this might have been intended for logging or inclusion in the prompt, it currently serves no purpose.

♻️ Proposed fix

Either remove the unused parameter:

-func analyzeParticipantPersonality(name, transcript string) (*models.PersonalityProfile, error) {
+func analyzeParticipantPersonality(transcript string) (*models.PersonalityProfile, error) {

And update the caller at line 85:

-		profile, err := analyzeParticipantPersonality(name, transcript)
+		profile, err := analyzeParticipantPersonality(transcript)

Or use it meaningfully (e.g., for logging or in the prompt).


138-138: Consider migrating to a newer GPT-4 mini variant.

The model gpt-4o-mini-2024-07-18 is currently available but OpenAI has scheduled it for retirement no earlier than September 15, 2025. OpenAI recommends users migrate to newer variants such as GPT-4.1 or GPT-4.1-mini. If this service is expected to remain active beyond that date, evaluate whether to upgrade to a recommended newer model version.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb07eaf and 6f67a1f.

📒 Files selected for processing (16)
  • .env.example
  • backend/cmd/server/main.go
  • backend/config/config.prod.sample.yml
  • backend/controllers/auth.go
  • backend/controllers/personality_controller.go
  • backend/models/personality.go
  • backend/routes/personality.go
  • backend/services/personality_service.go
  • backend/structs/auth.go
  • frontend/src/Pages/Authentication/forms.tsx
  • frontend/src/Pages/DebateRoom.tsx
  • frontend/src/Pages/ViewDebate.tsx
  • frontend/src/components/JudgementPopup.tsx
  • frontend/src/components/PersonalityProfile.tsx
  • frontend/src/components/SavedTranscripts.tsx
  • frontend/src/services/transcriptService.ts
💤 Files with no reviewable changes (2)
  • backend/config/config.prod.sample.yml
  • .env.example
🧰 Additional context used
🧬 Code graph analysis (7)
backend/cmd/server/main.go (2)
backend/services/personality_service.go (1)
  • InitPersonalityService (21-23)
backend/routes/personality.go (1)
  • SetupPersonalityRoutes (10-13)
backend/routes/personality.go (1)
backend/controllers/personality_controller.go (2)
  • GeneratePersonalityProfilesHandler (12-40)
  • GetPersonalityProfilesHandler (43-62)
backend/controllers/personality_controller.go (1)
backend/services/personality_service.go (2)
  • GetPersonalityProfiles (159-181)
  • GeneratePersonalityProfiles (25-107)
frontend/src/components/JudgementPopup.tsx (3)
frontend/src/services/transcriptService.ts (2)
  • PersonalityProfileData (23-33)
  • transcriptService (64-290)
backend/models/personality.go (1)
  • PersonalityProfile (10-21)
frontend/src/components/PersonalityProfile.tsx (1)
  • PersonalityProfile (27-84)
frontend/src/services/transcriptService.ts (1)
frontend/src/utils/auth.ts (1)
  • getAuthToken (5-7)
backend/services/personality_service.go (3)
backend/services/ai.go (2)
  • ChatGPT (26-29)
  • NewChatGPT (31-36)
backend/models/personality.go (1)
  • PersonalityProfile (10-21)
backend/db/db.go (1)
  • MongoDatabase (18-18)
frontend/src/Pages/DebateRoom.tsx (1)
frontend/src/services/vsbot.ts (1)
  • concedeDebate (103-118)
🔇 Additional comments (19)
frontend/src/Pages/Authentication/forms.tsx (2)

31-34: Password validation correctly uses the MIN_PASSWORD_LENGTH constant.

The implementation consistently applies the minimum password length across all authentication forms (login, signup, reset). The constant-based approach ensures maintainability.

However, please address the security concern raised about the 6-character minimum length.

Also applies to: 143-146, 352-355


8-8: Reconsider the security impact of reducing the password minimum length to 6 characters.

The password minimum length affects security, but the justification in the original concern contains factual inaccuracies:

  • NIST SP 800-63B recommends 15 characters for single-factor passwords, or 8 characters when used as part of multi-factor authentication—not a universal 8-character minimum.
  • Industry practices are mixed: Google defaults to 6 characters; AWS allows 6–128 (default 8); GitHub enforces 8+; Microsoft recommends 12–14. Not all enforce 8+ as claimed.
  • A 6-character minimum is weaker than 8, but verify whether this system's password usage context (single-factor vs. multi-factor, deployment environment, overall authentication architecture) justifies this trade-off.

If moving to 6 is intentional for usability or backwards compatibility, document the decision and consider compensating controls (e.g., rate limiting, MFA enforcement).

Likely an incorrect or invalid review comment.

backend/cmd/server/main.go (2)

31-31: Personality service initialization properly placed.

The InitPersonalityService(cfg) call is correctly positioned after rating service initialization and before database operations, following the established service initialization pattern in this codebase.


163-165: Personality routes correctly registered in authenticated router group.

The SetupPersonalityRoutes(auth) call properly registers personality endpoints within the authenticated router group, consistent with other protected features like team routes and community routes. This ensures personality analysis endpoints are protected by JWT authentication middleware.

backend/routes/personality.go (1)

1-13: Personality routes follow RESTful conventions and established patterns.

The route setup is well-structured:

  • POST endpoint for generating personality profiles (write operation)
  • GET endpoint for retrieving existing profiles (read operation)
  • Consistent with other route files in the codebase (e.g., transcript routes, team routes)
  • Uses standard Gin RouterGroup pattern for modular route registration
  • Path parameter :id appropriately captures debate ID

The handlers referenced (GeneratePersonalityProfilesHandler, GetPersonalityProfilesHandler) are confirmed to exist in the controllers package.

backend/models/personality.go (1)

9-21: Well-structured model definition.

The PersonalityProfile struct is appropriately designed with proper BSON/JSON tags for MongoDB serialization. The use of primitive.ObjectID for DebateID maintains referential integrity with the debate collection.

Consider adding validation at the service layer to ensure EvidenceUsage, Clarity, and Responsiveness remain within the expected 0-10 range, since int allows values outside this bound.

frontend/src/Pages/DebateRoom.tsx (1)

739-751: Good integration of debateId for personality profiles.

Passing debateId={debateData.debateId} to JudgmentPopup correctly enables the personality profile feature to fetch and display analysis for the completed debate.

frontend/src/Pages/ViewDebate.tsx (1)

767-783: Clean conditional rendering for personality analysis.

The UI section appropriately guards rendering with loadingPersonality || personalityProfiles.length > 0, displays a loading spinner during fetch, and renders the PersonalityProfile component when data is available.

backend/controllers/personality_controller.go (1)

12-40: Good idempotent design for profile generation.

The handler appropriately checks for existing profiles before generating new ones, preventing duplicate entries and making the POST endpoint safe to call multiple times. Returning the existing profiles with a "Profiles already exist" message provides clear feedback.

frontend/src/components/JudgementPopup.tsx (1)

728-744: Appropriate UI rendering for personality analysis within judgment popup.

The personality analysis section is correctly placed after the verdict and conditionally rendered based on loading state and profile availability. The loading spinner with contextual message ("AI is analyzing your debate style...") provides good UX feedback.

frontend/src/components/SavedTranscripts.tsx (1)

496-514: Complete UI state handling for personality profiles.

The section appropriately handles all three states: loading spinner, profiles display via PersonalityProfile component, and a fallback message when no profiles are available. This provides better UX than the other components which hide the section entirely when no profiles exist.

frontend/src/components/PersonalityProfile.tsx (2)

32-37: Radar chart with only 3 data points forms a triangle.

Three metrics create a triangular radar chart, which works but may not fully leverage the radar visualization format. If additional metrics become available in the future (e.g., logical rigor, persuasiveness), the chart will become more informative. Current implementation is functional.


39-80: Well-implemented profile card with radar visualization.

The component properly handles:

  • Responsive chart container
  • Dark mode support with appropriate color classes
  • Summary section with visual distinction
  • AI-generated disclaimer for transparency

Using index as the React key is acceptable here since the profiles array is static and not subject to reordering during the component's lifecycle.

frontend/src/services/transcriptService.ts (2)

233-259: LGTM!

The method follows established patterns in this file for authentication, error handling, and data fetching. The fallback to an empty array when no profiles exist is appropriate.


261-289: LGTM!

The method correctly triggers personality profile generation via POST and handles errors consistently with the existing codebase patterns.

backend/services/personality_service.go (4)

21-23: LGTM!

The singleton initialization pattern is straightforward and appropriate for this use case.


26-43: LGTM!

The debate validation logic correctly checks for completion status and handles ID conversion errors appropriately. The 2-minute timeout is reasonable for AI processing.


48-55: LGTM!

The user_vs_bot transcript aggregation correctly iterates through messages and groups by sender, with appropriate handling for empty sender values.


159-181: LGTM!

The retrieval logic is straightforward and correctly queries MongoDB for personality profiles associated with the debate.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/controllers/debatevsbot_controller.go (1)

270-279: Prefer nil over make(map[...]) to preserve omitempty semantics (unless you need {} specifically). Empty maps can get persisted as empty objects and may change client behavior vs “absent”.

Proposed change
-        make(map[string]models.TranscriptEntry),
+        nil,

Also applies to: 586-595

🤖 Fix all issues with AI agents
In @backend/controllers/post_controller.go:
- Around line 22-25: The transcriptPreview type currently exposes
models.TranscriptEntry (which includes email) via the Transcripts map; to avoid
leaking PII, replace or map that field to a sanitized DTO when returning public
posts—either change transcriptPreview.Transcripts to
map[string]TranscriptEntryPreview (a new struct without email or with a
displayName only) or, if you build the map in the handler, explicitly
strip/replace the email on each models.TranscriptEntry before adding it for
non-owner or public views (use the post/ownership check already in the handler
to decide), ensuring only owners see the raw email.

In @backend/services/personality_service.go:
- Around line 145-150: The JSON cleaning currently uses sequential TrimPrefix
calls that miss cases like "```json\n{...}\n```"; update the logic around the
cleanResponse variable so you first TrimPrefix "```json\n" (or check HasPrefix
"```json") then TrimPrefix "```" and TrimSuffix "```", or implement the robust
approach: if cleanResponse starts with "```" split on "\n" and join
lines[1:len(lines)-1] to extract the inner JSON, then TrimSpace; apply this
change where response is cleaned (cleanResponse variable in
personality_service.go).
- Around line 101-106: The loop currently calls profileCollection.InsertOne and
returns immediately on the first insert error, causing partial inserts with no
rollback; either make the loop atomic by running all inserts inside a MongoDB
transaction (use client.StartSession and session.WithTransaction, perform
profileCollection.InsertOne calls inside the transaction and only append to
profiles on success), or switch to a partial-result pattern: keep inserting,
append successful inserts to the profiles slice, collect insert errors into an
errs slice and at the end return the profiles plus a combined error (or nil if
no errors) instead of returning immediately; update callers accordingly to
handle the chosen behavior.
- Around line 21-23: InitPersonalityService currently assumes cfg and nested
fields exist and that the API key is non-empty; add defensive validation: check
that cfg is not nil, cfg.Openai (or equivalent struct) is not nil, and
cfg.Openai.GptApiKey is not empty before calling NewChatGPT; if validation
fails, return an error from InitPersonalityService (change its signature to
return error) or log a clear fatal/error message and avoid setting
personalityGPT so GeneratePersonalityProfiles won't panic, and include
references to personalityGPT, NewChatGPT, InitPersonalityService and
GeneratePersonalityProfiles when making the change.
- Around line 74-82: The loop over participants currently swallows errors from
analyzeParticipantPersonality(name, transcript); instead of silently continuing,
log the error (including the participant name and error message, and optionally
transcript length or snippet) before continuing so failures are visible; update
the error branch in the participants loop inside personality_service.go to call
the service/global logger (e.g., svc.logger or log.Printf) with a clear message
and the err, then continue, keeping the existing behavior for DB insert errors
unchanged.
- Around line 25-109: GeneratePersonalityProfiles is not idempotent because it
uses profileCollection.InsertOne which will create duplicates; change the logic
to either clear existing profiles for the debate before regenerating (call
profileCollection.DeleteMany with filter bson.M{"debate_id": objID}) or replace
each profile with an upsert (use profileCollection.ReplaceOne with filter
bson.M{"debate_id": objID, "participant_id": profile.ParticipantID} and
options.Replace().SetUpsert(true)) in place of InsertOne; keep the rest of the
loop and ensure profile.DebateID and profile.ParticipantID are set before the
upsert/delete operations so the operation is atomic and safe for direct service
calls.

In @frontend/src/components/SavedTranscripts.tsx:
- Around line 96-120: When opening a transcript in handleViewTranscript, clear
stale profiles immediately (call setPersonalityProfiles([])) and only
fetch/generate profiles for debates that are completed: in
fetchPersonalityProfiles (called with debateId and/or the transcript object),
call transcriptService.getPersonalityProfiles(debateId) but only call
transcriptService.generatePersonalityProfiles(debateId) if the transcript.status
=== 'completed' (do not POST for 'pending'); ensure
setLoadingPersonality(true/false) and setPersonalityProfiles([]) are used to
avoid showing prior transcript data while loading or when generation is skipped.

In @frontend/src/services/transcriptService.ts:
- Around line 269-297: In generatePersonalityProfiles, make the POST explicit by
adding Content-Type: 'application/json' (and optionally Accept:
'application/json') to the headers and, to avoid ambiguity with empty POSTs,
include an explicit empty JSON body (e.g., JSON.stringify({})); update the
headers object in the fetch call inside generatePersonalityProfiles to include
these keys so the request is consistent with other API calls.
- Around line 241-267: The getPersonalityProfiles function currently throws on
any non-OK response; change its error handling so a 404 returns an empty array
instead of throwing (so the UI can fall back to generation): after the fetch in
getPersonalityProfiles check if response.status === 404 and return []
immediately; otherwise keep the existing logic (parse JSON and throw on other
non-OK statuses), and ensure you only call response.json() once and handle
missing data by returning data.profiles || [].
🧹 Nitpick comments (11)
backend/controllers/transcript_controller.go (1)

409-417: Avoid fetching email if it’s intentionally unused. The _ = email workaround suggests UpdatePendingTranscriptsHandler only needs token validity; consider adding a “validate token only” helper to avoid unused outputs.

frontend/src/components/SavedTranscripts.tsx (2)

463-474: Owner-only PII display is fine, but avoid assuming entry.email/role are always present. If older transcripts exist, guard with fallbacks to prevent odd UI output.


499-516: Consider exposing errors to the user (not just console.error). Right now failures render as “No personality profiles available”, which is misleading when the backend returns “debate not completed” / AI errors.

backend/services/transcriptservice.go (2)

180-221: TranscriptEntry map construction looks correct; consider deduplicating the phase lists. The phase arrays are repeated twice; a small helper/const slice would reduce drift risk.


705-705: Signature change is fine; ensure nil/empty map semantics are intentional. omitempty + $set can still store null/{} depending on caller input; make sure callers don’t accidentally overwrite existing transcripts with empty maps.

backend/models/transcript.go (1)

46-60: MarshalJSON relies on field shadowing for id/userId; consider making it unambiguous. Prefer a dedicated response struct (or an alias type that omits ID/UserID) to avoid duplicate json tag collisions.

backend/services/personality_service.go (5)

41-43: Strengthen debate completion validation.

The check only excludes "pending" status but doesn't validate that the debate has a recognized completion state. An empty string or invalid Result value would pass this check.

✅ Proposed fix to use positive validation
-	if debate.Result == "pending" {
-		return nil, errors.New("debate is not yet completed")
+	if debate.Result != "win" && debate.Result != "loss" && debate.Result != "draw" {
+		return nil, fmt.Errorf("debate is not completed (current status: %s)", debate.Result)
 	}

111-111: Unused parameter: name is never used in the function.

The name parameter is passed to analyzeParticipantPersonality but never referenced in the function body. This could indicate incomplete implementation or unnecessary parameter passing.

Consider either:

  1. Removing the parameter if not needed, or
  2. Including the participant name in the analysis prompt for more contextual analysis

152-156: Missing validation of AI response fields.

After unmarshaling the JSON response, there's no validation that:

  • Numeric scores (EvidenceUsage, Clarity, Responsiveness) are within the expected 0-10 range
  • Required string fields (ArgumentStyle, Tone, Summary) are non-empty

Invalid AI responses could result in storing malformed profiles.

✅ Add validation after unmarshal
 	var profile models.PersonalityProfile
 	err = json.Unmarshal([]byte(cleanResponse), &profile)
 	if err != nil {
 		return nil, fmt.Errorf("failed to parse AI response: %v", err)
 	}
+
+	// Validate ranges and required fields
+	if profile.EvidenceUsage < 0 || profile.EvidenceUsage > 10 ||
+		profile.Clarity < 0 || profile.Clarity > 10 ||
+		profile.Responsiveness < 0 || profile.Responsiveness > 10 {
+		return nil, fmt.Errorf("AI returned scores outside valid range (0-10)")
+	}
+	if profile.ArgumentStyle == "" || profile.Tone == "" || profile.Summary == "" {
+		return nil, fmt.Errorf("AI returned incomplete profile")
+	}
 
 	return &profile, nil

161-183: LGTM with minor suggestion.

The retrieval logic is straightforward and correct. The function appropriately returns an empty slice (not an error) when no profiles exist.

Minor improvement: consider adding more context to the error on line 167 for better debugging:

💡 Optional improvement
 	objID, err := primitive.ObjectIDFromHex(debateID)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("invalid debate ID: %v", err)
 	}

140-140: Consider making the GPT model version configurable.

The hardcoded model version "gpt-4o-mini-2024-07-18" works today but has a scheduled retirement on March 31, 2026. Extracting this to a configuration parameter will simplify future model upgrades and avoid code changes when switching to gpt-4.1-mini or other variants.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f67a1f and 91332c2.

📒 Files selected for processing (8)
  • backend/controllers/debatevsbot_controller.go
  • backend/controllers/post_controller.go
  • backend/controllers/transcript_controller.go
  • backend/models/transcript.go
  • backend/services/personality_service.go
  • backend/services/transcriptservice.go
  • frontend/src/components/SavedTranscripts.tsx
  • frontend/src/services/transcriptService.ts
🧰 Additional context used
🧬 Code graph analysis (8)
backend/controllers/debatevsbot_controller.go (1)
backend/models/transcript.go (1)
  • TranscriptEntry (10-14)
frontend/src/components/SavedTranscripts.tsx (3)
frontend/src/services/transcriptService.ts (2)
  • PersonalityProfileData (27-37)
  • transcriptService (72-298)
backend/models/personality.go (1)
  • PersonalityProfile (10-21)
frontend/src/components/PersonalityProfile.tsx (1)
  • PersonalityProfile (27-84)
backend/controllers/transcript_controller.go (1)
backend/models/transcript.go (1)
  • TranscriptEntry (10-14)
backend/controllers/post_controller.go (1)
backend/models/transcript.go (1)
  • TranscriptEntry (10-14)
frontend/src/services/transcriptService.ts (1)
frontend/src/utils/auth.ts (1)
  • getAuthToken (5-7)
backend/services/personality_service.go (4)
backend/services/ai.go (2)
  • ChatGPT (26-29)
  • NewChatGPT (31-36)
backend/models/personality.go (1)
  • PersonalityProfile (10-21)
backend/db/db.go (1)
  • MongoDatabase (18-18)
backend/models/transcript.go (1)
  • SavedDebateTranscript (32-44)
backend/models/transcript.go (3)
backend/websocket/websocket.go (1)
  • Message (71-94)
backend/services/ai.go (1)
  • Message (21-24)
backend/models/debatevsbot.go (1)
  • Message (6-10)
backend/services/transcriptservice.go (1)
backend/models/transcript.go (1)
  • TranscriptEntry (10-14)
🔇 Additional comments (5)
backend/controllers/transcript_controller.go (2)

14-15: Prod gating for test endpoints is a good safeguard; keep it strict.

Also applies to: 260-263, 313-316


52-55: Good: reject tokens that validate but don’t yield an email. Prevents creating “anonymous” submissions tied to empty identifiers.

backend/models/transcript.go (1)

10-14: Model change is coherent and enables richer transcripts.

Also applies to: 33-43

backend/services/transcriptservice.go (1)

223-246: The review comment identifies a non-existent functional gap. The personality service already handles this correctly: GeneratePersonalityProfiles explicitly checks debate.DebateType and uses debate.Transcripts for user-vs-user debates (line 59 in personality_service.go), not debate.Messages. The design is intentional—Messages is used only for user-vs-bot debates, while Transcripts (populated by forTranscriptsMap and againstTranscriptsMap in the reviewed code) is the source for user-vs-user personality analysis. No changes are required.

Likely an incorrect or invalid review comment.

backend/services/personality_service.go (1)

45-69: LGTM!

The participant grouping logic correctly handles both debate types (user_vs_bot and user_vs_user) with appropriate field mappings and defensive empty checks.

Comment on lines 22 to 25
type transcriptPreview struct {
Messages []models.Message `json:"messages"`
Transcripts map[string]string `json:"transcripts,omitempty"`
Messages []models.Message `json:"messages"`
Transcripts map[string]models.TranscriptEntry `json:"transcripts,omitempty"`
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

PII risk: public feed now includes transcript-entry email values. If posts are publicly visible, avoid returning raw emails in transcript.transcripts (sanitize, replace with display name, or omit email entirely for non-owners).

🤖 Prompt for AI Agents
In @backend/controllers/post_controller.go around lines 22 - 25, The
transcriptPreview type currently exposes models.TranscriptEntry (which includes
email) via the Transcripts map; to avoid leaking PII, replace or map that field
to a sanitized DTO when returning public posts—either change
transcriptPreview.Transcripts to map[string]TranscriptEntryPreview (a new struct
without email or with a displayName only) or, if you build the map in the
handler, explicitly strip/replace the email on each models.TranscriptEntry
before adding it for non-owner or public views (use the post/ownership check
already in the handler to decide), ensuring only owners see the raw email.

Comment on lines +34 to 40
DebateType string `json:"debateType" binding:"required"`
Topic string `json:"topic" binding:"required"`
Opponent string `json:"opponent" binding:"required"`
Result string `json:"result"`
Messages []models.Message `json:"messages"`
Transcripts map[string]models.TranscriptEntry `json:"transcripts,omitempty"`
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Breaking API change: transcripts shape changed from map[string]string to map[string]TranscriptEntry. If any non-frontend clients exist, consider backward-compatible unmarshalling (accept string or {text,email,role}) or version the endpoint/field.

Comment on lines +21 to +23
func InitPersonalityService(cfg *config.Config) {
personalityGPT = NewChatGPT(cfg.Openai.GptApiKey)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add validation for configuration and API key.

The initialization function lacks nil checks for the config object and its nested fields, and doesn't validate that the API key is non-empty. If the API key is missing or invalid, subsequent calls to GeneratePersonalityProfiles will fail with cryptic errors or panics.

🛡️ Proposed fix to add validation
 func InitPersonalityService(cfg *config.Config) {
+	if cfg == nil || cfg.Openai.GptApiKey == "" {
+		panic("personality service requires a valid OpenAI API key in config")
+	}
 	personalityGPT = NewChatGPT(cfg.Openai.GptApiKey)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func InitPersonalityService(cfg *config.Config) {
personalityGPT = NewChatGPT(cfg.Openai.GptApiKey)
}
func InitPersonalityService(cfg *config.Config) {
if cfg == nil || cfg.Openai.GptApiKey == "" {
panic("personality service requires a valid OpenAI API key in config")
}
personalityGPT = NewChatGPT(cfg.Openai.GptApiKey)
}
🤖 Prompt for AI Agents
In @backend/services/personality_service.go around lines 21 - 23,
InitPersonalityService currently assumes cfg and nested fields exist and that
the API key is non-empty; add defensive validation: check that cfg is not nil,
cfg.Openai (or equivalent struct) is not nil, and cfg.Openai.GptApiKey is not
empty before calling NewChatGPT; if validation fails, return an error from
InitPersonalityService (change its signature to return error) or log a clear
fatal/error message and avoid setting personalityGPT so
GeneratePersonalityProfiles won't panic, and include references to
personalityGPT, NewChatGPT, InitPersonalityService and
GeneratePersonalityProfiles when making the change.

Comment on lines +25 to +109
func GeneratePersonalityProfiles(debateID string) ([]models.PersonalityProfile, error) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()

objID, err := primitive.ObjectIDFromHex(debateID)
if err != nil {
return nil, fmt.Errorf("invalid debate ID: %v", err)
}

collection := db.MongoDatabase.Collection("saved_debate_transcripts")
var debate models.SavedDebateTranscript
err = collection.FindOne(ctx, bson.M{"_id": objID}).Decode(&debate)
if err != nil {
return nil, fmt.Errorf("debate not found: %v", err)
}

if debate.Result == "pending" {
return nil, errors.New("debate is not yet completed")
}

// Group messages by participant
participants := make(map[string]string)

if debate.DebateType == "user_vs_bot" {
for _, msg := range debate.Messages {
sender := msg.Sender
if sender == "" {
continue
}
participants[sender] += msg.Text + "\n"
}
} else {
// User vs User
// Map phases to participants using explicit metadata
for _, entry := range debate.Transcripts {
if entry.Email == "" {
continue
}
participants[entry.Email] += entry.Text + "\n"
}
}

if len(participants) == 0 {
return nil, errors.New("no transcript found for participants")
}

var profiles []models.PersonalityProfile
profileCollection := db.MongoDatabase.Collection("personality_profiles")

for name, transcript := range participants {
if strings.TrimSpace(transcript) == "" {
continue
}

profile, err := analyzeParticipantPersonality(name, transcript)
if err != nil {
continue // Skip failed analysis for one participant
}

profile.DebateID = objID
profile.ParticipantID = name

// Map "User" and "Bot" keys to actual identifiers for user_vs_bot
if debate.DebateType == "user_vs_bot" {
if name == "User" {
profile.ParticipantID = debate.Email
} else if name == "Bot" {
profile.ParticipantID = debate.Opponent
} else if name == "Judge" {
continue // Skip analyzing the judge
}
} else {
// For user_vs_user, ParticipantID is already the email
}
profile.CreatedAt = time.Now()

_, err = profileCollection.InsertOne(ctx, profile)
if err != nil {
return nil, fmt.Errorf("failed to save profile: %v", err)
}
profiles = append(profiles, *profile)
}

return profiles, nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's search for how GeneratePersonalityProfiles is called
rg -n "GeneratePersonalityProfiles" --type=go

Repository: AOSSIE-Org/DebateAI

Length of output: 692


🏁 Script executed:

# Look for MongoDB index definitions or unique constraints on personality_profiles
rg -n -A5 "personality_profiles" --type=go | head -100

Repository: AOSSIE-Org/DebateAI

Length of output: 972


🏁 Script executed:

# Check for upsert patterns or duplicate prevention in the codebase
rg -n -B2 -A5 "ReplaceOne|Upsert|UpdateOne.*upsert" --type=go backend/services/ backend/controllers/

Repository: AOSSIE-Org/DebateAI

Length of output: 602


🏁 Script executed:

# Look for any migration files or schema definitions
fd -e js -e go -e json . | grep -i "migrat\|schema\|index" | head -20

Repository: AOSSIE-Org/DebateAI

Length of output: 45


🏁 Script executed:

# Check the personality controller for any safeguards
cat backend/controllers/personality_controller.go

Repository: AOSSIE-Org/DebateAI

Length of output: 1710


🏁 Script executed:

# Check the personality profile model to see if there are any constraints defined
rg -n "type PersonalityProfile struct" -A 15 --type=go

Repository: AOSSIE-Org/DebateAI

Length of output: 1255


🏁 Script executed:

# Check if there's any index creation code in the codebase
rg -n "CreateIndex\|EnsureIndex" --type=go backend/

Repository: AOSSIE-Org/DebateAI

Length of output: 45


🏁 Script executed:

# Find GetPersonalityProfiles implementation
rg -n "func GetPersonalityProfiles" -A 20 --type=go

Repository: AOSSIE-Org/DebateAI

Length of output: 3121


🏁 Script executed:

# Check if GeneratePersonalityProfiles is called from anywhere else besides the controller
rg -n "GeneratePersonalityProfiles\(" --type=go -B 2 -A 2

Repository: AOSSIE-Org/DebateAI

Length of output: 894


Move duplicate prevention to service layer for robust design.

While the controller prevents duplicate calls via GetPersonalityProfiles, the service function GeneratePersonalityProfiles is not idempotent. It uses InsertOne without any safeguard, so direct calls to the service would create duplicates. Additionally, no database-level unique constraint on (debateID, participantID) exists.

Implement idempotency in the service function by adding a check before insertion or using upsert logic:

  1. Query existing profiles by debateID and delete before regenerating, or
  2. Use ReplaceOne with upsert instead of InsertOne to replace existing profiles atomically

This ensures the function is safe regardless of how it's called.

🤖 Prompt for AI Agents
In @backend/services/personality_service.go around lines 25 - 109,
GeneratePersonalityProfiles is not idempotent because it uses
profileCollection.InsertOne which will create duplicates; change the logic to
either clear existing profiles for the debate before regenerating (call
profileCollection.DeleteMany with filter bson.M{"debate_id": objID}) or replace
each profile with an upsert (use profileCollection.ReplaceOne with filter
bson.M{"debate_id": objID, "participant_id": profile.ParticipantID} and
options.Replace().SetUpsert(true)) in place of InsertOne; keep the rest of the
loop and ensure profile.DebateID and profile.ParticipantID are set before the
upsert/delete operations so the operation is atomic and safe for direct service
calls.

Comment on lines +74 to +82
for name, transcript := range participants {
if strings.TrimSpace(transcript) == "" {
continue
}

profile, err := analyzeParticipantPersonality(name, transcript)
if err != nil {
continue // Skip failed analysis for one participant
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent error handling: analysis failures are silently skipped.

When analyzeParticipantPersonality fails (line 81), the error is silently ignored without logging, making it difficult to diagnose AI analysis issues. This is inconsistent with the DB insert error handling below (lines 102-104) which aborts the entire operation.

Consider at least logging the error:

📝 Suggested improvement
 		profile, err := analyzeParticipantPersonality(name, transcript)
 		if err != nil {
+			fmt.Printf("Warning: failed to analyze personality for %s: %v\n", name, err)
 			continue // Skip failed analysis for one participant
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for name, transcript := range participants {
if strings.TrimSpace(transcript) == "" {
continue
}
profile, err := analyzeParticipantPersonality(name, transcript)
if err != nil {
continue // Skip failed analysis for one participant
}
for name, transcript := range participants {
if strings.TrimSpace(transcript) == "" {
continue
}
profile, err := analyzeParticipantPersonality(name, transcript)
if err != nil {
fmt.Printf("Warning: failed to analyze personality for %s: %v\n", name, err)
continue // Skip failed analysis for one participant
}
🤖 Prompt for AI Agents
In @backend/services/personality_service.go around lines 74 - 82, The loop over
participants currently swallows errors from analyzeParticipantPersonality(name,
transcript); instead of silently continuing, log the error (including the
participant name and error message, and optionally transcript length or snippet)
before continuing so failures are visible; update the error branch in the
participants loop inside personality_service.go to call the service/global
logger (e.g., svc.logger or log.Printf) with a clear message and the err, then
continue, keeping the existing behavior for DB insert errors unchanged.

Comment on lines +101 to +106
_, err = profileCollection.InsertOne(ctx, profile)
if err != nil {
return nil, fmt.Errorf("failed to save profile: %v", err)
}
profiles = append(profiles, *profile)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent error handling and missing rollback.

If InsertOne fails for one profile after successfully inserting others, the function returns an error but doesn't roll back previously inserted profiles. This creates partial data and is inconsistent with line 81 where analysis failures are silently skipped.

Consider either:

  1. Making all insertions atomic (transaction), or
  2. Continuing on insert failures like analysis failures, or
  3. Collecting errors and returning them with partial results
🤖 Prompt for AI Agents
In @backend/services/personality_service.go around lines 101 - 106, The loop
currently calls profileCollection.InsertOne and returns immediately on the first
insert error, causing partial inserts with no rollback; either make the loop
atomic by running all inserts inside a MongoDB transaction (use
client.StartSession and session.WithTransaction, perform
profileCollection.InsertOne calls inside the transaction and only append to
profiles on success), or switch to a partial-result pattern: keep inserting,
append successful inserts to the profiles slice, collect insert errors into an
errs slice and at the end return the profiles plus a combined error (or nil if
no errors) instead of returning immediately; update callers accordingly to
handle the chosen behavior.

Comment on lines +145 to +150
// Clean JSON response (strip markdown fences if any)
cleanResponse := strings.TrimSpace(response)
cleanResponse = strings.TrimPrefix(cleanResponse, "```json")
cleanResponse = strings.TrimPrefix(cleanResponse, "```")
cleanResponse = strings.TrimSuffix(cleanResponse, "```")
cleanResponse = strings.TrimSpace(cleanResponse)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incorrect JSON cleaning logic with sequential TrimPrefix.

The sequential TrimPrefix calls won't work correctly. After removing "json" on line 147, the string will no longer start with "", so line 148 has no effect. If the response is wrapped as ```json\n{...}\n```, line 148 never executes.

🔧 Proposed fix
 	// Clean JSON response (strip markdown fences if any)
 	cleanResponse := strings.TrimSpace(response)
-	cleanResponse = strings.TrimPrefix(cleanResponse, "```json")
-	cleanResponse = strings.TrimPrefix(cleanResponse, "```")
+	cleanResponse = strings.TrimPrefix(cleanResponse, "```json\n")
+	cleanResponse = strings.TrimPrefix(cleanResponse, "```")
 	cleanResponse = strings.TrimSuffix(cleanResponse, "```")
 	cleanResponse = strings.TrimSpace(cleanResponse)

Or use a more robust approach:

 	// Clean JSON response (strip markdown fences if any)
 	cleanResponse := strings.TrimSpace(response)
-	cleanResponse = strings.TrimPrefix(cleanResponse, "```json")
-	cleanResponse = strings.TrimPrefix(cleanResponse, "```")
-	cleanResponse = strings.TrimSuffix(cleanResponse, "```")
-	cleanResponse = strings.TrimSpace(cleanResponse)
+	if strings.HasPrefix(cleanResponse, "```") {
+		lines := strings.Split(cleanResponse, "\n")
+		if len(lines) > 2 {
+			cleanResponse = strings.Join(lines[1:len(lines)-1], "\n")
+		}
+	}
+	cleanResponse = strings.TrimSpace(cleanResponse)
🤖 Prompt for AI Agents
In @backend/services/personality_service.go around lines 145 - 150, The JSON
cleaning currently uses sequential TrimPrefix calls that miss cases like
"```json\n{...}\n```"; update the logic around the cleanResponse variable so you
first TrimPrefix "```json\n" (or check HasPrefix "```json") then TrimPrefix
"```" and TrimSuffix "```", or implement the robust approach: if cleanResponse
starts with "```" split on "\n" and join lines[1:len(lines)-1] to extract the
inner JSON, then TrimSpace; apply this change where response is cleaned
(cleanResponse variable in personality_service.go).

Comment on lines 96 to 120
const handleViewTranscript = (transcript: SavedDebateTranscript) => {
setSelectedTranscript(transcript);
setIsViewDialogOpen(true);
fetchPersonalityProfiles(transcript.id);
};

const fetchPersonalityProfiles = async (debateId: string) => {
try {
setLoadingPersonality(true);
// Try to GET first
let profiles = await transcriptService.getPersonalityProfiles(debateId);

// If none found and debate is completed, trigger POST
if (profiles.length === 0) {
profiles = await transcriptService.generatePersonalityProfiles(debateId);
}

setPersonalityProfiles(profiles);
} catch (err) {
console.error('Error fetching personality profiles:', err);
setPersonalityProfiles([]);
} finally {
setLoadingPersonality(false);
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t auto-generate personality profiles for pending debates; also clear stale profiles on open. Current flow will POST for pending debates (wasted calls + confusing “No profiles” UI) and can momentarily show prior transcript’s profiles.

Proposed change
 const handleViewTranscript = (transcript: SavedDebateTranscript) => {
   setSelectedTranscript(transcript);
   setIsViewDialogOpen(true);
-  fetchPersonalityProfiles(transcript.id);
+  setPersonalityProfiles([]);
+  if (transcript.result !== 'pending') {
+    fetchPersonalityProfiles(transcript.id);
+  }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleViewTranscript = (transcript: SavedDebateTranscript) => {
setSelectedTranscript(transcript);
setIsViewDialogOpen(true);
fetchPersonalityProfiles(transcript.id);
};
const fetchPersonalityProfiles = async (debateId: string) => {
try {
setLoadingPersonality(true);
// Try to GET first
let profiles = await transcriptService.getPersonalityProfiles(debateId);
// If none found and debate is completed, trigger POST
if (profiles.length === 0) {
profiles = await transcriptService.generatePersonalityProfiles(debateId);
}
setPersonalityProfiles(profiles);
} catch (err) {
console.error('Error fetching personality profiles:', err);
setPersonalityProfiles([]);
} finally {
setLoadingPersonality(false);
}
};
const handleViewTranscript = (transcript: SavedDebateTranscript) => {
setSelectedTranscript(transcript);
setIsViewDialogOpen(true);
setPersonalityProfiles([]);
if (transcript.result !== 'pending') {
fetchPersonalityProfiles(transcript.id);
}
};
const fetchPersonalityProfiles = async (debateId: string) => {
try {
setLoadingPersonality(true);
// Try to GET first
let profiles = await transcriptService.getPersonalityProfiles(debateId);
// If none found and debate is completed, trigger POST
if (profiles.length === 0) {
profiles = await transcriptService.generatePersonalityProfiles(debateId);
}
setPersonalityProfiles(profiles);
} catch (err) {
console.error('Error fetching personality profiles:', err);
setPersonalityProfiles([]);
} finally {
setLoadingPersonality(false);
}
};
🤖 Prompt for AI Agents
In @frontend/src/components/SavedTranscripts.tsx around lines 96 - 120, When
opening a transcript in handleViewTranscript, clear stale profiles immediately
(call setPersonalityProfiles([])) and only fetch/generate profiles for debates
that are completed: in fetchPersonalityProfiles (called with debateId and/or the
transcript object), call transcriptService.getPersonalityProfiles(debateId) but
only call transcriptService.generatePersonalityProfiles(debateId) if the
transcript.status === 'completed' (do not POST for 'pending'); ensure
setLoadingPersonality(true/false) and setPersonalityProfiles([]) are used to
avoid showing prior transcript data while loading or when generation is skipped.

Comment on lines +241 to +267
// Get personality profiles for a debate
async getPersonalityProfiles(
debateId: string
): Promise<PersonalityProfileData[]> {
const token = getAuthToken();
if (!token) {
throw new Error('Authentication token not found');
}

const response = await fetch(
`${API_BASE_URL}/api/debates/${debateId}/personality`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
}
);

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to fetch personality profiles');
}

const data = await response.json();
return data.profiles || [];
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle “no profiles yet” without throwing (e.g., treat 404 as []). This enables the UI to fall back to generation when appropriate.

Proposed change
     const response = await fetch(
       `${API_BASE_URL}/api/debates/${debateId}/personality`,
       {
         method: 'GET',
         headers: {
           Authorization: `Bearer ${token}`,
         },
       }
     );

-    if (!response.ok) {
+    if (response.status === 404) {
+      return [];
+    }
+    if (!response.ok) {
       const errorData = await response.json();
       throw new Error(errorData.error || 'Failed to fetch personality profiles');
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Get personality profiles for a debate
async getPersonalityProfiles(
debateId: string
): Promise<PersonalityProfileData[]> {
const token = getAuthToken();
if (!token) {
throw new Error('Authentication token not found');
}
const response = await fetch(
`${API_BASE_URL}/api/debates/${debateId}/personality`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to fetch personality profiles');
}
const data = await response.json();
return data.profiles || [];
},
// Get personality profiles for a debate
async getPersonalityProfiles(
debateId: string
): Promise<PersonalityProfileData[]> {
const token = getAuthToken();
if (!token) {
throw new Error('Authentication token not found');
}
const response = await fetch(
`${API_BASE_URL}/api/debates/${debateId}/personality`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.status === 404) {
return [];
}
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to fetch personality profiles');
}
const data = await response.json();
return data.profiles || [];
},
🤖 Prompt for AI Agents
In @frontend/src/services/transcriptService.ts around lines 241 - 267, The
getPersonalityProfiles function currently throws on any non-OK response; change
its error handling so a 404 returns an empty array instead of throwing (so the
UI can fall back to generation): after the fetch in getPersonalityProfiles check
if response.status === 404 and return [] immediately; otherwise keep the
existing logic (parse JSON and throw on other non-OK statuses), and ensure you
only call response.json() once and handle missing data by returning
data.profiles || [].

Comment on lines +269 to +297
// Generate personality profiles for a debate
async generatePersonalityProfiles(
debateId: string
): Promise<PersonalityProfileData[]> {
const token = getAuthToken();
if (!token) {
throw new Error('Authentication token not found');
}

const response = await fetch(
`${API_BASE_URL}/api/debates/${debateId}/personality`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
}
);

if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData.error || 'Failed to generate personality profiles'
);
}

const data = await response.json();
return data.profiles || [];
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

POST should be explicit about headers (and be consistent). Even with an empty body, adding Content-Type: application/json reduces proxy/framework ambiguity.

Proposed change
       {
         method: 'POST',
         headers: {
+          'Content-Type': 'application/json',
           Authorization: `Bearer ${token}`,
         },
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Generate personality profiles for a debate
async generatePersonalityProfiles(
debateId: string
): Promise<PersonalityProfileData[]> {
const token = getAuthToken();
if (!token) {
throw new Error('Authentication token not found');
}
const response = await fetch(
`${API_BASE_URL}/api/debates/${debateId}/personality`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData.error || 'Failed to generate personality profiles'
);
}
const data = await response.json();
return data.profiles || [];
},
// Generate personality profiles for a debate
async generatePersonalityProfiles(
debateId: string
): Promise<PersonalityProfileData[]> {
const token = getAuthToken();
if (!token) {
throw new Error('Authentication token not found');
}
const response = await fetch(
`${API_BASE_URL}/api/debates/${debateId}/personality`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
}
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData.error || 'Failed to generate personality profiles'
);
}
const data = await response.json();
return data.profiles || [];
},
🤖 Prompt for AI Agents
In @frontend/src/services/transcriptService.ts around lines 269 - 297, In
generatePersonalityProfiles, make the POST explicit by adding Content-Type:
'application/json' (and optionally Accept: 'application/json') to the headers
and, to avoid ambiguity with empty POSTs, include an explicit empty JSON body
(e.g., JSON.stringify({})); update the headers object in the fetch call inside
generatePersonalityProfiles to include these keys so the request is consistent
with other API calls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add Debate Personality Profiles for Human and AI Participants

1 participant