Skip to content

Commit 8b339d2

Browse files
committed
refactor: use typescript in the components
1 parent f1640f8 commit 8b339d2

14 files changed

Lines changed: 4362 additions & 3141 deletions

frontend/package-lock.json

Lines changed: 4155 additions & 2900 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
"devDependencies": {
3232
"@edx/browserslist-config": "1.1.1",
3333
"@openedx/frontend-build": "14.6.0",
34+
"@edx/typescript-config": "^1.0.1",
35+
"@types/react": "^18",
36+
"@types/react-dom": "^18",
3437
"@openedx/paragon": "23.14.0",
3538
"jest": "29.7.0",
3639
"react": "18.3.1",
3740
"react-dom": "18.3.1"
3841
},
3942
"dependencies": {
40-
"prop-types": "^15.8.1",
4143
"react-markdown": "^8.0.0 || ^9.0.0"
4244
}
4345
}
Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, {
22
useState, useEffect, useRef, useCallback,
33
} from 'react';
4-
import PropTypes from 'prop-types';
4+
import { logError } from '@edx/frontend-platform/logging';
55
import { Spinner, Alert } from '@openedx/paragon';
66

77
// Import services
@@ -22,6 +22,8 @@ import {
2222
AIEducatorLibraryAssistComponent,
2323
AIEducatorLibraryResponseComponent,
2424
} from './components';
25+
import { PluginConfiguration } from './types';
26+
import { WORKFLOW_ACTIONS } from './constants';
2527

