diff --git a/.context/log.txt b/.context/log.txt index 1c243af..7f31e2d 100644 --- a/.context/log.txt +++ b/.context/log.txt @@ -1,8 +1,4 @@ -manifest.json:1 Manifest: Line: 1, column: 1, Syntax error. -websocket.ts:20 WebSocket connected -websocket.ts:28 WebSocketService: Received and parsed message: {type: 'STATE_UPDATE', payload: {…}}payload: session: clients: [[Prototype]]: ObjectexpectedResponses: 0finalResult: nullresponses: [[Prototype]]: Objectstate: "SETUP"submittedCount: 0[[Prototype]]: Object[[Prototype]]: Objecttype: "STATE_UPDATE"[[Prototype]]: Object -useSession.ts:81 useSession: Processing incoming message: {type: 'STATE_UPDATE', payload: {…}}payload: session: clients: [[Prototype]]: ObjectexpectedResponses: 0finalResult: nullresponses: [[Prototype]]: Objectstate: "SETUP"submittedCount: 0[[Prototype]]: Object[[Prototype]]: Objecttype: "STATE_UPDATE"[[Prototype]]: Object -websocket.ts:28 WebSocketService: Received and parsed message: {type: 'STATE_UPDATE', payload: {…}}payload: session: clients: [[Prototype]]: ObjectexpectedResponses: 2finalResult: nullresponses: [[Prototype]]: Objectstate: "GATHERING"submittedCount: 0[[Prototype]]: Object[[Prototype]]: Objecttype: "STATE_UPDATE"[[Prototype]]: Object -useSession.ts:81 useSession: Processing incoming message: {type: 'STATE_UPDATE', payload: {…}}payload: session: clients: [[Prototype]]: ObjectexpectedResponses: 2finalResult: nullresponses: [[Prototype]]: Objectstate: "GATHERING"submittedCount: 0[[Prototype]]: Object[[Prototype]]: Objecttype: "STATE_UPDATE"[[Prototype]]: Object -browser-integration.js:2 port disconnected from addon code: 8258ac25-409c-4e4d-8c4a-46b4ab8d1ff7 -(anonymous) @ browser-integration.js:2 +websocket.ts:28 WebSocketService: Received and parsed message: {type: 'STATE_UPDATE', payload: {…}}payload: session: clients: Array(2)0: "24620bff-a27e-49f7-8e1a-bebc5f4aeec7"1: "685ac684-d79c-4ae6-89cb-7665870f7c0e"length: 2[[Prototype]]: Array(0)expectedResponses: 2finalResult: nullresponses: 685ac684-d79c-4ae6-89cb-7665870f7c0e: {participantId: '685ac684-d79c-4ae6-89cb-7665870f7c0e', wants: Array(1), accepts: Array(1), noGoes: Array(1)}24620bff-a27e-49f7-8e1a-bebc5f4aeec7: {participantId: '24620bff-a27e-49f7-8e1a-bebc5f4aeec7', wants: Array(1), accepts: Array(1), noGoes: Array(1)}[[Prototype]]: Objectstate: "HARMONIZING"submittedCount: 2topic: "Drinks?"[[Prototype]]: Object[[Prototype]]: Objecttype: "STATE_UPDATE"[[Prototype]]: Object +useSession.ts:81 useSession: Processing incoming message: {type: 'STATE_UPDATE', payload: {…}}payload: session: {state: 'HARMONIZING', topic: 'Drinks?', expectedResponses: 2, submittedCount: 2, responses: {…}, …}[[Prototype]]: Objecttype: "STATE_UPDATE"[[Prototype]]: Object +websocket.ts:28 WebSocketService: Received and parsed message: {type: 'STATE_UPDATE', payload: {…}}payload: session: clients: Array(2)0: "24620bff-a27e-49f7-8e1a-bebc5f4aeec7"1: "685ac684-d79c-4ae6-89cb-7665870f7c0e"length: 2[[Prototype]]: Array(0)expectedResponses: 2finalResult: nullresponses: 685ac684-d79c-4ae6-89cb-7665870f7c0e: {participantId: '685ac684-d79c-4ae6-89cb-7665870f7c0e', wants: Array(1), accepts: Array(1), noGoes: Array(1)}24620bff-a27e-49f7-8e1a-bebc5f4aeec7: {participantId: '24620bff-a27e-49f7-8e1a-bebc5f4aeec7', wants: Array(1), accepts: Array(1), noGoes: Array(1)}[[Prototype]]: Objectstate: "GATHERING"submittedCount: 2topic: "Drinks?"[[Prototype]]: Object[[Prototype]]: Objecttype: "STATE_UPDATE"[[Prototype]]: Object +useSession.ts:81 useSession: Processing incoming message: {type: 'STATE_UPDATE', payload: {…}}payload: session: clients: Array(2)0: "24620bff-a27e-49f7-8e1a-bebc5f4aeec7"1: "685ac684-d79c-4ae6-89cb-7665870f7c0e"length: 2[[Prototype]]: Array(0)expectedResponses: 2finalResult: nullresponses: 685ac684-d79c-4ae6-89cb-7665870f7c0e: {participantId: '685ac684-d79c-4ae6-89cb-7665870f7c0e', wants: Array(1), accepts: Array(1), noGoes: Array(1)}24620bff-a27e-49f7-8e1a-bebc5f4aeec7: {participantId: '24620bff-a27e-49f7-8e1a-bebc5f4aeec7', wants: Array(1), accepts: Array(1), noGoes: Array(1)}[[Prototype]]: Objectstate: "GATHERING"submittedCount: 2topic: "Drinks?"[[Prototype]]: Object[[Prototype]]: Objecttype: "STATE_UPDATE"[[Prototype]]: Object diff --git a/.gitignore b/.gitignore index d846960..95ef1f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /frontend/node_modules -/backend/node_modules \ No newline at end of file +/backend/node_modules +/.context \ No newline at end of file diff --git a/backend/.env b/backend/.env index ebd7491..6d29973 100644 --- a/backend/.env +++ b/backend/.env @@ -1 +1 @@ -GEMINI_API_KEY=AIzaSyDke9H2NhiG6rBwxT0qrdYgnNoNZm_0j58 \ No newline at end of file +GEMINI_API_KEY="AIzaSyDke9H2NhiG6rBwxT0qrdYgnNoNZm_0j58" \ No newline at end of file diff --git a/backend/src/ws/index.ts b/backend/src/ws/index.ts index 17398d0..b0a08e5 100644 --- a/backend/src/ws/index.ts +++ b/backend/src/ws/index.ts @@ -21,6 +21,7 @@ export enum SessionState { GATHERING = 'GATHERING', HARMONIZING = 'HARMONIZING', FINAL = 'FINAL', + ERROR = 'ERROR', } // A map to hold session data, including clients and the latest state @@ -146,85 +147,89 @@ export const createWebSocketServer = (server: any) => { broadcastToSession(sessionId, { type: 'STATE_UPDATE', payload: {} }); console.log(`Session ${sessionId} moved to HARMONIZING. Triggering LLM analysis.`); - try { - const allDesires = Array.from(sessionData.responses.values()); - const canonicalMap = await llmService.analyzeDesires(allDesires); + // Perform LLM analysis asynchronously + (async () => { + try { + const allDesires = Array.from(sessionData.responses.values()); + const canonicalMap = await llmService.analyzeDesires(allDesires); - const semanticDesiresMap = new Map(); + const semanticDesiresMap = new Map(); - for (const originalDesire in canonicalMap) { - const canonicalName = canonicalMap[originalDesire]; - if (!semanticDesiresMap.has(canonicalName)) { - semanticDesiresMap.set(canonicalName, { title: canonicalName, rawInputs: [] }); + for (const originalDesire in canonicalMap) { + const canonicalName = canonicalMap[originalDesire]; + if (!semanticDesiresMap.has(canonicalName)) { + semanticDesiresMap.set(canonicalName, { title: canonicalName, rawInputs: [] }); + } + semanticDesiresMap.get(canonicalName)?.rawInputs.push(originalDesire); } - semanticDesiresMap.get(canonicalName)?.rawInputs.push(originalDesire); + + const decision: Decision = { + goTos: [], + alsoGoods: [], + considerables: [], + noGoes: [], + }; + + const participantIds = Array.from(sessionData.responses.keys()); + + semanticDesiresMap.forEach(semanticDesire => { + let isNoGo = false; + let allWant = true; + let atLeastOneWant = false; + let allAcceptOrWant = true; + + for (const pId of participantIds) { + const participantDesireSet = sessionData.responses.get(pId); + if (!participantDesireSet) continue; + + const participantWants = new Set(participantDesireSet.wants.map((d: string) => canonicalMap[d] || d)); + const participantAccepts = new Set(participantDesireSet.accepts.map((d: string) => canonicalMap[d] || d)); + const participantNoGoes = new Set(participantDesireSet.noGoes.map((d: string) => canonicalMap[d] || d)); + + const canonicalTitle = semanticDesire.title; + + if (participantNoGoes.has(canonicalTitle)) { + isNoGo = true; + break; + } + + if (!participantWants.has(canonicalTitle)) { + allWant = false; + } + + if (participantWants.has(canonicalTitle)) { + atLeastOneWant = true; + } + + if (!participantWants.has(canonicalTitle) && !participantAccepts.has(canonicalTitle)) { + allAcceptOrWant = false; + } + } + + if (isNoGo) { + decision.noGoes.push(semanticDesire); + } else if (allWant) { + decision.goTos.push(semanticDesire); + } else if (atLeastOneWant && allAcceptOrWant) { + decision.alsoGoods.push(semanticDesire); + } else if (atLeastOneWant || !allAcceptOrWant) { + decision.considerables.push(semanticDesire); + } + }); + + sessionData.finalResult = decision; + sessionData.state = SessionState.FINAL; + broadcastToSession(sessionId, { type: 'STATE_UPDATE', payload: {} }); + console.log(`Analysis complete for session ${sessionId}. Result:`, decision); + + } catch (error) { + console.error(`Error during analysis for session ${sessionId}:`, error); + sessionData.state = SessionState.ERROR; + broadcastToSession(sessionId, { type: 'STATE_UPDATE', payload: {} }); } - - const decision: Decision = { - goTos: [], - alsoGoods: [], - considerables: [], - noGoes: [], - }; - - const participantIds = Array.from(sessionData.responses.keys()); - - semanticDesiresMap.forEach(semanticDesire => { - let isNoGo = false; - let allWant = true; - let atLeastOneWant = false; - let allAcceptOrWant = true; - - for (const pId of participantIds) { - const participantDesireSet = sessionData.responses.get(pId); - if (!participantDesireSet) continue; - - const participantWants = new Set(participantDesireSet.wants.map((d: string) => canonicalMap[d] || d)); - const participantAccepts = new Set(participantDesireSet.accepts.map((d: string) => canonicalMap[d] || d)); - const participantNoGoes = new Set(participantDesireSet.noGoes.map((d: string) => canonicalMap[d] || d)); - - const canonicalTitle = semanticDesire.title; - - if (participantNoGoes.has(canonicalTitle)) { - isNoGo = true; - break; - } - - if (!participantWants.has(canonicalTitle)) { - allWant = false; - } - - if (participantWants.has(canonicalTitle)) { - atLeastOneWant = true; - } - - if (!participantWants.has(canonicalTitle) && !participantAccepts.has(canonicalTitle)) { - allAcceptOrWant = false; - } - } - - if (isNoGo) { - decision.noGoes.push(semanticDesire); - } else if (allWant) { - decision.goTos.push(semanticDesire); - } else if (atLeastOneWant && allAcceptOrWant) { - decision.alsoGoods.push(semanticDesire); - } else if (atLeastOneWant || !allAcceptOrWant) { - decision.considerables.push(semanticDesire); - } - }); - - sessionData.finalResult = decision; - sessionData.state = SessionState.FINAL; - broadcastToSession(sessionId, { type: 'STATE_UPDATE', payload: {} }); - console.log(`Analysis complete for session ${sessionId}. Result:`, decision); - - } catch (error) { - console.error(`Error during analysis for session ${sessionId}:`, error); - sessionData.state = SessionState.GATHERING; - broadcastToSession(sessionId, { type: 'STATE_UPDATE', payload: {} }); - } + })(); } else { + // Only broadcast the latest count if the session is not yet harmonizing broadcastToSession(sessionId, { type: 'STATE_UPDATE', payload: {} }); } } else { diff --git a/frontend/src/hooks/useSession.ts b/frontend/src/hooks/useSession.ts index 745fcb0..19c5584 100644 --- a/frontend/src/hooks/useSession.ts +++ b/frontend/src/hooks/useSession.ts @@ -35,6 +35,7 @@ export enum SessionState { GATHERING = 'GATHERING', HARMONIZING = 'HARMONIZING', FINAL = 'FINAL', + ERROR = 'ERROR', } export interface Session { diff --git a/frontend/src/pages/SessionPage.tsx b/frontend/src/pages/SessionPage.tsx index 2f18980..08b83cf 100644 --- a/frontend/src/pages/SessionPage.tsx +++ b/frontend/src/pages/SessionPage.tsx @@ -121,9 +121,16 @@ const SessionPage = () => { Harmonizing Desires... )} + {session.state === SessionState.FINAL && session.finalResult && ( )} + + {session.state === SessionState.ERROR && ( + + An error occurred during analysis. Please try again. + + )} );