-
Notifications
You must be signed in to change notification settings - Fork 51
PM-3813 ai reviewer configs #1737
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
758e5c3
4dfa263
8c4214a
4419e7b
ff154a2
126a57a
da740a9
4fa4d79
1b601cf
5a32ff8
3f71798
7e375bc
2cb88bd
ca0fcc0
5cee3cb
a4998fc
d68e75b
87bea15
f9c8be2
819d894
69c9636
a9c64a3
71dfee2
54ade78
2fb7a4c
d4c4f25
6e4fe99
e102a09
a94041a
2eac344
928f7da
75b447d
2220084
251f32b
3ea0d0b
0b47944
66c3571
fd20bf8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,4 +52,10 @@ | |
| color: $tc-gray-40; | ||
| } | ||
| } | ||
|
|
||
| &.minWidth { | ||
| width: min-content; | ||
| padding-left: 16px; | ||
| padding-right: 16px; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import React from 'react' | ||
| import PropTypes from 'prop-types' | ||
| import styles from './AiWorkflowCard.module.scss' | ||
|
|
||
| const AIWorkflowCard = ({ workflow, scorecardId, description, onRemove, readOnly = false }) => { | ||
| return ( | ||
| <div className={styles.workflowCard}> | ||
| <div className={styles.workflowcardHeader}> | ||
| <div className={styles.workflowInfo}> | ||
| <div className={styles.workflowName}> | ||
| <span className={styles.workflowIcon}>🤖</span> | ||
| <span className={styles.workflowTitle}>{workflow.name}</span> | ||
| </div> | ||
| </div> | ||
| {!readOnly && onRemove && ( | ||
| <button | ||
| className={styles.workflowRemoveBtn} | ||
| onClick={onRemove} | ||
| title='Remove workflow' | ||
| aria-label='Remove workflow' | ||
| > | ||
| ✕ | ||
| </button> | ||
| )} | ||
| </div> | ||
|
|
||
| <div className={styles.workflowcardContent}> | ||
| {description && ( | ||
| <div className={styles.workflowDescription}> | ||
| <strong>Description:</strong> | ||
| <p>{description}</p> | ||
| </div> | ||
| )} | ||
|
|
||
| {scorecardId && ( | ||
| <div className={styles.workflowScorecard}> | ||
| <strong>Scorecard:</strong> | ||
| {scorecardId} | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| AIWorkflowCard.propTypes = { | ||
| workflow: PropTypes.shape({ | ||
| id: PropTypes.string, | ||
| name: PropTypes.string.isRequired | ||
| }).isRequired, | ||
| scorecardId: PropTypes.string, | ||
| description: PropTypes.string, | ||
| onRemove: PropTypes.func, | ||
| readOnly: PropTypes.bool | ||
| } | ||
|
|
||
| export default AIWorkflowCard |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| import React, { useCallback, useMemo } from 'react' | ||
| import PropTypes from 'prop-types' | ||
| import { isAIReviewer } from './utils' | ||
| import { deleteAIReviewConfig } from '../../../../services/aiReviewConfigs' | ||
| import styles from './AiReviewTab.module.scss' | ||
| import sharedStyles from '../shared.module.scss' | ||
| import useConfigurationState from './hooks/useConfigurationState' | ||
| import InitialStateView from './views/InitialStateView' | ||
| import TemplateConfigurationView from './views/TemplateConfigurationView' | ||
| import ManualConfigurationView from './views/ManualConfigurationView' | ||
| import { pick } from 'lodash' | ||
|
|
||
| /** | ||
| * AiReviewTab - Main component for managing AI review configuration | ||
| * Orchestrates between different views: initial state, template, manual, and legacy | ||
| */ | ||
| const AiReviewTab = ({ challenge, onUpdateReviewers, metadata = {}, isLoading, readOnly = false }) => { | ||
| const { | ||
| isLoading: isLoadingConfigs, | ||
| configuration, | ||
| configurationMode, | ||
| setConfigurationMode, | ||
| updateConfiguration, | ||
| addWorkflow, | ||
| updateWorkflow, | ||
| removeWorkflow, | ||
| resetConfiguration, | ||
| applyTemplate, | ||
| isSaving, | ||
| configId | ||
| } = useConfigurationState(challenge.id) | ||
|
|
||
| const aiReviewers = useMemo(() => ( | ||
| (challenge.reviewers || []).filter(isAIReviewer) | ||
| ), [challenge.reviewers]) | ||
|
|
||
| const removeAIReviewer = useCallback((index) => { | ||
| const allChallengeReviewers = challenge.reviewers || [] | ||
| // Map the AI reviewer index to the actual index in the full reviewers array | ||
| const reviewerToRemove = aiReviewers[index] | ||
| const actualIndex = allChallengeReviewers.indexOf(reviewerToRemove) | ||
|
vas3a marked this conversation as resolved.
|
||
|
|
||
| if (actualIndex !== -1) { | ||
| const updatedReviewers = allChallengeReviewers.filter((_, i) => i !== actualIndex) | ||
| onUpdateReviewers({ field: 'reviewers', value: updatedReviewers }) | ||
| } | ||
| }, [challenge.reviewers, onUpdateReviewers, aiReviewers]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
|
|
||
| const handleRemoveConfiguration = useCallback(() => { | ||
| // Call delete API if config exists | ||
| if (configId) { | ||
| deleteAIReviewConfig(configId).catch(err => { | ||
| console.error('Error deleting AI review configuration:', err) | ||
| }) | ||
| } | ||
| setConfigurationMode(null) | ||
| resetConfiguration() | ||
| }, [setConfigurationMode, resetConfiguration, configId]) | ||
|
|
||
| const handleSwitchConfigurationMode = useCallback((mode, template) => { | ||
| if (mode === 'manual') { | ||
| if (template) { | ||
| applyTemplate(pick(template, [ | ||
| 'mode', | ||
| 'minPassingThreshold', | ||
| 'autoFinalize', | ||
| 'formula', | ||
| 'workflows' | ||
| ])) | ||
| } | ||
| } else { | ||
| resetConfiguration() | ||
| } | ||
| setConfigurationMode(mode) | ||
| }, [setConfigurationMode, applyTemplate, resetConfiguration]) | ||
|
|
||
| if (isLoading || isLoadingConfigs) { | ||
| return <div className={styles.loading}>Loading...</div> | ||
| } | ||
|
|
||
| return ( | ||
| <div className={sharedStyles.tabContent}> | ||
| {isSaving && <div className={styles.autoSaveIndicator}>💾 Saving...</div>} | ||
|
|
||
| {/* initial state (no configuration mode was selected: template/manual) */} | ||
| {configurationMode === null && ( | ||
| <InitialStateView | ||
| aiReviewers={aiReviewers} | ||
| metadata={metadata} | ||
| onSelectTemplate={() => setConfigurationMode('template')} | ||
| onSelectManual={() => setConfigurationMode('manual')} | ||
| onRemoveReviewer={removeAIReviewer} | ||
| readOnly={readOnly} | ||
| /> | ||
| )} | ||
|
|
||
| {/* Show template configuration if in template mode */} | ||
| {configurationMode === 'template' && ( | ||
| <TemplateConfigurationView | ||
| challenge={challenge} | ||
| configuration={configuration} | ||
| onTemplateChange={applyTemplate} | ||
| onUpdateConfiguration={updateConfiguration} | ||
| onSwitchMode={handleSwitchConfigurationMode} | ||
| onRemoveConfig={handleRemoveConfiguration} | ||
| readOnly={readOnly} | ||
| availableWorkflows={metadata.workflows || []} | ||
| /> | ||
| )} | ||
|
|
||
| {/* Show manual configuration if in manual mode */} | ||
| {configurationMode === 'manual' && ( | ||
| <ManualConfigurationView | ||
| challenge={challenge} | ||
| configuration={configuration} | ||
| availableWorkflows={metadata.workflows || []} | ||
| onUpdateConfiguration={updateConfiguration} | ||
| onAddWorkflow={addWorkflow} | ||
| onUpdateWorkflow={updateWorkflow} | ||
| onRemoveWorkflow={removeWorkflow} | ||
| onSwitchMode={handleSwitchConfigurationMode} | ||
| onRemoveConfig={handleRemoveConfiguration} | ||
| readOnly={readOnly} | ||
| /> | ||
| )} | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| AiReviewTab.propTypes = { | ||
| challenge: PropTypes.object.isRequired, | ||
| onUpdateReviewers: PropTypes.func.isRequired, | ||
| metadata: PropTypes.shape({ | ||
| workflows: PropTypes.array, | ||
| challengeTracks: PropTypes.array | ||
| }), | ||
| isLoading: PropTypes.bool, | ||
| readOnly: PropTypes.bool | ||
| } | ||
|
|
||
| AiReviewTab.defaultProps = { | ||
| metadata: {}, | ||
| isLoading: false, | ||
| readOnly: false | ||
| } | ||
|
|
||
| export default AiReviewTab | ||
Uh oh!
There was an error while loading. Please reload this page.