2628
/**
2729
* Component Registry
@@ -42,16 +44,23 @@ const COMPONENT_REGISTRY = {
4244
* AIRequestComponent and AIResponseComponent with configuration.
4345
* Manages state and orchestrates the AI interaction flow.
4446
*/
47+
48+
interface ConfigurableAIAssistanceProps {
49+
fallbackConfig?: PluginConfiguration | null;
50+
onConfigLoad?: (config: any) => void;
51+
onConfigError?: (error: any) => void;
52+
};
53+
4554
const ConfigurableAIAssistance = ({
46-
fallbackConfig,
55+
fallbackConfig = null,
4756
onConfigLoad,
4857
onConfigError,
4958
...additionalProps
50-
}) => {
59+
}: ConfigurableAIAssistanceProps) => {
5160
// Configuration state
5261
const [isLoadingConfig, setIsLoadingConfig] = useState(true);
53-
const [configError, setConfigError] = useState(null);
54-
const [config, setConfig] = useState(null);
62+
const [configError, setConfigError] = useState<null | string>(null);
63+
const [config, setConfig] = useState<PluginConfiguration | null>(null);
5564

5665
// AI interaction state
5766
const [isLoading, setIsLoading] = useState(false);
@@ -91,17 +100,20 @@ const ConfigurableAIAssistance = ({
91100
}
92101
}
93102
} catch (err) {
103+
// Type guard for error
104+
const error = err instanceof Error ? err : new Error(String(err));
105+
94106
// Ignore aborted requests
95-
if (err.name === 'AbortError' || err.message?.includes('aborted')) {
107+
if (error.name === 'AbortError' || error.message?.includes('aborted')) {
96108
return;
97109
}
98110

99111
// Only update state if this is still the latest request
100112
if (currentRequestId === requestIdRef.current) {
101113
// eslint-disable-next-line no-console
102-
console.error('[ConfigurableAIAssistance] Configuration error:', err);
114+
console.error('[ConfigurableAIAssistance] Configuration error:', error);
103115

104-
setConfigError(err.message);
116+
setConfigError(error.message);
105117

106118
if (fallbackConfig) {
107119
setConfig(fallbackConfig);
@@ -141,17 +153,17 @@ const ConfigurableAIAssistance = ({
141153
...additionalProps,
142154
});
143155

144-
const requestMessage = config?.config?.customMessage
145-
|| config?.config?.requestMessage
156+
const requestMessage = config?.request.config?.customMessage
157+
|| config?.request.config?.requestMessage
146158
|| null;
147159

148160
let buffer = '';
149161
// Make API call
150162
const data = await callWorkflowService({
151163
context: contextData,
152-
action: 'run',
153164
userInput: requestMessage,
154165
payload: {
166+
action: WORKFLOW_ACTIONS.RUN,
155167
requestId: `ai-request-${Date.now()}`,
156168
},
157169
onStreamChunk: (chunk) => {
@@ -183,8 +195,7 @@ const ConfigurableAIAssistance = ({
183195

184196
setHasAsked(true);
185197
} catch (err) {
186-
// eslint-disable-next-line no-console
187-
console.error('[ConfigurableAIAssistance] AI Assistant Error:', err);
198+
logError('[ConfigurableAIAssistance] AI Assistant Error:', err);
188199
const userFriendlyError = formatErrorMessage(err);
189200
setError(userFriendlyError);
190201
} finally {
@@ -331,19 +342,4 @@ const ConfigurableAIAssistance = ({
331342
return null;
332343
};
333344

334-
ConfigurableAIAssistance.propTypes = {
335-
fallbackConfig: PropTypes.shape({
336-
component: PropTypes.string.isRequired,
337-
config: PropTypes.shape({}),
338-
}),
339-
onConfigLoad: PropTypes.func,
340-
onConfigError: PropTypes.func,
341-
};
342-
343-
ConfigurableAIAssistance.defaultProps = {
344-
fallbackConfig: null,
345-
onConfigLoad: null,
346-
onConfigError: null,
347-
};
348-
349345
export default ConfigurableAIAssistance;
Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React, { useState, useCallback } from 'react';
2-
import PropTypes from 'prop-types';
32

43
// Import service modules
54
import {
@@ -13,22 +12,28 @@ import {
1312
AIRequestComponent,
1413
AIResponseComponent,
1514
} from './components';
15+
import { WORKFLOW_ACTIONS } from './constants';
16+
17+
interface GetAIAssistanceButtonProps {
18+
requestMessage?: string;
19+
buttonText?: string;
20+
}
1621

1722
/**
1823
* Main AI Assistant Plugin Component
1924
* Orchestrates the AI assistance flow using modular components
2025
*/
2126
const GetAIAssistanceButton = ({
22-
requestMessage,
23-
buttonText,
27+
requestMessage = 'Need help understanding this content?',
28+
buttonText = 'Get AI Assistance',
2429
...props
25-
}) => {
30+
}: GetAIAssistanceButtonProps) => {
2631
// Core state management
2732
const [isLoading, setIsLoading] = useState(false);
2833
const [response, setResponse] = useState('');
2934
const [error, setError] = useState('');
3035
const [hasAsked, setHasAsked] = useState(false);
31-
const [requestId, setRequestId] = useState(null);
36+
const [requestId, setRequestId] = useState<string | null>(null);
3237

3338
/**
3439
* Handle AI assistant request
@@ -51,9 +56,9 @@ const GetAIAssistanceButton = ({
5156
// Make API call with flexible parameters
5257
const data = await callWorkflowService({
5358
context: contextData,
54-
action: 'simple_button_assistance',
5559
userInput: requestMessage || 'Provide learning assistance for this content',
5660
payload: {
61+
action: WORKFLOW_ACTIONS.SIMPLE_BUTTON_ASSISTANCE,
5762
requestId: `ai-request-${Date.now()}`,
5863
},
5964
onStreamChunk: (chunk) => {
@@ -141,7 +146,6 @@ const GetAIAssistanceButton = ({
141146
response={response}
142147
error={error}
143148
isLoading={isLoading}
144-
onAskAgain={handleAskAI}
145149
onClear={handleReset}
146150
onError={handleClearError}
147151
/>
@@ -161,14 +165,4 @@ const GetAIAssistanceButton = ({
161165
);
162166
};
163167

164-
GetAIAssistanceButton.propTypes = {
165-
requestMessage: PropTypes.string,
166-
buttonText: PropTypes.string,
167-
};
168-
169-
GetAIAssistanceButton.defaultProps = {
170-
requestMessage: 'Need help understanding this content?',
171-
buttonText: 'Get AI Assistance',
172-
};
173-
174168
export default GetAIAssistanceButton;

frontend/src/components/AIEducatorLibraryAssistComponent.jsx renamed to frontend/src/components/AIEducatorLibraryAssistComponent.tsx

Lines changed: 40 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useEffect, useRef } from 'react';
2-
import PropTypes from 'prop-types';
2+
import { logError, logInfo } from '@edx/frontend-platform/logging';
33
import {
44
Button,
55
Form,
@@ -11,6 +11,23 @@ import { AutoAwesome, Close } from '@openedx/paragon/icons';
1111
import { getConfig } from '@edx/frontend-platform';
1212
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
1313
import { callWorkflowService, prepareContextData } from '../services';
14+
import { WORKFLOW_ACTIONS } from '../constants';
15+
16+
interface AIEducatorLibraryAssistComponentProps {
17+
courseId: string;
18+
locationId: string;
19+
setResponse: (response: string) => void;
20+
hasAsked: boolean;
21+
setHasAsked: (hasAsked: boolean) => void;
22+
libraries?: Array<{ id: string; title: string }>;
23+
titleText?: string;
24+
buttonText?: string;
25+
preloadPreviousSession?: boolean;
26+
customMessage?: string;
27+
onSuccess?: () => void;
28+
onError?: (error: any) => void;
29+
debug?: boolean;
30+
}
1431

1532
/**
1633
* AI Educator Library Assist Component
@@ -24,14 +41,14 @@ const AIEducatorLibraryAssistComponent = ({
2441
hasAsked,
2542
setHasAsked,
2643
libraries: librariesProp,
27-
titleText,
28-
buttonText,
29-
preloadPreviousSession,
30-
customMessage,
44+
titleText = 'AI Assistant',
45+
buttonText = 'Start',
46+
customMessage = 'Use an AI workflow to create multiple answer questions from this unit in a content library',
47+
preloadPreviousSession = false,
3148
onSuccess,
3249
onError,
33-
debug,
34-
}) => {
50+
debug = false,
51+
}: AIEducatorLibraryAssistComponentProps) => {
3552
const [showForm, setShowForm] = useState(false);
3653
const [isLoading, setIsLoading] = useState(false);
3754
const [error, setError] = useState('');
@@ -66,8 +83,7 @@ const AIEducatorLibraryAssistComponent = ({
6683
const endpoint = `${baseUrl}/api/libraries/v2/?pagination=false&order=title`;
6784

6885
if (debug) {
69-
// eslint-disable-next-line no-console
70-
console.log('Fetching libraries from:', endpoint);
86+
logInfo('Fetching libraries from:', endpoint);
7187
}
7288

7389
const { data } = await getAuthenticatedHttpClient().get(endpoint);
@@ -79,12 +95,10 @@ const AIEducatorLibraryAssistComponent = ({
7995
setLibrariesFetched(true);
8096

8197
if (debug) {
82-
// eslint-disable-next-line no-console
83-
console.log('Fetched libraries:', fetchedLibraries);
98+
logInfo('Fetched libraries:', fetchedLibraries);
8499
}
85100
} catch (err) {
86-
// eslint-disable-next-line no-console
87-
console.error('Error fetching libraries:', err);
101+
logError('Error fetching libraries:', err);
88102
setError('Failed to load libraries. Please try again.');
89103
} finally {
90104
setIsLoadingLibraries(false);
@@ -116,8 +130,8 @@ const AIEducatorLibraryAssistComponent = ({
116130

117131
const data = await callWorkflowService({
118132
context: contextData,
119-
action: 'get_current_session_response',
120133
payload: {
134+
action: WORKFLOW_ACTIONS.GET_CURRENT_SESSION_RESPONSE,
121135
requestId: `ai-request-${Date.now()}`,
122136
},
123137
});
@@ -128,14 +142,12 @@ const AIEducatorLibraryAssistComponent = ({
128142
setHasAsked(true);
129143
} else if (debug) {
130144
// No previous session or empty response - do nothing, show normal component
131-
// eslint-disable-next-line no-console
132-
console.log('No previous session found or empty response');
145+
logInfo('No previous session found or empty response');
133146
}
134147
} catch (err) {
135148
// Silent fail - no previous session is not an error
136149
if (debug) {
137-
// eslint-disable-next-line no-console
138-
console.log('Error loading previous session:', err);
150+
logInfo('Error loading previous session:', err);
139151
}
140152
} finally {
141153
setIsLoading(false);
@@ -179,13 +191,13 @@ const AIEducatorLibraryAssistComponent = ({
179191

180192
const data = await callWorkflowService({
181193
context: contextData,
182-
action: 'run_async',
183194
payload: {
195+
action: WORKFLOW_ACTIONS.RUN_ASYNC,
184196
requestId: `ai-request-${Date.now()}`,
185-
user_input: {
186-
library_id: selectedLibrary,
187-
num_questions: numberOfQuestions,
188-
extra_instructions: additionalInstructions,
197+
userInput: {
198+
libraryId: selectedLibrary,
199+
numQuestions: numberOfQuestions,
200+
extraInstructions: additionalInstructions,
189201
},
190202
},
191203
});
@@ -225,8 +237,11 @@ const AIEducatorLibraryAssistComponent = ({
225237
} catch (err) {
226238
// eslint-disable-next-line no-console
227239
console.error('Error generating library questions:', err);
228-
const errorMessage = err.response?.data?.error
229-
|| err.message
240+
241+
// Type guard for error
242+
const error = err instanceof Error ? err : new Error(String(err));
243+
const errorMessage = (err as any)?.response?.data?.error
244+
|| error.message
230245
|| 'Failed to generate questions. Please try again.';
231246
setError(errorMessage);
232247

@@ -419,36 +434,4 @@ const AIEducatorLibraryAssistComponent = ({
419434
);
420435
};
421436

422-
AIEducatorLibraryAssistComponent.propTypes = {
423-
courseId: PropTypes.string.isRequired,
424-
locationId: PropTypes.string.isRequired,
425-
hasAsked: PropTypes.bool.isRequired,
426-
setResponse: PropTypes.func.isRequired,
427-
setHasAsked: PropTypes.func.isRequired,
428-
libraries: PropTypes.arrayOf(
429-
PropTypes.shape({
430-
id: PropTypes.string.isRequired,
431-
title: PropTypes.string.isRequired,
432-
}),
433-
),
434-
titleText: PropTypes.string,
435-
buttonText: PropTypes.string,
436-
preloadPreviousSession: PropTypes.bool,
437-
customMessage: PropTypes.string,
438-
onSuccess: PropTypes.func,
439-
onError: PropTypes.func,
440-
debug: PropTypes.bool,
441-
};
442-
443-
AIEducatorLibraryAssistComponent.defaultProps = {
444-
libraries: null,
445-
titleText: 'AI Assistant',
446-
buttonText: 'Start',
447-
customMessage: 'Use an AI workflow to create multiple answer questions from this unit in a content library',
448-
preloadPreviousSession: false,
449-
onSuccess: null,
450-
onError: null,
451-
debug: false,
452-
};
453-
454437
export default AIEducatorLibraryAssistComponent;

0 commit comments

Comments
 (0)