Session Details implemented
This commit is contained in:
@@ -9,6 +9,7 @@ router.post('/sessions', (req, res) => {
|
|||||||
sessions.set(sessionId, {
|
sessions.set(sessionId, {
|
||||||
state: SessionState.SETUP,
|
state: SessionState.SETUP,
|
||||||
topic: null,
|
topic: null,
|
||||||
|
description: null,
|
||||||
expectedResponses: 0,
|
expectedResponses: 0,
|
||||||
submittedCount: 0,
|
submittedCount: 0,
|
||||||
responses: new Map(),
|
responses: new Map(),
|
||||||
|
|||||||
@@ -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.
|
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:
|
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.
|
- "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.
|
- "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.
|
- "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.
|
- "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.
|
- "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.
|
'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".
|
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".
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ interface EncryptedResponseData {
|
|||||||
interface SessionData {
|
interface SessionData {
|
||||||
state: SessionState; // Current phase of the session
|
state: SessionState; // Current phase of the session
|
||||||
topic: string | null; // The topic 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.
|
expectedResponses: number; // The number set by the first user in State A.
|
||||||
submittedCount: number; // The current count of submitted responses.
|
submittedCount: number; // The current count of submitted responses.
|
||||||
responses: Map<string, EncryptedResponseData>; // Stores the submitted desire objects. Map<ClientID, EncryptedResponseData>
|
responses: Map<string, EncryptedResponseData>; // Stores the submitted desire objects. Map<ClientID, EncryptedResponseData>
|
||||||
@@ -117,6 +118,7 @@ export const createWebSocketServer = (server: any) => {
|
|||||||
sessions.set(sessionId, {
|
sessions.set(sessionId, {
|
||||||
state: SessionState.SETUP,
|
state: SessionState.SETUP,
|
||||||
topic: null,
|
topic: null,
|
||||||
|
description: null,
|
||||||
expectedResponses: 0,
|
expectedResponses: 0,
|
||||||
submittedCount: 0,
|
submittedCount: 0,
|
||||||
responses: new Map<string, any>(),
|
responses: new Map<string, any>(),
|
||||||
@@ -204,13 +206,14 @@ export const handleWebSocketMessage = async (ws: WebSocket, sessionId: string, p
|
|||||||
|
|
||||||
case 'SETUP_SESSION':
|
case 'SETUP_SESSION':
|
||||||
if (sessionData.state === SessionState.SETUP) {
|
if (sessionData.state === SessionState.SETUP) {
|
||||||
const { expectedResponses, topic } = payload;
|
const { expectedResponses, topic, description } = payload;
|
||||||
if (typeof expectedResponses !== 'number' || expectedResponses <= 0) {
|
if (typeof expectedResponses !== 'number' || expectedResponses <= 0) {
|
||||||
ws.send(JSON.stringify({ type: 'ERROR', payload: { message: 'Invalid expectedResponses' } }));
|
ws.send(JSON.stringify({ type: 'ERROR', payload: { message: 'Invalid expectedResponses' } }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sessionData.expectedResponses = expectedResponses;
|
sessionData.expectedResponses = expectedResponses;
|
||||||
sessionData.topic = topic || 'Untitled Session';
|
sessionData.topic = topic || 'Untitled Session';
|
||||||
|
sessionData.description = description || null;
|
||||||
sessionData.state = SessionState.GATHERING;
|
sessionData.state = SessionState.GATHERING;
|
||||||
broadcastToSession(sessionId, { type: 'STATE_UPDATE', payload: {} });
|
broadcastToSession(sessionId, { type: 'STATE_UPDATE', payload: {} });
|
||||||
console.log(`Session ${sessionId} moved to GATHERING with topic "${sessionData.topic}" and ${expectedResponses} expected responses.`);
|
console.log(`Session ${sessionId} moved to GATHERING with topic "${sessionData.topic}" and ${expectedResponses} expected responses.`);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const ResultsDisplay: React.FC<ResultsDisplayProps> = ({ decision }) => {
|
|||||||
|
|
||||||
<CategorySection title="Go-to" desire={decision.goTo} />
|
<CategorySection title="Go-to" desire={decision.goTo} />
|
||||||
<CategorySection title="Also good" desire={decision.alsoGood} />
|
<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="No-goes" desire={decision.noGoes} />
|
||||||
<CategorySection title="Needs discussion" desire={decision.needsDiscussion} />
|
<CategorySection title="Needs discussion" desire={decision.needsDiscussion} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export interface Session {
|
|||||||
responses: { [clientId: string]: DesireSet }; // Map of clientId to their submitted DesireSet
|
responses: { [clientId: string]: DesireSet }; // Map of clientId to their submitted DesireSet
|
||||||
finalResult: Decision | null;
|
finalResult: Decision | null;
|
||||||
topic?: string; // This might be part of the initial setup payload
|
topic?: string; // This might be part of the initial setup payload
|
||||||
|
description?: string; // Add description field
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility to generate a persistent client ID
|
// Utility to generate a persistent client ID
|
||||||
|
|||||||
@@ -10,10 +10,21 @@ const SessionPage = () => {
|
|||||||
const { sessionId } = useParams<{ sessionId: string }>();
|
const { sessionId } = useParams<{ sessionId: string }>();
|
||||||
const [session, setSession, sendMessage, clientId, wsError] = useSession(sessionId || '');
|
const [session, setSession, sendMessage, clientId, wsError] = useSession(sessionId || '');
|
||||||
const [expectedResponses, setExpectedResponses] = useState(2);
|
const [expectedResponses, setExpectedResponses] = useState(2);
|
||||||
|
const [expectedResponsesError, setExpectedResponsesError] = useState(false);
|
||||||
const [topic, setTopic] = useState('');
|
const [topic, setTopic] = useState('');
|
||||||
|
const [topicError, setTopicError] = useState(false);
|
||||||
|
const [description, setDescription] = useState('');
|
||||||
|
|
||||||
const handleSetupSession = () => {
|
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 }) => {
|
const handleSubmitDesires = (desires: { wants: string[], accepts: string[], noGoes: string[], afraidToAsk: string }) => {
|
||||||
@@ -48,21 +59,45 @@ const SessionPage = () => {
|
|||||||
<Typography variant="h4" component="h1" gutterBottom>
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
{session.topic || session.sessionId}
|
{session.topic || session.sessionId}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{session.description && (
|
||||||
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
|
{session.description}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
{session.state === SessionState.SETUP && (
|
{session.state === SessionState.SETUP && (
|
||||||
<Box sx={{ mt: 4, p: 3, border: '1px dashed grey', borderRadius: '4px', textAlign: 'center' }}>
|
<Box sx={{ mt: 4, p: 3, border: '1px dashed grey', borderRadius: '4px', textAlign: 'center' }}>
|
||||||
<Typography variant="h6" component="p" gutterBottom>
|
<Typography variant="h6" component="p" gutterBottom>
|
||||||
Set Up Session
|
Set Up Desires Harmonization
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
id="topic"
|
id="topic"
|
||||||
label="Session Topic"
|
label="Topic"
|
||||||
name="topic"
|
name="topic"
|
||||||
autoFocus
|
autoFocus
|
||||||
|
required
|
||||||
value={topic}
|
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
|
<TextField
|
||||||
margin="normal"
|
margin="normal"
|
||||||
@@ -73,9 +108,15 @@ const SessionPage = () => {
|
|||||||
type="number"
|
type="number"
|
||||||
id="expectedResponses"
|
id="expectedResponses"
|
||||||
value={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={{
|
||||||
inputProps: { min: 1 }
|
inputProps: { min: 1, max: 12 }
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user