Session Details implemented

This commit is contained in:
aodulov
2025-10-13 13:50:49 +03:00
parent 5f8541a5f3
commit 691a2fa422
6 changed files with 59 additions and 13 deletions

View File

@@ -9,6 +9,7 @@ router.post('/sessions', (req, res) => {
sessions.set(sessionId, {
state: SessionState.SETUP,
topic: null,
description: null,
expectedResponses: 0,
submittedCount: 0,
responses: new Map(),

View File

@@ -34,15 +34,15 @@ export class LLMService {
Each participant's desire set includes 'wants', 'accepts', 'noGoes', and an 'afraidToAsk' field. The 'afraidToAsk' field contains a sensitive idea that the participant is hesitant to express publicly.
Here are the rules for categorization and synthesis, with special handling for 'afraidToAsk' ideas:
- "goTo": Synthesize a text describing what ALL participants want without contradictions. This should include 'afraidToAsk' ideas that semantically match all other participant's 'wants' or 'afraidToAsk'. If an 'afraidToAsk' idea matches, it should be treated as a 'want' for the submitting participant.
- "alsoGood": Synthesize a text describing what at least one participant wants (including matched 'afraidToAsk' ideas), not everyone wants but all other participants at least accept, and is not a "noGoes" for anyone. This should reflect a generally agreeable outcome.
- "considerable": Synthesize a text describing what is wanted or accepted by some, but not all, participants (including matched 'afraidToAsk' ideas), and is not a "noGoes" for anyone. This should highlight areas of partial agreement or options that could be explored.
- "noGoes": Synthesize a text describing what at least ONE participant does not want. This should clearly state the collective exclusions.
- "goTo": Synthesize a text describing what ALL participants want without contradictions. This should include 'afraidToAsk' ideas that semantically match all other participant's 'wants' or 'afraidToAsk'. If an 'afraidToAsk' idea matches, it should be treated as a 'want' for the submitting participant. Use the more specific opinions and leave all the specific options if they do not contradict each other drastically.
- "alsoGood": Synthesize a text describing what at least one participant wants (including matched 'afraidToAsk' ideas), not everyone wants but all other participants at least accept, and is not a "noGoes" for anyone. This should reflect a generally agreeable outcome. Use the more specific opinions and leave all the specific options if they do not contradict each other drastically.
- "considerable": Synthesize a text describing what is wanted or accepted by some, but not all, participants (including matched 'afraidToAsk' ideas), and is not a "noGoes" for anyone. This should highlight areas of partial agreement or options that could be explored. Use the more specific opinions and leave all the specific options if they do not contradict each other drastically.
- "noGoes": Synthesize a text describing what at least ONE participant does not want. This should clearly state the collective exclusions. Use the more broad opinions summarizing all the specific options if they do not contradict each other drastically.
- "needsDiscussion": Synthesize a text describing where there is a direct conflict (e.g., one participant wants it, another does not want it). This should highlight areas requiring further negotiation. Do not include 'afraidToAsk' in this category.
'AfraidToAsk' ideas that do NOT semantically match any other participant's 'wants' or 'accepts' should remain private and NOT be included in any of the synthesized categories.
Prioritize more specific desires over more broad ones for positive categories ("goTo", "alsoGood", "considerable"). For negative categories ("noGoes", "needsDiscussion"), prioritize more broad ideas over more specific ones. Formulate common ideas from the point of 'us', e.g. "We are going to...", or "We want to...", or "We think..."
Formulate common ideas from the point of 'us', e.g. "We are going to...", or "We want to...", or "We think...", or "We do not...".
The input will be a JSON object containing a list of desire sets. Each desire set has a participantId (implicitly handled by the array index) and four arrays/strings: "wants", "accepts", "noGoes", and "afraidToAsk".

View File

@@ -35,6 +35,7 @@ interface EncryptedResponseData {
interface SessionData {
state: SessionState; // Current phase of the session
topic: string | null; // The topic of the session
description: string | null; // The description of the session
expectedResponses: number; // The number set by the first user in State A.
submittedCount: number; // The current count of submitted responses.
responses: Map<string, EncryptedResponseData>; // Stores the submitted desire objects. Map<ClientID, EncryptedResponseData>
@@ -117,6 +118,7 @@ export const createWebSocketServer = (server: any) => {
sessions.set(sessionId, {
state: SessionState.SETUP,
topic: null,
description: null,
expectedResponses: 0,
submittedCount: 0,
responses: new Map<string, any>(),
@@ -204,13 +206,14 @@ export const handleWebSocketMessage = async (ws: WebSocket, sessionId: string, p
case 'SETUP_SESSION':
if (sessionData.state === SessionState.SETUP) {
const { expectedResponses, topic } = payload;
const { expectedResponses, topic, description } = payload;
if (typeof expectedResponses !== 'number' || expectedResponses <= 0) {
ws.send(JSON.stringify({ type: 'ERROR', payload: { message: 'Invalid expectedResponses' } }));
return;
}
sessionData.expectedResponses = expectedResponses;
sessionData.topic = topic || 'Untitled Session';
sessionData.description = description || null;
sessionData.state = SessionState.GATHERING;
broadcastToSession(sessionId, { type: 'STATE_UPDATE', payload: {} });
console.log(`Session ${sessionId} moved to GATHERING with topic "${sessionData.topic}" and ${expectedResponses} expected responses.`);

View File

@@ -48,7 +48,7 @@ const ResultsDisplay: React.FC<ResultsDisplayProps> = ({ decision }) => {
<CategorySection title="Go-to" desire={decision.goTo} />
<CategorySection title="Also good" desire={decision.alsoGood} />
<CategorySection title="Considerable" desire={decision.considerable} defaultExpanded={false} />
<CategorySection title="Considerable" desire={decision.considerable} defaultExpanded={true} />
<CategorySection title="No-goes" desire={decision.noGoes} />
<CategorySection title="Needs discussion" desire={decision.needsDiscussion} />
</Box>

View File

@@ -43,6 +43,7 @@ export interface Session {
responses: { [clientId: string]: DesireSet }; // Map of clientId to their submitted DesireSet
finalResult: Decision | null;
topic?: string; // This might be part of the initial setup payload
description?: string; // Add description field
}
// Utility to generate a persistent client ID

View File

@@ -10,10 +10,21 @@ const SessionPage = () => {
const { sessionId } = useParams<{ sessionId: string }>();
const [session, setSession, sendMessage, clientId, wsError] = useSession(sessionId || '');
const [expectedResponses, setExpectedResponses] = useState(2);
const [expectedResponsesError, setExpectedResponsesError] = useState(false);
const [topic, setTopic] = useState('');
const [topicError, setTopicError] = useState(false);
const [description, setDescription] = useState('');
const handleSetupSession = () => {
sendMessage({ type: 'SETUP_SESSION', payload: { expectedResponses, topic } });
if (!topic.trim()) {
setTopicError(true);
return;
}
if (expectedResponses < 2 || expectedResponses > 12) {
setExpectedResponsesError(true);
return;
}
sendMessage({ type: 'SETUP_SESSION', payload: { expectedResponses, topic, description } });
};
const handleSubmitDesires = (desires: { wants: string[], accepts: string[], noGoes: string[], afraidToAsk: string }) => {
@@ -48,21 +59,45 @@ const SessionPage = () => {
<Typography variant="h4" component="h1" gutterBottom>
{session.topic || session.sessionId}
</Typography>
{session.description && (
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
{session.description}
</Typography>
)}
{session.state === SessionState.SETUP && (
<Box sx={{ mt: 4, p: 3, border: '1px dashed grey', borderRadius: '4px', textAlign: 'center' }}>
<Typography variant="h6" component="p" gutterBottom>
Set Up Session
Set Up Desires Harmonization
</Typography>
<TextField
margin="normal"
fullWidth
id="topic"
label="Session Topic"
label="Topic"
name="topic"
autoFocus
required
value={topic}
onChange={(e) => setTopic(e.target.value)}
onChange={(e) => {
setTopic(e.target.value);
setTopicError(!e.target.value.trim());
}}
error={topicError}
helperText={topicError ? 'Topic is required' : ''}
/>
<TextField
margin="normal"
fullWidth
id="description"
label="Details (Optional)"
name="description"
multiline
rows={3}
value={description}
onChange={(e) => setDescription(e.target.value)}
inputProps={{ maxLength: 500 }}
helperText={`Provide more details about what should be addressed in this session (max 500 characters). ${description.length}/500`}
/>
<TextField
margin="normal"
@@ -73,9 +108,15 @@ const SessionPage = () => {
type="number"
id="expectedResponses"
value={expectedResponses}
onChange={(e) => setExpectedResponses(parseInt(e.target.value, 10))}
onChange={(e) => {
const value = parseInt(e.target.value, 10);
setExpectedResponses(value);
setExpectedResponsesError(value < 2 || value > 12);
}}
error={expectedResponsesError}
helperText={expectedResponsesError ? 'Must be an integer between 2 and 12' : 'Enter the number of participants expected (min 2, max 12)'}
InputProps={{
inputProps: { min: 1 }
inputProps: { min: 1, max: 12 }
}}
/>
<Button