From 386af7dc8826ec95fa6f348f366e6655b37fa4d5 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 5 Nov 2025 12:41:59 +0200 Subject: [PATCH 1/3] PM-2135 - AI Workflows - scorecard header --- .../review/src/lib/assets/icons/deepseek.svg | 3 + .../src/lib/assets/icons/icon-clock.svg | 3 + .../src/lib/assets/icons/icon-premium.svg | 3 + src/apps/review/src/lib/assets/icons/index.ts | 6 + .../src/lib/hooks/useFetchAiWorkflowRuns.ts | 11 ++ .../AiModelModal/AiModelModal.module.scss | 49 ++++++++ .../components/AiModelModal/AiModelModal.tsx | 42 +++++++ .../components/AiModelModal/index.ts | 1 + .../ScorecardHeader.module.scss | 110 ++++++++++++++++++ .../ScorecardHeader/ScorecardHeader.tsx | 71 ++++++++++- 10 files changed, 296 insertions(+), 3 deletions(-) create mode 100644 src/apps/review/src/lib/assets/icons/deepseek.svg create mode 100644 src/apps/review/src/lib/assets/icons/icon-clock.svg create mode 100644 src/apps/review/src/lib/assets/icons/icon-premium.svg create mode 100644 src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.module.scss create mode 100644 src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.tsx create mode 100644 src/apps/review/src/pages/ai-scorecards/components/AiModelModal/index.ts diff --git a/src/apps/review/src/lib/assets/icons/deepseek.svg b/src/apps/review/src/lib/assets/icons/deepseek.svg new file mode 100644 index 000000000..e54d70391 --- /dev/null +++ b/src/apps/review/src/lib/assets/icons/deepseek.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/review/src/lib/assets/icons/icon-clock.svg b/src/apps/review/src/lib/assets/icons/icon-clock.svg new file mode 100644 index 000000000..bc8dd3a99 --- /dev/null +++ b/src/apps/review/src/lib/assets/icons/icon-clock.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/review/src/lib/assets/icons/icon-premium.svg b/src/apps/review/src/lib/assets/icons/icon-premium.svg new file mode 100644 index 000000000..afa0cf4d4 --- /dev/null +++ b/src/apps/review/src/lib/assets/icons/icon-premium.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/review/src/lib/assets/icons/index.ts b/src/apps/review/src/lib/assets/icons/index.ts index 7ec1bf70b..80589e44d 100644 --- a/src/apps/review/src/lib/assets/icons/index.ts +++ b/src/apps/review/src/lib/assets/icons/index.ts @@ -9,6 +9,9 @@ import { ReactComponent as IconReview } from './icon-phase-review.svg' import { ReactComponent as IconAppeal } from './icon-phase-appeal.svg' import { ReactComponent as IconAppealResponse } from './icon-phase-appeal-response.svg' import { ReactComponent as IconPhaseWinners } from './icon-phase-winners.svg' +import { ReactComponent as IconDeepseekAi } from './deepseek.svg' +import { ReactComponent as IconClock } from './icon-clock.svg' +import { ReactComponent as IconPremium } from './icon-premium.svg' export * from './editor/bold' export * from './editor/code' @@ -37,6 +40,9 @@ export { IconAppeal, IconAppealResponse, IconPhaseWinners, + IconDeepseekAi, + IconClock, + IconPremium, } export const phasesIcons = { diff --git a/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts b/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts index 1e3a69717..8787d55da 100644 --- a/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts +++ b/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts @@ -25,10 +25,21 @@ export interface AiWorkflow { name: string; description: string; scorecard?: Scorecard + defUrl: string + llm: { + name: string + description: string + icon: string + url: string + provider: { + name: string + } + } } export interface AiWorkflowRun { id: string; + startedAt: string; completedAt: string; status: AiWorkflowRunStatusEnum; score: number; diff --git a/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.module.scss b/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.module.scss new file mode 100644 index 000000000..fbedc3b5b --- /dev/null +++ b/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.module.scss @@ -0,0 +1,49 @@ +@import '@libs/ui/styles/includes'; + +.modelNameWrap { + display: flex; + align-items: center; + gap: $sp-4; +} + +.modelIcon { + width: 60px; + height: 60px; + border-radius: $sp-1; + border: 1px solid #A8A8A8; + padding: $sp-1; + + align-items: center; + display: flex; + justify-content: center; + + flex: 0 0 auto; +} + +.modelName { + display: flex; + align-items: center; + gap: $sp-3; + + h3 { + font-family: "Figtree", sans-serif; + font-size: 26px; + font-weight: 700; + line-height: 30px; + color: #0A0A0A; + } + + svg { + display: block; + width: 16px; + height: 16px; + } +} + +.modelDescription { + margin-top: $sp-6; + font-family: "Nunito Sans", sans-serif; + font-size: 14px; + line-height: 20px; + color: #0A0A0A; +} diff --git a/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.tsx b/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.tsx new file mode 100644 index 000000000..2389e0ad2 --- /dev/null +++ b/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.tsx @@ -0,0 +1,42 @@ +import { FC } from 'react' + +import { BaseModal } from '~/libs/ui' +import { AiWorkflow } from '~/apps/review/src/lib/hooks' +import { IconExternalLink } from '~/apps/review/src/lib/assets/icons' + +import styles from './AiModelModal.module.scss' + +interface AiModelModalProps { + model: AiWorkflow['llm'] + onClose: () => void +} + +const AiModelModal: FC = props => ( + +
+
+
+ {props.model.name} +
+
+

{props.model.name}

+ + + +
+
+ +

+ {props.model.description} +

+
+
+) + +export default AiModelModal diff --git a/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/index.ts b/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/index.ts new file mode 100644 index 000000000..948754b83 --- /dev/null +++ b/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/index.ts @@ -0,0 +1 @@ +export { default as AiModelModal } from './AiModelModal' diff --git a/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.module.scss b/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.module.scss index e69de29bb..77dd052ed 100644 --- a/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.module.scss +++ b/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.module.scss @@ -0,0 +1,110 @@ +@import '@libs/ui/styles/includes'; + +.wrap { + width: 100%; +} + +.headerWrap { + display: flex; + align-items: flex-start; +} + +.workflowInfo { + display: flex; + align-items: flex-start; + gap: $sp-4; +} + +.workflowIcon { + width: 60px; + height: 60px; + border-radius: $sp-1; + border: 1px solid #A8A8A8; + padding: $sp-1; + + align-items: center; + display: flex; + justify-content: center; + + flex: 0 0 auto; +} + +.workflowName { + display: flex; + flex-direction: column; + gap: $sp-2; + + h3 { + font-family: "Figtree", sans-serif; + font-size: 26px; + font-weight: 700; + line-height: 30px; + color: #0A0A0A; + } + + span { + color: $link-blue-dark; + font-family: "Nunito Sans", sans-serif; + font-weight: bold; + font-size: 16px; + line-height: 22px; + } +} + +.workflowRunStats { + margin-left: auto; + display: flex; + flex-direction: column; + gap: $sp-1; + + flex: 0 0 auto; + + > span { + display: flex; + align-items: center; + gap: $sp-2; + + font-family: "Nunito Sans", sans-serif; + font-size: 14px; + line-height: 19px; + color: var(--GrayFontColor); + } + + strong { + font-family: "Nunito Sans", sans-serif; + font-size: 14px; + font-weight: 700; + line-height: 19px; + color: var(--FontColor); + } +} + +.workflowDescription { + margin-top: $sp-6; + font-family: "Nunito Sans", sans-serif; + font-size: 14px; + line-height: 20px; + color: #0A0A0A; +} + +.workflowFileLink { + margin-top: $sp-4; + a { + display: flex; + align-items: center; + gap: $sp-1; + color: $link-blue-dark; + font-family: "Nunito Sans", sans-serif; + font-size: 14px; + line-height: 20px; + + svg { + width: 12px; + height: 12px; + path { + fill: $link-blue-dark; + } + } + } + +} diff --git a/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx b/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx index dc95ef666..e8066582e 100644 --- a/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx +++ b/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx @@ -1,17 +1,82 @@ -import { FC } from 'react' +import { FC, useCallback, useMemo, useState } from 'react' +import moment, { Duration } from 'moment' import { AiScorecardContextModel } from '~/apps/review/src/lib/models' import { useAiScorecardContext } from '../../AiScorecardContext' +import { IconClock, IconDeepseekAi, IconPremium } from '../../../../lib/assets/icons' +import { AiModelModal } from '../AiModelModal' import styles from './ScorecardHeader.module.scss' +const formatDuration = (duration: Duration): string => [ + !!duration.hours() && `${duration.hours()}h`, + !!duration.minutes() && `${duration.minutes()}m`, + !!duration.seconds() && `${duration.seconds()}s`, +].filter(Boolean) + .join(' ') + const ScorecardHeader: FC = () => { - const { workflow }: AiScorecardContextModel = useAiScorecardContext() + const { workflow, workflowRun }: AiScorecardContextModel = useAiScorecardContext() + const runDuration = useMemo(() => ( + workflowRun && moment.duration( + +new Date(workflowRun.completedAt) - +new Date(workflowRun.startedAt), + 'milliseconds', + ) + ), [workflowRun]) + const [modelDetailsModalVisible, setModelDetailsModalVisible] = useState(false) + + const toggleModelDetails = useCallback(() => { + setModelDetailsModalVisible(wasVisible => !wasVisible) + }, []) + + if (!workflow || !workflowRun) { + return <> + } return (
- {workflow?.name} +
+
+ +
+

{workflow.name}

+ {workflow.llm.name} +
+
+
+ + + + Minimum passing score: + {' '} + {workflow.scorecard?.minimumPassingScore.toFixed(2)} + + + + + + Duration: + {' '} + {!!runDuration && formatDuration(runDuration)} + + +
+
+

+ {workflow.description} +

+ {/*
+ + Workflow File + {' '} + + +
*/} + + {modelDetailsModalVisible && ( + + )}
) } From 6251eaefac9fbadabd77aec2e952129daa6087c4 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 5 Nov 2025 12:52:17 +0200 Subject: [PATCH 2/3] PM-2135 - mobile ui --- .../AiModelModal/AiModelModal.module.scss | 17 ++++++++++ .../ScorecardHeader.module.scss | 31 ++++++++++++++++--- .../ScorecardHeader/ScorecardHeader.tsx | 2 +- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.module.scss b/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.module.scss index fbedc3b5b..3f0514beb 100644 --- a/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.module.scss +++ b/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.module.scss @@ -1,9 +1,19 @@ @import '@libs/ui/styles/includes'; +.wrap { + @include ltemd { + padding-top: $sp-15; + } +} + .modelNameWrap { display: flex; align-items: center; gap: $sp-4; + @include ltemd { + flex-direction: column; + gap: $sp-4; + } } .modelIcon { @@ -38,6 +48,13 @@ width: 16px; height: 16px; } + + @include ltemd { + h3 { + font-size: 22px; + line-height: 26px; + } + } } .modelDescription { diff --git a/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.module.scss b/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.module.scss index 77dd052ed..b84af1272 100644 --- a/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.module.scss +++ b/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.module.scss @@ -2,11 +2,18 @@ .wrap { width: 100%; + color: #0A0A0A; } .headerWrap { display: flex; align-items: flex-start; + + @include ltemd { + flex-direction: column; + align-items: stretch; + gap: $sp-6; + } } .workflowInfo { @@ -27,19 +34,20 @@ justify-content: center; flex: 0 0 auto; + @include ltemd { + width: 56px; + height: 56px; + } } .workflowName { - display: flex; - flex-direction: column; - gap: $sp-2; - h3 { font-family: "Figtree", sans-serif; font-size: 26px; font-weight: 700; line-height: 30px; color: #0A0A0A; + margin-bottom: $sp-2; } span { @@ -49,6 +57,17 @@ font-size: 16px; line-height: 22px; } + + .modelName { + cursor: pointer; + } + + @include ltemd { + h3 { + font-size: 22px; + line-height: 26px; + } + } } .workflowRunStats { @@ -77,6 +96,10 @@ line-height: 19px; color: var(--FontColor); } + + @include ltemd { + margin-left: 0; + } } .workflowDescription { diff --git a/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx b/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx index e8066582e..5977e71fa 100644 --- a/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx +++ b/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx @@ -41,7 +41,7 @@ const ScorecardHeader: FC = () => {

{workflow.name}

- {workflow.llm.name} + {workflow.llm.name}
From e1dc7fd5918954f810dca0ede029d43cf8ef92bd Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 5 Nov 2025 13:24:04 +0200 Subject: [PATCH 3/3] PM-2135 - fallback for ai model icon --- .../ai-scorecards/components/AiModelIcon.tsx | 27 +++++++++++++++++++ .../components/AiModelModal/AiModelModal.tsx | 6 +++-- .../ScorecardHeader/ScorecardHeader.tsx | 9 ++++--- 3 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 src/apps/review/src/pages/ai-scorecards/components/AiModelIcon.tsx diff --git a/src/apps/review/src/pages/ai-scorecards/components/AiModelIcon.tsx b/src/apps/review/src/pages/ai-scorecards/components/AiModelIcon.tsx new file mode 100644 index 000000000..9d296780d --- /dev/null +++ b/src/apps/review/src/pages/ai-scorecards/components/AiModelIcon.tsx @@ -0,0 +1,27 @@ +import { FC, useCallback, useRef } from 'react' + +import iconDeepseekAi from '~/apps/review/src/lib/assets/icons/deepseek.svg' + +import { AiWorkflow } from '../../../lib/hooks' + +interface AiModelIconProps { + model: AiWorkflow['llm'] +} + +const AiModelIcon: FC = props => { + const llmIconImgRef = useRef(null) + + const handleError = useCallback(() => { + if (!llmIconImgRef.current) { + return + } + + llmIconImgRef.current.src = iconDeepseekAi + }, []) + + return ( + {props.model.name} + ) +} + +export default AiModelIcon diff --git a/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.tsx b/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.tsx index 2389e0ad2..8aa2e02ae 100644 --- a/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.tsx +++ b/src/apps/review/src/pages/ai-scorecards/components/AiModelModal/AiModelModal.tsx @@ -4,6 +4,8 @@ import { BaseModal } from '~/libs/ui' import { AiWorkflow } from '~/apps/review/src/lib/hooks' import { IconExternalLink } from '~/apps/review/src/lib/assets/icons' +import AiModelIcon from '../AiModelIcon' + import styles from './AiModelModal.module.scss' interface AiModelModalProps { @@ -22,11 +24,11 @@ const AiModelModal: FC = props => (
- {props.model.name} +

{props.model.name}

- +
diff --git a/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx b/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx index 5977e71fa..442ee1232 100644 --- a/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx +++ b/src/apps/review/src/pages/ai-scorecards/components/ScorecardHeader/ScorecardHeader.tsx @@ -4,8 +4,9 @@ import moment, { Duration } from 'moment' import { AiScorecardContextModel } from '~/apps/review/src/lib/models' import { useAiScorecardContext } from '../../AiScorecardContext' -import { IconClock, IconDeepseekAi, IconPremium } from '../../../../lib/assets/icons' +import { IconClock, IconPremium } from '../../../../lib/assets/icons' import { AiModelModal } from '../AiModelModal' +import AiModelIcon from '../AiModelIcon' import styles from './ScorecardHeader.module.scss' @@ -19,7 +20,7 @@ const formatDuration = (duration: Duration): string => [ const ScorecardHeader: FC = () => { const { workflow, workflowRun }: AiScorecardContextModel = useAiScorecardContext() const runDuration = useMemo(() => ( - workflowRun && moment.duration( + workflowRun && workflowRun.completedAt && workflowRun.startedAt && moment.duration( +new Date(workflowRun.completedAt) - +new Date(workflowRun.startedAt), 'milliseconds', ) @@ -38,7 +39,9 @@ const ScorecardHeader: FC = () => {
- +
+ +

{workflow.name}

{workflow.llm.name}