Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
758e5c3
PM-3851 #time 5h Split reviews in 2 tabs: human & ai
vas3a Feb 17, 2026
4dfa263
PM-3851 #time 3h ai tab split into template, manual config
vas3a Feb 17, 2026
8c4214a
PM-3851 #time 4h mock reviewers API servies
vas3a Feb 18, 2026
4419e7b
PM-3851 #time 3h use template view
vas3a Feb 18, 2026
ff154a2
PM-3851 #3.5h manual config for ai reviewers
vas3a Feb 19, 2026
126a57a
PM-3851 - #4.5h start integration
vas3a Feb 20, 2026
da740a9
PM-3851 #time 2h refactor manual & template views
vas3a Feb 20, 2026
4fa4d79
PM-3851 #time 1h cleanup
vas3a Feb 20, 2026
1b601cf
PM-3851 #time 1h more cleanup
vas3a Feb 20, 2026
5a32ff8
PM-3851 #30m render ai & human reviewers tabs from start, to keep sta…
vas3a Feb 20, 2026
3f71798
PM-3851 #time 1h autosave ai review config
vas3a Feb 20, 2026
7e375bc
PM-3851 #time 4h work on integration
vas3a Feb 20, 2026
2cb88bd
small updates
vas3a Feb 20, 2026
ca0fcc0
PM-3851 #time 30m update weights validation
vas3a Feb 23, 2026
5cee3cb
PM-3851 - #time 1h review configuration summary (in readonly mode)
vas3a Feb 23, 2026
a4998fc
sast fix
vas3a Feb 23, 2026
d68e75b
deploy to dev
vas3a Feb 23, 2026
87bea15
Merge pull request #1733 from topcoder-platform/PM-3851_ai-review-con…
vas3a Feb 23, 2026
f9c8be2
PM-3851 lint & cleanup
vas3a Feb 23, 2026
819d894
fix api for templates fetching
vas3a Feb 23, 2026
69c9636
fetch templates: track to uppercase
vas3a Feb 24, 2026
a9c64a3
PM-4040 #time 30m update summary render for human reviewers
vas3a Feb 24, 2026
71dfee2
PM-4037 #time 30m review summary: update how we preview legacy ai wor…
vas3a Feb 24, 2026
54ade78
PM-4030 #time 10m When Ch already has a submission, AI config must no…
vas3a Feb 24, 2026
2fb7a4c
PM-4028 #time 5m Weight Validation message does not display all weights
vas3a Feb 24, 2026
d4c4f25
lint
vas3a Feb 24, 2026
6e4fe99
PM-4016 #time 1h fix saving member reviewer
vas3a Feb 24, 2026
e102a09
Fix human review asignment
vas3a Feb 24, 2026
a94041a
lint
vas3a Feb 24, 2026
2eac344
Merge branch 'develop' of github.com:topcoder-platform/work-manager i…
vas3a Feb 25, 2026
928f7da
PM-4030 #time 30m update ai config state for existing challenges
vas3a Feb 25, 2026
75b447d
PM-4040 #time 30m show assigned members for human review
vas3a Feb 25, 2026
2220084
lint
vas3a Feb 25, 2026
251f32b
Merge branch 'develop' of github.com:topcoder-platform/work-manager i…
vas3a Mar 11, 2026
3ea0d0b
fix circleci
vas3a Mar 11, 2026
0b47944
lint
vas3a Mar 11, 2026
66c3571
PM-3813 #time 1h PR feedback
vas3a Mar 12, 2026
fd20bf8
lint
vas3a Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ workflows:
context: org-global
filters: &filters-dev
branches:
only: ["develop", "pm-2917", "points", "pm-3270", "projects-api-v6"]
only: ["develop", "pm-2917", "points", "pm-3270", "projects-api-v6", "PM-3813_ai-reviewer-configs"]
Comment thread
kkartunov marked this conversation as resolved.

# Production builds are exectuted only on tagged commits to the
# master branch.
Expand Down
6 changes: 6 additions & 0 deletions src/components/Buttons/OutlineButton/Outline.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@
color: $tc-gray-40;
}
}

&.minWidth {
width: min-content;
padding-left: 16px;
padding-right: 16px;
}
}
5 changes: 3 additions & 2 deletions src/components/Buttons/OutlineButton/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cn from 'classnames'
import styles from './Outline.module.scss'
import _ from 'lodash'

const OutlineButton = ({ type, text, link, onClick, url, className, submit, disabled, target = 'self', rel }) => {
const OutlineButton = ({ type, text, link, onClick, url, className, submit, disabled, target = 'self', rel, minWidth = false }) => {
const containerClassName = cn(styles.container, styles[type], className)

const handleUrlClick = (event) => {
Expand Down Expand Up @@ -34,7 +34,7 @@ const OutlineButton = ({ type, text, link, onClick, url, className, submit, disa
return (
<button
type={submit ? 'submit' : 'button'}
className={cn(containerClassName, disabled && styles.disable)}
className={cn(containerClassName, disabled && styles.disable, minWidth && styles.minWidth)}
onClick={submit ? null : onClick}
disabled={disabled}
>
Expand Down Expand Up @@ -67,6 +67,7 @@ const OutlineButton = ({ type, text, link, onClick, url, className, submit, disa
}

OutlineButton.propTypes = {
minWidth: PropTypes.bool,
type: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
link: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
Expand Down
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)
Comment thread
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])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
The addition of aiReviewers to the dependency array of the useCallback hook is correct to ensure that the callback updates when aiReviewers changes. However, ensure that aiReviewers is stable and doesn't change unnecessarily, as it could cause unnecessary re-renders or function re-creations.


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
Loading
Loading