feat: Enhance result analysis and error display

This commit is contained in:
AG
2025-10-11 17:38:31 +03:00
parent 4c420acc08
commit 641caf74eb
8 changed files with 56 additions and 103 deletions

View File

@@ -10,11 +10,11 @@ interface DesireSet {
interface Decision { interface Decision {
goTo: string[]; goTo: string;
alsoGood: string[]; alsoGood: string;
considerable: string[]; considerable: string;
noGoes: string[]; noGoes: string;
needsDiscussion: string[]; needsDiscussion: string;
} }
export class LLMService { export class LLMService {
@@ -28,24 +28,24 @@ export class LLMService {
async analyzeDesires(desireSets: DesireSet[]): Promise<Decision> { async analyzeDesires(desireSets: DesireSet[]): Promise<Decision> {
const prompt = ` const prompt = `
You are an AI assistant that analyzes and categorizes user desires from a session. Given a list of desire sets from multiple participants, your task is to categorize them into the following groups: "goTo", "alsoGood", "considerable", "noGoes", and "needsDiscussion". You are an AI assistant that analyzes and synthesizes cooperative decisions from a group's desires. Given a list of desire sets from multiple participants, your task is to generate a concise, synthesized text for each of the following categories, reflecting the collective opinion:
Here are the rules for categorization: Here are the rules for categorization and synthesis:
- "goTo": Items that ALL participants want. - "goTo": Synthesize a text describing what ALL participants want without contradictions. This should be a clear, affirmative statement of shared desire.
- "alsoGood": Items that at least one participant wants, and all other participants accept. - "alsoGood": Synthesize a text describing what at least one participant wants, and all other participants accept, and is not a "noGoes" for anyone. This should reflect a generally agreeable outcome.
- "considerable": Items that are wanted or accepted by some, but not all, participants, and are not "noGoes" for anyone. - "considerable": Synthesize a text describing what is wanted or accepted by some, but not all, participants, and is not a "noGoes" for anyone. This should highlight areas of partial agreement or options that could be explored.
- "noGoes": Items that at least ONE participant does not want. These items must be excluded from all other categories. - "noGoes": Synthesize a text describing what at least ONE participant does not want. This should clearly state the collective exclusions.
- "needsDiscussion": Items where there is a direct conflict (e.g., one participant wants it, another does not want it). - "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.
The input will be a JSON object containing a list of desire sets. Each desire set has a participantId and three arrays of strings: "wants", "accepts", and "noGoes". The input will be a JSON object containing a list of desire sets. Each desire set has a participantId and three arrays of strings: "wants", "accepts", and "noGoes".
The output should be a JSON object with the following structure: The output should be a JSON object with the following structure, where each category contains a single synthesized text:
{ {
"goTo": ["item1", "item2"], "goTo": "Synthesized text for go-to items.",
"alsoGood": ["item3"], "alsoGood": "Synthesized text for also good items.",
"considerable": ["item4"], "considerable": "Synthesized text for considerable items.",
"noGoes": ["item5"], "noGoes": "Synthesized text for no-goes items.",
"needsDiscussion": ["item6"] "needsDiscussion": "Synthesized text for needs discussion items."
} }
Here is the input data: Here is the input data:
@@ -76,7 +76,7 @@ export class LLMService {
async checkForInnerContradictions(desireSet: DesireSet): Promise<boolean> { async checkForInnerContradictions(desireSet: DesireSet): Promise<boolean> {
const prompt = ` const prompt = `
You are an AI assistant that detects contradictions in a list of desires. Given a JSON object with three lists of desires (wants, accepts, noGoes), determine if there are any contradictions WITHIN each list. For example, "I want a dog" and "I don't want any pets" in the same "wants" list is a contradiction. Respond with only "true" if a contradiction is found, and "false" otherwise. You are an AI assistant that detects contradictions in a list of desires. Given a JSON object with three lists of desires (wants, accepts, noGoes), determine if there are any contradictions WITHIN each list and across the lists. For example, "I want a dog" and "I don't want any pets" in the same "wants" list is a contradiction; "Pizza" in "wants" and "food" in "do not want" is a contradiction. Respond with only "true" if a contradiction is found, and "false" otherwise.
Here is the desire set: Here is the desire set:
${JSON.stringify(desireSet)} ${JSON.stringify(desireSet)}
@@ -92,5 +92,4 @@ export class LLMService {
throw error; throw error;
} }
} }
} }

View File

@@ -4,11 +4,11 @@ import { v4 as uuidv4 } from 'uuid';
// Types from the frontend // Types from the frontend
interface Decision { interface Decision {
goTo: string[]; goTo: string;
alsoGood: string[]; alsoGood: string;
considerable: string[]; considerable: string;
noGoes: string[]; noGoes: string;
needsDiscussion: string[]; needsDiscussion: string;
} }
// Define the SessionState enum // Define the SessionState enum

View File

@@ -10,6 +10,6 @@
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
}, },
"include": ["src/**/*", "tests/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist", "tests"]
} }

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { ThemeProvider, CssBaseline } from '@mui/material'; import { ThemeProvider, CssBaseline } from '@mui/material';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import theme from './theme'; import theme from './theme';
import StartPage from './pages/StartPage';
import CreateSession from './pages/CreateSession'; import CreateSession from './pages/CreateSession';
import SessionPage from './pages/SessionPage'; import SessionPage from './pages/SessionPage';
@@ -13,8 +12,7 @@ function App() {
<CssBaseline /> <CssBaseline />
<Router> <Router>
<Routes> <Routes>
<Route path="/" element={<StartPage />} /> <Route path="/" element={<CreateSession />} />
<Route path="/create" element={<CreateSession />} />
{/* Other routes will be added here */} {/* Other routes will be added here */}
<Route path="/session/:sessionId" element={<SessionPage />} /> <Route path="/session/:sessionId" element={<SessionPage />} />
</Routes> </Routes>

View File

@@ -8,11 +8,11 @@ interface ResultsDisplayProps {
decision: Decision; decision: Decision;
} }
const CategorySection: React.FC<{ title: string; desires: string[]; defaultExpanded?: boolean }> const CategorySection: React.FC<{ title: string; desire: string; defaultExpanded?: boolean }>
= ({ title, desires, defaultExpanded = true }) => { = ({ title, desire, defaultExpanded = true }) => {
const [expanded, setExpanded] = React.useState(defaultExpanded); const [expanded, setExpanded] = React.useState(defaultExpanded);
if (!desires || desires.length === 0) { if (!desire) {
return null; return null;
} }
@@ -27,13 +27,9 @@ const CategorySection: React.FC<{ title: string; desires: string[]; defaultExpan
</IconButton> </IconButton>
</Box> </Box>
<Collapse in={expanded} timeout="auto" unmountOnExit> <Collapse in={expanded} timeout="auto" unmountOnExit>
<List dense> <Typography variant="body1" sx={{ mt: 1 }}>
{desires.map((desire, index) => ( {desire}
<ListItem key={index}> </Typography>
<ListItemText primary={desire} />
</ListItem>
))}
</List>
</Collapse> </Collapse>
</Box> </Box>
); );
@@ -50,11 +46,11 @@ const ResultsDisplay: React.FC<ResultsDisplayProps> = ({ decision }) => {
Cooperative Decision Cooperative Decision
</Typography> </Typography>
<CategorySection title="Go-to" desires={decision.goTo} /> <CategorySection title="Go-to" desire={decision.goTo} />
<CategorySection title="Also good" desires={decision.alsoGood} /> <CategorySection title="Also good" desire={decision.alsoGood} />
<CategorySection title="Considerable" desires={decision.considerable} defaultExpanded={false} /> <CategorySection title="Considerable" desire={decision.considerable} defaultExpanded={false} />
<CategorySection title="No-goes" desires={decision.noGoes} /> <CategorySection title="No-goes" desire={decision.noGoes} />
<CategorySection title="Needs discussion" desires={decision.needsDiscussion} /> <CategorySection title="Needs discussion" desire={decision.needsDiscussion} />
</Box> </Box>
); );
}; };

