diff --git a/docs/assets/api/schemas.json b/docs/assets/api/schemas.json
index 3746375a7..0a3786c36 100644
--- a/docs/assets/api/schemas.json
+++ b/docs/assets/api/schemas.json
@@ -1450,6 +1450,10 @@
"type": "string"
}
]
+ },
+ "informalNameStyle": {
+ "default": false,
+ "type": "boolean"
}
}
},
diff --git a/frontend/src/components/stages/profile_stage_editor.scss b/frontend/src/components/stages/profile_stage_editor.scss
index f3308ec9e..56448a9b2 100644
--- a/frontend/src/components/stages/profile_stage_editor.scss
+++ b/frontend/src/components/stages/profile_stage_editor.scss
@@ -7,25 +7,27 @@
height: 100%;
}
-.checkbox-wrapper {
+.profile-option {
@include common.flex-row-align-center;
- gap: common.$spacing-medium;
+ cursor: pointer;
+ gap: common.$spacing-small;
}
-md-checkbox {
- flex-shrink: 0;
+.divider {
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
}
-.profile-options {
- @include common.flex-column;
- gap: common.$spacing-medium;
+.title {
+ @include typescale.title-medium;
+ color: var(--md-sys-color-secondary);
}
-.profile-option {
+.checkbox-wrapper {
@include common.flex-row-align-center;
+ cursor: pointer;
gap: common.$spacing-small;
+}
- label {
- cursor: pointer;
- }
+md-checkbox {
+ flex-shrink: 0;
}
diff --git a/frontend/src/components/stages/profile_stage_editor.ts b/frontend/src/components/stages/profile_stage_editor.ts
index 760ade01d..cf37a890a 100644
--- a/frontend/src/components/stages/profile_stage_editor.ts
+++ b/frontend/src/components/stages/profile_stage_editor.ts
@@ -1,4 +1,5 @@
import '../../pair-components/textarea';
+import '@material/web/checkbox/checkbox.js';
import '@material/web/radio/radio';
import {MobxLitElement} from '@adobe/lit-mobx';
@@ -8,11 +9,7 @@ import {customElement, property} from 'lit/decorators.js';
import {core} from '../../core/core';
import {ExperimentEditor} from '../../services/experiment.editor';
-import {
- ProfileType,
- ProfileStageConfig,
- StageKind,
-} from '@deliberation-lab/utils';
+import {ProfileType, ProfileStageConfig} from '@deliberation-lab/utils';
import {styles} from './profile_stage_editor.scss';
@@ -65,56 +62,78 @@ export class ProfileStageEditorComponent extends MobxLitElement {
});
};
+ const isAnonymous =
+ this.stage.profileType === ProfileType.ANONYMOUS_ANIMAL ||
+ this.stage.profileType === ProfileType.ANONYMOUS_PARTICIPANT;
+
return html`
-
-
- handleProfileTypeChange(ProfileType.DEFAULT)}
- >
-
-
-
-
- handleProfileTypeChange(ProfileType.DEFAULT_GENDERED)}
- >
-
-
-
-
- handleProfileTypeChange(ProfileType.ANONYMOUS_ANIMAL)}
- >
-
-
-
-
- handleProfileTypeChange(ProfileType.ANONYMOUS_PARTICIPANT)}
- >
-
-
-
+ Participant-created profiles
+
+
+
+ Anonymous profiles
+
+
+ ${isAnonymous
+ ? html`
+
+ `
+ : nothing}
`;
}
}
diff --git a/frontend/src/shared/templates/quickstart_private_chat.ts b/frontend/src/shared/templates/quickstart_private_chat.ts
index 06b30460d..54c25a8c9 100644
--- a/frontend/src/shared/templates/quickstart_private_chat.ts
+++ b/frontend/src/shared/templates/quickstart_private_chat.ts
@@ -46,7 +46,7 @@ const CHAT_STAGE_ID = 'chat';
function getStageConfigs(): StageConfig[] {
const stages: StageConfig[] = [];
stages.push(
- createProfileStage(),
+ createProfileStage({profileType: ProfileType.ANONYMOUS_ANIMAL}),
createPrivateChatStage({
id: CHAT_STAGE_ID,
name: 'Private chat with agent',
diff --git a/functions/src/participant.endpoints.ts b/functions/src/participant.endpoints.ts
index d982400c8..afe209c40 100644
--- a/functions/src/participant.endpoints.ts
+++ b/functions/src/participant.endpoints.ts
@@ -140,8 +140,15 @@ export const createParticipant = onCall(async (request) => {
) as ProfileStageConfig | undefined;
const profileType =
profileStage?.profileType || ProfileType.ANONYMOUS_ANIMAL;
-
- setProfile(numParticipants, participantConfig, true, profileType);
+ const informalNameStyle = profileStage?.informalNameStyle ?? false;
+
+ setProfile(
+ numParticipants,
+ participantConfig,
+ true,
+ profileType,
+ informalNameStyle,
+ );
} else {
setProfile(numParticipants, participantConfig, false);
}
diff --git a/scripts/deliberate_lab/types.py b/scripts/deliberate_lab/types.py
index 0d717a9c6..d086be45c 100644
--- a/scripts/deliberate_lab/types.py
+++ b/scripts/deliberate_lab/types.py
@@ -391,6 +391,7 @@ class ProfileStageConfig(BaseModel):
descriptions: StageTextConfig
progress: StageProgressConfig
profileType: ProfileType
+ informalNameStyle: bool | None = False
class Strategy(StrEnum):
diff --git a/utils/src/participant.ts b/utils/src/participant.ts
index 044ede71c..dc017ed63 100644
--- a/utils/src/participant.ts
+++ b/utils/src/participant.ts
@@ -191,78 +191,67 @@ export function setProfile(
config: ParticipantProfileExtended,
setAnonymousProfile = false,
profileType: ProfileType = ProfileType.ANONYMOUS_ANIMAL,
+ informalNameStyle = false,
) {
- const generateProfileFromSet = (
+ const randomNumber = Math.floor(Math.random() * 10000);
+
+ // Format name with random number.
+ // Informal style: "bear123" (lowercase, no space)
+ // Default style: "Bear 1002"
+ const formatName = (name: string) => {
+ if (informalNameStyle) {
+ return `${name.toLowerCase()}${randomNumber}`;
+ }
+ return `${name} ${randomNumber}`;
+ };
+
+ // Create anonymous profile from a named profile set.
+ const profileFromSet = (
profileSet: {name: string; avatar: string}[],
): AnonymousProfileMetadata => {
- // TODO: Randomly select from set
const {name, avatar} = profileSet[participantNumber % profileSet.length];
return {
- name,
+ name: formatName(name),
avatar,
repeat: Math.floor(participantNumber / profileSet.length),
};
};
- const generateRandomHashProfile = (): AnonymousProfileMetadata => {
- return {
- name: generateId(),
- avatar: '',
- repeat: 0,
- };
- };
-
- // Generate random number for unique participant ID (used in publicID and anonymous participant profile)
- const randomNumber = Math.floor(Math.random() * 10000);
-
- const generateAnonymousParticipantProfile = (): AnonymousProfileMetadata => {
- return {
- name: `Participant ${randomNumber}`,
+ // Set anonymous profiles for each profile set
+ config.anonymousProfiles = {
+ [PROFILE_SET_ANIMALS_1_ID]: profileFromSet(PROFILE_SET_ANIMALS_1),
+ [PROFILE_SET_ANIMALS_2_ID]: profileFromSet(PROFILE_SET_ANIMALS_2),
+ [PROFILE_SET_NATURE_ID]: profileFromSet(PROFILE_SET_NATURE),
+ [PROFILE_SET_ANONYMOUS_PARTICIPANT_ID]: {
+ name: formatName('Participant'),
avatar: '👤',
repeat: 0,
- };
+ },
+ // Random hashes for ordering/randomization
+ [PROFILE_SET_RANDOM_1_ID]: {name: generateId(), avatar: '', repeat: 0},
+ [PROFILE_SET_RANDOM_2_ID]: {name: generateId(), avatar: '', repeat: 0},
+ [PROFILE_SET_RANDOM_3_ID]: {name: generateId(), avatar: '', repeat: 0},
};
- // Set anonymous profiles
- const profileAnimal1 = generateProfileFromSet(PROFILE_SET_ANIMALS_1);
- const profileAnimal2 = generateProfileFromSet(PROFILE_SET_ANIMALS_2);
- const profileNature = generateProfileFromSet(PROFILE_SET_NATURE);
- const profileAnonymousParticipant = generateAnonymousParticipantProfile();
-
- config.anonymousProfiles[PROFILE_SET_ANIMALS_1_ID] = profileAnimal1;
- config.anonymousProfiles[PROFILE_SET_ANIMALS_2_ID] = profileAnimal2;
- config.anonymousProfiles[PROFILE_SET_NATURE_ID] = profileNature;
- config.anonymousProfiles[PROFILE_SET_ANONYMOUS_PARTICIPANT_ID] =
- profileAnonymousParticipant;
-
- // Set random hashes (can be used for random ordering, etc.)
- config.anonymousProfiles[PROFILE_SET_RANDOM_1_ID] =
- generateRandomHashProfile();
- config.anonymousProfiles[PROFILE_SET_RANDOM_2_ID] =
- generateRandomHashProfile();
- config.anonymousProfiles[PROFILE_SET_RANDOM_3_ID] =
- generateRandomHashProfile();
-
- // Define public ID (using anonymous animal 1 set)
- const mainProfile = profileAnimal1;
+ // Define public ID using base animal name (without number suffix)
+ const baseName =
+ PROFILE_SET_ANIMALS_1[participantNumber % PROFILE_SET_ANIMALS_1.length]
+ .name;
const color = COLORS[Math.floor(Math.random() * COLORS.length)];
+ config.publicId = `${baseName}-${color}-${randomNumber}`.toLowerCase();
- config.publicId =
- `${mainProfile.name}-${color}-${randomNumber}`.toLowerCase();
-
+ // Set display profile for anonymous participants
if (setAnonymousProfile) {
- if (profileType === ProfileType.ANONYMOUS_PARTICIPANT) {
- // Use participant number profile
- const participantProfile =
- config.anonymousProfiles[PROFILE_SET_ANONYMOUS_PARTICIPANT_ID];
- config.name = participantProfile.name;
- config.avatar = participantProfile.avatar;
- } else if (profileType === ProfileType.ANONYMOUS_ANIMAL) {
- // Use animal profile (default)
- config.name = `${mainProfile.name}${mainProfile.repeat === 0 ? '' : ` ${mainProfile.repeat + 1}`}`;
- config.avatar = mainProfile.avatar;
+ const profileSetMap: Partial> = {
+ [ProfileType.ANONYMOUS_ANIMAL]: PROFILE_SET_ANIMALS_1_ID,
+ [ProfileType.ANONYMOUS_PARTICIPANT]: PROFILE_SET_ANONYMOUS_PARTICIPANT_ID,
+ };
+ const profileSetId = profileSetMap[profileType];
+ if (profileSetId) {
+ const profile = config.anonymousProfiles[profileSetId];
+ config.name = profile.name;
+ config.avatar = profile.avatar;
}
- // Note: ProfileType.DEFAULT should not reach here as setAnonymousProfile would be false
config.pronouns = '';
}
}
diff --git a/utils/src/stages/profile_stage.ts b/utils/src/stages/profile_stage.ts
index f10607691..5326636b2 100644
--- a/utils/src/stages/profile_stage.ts
+++ b/utils/src/stages/profile_stage.ts
@@ -22,6 +22,7 @@ export enum ProfileType {
export interface ProfileStageConfig extends BaseStageConfig {
kind: StageKind.PROFILE;
profileType: ProfileType;
+ informalNameStyle?: boolean; // e.g., "bear123" instead of "Bear 1002"
}
// ************************************************************************* //
@@ -39,5 +40,6 @@ export function createProfileStage(
descriptions: config.descriptions ?? createStageTextConfig(),
progress: config.progress ?? createStageProgressConfig(),
profileType: config.profileType ?? ProfileType.DEFAULT,
+ informalNameStyle: config.informalNameStyle ?? false,
};
}
diff --git a/utils/src/stages/profile_stage.validation.ts b/utils/src/stages/profile_stage.validation.ts
index d1fbe4183..b98592a1d 100644
--- a/utils/src/stages/profile_stage.validation.ts
+++ b/utils/src/stages/profile_stage.validation.ts
@@ -23,6 +23,7 @@ export const ProfileStageConfigData = Type.Composite(
Type.Literal(ProfileType.ANONYMOUS_ANIMAL),
Type.Literal(ProfileType.ANONYMOUS_PARTICIPANT),
]),
+ informalNameStyle: Type.Optional(Type.Boolean({default: false})),
},
strict,
),