mirror of
https://github.com/magnum-opus-nn-cp/frontend.git
synced 2024-11-21 17:16:36 +03:00
feat: add nearest type
This commit is contained in:
parent
2ee63822cf
commit
fc6a3d8f9c
|
@ -1,9 +1,17 @@
|
|||
export type ScoreType = 'bert' | 'f';
|
||||
export type DetailFeatures = [answer: string, metric: number, texts: string[]];
|
||||
|
||||
export type DetailDescriptor = {
|
||||
text: string;
|
||||
features: DetailFeatures;
|
||||
};
|
||||
|
||||
export type ScoreType = 'bert' | 'f' | 'nearest';
|
||||
|
||||
export type ScoreDescriptor = {
|
||||
[key in ScoreType]: {
|
||||
text: string;
|
||||
answer: string;
|
||||
metric?: number;
|
||||
// detailed?: DetailDescriptor[];
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -15,7 +23,7 @@ export type TextDescriptor = {
|
|||
[key in ScoreType]?: {
|
||||
file?: string;
|
||||
pdf?: string;
|
||||
text: string;
|
||||
text: string | DetailDescriptor[];
|
||||
};
|
||||
}
|
||||
| null;
|
||||
|
|
|
@ -16,7 +16,6 @@ export interface ModalProps {
|
|||
|
||||
export const Modal = (props: ModalProps) => {
|
||||
const { className, children, isOpen, onClose, isClosing, preventWindowScroll } = props;
|
||||
|
||||
const nodeRef = useRef(null);
|
||||
usePreventWindowScroll(preventWindowScroll ?? isOpen);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useCallback, useEffect } from 'react';
|
|||
|
||||
export const usePreventWindowScroll = (isPrevented?: boolean) => {
|
||||
const preventScroll = useCallback((isPrevented: boolean) => {
|
||||
document.body.classList.toggle('scroll-prevented', isPrevented);
|
||||
document.documentElement.classList.toggle('scroll-prevented', isPrevented);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -69,7 +69,7 @@ export const ResponsePage: FC = () => {
|
|||
<th>Имя</th>
|
||||
<th>М. н-с.</th>
|
||||
<th>М. стат.</th>
|
||||
{/*<th>М. п.</th>*/}
|
||||
<th>М. c.</th>
|
||||
{/*<th>Рез.</th>*/}
|
||||
<th>Крат. сод.</th>
|
||||
</tr>
|
||||
|
@ -88,6 +88,10 @@ export const ResponsePage: FC = () => {
|
|||
{text.score.f.answer}
|
||||
{/*| <span style={{ color: getPercentageColor(0.99) }}>0.99</span>*/}
|
||||
</td>
|
||||
<td>
|
||||
{text.score.nearest.answer}
|
||||
{/*| <span style={{ color: getPercentageColor(0.99) }}>0.99</span>*/}
|
||||
</td>
|
||||
{/*<td>*/}
|
||||
{/* AA+ | <span style={{ color: getPercentageColor(0.95) }}>0.95</span>*/}
|
||||
{/*</td>*/}
|
||||
|
@ -107,34 +111,6 @@ export const ResponsePage: FC = () => {
|
|||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
{/*<tr>*/}
|
||||
{/* <td>1</td>*/}
|
||||
{/* <td>{EMDASH}</td>*/}
|
||||
{/* <td>*/}
|
||||
{/* AA+ | <span style={{ color: getPercentageColor(0.63) }}>0.63</span>*/}
|
||||
{/* </td>*/}
|
||||
{/* <td>*/}
|
||||
{/* AA+ | <span style={{ color: getPercentageColor(0.95) }}>0.95</span>*/}
|
||||
{/* </td>*/}
|
||||
{/* <td>*/}
|
||||
{/* AA+ | <span style={{ color: getPercentageColor(0.95) }}>0.95</span>*/}
|
||||
{/* </td>*/}
|
||||
{/* <td>*/}
|
||||
{/* AA+ | <span style={{ color: getPercentageColor(0.95) }}>0.95</span>*/}
|
||||
{/* </td>*/}
|
||||
{/* <td className={s.ResponsePage__tableSummary}>*/}
|
||||
{/* <Link*/}
|
||||
{/* component={'button'}*/}
|
||||
{/* standalone={false}*/}
|
||||
{/* onClick={(e) => {*/}
|
||||
{/* e.stopPropagation();*/}
|
||||
{/* setIsOpen(true);*/}
|
||||
{/* }}>*/}
|
||||
{/* Открыть*/}
|
||||
{/* </Link>*/}
|
||||
{/* </td>*/}
|
||||
{/*</tr>*/}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
|
|
|
@ -132,4 +132,22 @@
|
|||
.TextPage__loader {
|
||||
--loader-size: 96px;
|
||||
color: $color-background-secondary !important;
|
||||
}
|
||||
|
||||
.TextPage__modalBody {
|
||||
@include flex-col;
|
||||
line-height: $line-height-24;
|
||||
}
|
||||
|
||||
.TextPage__modalParagraph {
|
||||
margin-top: $spacing-small-3x;
|
||||
padding-top: $spacing-small-3x;
|
||||
border-top: 1px solid $color-divider-darker;
|
||||
}
|
||||
|
||||
:global {
|
||||
.detailedText {
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { FC, useEffect, useMemo, useRef } from 'react';
|
||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Heading, HeadingSize } from '../../components/Heading';
|
||||
import { useUrlParam } from '../../hooks/useUrlParam';
|
||||
|
@ -7,10 +7,13 @@ import { ETextVariants, Text } from '../../components/Text';
|
|||
import { Tooltip } from '../../components/Tooltip';
|
||||
import { Link } from '../../components/Link';
|
||||
import { useSingleTimeout } from '../../hooks/useSingleTimeout';
|
||||
import { ScoreType, useText } from '../../api/process';
|
||||
import { DetailDescriptor, ScoreType, useText } from '../../api/process';
|
||||
import { Loader } from '../../components/Loader';
|
||||
import { BACKEND_MEDIA_PORT, BACKEND_URL } from '../../config';
|
||||
import { getPercentageColor } from '../../utils/getPercentageColor';
|
||||
import { ModalBody, ModalContainer, useModal } from '../../components/Modal';
|
||||
import { getEntriesFromText } from './utils/getEntriesFromText';
|
||||
import { getTextFromDetailed } from './utils/getTextFromDetailed';
|
||||
import s from './TextPage.module.scss';
|
||||
|
||||
export type TextFields = {
|
||||
|
@ -31,6 +34,7 @@ export const TextPage: FC = () => {
|
|||
});
|
||||
|
||||
const scoreType = watch('type');
|
||||
const isDetailedScoreType = !(['bert', 'f'] as ScoreType[]).includes(scoreType);
|
||||
|
||||
const {
|
||||
data: textEntity,
|
||||
|
@ -42,13 +46,19 @@ export const TextPage: FC = () => {
|
|||
config: {
|
||||
enabled: !!textId,
|
||||
refetchInterval: (data) =>
|
||||
data?.description?.[scoreType]?.file && data?.description?.[scoreType]?.pdf ? false : TEXT_REFETCH_MS
|
||||
// !isDetailedScoreType &&
|
||||
!data?.description?.[scoreType]?.file || !data?.description?.[scoreType]?.pdf ? TEXT_REFETCH_MS : false
|
||||
}
|
||||
});
|
||||
|
||||
console.log(textEntity?.description?.nearest?.text);
|
||||
|
||||
const parsedText = useMemo(
|
||||
() => getEntriesFromText(textEntity?.description?.[scoreType]?.text || ''),
|
||||
[scoreType, textEntity]
|
||||
() =>
|
||||
isDetailedScoreType
|
||||
? getTextFromDetailed(textEntity?.description?.nearest?.text as DetailDescriptor[])
|
||||
: getEntriesFromText((textEntity?.description?.[scoreType]?.text as string) || ''),
|
||||
[isDetailedScoreType, scoreType, textEntity]
|
||||
);
|
||||
|
||||
const docxHref = textEntity?.description?.[scoreType]?.file
|
||||
|
@ -64,11 +74,26 @@ export const TextPage: FC = () => {
|
|||
const tipRef = useRef<HTMLDivElement>(null);
|
||||
const timeout = useSingleTimeout();
|
||||
|
||||
// Модалка
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [activeDetailedIndex, setActiveDetailedIndex] = useState<number>(-1);
|
||||
const onClose = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
}, []);
|
||||
const { modalIsVisible, isClosing, close } = useModal({ isOpen, onClose });
|
||||
const openDetailed = useCallback((index: number = -1) => {
|
||||
setActiveDetailedIndex(index);
|
||||
setIsOpen(true);
|
||||
}, []);
|
||||
const activeDetailed = (textEntity?.description?.nearest?.text as DetailDescriptor[])?.[activeDetailedIndex];
|
||||
|
||||
useEffect(() => {
|
||||
if (!textRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ------ Обработка хинтов ------
|
||||
|
||||
const resetTip = () => {
|
||||
if (tipRef.current) {
|
||||
tipRef.current.style.opacity = `0`;
|
||||
|
@ -84,7 +109,7 @@ export const TextPage: FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
textRef.current.querySelectorAll('span').forEach((item) => {
|
||||
textRef.current.querySelectorAll('.hintText').forEach((item) => {
|
||||
item.addEventListener('mouseover', () => {
|
||||
const rect = item.getBoundingClientRect();
|
||||
const value = Number(item.getAttribute('data-value'));
|
||||
|
@ -104,8 +129,18 @@ export const TextPage: FC = () => {
|
|||
|
||||
window.addEventListener('scroll', resetTip);
|
||||
window.addEventListener('touchmove', resetTip);
|
||||
|
||||
// ------ Обработка текста с похожими текстами ------
|
||||
|
||||
textRef.current.querySelectorAll('.detailedText').forEach((item) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
const index = Number(item.getAttribute('data-index')) ?? -1;
|
||||
openDetailed(index);
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [parsedText]);
|
||||
}, [parsedText, openDetailed]);
|
||||
|
||||
// ------ Обработка ошибки ------
|
||||
|
||||
|
@ -147,13 +182,21 @@ export const TextPage: FC = () => {
|
|||
{/*| Accuracy: <span style={{ color: getPercentageColor(0.71) }}>0.71</span>*/}
|
||||
</Text>
|
||||
|
||||
{/*<Text component={'div'} className={s.TextPage__prop} variant={ETextVariants.PROGRAMMING_CODE_REGULAR}>*/}
|
||||
{/* Результат по методу{' '}*/}
|
||||
{/* <Tooltip className={s.TextPage__tooltip} content={'Bert + Annoy'}>*/}
|
||||
{/* <span className={s.TextPage__underline}>похожести</span>*/}
|
||||
{/* </Tooltip>*/}
|
||||
{/* : АА+ | Accuracy: <span style={{ color: getPercentageColor(0.63) }}>0.63</span>*/}
|
||||
{/*</Text>*/}
|
||||
<Text component={'div'} className={s.TextPage__prop} variant={ETextVariants.PROGRAMMING_CODE_REGULAR}>
|
||||
Результат по методу{' '}
|
||||
<Tooltip className={s.TextPage__tooltip} content={'Bert + Annoy'}>
|
||||
<span className={s.TextPage__underline}>схожести</span>
|
||||
</Tooltip>
|
||||
: {textEntity.score.nearest.answer}
|
||||
{textEntity.score.nearest.metric && (
|
||||
<>
|
||||
| Точность:{' '}
|
||||
<span style={{ color: getPercentageColor(textEntity.score.nearest.metric / 100) }}>
|
||||
{(textEntity.score.nearest.metric / 100).toFixed(2)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className={s.TextPage__summary}>
|
||||
|
@ -177,7 +220,7 @@ export const TextPage: FC = () => {
|
|||
<select className={s.TextPage__select} {...register('type')}>
|
||||
<option value="bert">Нейросетевой</option>
|
||||
<option value="f">Статистический</option>
|
||||
{/*<option value="f">Схожести</option>*/}
|
||||
<option value="nearest">Схожести</option>
|
||||
</select>
|
||||
|
||||
<Link
|
||||
|
@ -199,9 +242,16 @@ export const TextPage: FC = () => {
|
|||
Скачать PDF
|
||||
</Link>
|
||||
|
||||
<div className={s.TextPage__fullText} dangerouslySetInnerHTML={{ __html: parsedText }} ref={textRef} />
|
||||
|
||||
<div className={s.TextPage__textTip} ref={tipRef} />
|
||||
{isDetailedScoreType ? (
|
||||
<>
|
||||
<div className={s.TextPage__fullText} dangerouslySetInnerHTML={{ __html: parsedText }} ref={textRef} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className={s.TextPage__fullText} dangerouslySetInnerHTML={{ __html: parsedText }} ref={textRef} />
|
||||
<div className={s.TextPage__textTip} ref={tipRef} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
@ -209,6 +259,29 @@ export const TextPage: FC = () => {
|
|||
<Loader className={s.TextPage__loader} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ModalContainer isOpen={modalIsVisible} onClose={close} isClosing={isClosing}>
|
||||
<ModalBody className={s.TextPage__modalBody}>
|
||||
{activeDetailed && (
|
||||
<>
|
||||
<p>
|
||||
<b>Рейтинг {activeDetailed.features[0]}</b>
|
||||
</p>
|
||||
<p>
|
||||
Точность{' '}
|
||||
<span style={{ color: getPercentageColor(activeDetailed.features[1] / 100) }}>
|
||||
{(activeDetailed.features[1] / 100).toFixed(2)}
|
||||
</span>
|
||||
</p>
|
||||
{activeDetailed.features[2].map((text, index) => (
|
||||
<p className={s.TextPage__modalParagraph} key={index}>
|
||||
{text}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
</ModalContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ export const getColorFromValue = (value: number) => {
|
|||
return `rgba(255, 255, 0, ${value > 0.1 ? value + 0.2 : 0})`;
|
||||
};
|
||||
|
||||
export const getEntriesFromText = (html: string) => {
|
||||
export const getEntriesFromText = (html: string = '') => {
|
||||
let copiedHtml = html;
|
||||
const matches = Array.from(html.matchAll(/<span[^>]+>(.*?)<\/span>/gi));
|
||||
|
||||
|
@ -13,7 +13,7 @@ export const getEntriesFromText = (html: string) => {
|
|||
|
||||
copiedHtml = copiedHtml.replace(
|
||||
match[0],
|
||||
`<span data-value="${value}" style="background-color: ${getColorFromValue(
|
||||
`<span class="hintText" data-value="${value}" style="background-color: ${getColorFromValue(
|
||||
value
|
||||
)}; /*padding: 0 2px; margin: 0 -2px;*/">${text}</span>`
|
||||
);
|
||||
|
|
14
src/pages/text/utils/getTextFromDetailed.ts
Normal file
14
src/pages/text/utils/getTextFromDetailed.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { DetailDescriptor } from '../../../api/process';
|
||||
import { getColorFromValue } from './getEntriesFromText';
|
||||
|
||||
export const getTextFromDetailed = (detailed: DetailDescriptor[] = []) => {
|
||||
let html = '';
|
||||
|
||||
detailed.forEach((item, index) => {
|
||||
const [_, metric] = item.features;
|
||||
const color = getColorFromValue(metric / 100);
|
||||
html += `<p class="detailedText" data-index="${index}" style="background-color: ${color};">${item.text}</p> `;
|
||||
});
|
||||
|
||||
return html;
|
||||
};
|
Loading…
Reference in New Issue
Block a user