Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
223 changes: 223 additions & 0 deletions docs/adr/0001-topscout-rag-management.md

Large diffs are not rendered by default.

14 changes: 10 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"deploy:dev": "BRANCH=$(git rev-parse --abbrev-ref HEAD) && TAG=\"dev-${BRANCH}\" && git tag -d \"$TAG\" 2>/dev/null; git push origin \":refs/tags/$TAG\" 2>/dev/null; git tag \"$TAG\" && git push origin \"$TAG\""
},
"dependencies": {
"@ai-sdk/react": "4.0.82",
"@assistant-ui/react": "0.15.16",
"@assistant-ui/react-ai-sdk": "1.4.7",
"@assistant-ui/react-markdown": "0.14.12",
"@codemirror/autocomplete": "^6.20.1",
"@codemirror/lang-java": "^6.0.2",
"@codemirror/language": "^6.12.2",
Expand All @@ -42,6 +46,7 @@
"@tinymce/tinymce-react": "^6.3.0",
"@types/codemirror": "5.60.17",
"@uiw/react-codemirror": "^4.25.8",
"ai": "7.0.79",
"amazon-s3-uri": "^0.1.1",
"apexcharts": "^3.54.1",
"axios": "^1.19.0",
Expand All @@ -64,10 +69,12 @@
"express-interceptor": "^1.2.0",
"fflate": "^0.8.2",
"filestack-js": "^3.44.2",
"flag-icons": "^6.7.0",
"highcharts": "^10.3.3",
"highcharts-react-official": "^3.2.3",
"highlight.js": "^11.11.1",
"html2canvas": "^1.4.1",
"i18n-iso-countries": "^3.7.1",
"lodash": "^4.18.1",
"markdown-it": "^14.3.0",
"marked": "4.3.0",
Expand Down Expand Up @@ -115,6 +122,7 @@
"remark-breaks": "^3.0.3",
"remark-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1",
"remark-gfm-v4": "npm:remark-gfm@^4.0.1",
"remark-parse": "^11.0.0",
"remove": "^0.1.5",
"sanitize-html": "^2.17.6",
Expand All @@ -123,12 +131,10 @@
"swr": "^1.3.0",
"tc-auth-lib": "topcoder-platform/tc-auth-lib#v2.0",
"tinymce": "^7.9.1",
"typescript": "^4.9.5",
"typescript": "5.5.4",
"universal-navigation": "https://github.com/topcoder-platform/universal-navigation#master",
"uuid": "^11.1.0",
"yup": "^1.7.1",
"flag-icons": "^6.7.0",
"i18n-iso-countries": "^3.7.1"
"yup": "^1.7.1"
},
"devDependencies": {
"@babel/core": "^7.29.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
}
}


