Skip to content

Commit 7e158b3

Browse files
committed
wip: crdt and webrtc state management
1 parent e23b13b commit 7e158b3

36 files changed

+1102
-229
lines changed

eslint.config.mjs

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import tsParser from '@typescript-eslint/parser';
66
import reactHooks from 'eslint-plugin-react-hooks';
77
import path from 'node:path';
88
import { fileURLToPath } from 'node:url';
9+
import globals from 'globals';
910

1011
// eslint-disable-next-line no-underscore-dangle
1112
const __filename = fileURLToPath(import.meta.url);
@@ -157,4 +158,13 @@ export default [
157158
'no-restricted-syntax': ['error'],
158159
},
159160
},
161+
{
162+
files: ['src/modules/appStateWorker/worker/appStateWorker.ts'],
163+
languageOptions: {
164+
globals: {
165+
...globals.worker,
166+
self: true,
167+
},
168+
},
169+
},
160170
];

package.json

+5
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@
5050
"lodash.clone": "^4.5.0",
5151
"lodash.isequal": "^4.5.0",
5252
"lodash.shuffle": "^4.2.0",
53+
"loro-crdt": "^1.3.5",
5354
"lucide-react": "^0.474.0",
5455
"papaparse": "^5.4.1",
56+
"peerjs": "^1.5.4",
5557
"plotly.js": "^2.29.1",
5658
"qs": "6.14.0",
5759
"react": "18.3.1",
@@ -121,6 +123,7 @@
121123
"eslint-plugin-prettier": "5.2.3",
122124
"eslint-plugin-react": "^7.33.2",
123125
"eslint-plugin-react-hooks": "5.1.0",
126+
"globals": "^15.15.0",
124127
"husky": "9.1.7",
125128
"jsdom": "^26.0.0",
126129
"lodash.clonedeep": "^4.5.0",
@@ -133,6 +136,8 @@
133136
"vite": "^6.0.0",
134137
"vite-plugin-checker": "^0.8.0",
135138
"vite-plugin-istanbul": "^6.0.0",
139+
"vite-plugin-top-level-await": "^1.4.4",
140+
"vite-plugin-wasm": "^3.4.1",
136141
"vitest": "^3.0.0"
137142
},
138143
"browserslist": {

src/config/appActionsTypes.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ActivityStep } from '@/interfaces/interactionProcess';
55
import { Prompt } from '@/interfaces/prompt';
66
import { ResponseData } from '@/interfaces/response';
77

8-
import { CurrentStateData, ResponseAppData, VoteAppData } from './appDataTypes';
8+
import { CurrentStateData, VoteAppData } from './appDataTypes';
99

