feat: Enhance result analysis and error display
This commit is contained in:
@@ -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)}
|
||||||
@@ -88,9 +88,8 @@ export class LLMService {
|
|||||||
const text = response.text().trim().toLowerCase();
|
const text = response.text().trim().toLowerCase();
|
||||||
return text === 'true';
|
return text === 'true';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error calling Gemini API for contradiction check:", error);
|
console.error("Error calling Gemini API for contradiction check:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
Reference in New Issue
Block a user