.desktopTable {
td {
vertical-align: middle;
Expand Down
89 changes: 73 additions & 16 deletions src/apps/admin/src/lib/components/ChallengeList/ChallengeList.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { Dispatch, FC, SetStateAction, useContext, useMemo, useState } from 'react'
import {
Dispatch,
FC,
SetStateAction,
useContext,
useMemo,
useState,
} from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
import _ from 'lodash'
import cn from 'classnames'
import moment from 'moment'

import { ChevronDownIcon } from '@heroicons/react/solid'
import { EnvironmentConfig } from '~/config'
import { useWindowSize, WindowSize } from '~/libs/shared'
import { ingestChallengeInRag, useWindowSize, WindowSize } from '~/libs/shared'
import { Button, Table, type TableColumn } from '~/libs/ui'

import { ReactComponent as RegistrantUserIcon } from '../../assets/i/registrant-user-icon.svg'
Expand All @@ -22,12 +30,10 @@ import { useEventCallback } from '../../hooks'
import { Challenge, ChallengeFilterCriteria, ChallengeType } from '../../models'
import { Paging } from '../../models/challenge-management/Pagination'
import { checkIsMM } from '../../utils/challenge'
import { handleError } from '../../utils'

import { MobileListView } from './MobileListView'
import {
canOpenReviewUi,
getReviewUiChallengeUrl,
} from './reviewUiLink'
import { canOpenReviewUi, getReviewUiChallengeUrl } from './reviewUiLink'
import { getChallengeSubTrackSuffix } from './challengeTypeTrack'
import styles from './ChallengeList.module.scss'

Expand Down Expand Up @@ -68,19 +74,25 @@ const ChallengeCurrentPhase: FC<{ challenge: Challenge }> = props => {
const ChallengeUserStats: FC<{ challenge: Challenge }> = props => (
<div className={styles.challengeUserStats}>
<span
title={`${props.challenge.numOfRegistrants} registrant${props.challenge.numOfRegistrants > 1 ? 's' : ''}`}
title={`${props.challenge.numOfRegistrants} registrant${
props.challenge.numOfRegistrants > 1 ? 's' : ''
}`}
>
<RegistrantUserIcon className='icon icon-fill' />
<span>{props.challenge.numOfRegistrants}</span>
</span>
<span
title={`${props.challenge.numOfSubmissions} submission${props.challenge.numOfSubmissions > 1 ? 's' : ''}`}
title={`${props.challenge.numOfSubmissions} submission${
props.challenge.numOfSubmissions > 1 ? 's' : ''
}`}
>
<SubmissionIcon className='icon icon-fill' />
<span>{props.challenge.numOfSubmissions}</span>
</span>
<span
title={`${props.challenge.groups.length} group${props.challenge.groups.length > 1 ? 's' : ''}`}
title={`${props.challenge.groups.length} group${
props.challenge.groups.length > 1 ? 's' : ''
}`}
>
<UserGroupIcon className='icon icon-fill' />
<span>{props.challenge.groups.length}</span>
Expand Down Expand Up @@ -131,10 +143,7 @@ const TrackIcon: FC<{ challenge: Challenge }> = props => {
const trackName = props.challenge.track?.name || ''

return (
<div
className={cn(styles.trackIcon)}
style={iconStyles(trackName)}
>
<div className={cn(styles.trackIcon)} style={iconStyles(trackName)}>
{type?.abbreviation}
</div>
)
Expand All @@ -145,6 +154,7 @@ const Actions: FC<{
currentFilters: ChallengeFilterCriteria
}> = props => {
const [openDropdown, setOpenDropdown] = useState(false)
const [isIngesting, setIsIngesting] = useState(false)
const navigate = useNavigate()
const isMM = useMemo(() => checkIsMM(props.challenge), [props.challenge])
const goToManageUser = useEventCallback(() => {
Expand All @@ -153,6 +163,38 @@ const Actions: FC<{
})
})

const handleIngestInRag = useEventCallback(async () => {
if (isIngesting) {
return
}

setIsIngesting(true)
const toastId = toast.loading(
`Ingesting "${props.challenge.name}" into RAG…`,
)

try {
const report = await ingestChallengeInRag(props.challenge.id)

const message = report.skipped
? 'Challenge has no description to index — nothing to ingest.'
: `Challenge ingested into RAG successfully (${report.chunks} `
+ `chunk${report.chunks === 1 ? '' : 's'}).`

toast.update(toastId, {
autoClose: 5000,
isLoading: false,
render: message,
type: 'success',
})
} catch (error) {
toast.dismiss(toastId)
handleError(error)
} finally {
setIsIngesting(false)
}
})

const manageDropdownMenuTrigger = useEventCallback(
(triggerProps: {
open: boolean
Expand Down Expand Up @@ -223,13 +265,24 @@ const Actions: FC<{
{isMM && (
<li
onClick={function onClick() {
navigate(`${props.challenge.id}/manage-marathon-match`)
navigate(
`${props.challenge.id}/manage-marathon-match`,
)
setOpenDropdown(false)
}}
>
Marathon Match
</li>
)}
<li
className={cn({ disabled: isIngesting })}
onClick={function onClick() {
setOpenDropdown(false)
handleIngestInRag()
}}
>
Upsert for TopScout
</li>
</ul>
</DropdownMenu>

Expand All @@ -240,7 +293,9 @@ const Actions: FC<{
classNames={{ menu: 'challenge-list-actions-dropdown-menu' }}
>
<ul>
<li className={cn({ disabled: !hasChallengeDetailsAccess })}>
<li
className={cn({ disabled: !hasChallengeDetailsAccess })}
>
{hasChallengeDetailsAccess && (
<a
href={`${EnvironmentConfig.ADMIN.CHALLENGE_URL}/${props.challenge.id}`}
Expand All @@ -250,7 +305,9 @@ const Actions: FC<{
Challenge Details
</a>
)}
{!hasChallengeDetailsAccess && <span>Challenge Details</span>}
{!hasChallengeDetailsAccess && (
<span>Challenge Details</span>
)}
</li>
<li className={cn({ disabled: !hasWorkManagerAccess })}>
{hasWorkManagerAccess && (
Expand Down
2 changes: 2 additions & 0 deletions src/apps/customer-portal/src/config/routes.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export const showcaseSearchRouteId = 'showcase'
export const flexiTalentRouteId = 'flexi-talent'
export const statisticsRouteId = 'statistics'
export const skillStatisticsRouteId = 'skill-statistics'
export const assistantsRouteId = 'assistants'
export const topScoutRouteId = 'topScout'
2 changes: 2 additions & 0 deletions src/apps/customer-portal/src/customer-portal.routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { customerPortalTalentSearchRoutes } from './pages/talent-search/talent-s
import { customerPortalProjectShowcaseRoutes } from './pages/project-showcase/project-showcase.routes'
import { customerPortalSkillStatisticsRoutes } from './pages/skill-statistics/skill-statistics.routes'
import { customerPortalStatisticsRoutes } from './pages/statistics/statistics.routes'
import { customerPortalAssistantsRoutes } from './pages/assistants/assistants.routes'

const CustomerPortalApp: LazyLoadedComponent = lazyLoad(() => import('./CustomerPortalApp'))

Expand All @@ -39,6 +40,7 @@ export const customerPortalRoutes: ReadonlyArray<PlatformRoute> = [
...customerPortalTalentSearchRoutes,
...customerPortalProjectShowcaseRoutes,
...customerPortalFlexiTalentRoutes,
...customerPortalAssistantsRoutes,
],
domain: AppSubdomain.customer,
element: <CustomerPortalApp />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import _ from 'lodash'

import { TabsNavItem } from '~/libs/ui'
import {
assistantsRouteId,
flexiTalentRouteId,
showcaseSearchRouteId,
skillStatisticsRouteId,
Expand All @@ -27,6 +28,9 @@ export function getTabsConfig(userRoles: string[], isAnonymous: boolean, isUnpri
}, {
id: flexiTalentRouteId,
title: 'Flexi-Talent',
}, {
id: assistantsRouteId,
title: 'Assistants',
}] : []),
]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@import '@libs/ui/styles/includes';

.wrap {
background-color: $tc-white;
border: 1px solid $black-10;
border-radius: 16px;
display: flex;
align-items: stretch;
color: $black-100;
text-decoration: none;
cursor: pointer;
overflow: hidden;
transition: box-shadow 0.15s ease, border-color 0.15s ease;

&:hover {
border-color: $black-20;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

@include ltesm {
flex-direction: column;
}
}

.content {
display: flex;
flex-direction: column;
gap: 8px;
padding: $sp-6;
flex: 1 1 auto;
min-width: 0;
}

.title {
color: $black-100;
font-feature-settings: 'liga' off, 'clig' off;
font-size: 22px;
font-style: normal;
font-weight: 800;
line-height: 28px;
margin: 0;
text-transform: none;
}

.description {
color: #545f71;
font-size: 14px;
font-weight: 400;
line-height: 22px;
margin: 0;
}

.agentRail {
display: flex;
flex: 0 0 104px;
align-items: center;
justify-content: center;
padding: $sp-4;
background-color: $black-5;
border-left: 1px solid $black-10;

@include ltesm {
flex: 0 0 auto;
border-left: none;
border-top: 1px solid $black-10;
}
}

.iconBadge {
display: flex;
align-items: center;
justify-content: center;
padding: $sp-3;
border-radius: 50%;
background-color: $tc-white;
}

.icon {
width: 24px;
height: 24px;
color: $blue-110;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ComponentType, FC, SVGProps } from 'react'
import { Link } from 'react-router-dom'

import styles from './AssistantCard.module.scss'

interface AssistantCardProps {
title: string
description: string
icon: ComponentType<SVGProps<SVGSVGElement>>
to: string
}

const AssistantCard: FC<AssistantCardProps> = props => {
const Icon = props.icon

return (
<Link className={styles.wrap} to={props.to}>
<div className={styles.content}>
<h3 className={styles.title}>{props.title}</h3>
<p className={styles.description}>{props.description}</p>
</div>

<div className={styles.agentRail}>
<span className={styles.iconBadge}>
<Icon aria-hidden='true' className={styles.icon} />
</span>
</div>
</Link>
)
}

export default AssistantCard
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as AssistantCard } from './AssistantCard'
Loading
Loading