1010
export enum AppActionTypes {
1111
SubmitNewResponse = 'submit-new-response',
@@ -39,7 +39,7 @@ export type DeleteResponseAction = Pick<AppAction, 'type' | 'data'> & {
3939

4040
export type ChooseResponseAction = Pick<AppAction, 'type' | 'data'> & {
4141
type: AppActionTypes.ChooseResponse;
42-
data: ResponseAppData;
42+
data: ResponseData;
4343
};
4444

4545
export type OpenAppAction = Pick<AppAction, 'type' | 'data'> & {

src/config/appDataTypes.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export enum AppDataTypes {
1818
ChatbotResponse = 'chatbot-response',
1919
Prompts = 'prompts',
2020
ListAssistantState = 'list-assistant-state',
21+
PeerData = 'peer-data',
2122
}
2223

2324
export type ResponseAppData<T extends ResponseEvaluation = undefined> =

src/config/queryClient.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const {
2222
GRAASP_APP_KEY,
2323
isStandalone: MOCK_API,
2424
WS_HOST,
25-
enableWebsocket: typeof WS_HOST !== 'undefined',
25+
enableWebsocket: false,
2626
});
2727

2828
export {

src/hooks/useActions.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ import {
2424
} from '@/config/appDataTypes';
2525
import { mutations } from '@/config/queryClient';
2626
import { ActivityStep } from '@/interfaces/interactionProcess';
27+
import { ResponseData } from '@/interfaces/response';
2728

2829
interface UseActionsValues {
2930
postSubmitNewResponseAction: (response: ResponseAppData) => void;
3031
postDeleteResponseAction: (id: ResponseAppData['id']) => void;
31-
postChooseResponseAction: (response: ResponseAppData) => void;
32+
postChooseResponseAction: (response: ResponseData<undefined>) => void;
3233
postOpenAppAction: (
3334
currentState?: CurrentStateData,
3435
context?: LocalContext,
@@ -74,7 +75,7 @@ const useActions = (): UseActionsValues => {
7475
);
7576

7677
const postChooseResponseAction = useMemo(
77-
() => (response: ResponseAppData) => {
78+
() => (response: ResponseData) => {
7879
const action: ChooseResponseAction = {
7980
type: AppActionTypes.ChooseResponse,
8081
data: response,

src/hooks/useActivityState.ts

+22-9
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ import { useMemo, useState } from 'react';
33
import { useLocalContext } from '@graasp/apps-query-client';
44
import { PermissionLevel } from '@graasp/sdk';
55

6-
import { CurrentStateData } from '@/config/appDataTypes';
6+
import { AppDataTypes, CurrentStateData } from '@/config/appDataTypes';
77
import { INITIAL_STATE } from '@/config/constants';
88
import {
99
ActivityStatus,
1010
ActivityStep,
1111
ActivityType,
1212
} from '@/interfaces/interactionProcess';
1313
import { useAppDataContext } from '@/modules/context/AppDataContext';
14-
import { useSettings } from '@/modules/context/SettingsContext';
15-
import { getAllStates, getCurrentState } from '@/utils/state';
14+
import { getAllStates } from '@/utils/state';
1615

1716
import useActions from './useActions';
1817

@@ -35,20 +34,34 @@ export interface UseActivityStateValues {
3534
}
3635

3736
const useActivityState = (): UseActivityStateValues => {
37+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3838
const [stateWarning, setStateWarning] = useState(false);
3939

4040
const { postPlayActivityAction, postPauseActivityAction } = useActions();
4141

4242
const { appData, postAppData, patchAppData, deleteAppData } =
4343
useAppDataContext();
44-
const { orchestrator } = useSettings();
44+
// const { orchestrator } = useSettings();
4545
const { permission } = useLocalContext();
4646

47-
const activityState = useMemo(() => {
48-
const state = getCurrentState(appData, orchestrator.id);
49-
setStateWarning(state?.multipleStatesFound === true);
50-
return state.currentState;
51-
}, [appData, orchestrator]);
47+
const activityState = useMemo(
48+
() =>
49+
// const state = getCurrentState(appData, orchestrator.id);
50+
// setStateWarning(state?.multipleStatesFound === true);
51+
// return state.currentState;
52+
({
53+
id: 'ercoaberub',
54+
type: AppDataTypes.CurrentState,
55+
data: {
56+
round: 1,
57+
status: ActivityStatus.Play,
58+
activity: ActivityType.Collection,
59+
startTime: new Date(),
60+
stepIndex: undefined,
61+
},
62+
}),
63+
[],
64+
);
5265

5366
const round = useMemo(() => activityState?.data.round || 0, [activityState]);
5467

src/hooks/useAssistants.ts

+41-14
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
ListAssistantStateData,
2929
} from '@/interfaces/assistant';
3030
import { ResponseVisibilityMode } from '@/interfaces/interactionProcess';
31+
import { responseDataFactory } from '@/interfaces/response';
3132
import { useActivityContext } from '@/modules/context/ActivityContext';
3233
import { useAppDataContext } from '@/modules/context/AppDataContext';
3334
import { useSettings } from '@/modules/context/SettingsContext';
@@ -184,21 +185,32 @@ const useAssistants = (): UseAssistantsValues => {
184185
if (assistantResponseAppData) {
185186
const { completion: response, assistantId } =
186187
assistantResponseAppData.data;
187-
return postResponse({
188-
response,
189-
round,
190-
bot: true,
191-
assistantId,
192-
});
188+
return postResponse(
189+
responseDataFactory(
190+
{
191+
response,
192+
round,
193+
bot: true,
194+
assistantId,
195+
},
196+
{
197+
// TODO: Change this
198+
id: accountId ?? '',
199+
name: accountId ?? '',
200+
},
201+
),
202+
);
193203
}
194204
return assistantResponseAppData;
195205
});
196206
},
197207
[
208+
accountId,
198209
allResponses,
199210
assistantsResponsesSets,
200211
includeDetails,
201-
instructions,
212+
instructions.details?.content,
213+
instructions.title.content,
202214
postResponse,
203215
promptAssistant,
204216
promptMode,
@@ -252,16 +264,31 @@ const useAssistants = (): UseAssistantsValues => {
252264
});
253265
}
254266
if (response) {
255-
return postResponse({
256-
response,
257-
round,
258-
bot: true,
259-
assistantId: id,
260-
});
267+
return postResponse(
268+
responseDataFactory(
269+
{
270+
response,
271+
round,
272+
bot: true,
273+
assistantId: id,
274+
},
275+
{
276+
// TODO: Change this
277+
id: accountId ?? '',
278+
name: accountId ?? '',
279+
},
280+
),
281+
);
261282
}
262283
return undefined;
263284
},
264-
[listAssistantsStates, postResponse, round, updateListAssistantState],
285+
[
286+
accountId,
287+
listAssistantsStates,
288+
postResponse,
289+
round,
290+
updateListAssistantState,
291+
],
265292
);
266293

267294
const generateResponsesWithEachAssistant = useCallback(async (): Promise<

src/hooks/useResponses.ts

+19-9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ResponseVisibilityMode } from '@/interfaces/interactionProcess';
1717
import {
1818
ResponseData,
1919
ResponseDataExchangeFormat,
20+
responseDataFactory,
2021
} from '@/interfaces/response';
2122
import { useAppDataContext } from '@/modules/context/AppDataContext';
2223
import { useSettings } from '@/modules/context/SettingsContext';
@@ -276,15 +277,24 @@ const useResponses = ({
276277
responsesData: Array<ResponseDataExchangeFormat>,
277278
): Promise<void> => {
278279
responsesData.forEach((r) =>
279-
postResponse({
280-
response: r.response,
281-
round: r?.round,
282-
bot: r?.bot,
283-
assistantId: r?.assistantId,
284-
encoding: r?.encoding,
285-
originalResponse: r?.originalResponse,
286-
givenPrompt: r?.givenPrompt,
287-
}),
280+
postResponse(
281+
responseDataFactory(
282+
{
283+
response: r.response,
284+
round: r?.round,
285+
bot: r?.bot,
286+
assistantId: r?.assistantId,
287+
encoding: r?.encoding,
288+
originalResponse: r?.originalResponse,
289+
givenPrompt: r?.givenPrompt,
290+
},
291+
{
292+
// TODO: Change this
293+
id: accountId ?? '',
294+
name: accountId ?? '',
295+
},
296+
),
297+
),
288298
);
289299
};
290300

src/hooks/utils/evaluation.ts

+8-11
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { ResponseAppData, VoteAppData } from '@/config/appDataTypes';
2-
import { ResponseVotes } from '@/interfaces/response';
1+
import { VoteAppData } from '@/config/appDataTypes';
2+
import { ResponseData, ResponseVotes } from '@/interfaces/response';
33

44
export const sortResponsesByNumberOfVote = (
5-
responses: Array<ResponseAppData>,
5+
responses: Array<ResponseData>,
66
votes: Array<VoteAppData>,
7-
): Array<ResponseAppData<ResponseVotes>> =>
7+
): Array<ResponseData<ResponseVotes>> =>
88
responses
99
.map((response) => {
1010
const votesForResponse = votes.filter(
@@ -13,17 +13,14 @@ export const sortResponsesByNumberOfVote = (
1313
const numberOfVotes = votesForResponse.length;
1414
return {
1515
...response,
16-
data: {
17-
...response.data,
18-
evaluation: {
19-
votes: numberOfVotes,
20-
},
16+
evaluation: {
17+
votes: numberOfVotes,
2118
},
2219
};
2320
})
2421
.sort((r1, r2) => {
25-
const v1 = r1.data?.evaluation?.votes || 0;
26-
const v2 = r2.data?.evaluation?.votes || 0;
22+
const v1 = r1.evaluation?.votes || 0;
23+
const v2 = r2.evaluation?.votes || 0;
2724
if (v1 > v2) {
2825
return -1;
2926
}

src/interfaces/author.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export type Author = {
2+
id: string;
3+
name: string;
4+
email?: string;
5+
extra?: unknown;
6+
};
7+
8+
export default {};

src/interfaces/response.ts

+28
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { v4 } from 'uuid';
2+
3+
import { Author } from '@/interfaces/author';
4+
15
import { AssistantId } from './assistant';
26

37
export type ResponseVotes = {
@@ -16,6 +20,8 @@ export type ResponseEvaluation = ResponseVotes | ResponseRatings | undefined;
1620
export type ResponseData<
1721
EvaluationType extends ResponseEvaluation = undefined,
1822
> = {
23+
id: string;
24+
author: Author;
1925
response: string | Array<string>;
2026
round?: number;
2127
bot?: boolean;
@@ -27,6 +33,28 @@ export type ResponseData<
2733
evaluation?: EvaluationType;
2834
};
2935

36+
export type InputResponseData = Pick<ResponseData, 'response'> &
37+
Partial<ResponseData>;
38+
39+
export const responseDataFactory = (
40+
partialResponse: InputResponseData,
41+
author: Author,
42+
): ResponseData => ({
43+
id: v4(),
44+
response: partialResponse.response,
45+
round: partialResponse?.round,
46+
assistantId: partialResponse?.assistantId,
47+
bot: !(
48+
partialResponse?.bot ?? typeof partialResponse?.assistantId !== 'undefined'
49+
),
50+
parentId: partialResponse?.parentId,
51+
encoding: partialResponse?.encoding ?? 'raw',
52+
originalResponse: partialResponse?.originalResponse,
53+
givenPrompt: partialResponse?.givenPrompt,
54+
evaluation: partialResponse?.evaluation,
55+
author: author ?? partialResponse.author,
56+
});
57+
3058
export type ResponseDataExchangeFormat = ResponseData<ResponseEvaluation> & {
3159
id: number;
3260
votes?: number;

0 commit comments

Comments
 (0)