View File

@@ -18,11 +18,11 @@ export interface DesireSet {
} }
export interface Decision { export interface Decision {
goTo: string[]; goTo: string;
alsoGood: string[]; alsoGood: string;
considerable: string[]; considerable: string;
noGoes: string[]; noGoes: string;
needsDiscussion: string[]; needsDiscussion: string;
} }
// Define the SessionState enum (mirroring backend) // Define the SessionState enum (mirroring backend)
@@ -54,8 +54,9 @@ const getOrCreateClientId = (): string => {
return clientId; return clientId;
}; };
export const useSession = (sessionId: string): [Session | null, Dispatch<SetStateAction<Session | null>>, (message: any) => void, string] => { export const useSession = (sessionId: string): [Session | null, Dispatch<SetStateAction<Session | null>>, (message: any) => void, string, string | null] => {
const clientId = getOrCreateClientId(); // Get or create clientId const clientId = getOrCreateClientId(); // Get or create clientId
const [error, setError] = useState<string | null>(null);
const getInitialState = useCallback((): Session | null => { const getInitialState = useCallback((): Session | null => {
try { try {
@@ -78,9 +79,10 @@ export const useSession = (sessionId: string): [Session | null, Dispatch<SetStat
console.log('useSession: Processing incoming message:', message); console.log('useSession: Processing incoming message:', message);
if (message.type === 'STATE_UPDATE') { if (message.type === 'STATE_UPDATE') {
setSession(message.payload.session); setSession(message.payload.session);
setError(null); // Clear error on successful state update
} else if (message.type === 'ERROR') { } else if (message.type === 'ERROR') {
console.error('WebSocket Error:', message.payload.message); console.error('WebSocket Error:', message.payload.message);
// Optionally, handle error display to the user setError(message.payload.message);
} }
// Add other message types as needed // Add other message types as needed
}; };
@@ -108,5 +110,6 @@ export const useSession = (sessionId: string): [Session | null, Dispatch<SetStat
webSocketService.sendMessage(message); webSocketService.sendMessage(message);
}, []); }, []);
return [session, setSession, sendMessage, clientId]; return [session, setSession, sendMessage, clientId, error];
}; };

View File

@@ -8,8 +8,7 @@ import ResultsDisplay from '../components/ResultsDisplay';
const SessionPage = () => { const SessionPage = () => {
const { sessionId } = useParams<{ sessionId: string }>(); const { sessionId } = useParams<{ sessionId: string }>();
const [session, setSession, sendMessage, clientId] = useSession(sessionId || ''); const [session, setSession, sendMessage, clientId, wsError] = useSession(sessionId || '');
const [error, setError] = useState<string | null>(null);
const [expectedResponses, setExpectedResponses] = useState(2); const [expectedResponses, setExpectedResponses] = useState(2);
const [topic, setTopic] = useState(''); const [topic, setTopic] = useState('');
@@ -43,7 +42,7 @@ const SessionPage = () => {
return ( return (
<Container maxWidth="md"> <Container maxWidth="md">
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>} {wsError && <Alert severity="error" sx={{ mb: 2 }}>{wsError}</Alert>}
<Typography variant="h4" component="h1" gutterBottom> <Typography variant="h4" component="h1" gutterBottom>
Session: {session.topic || session.sessionId} Session: {session.topic || session.sessionId}

View File

@@ -1,42 +0,0 @@
import React from 'react';
import { Box, Typography, Container, Button } from '@mui/material';
import { useNavigate } from 'react-router-dom';
const StartPage = () => {
const navigate = useNavigate();
const handleCreateClick = () => {
navigate('/create');
};
return (
<Container maxWidth="sm">
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center'
}}
>
<Typography component="h1" variant="h4" gutterBottom>
Welcome to Agree on Desires
</Typography>
<Typography variant="h6" color="text.secondary" paragraph>
A simple tool to help groups of people make decisions together.
</Typography>
<Button
variant="contained"
size="large"
sx={{ mt: 4 }}
onClick={handleCreateClick}
>
Create a New Session
</Button>
</Box>
</Container>
);
};
export default StartPage;