mirror of
https://github.com/magnum-opus-nn-cp/frontend.git
synced 2024-11-22 01:26:43 +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 = {
|
export type ScoreDescriptor = {
|
||||||
[key in ScoreType]: {
|
[key in ScoreType]: {
|
||||||
text: string;
|
|
||||||
answer: string;
|
answer: string;
|
||||||
|
metric?: number;
|
||||||
|
// detailed?: DetailDescriptor[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,7 +23,7 @@ export type TextDescriptor = {
|
||||||
[key in ScoreType]?: {
|
[key in ScoreType]?: {
|
||||||
file?: string;
|
file?: string;
|
||||||
pdf?: string;
|
pdf?: string;
|
||||||
text: string;
|
text: string | DetailDescriptor[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| null;
|
| null;
|
||||||
|
|
|
@ -16,7 +16,6 @@ export interface ModalProps {
|
||||||
|
|
||||||
export const Modal = (props: ModalProps) => {
|
export const Modal = (props: ModalProps) => {
|
||||||
const { className, children, isOpen, onClose, isClosing, preventWindowScroll } = props;
|
const { className, children, isOpen, onClose, isClosing, preventWindowScroll } = props;
|
||||||
|
|
||||||
const nodeRef = useRef(null);
|
const nodeRef = useRef(null);
|
||||||
usePreventWindowScroll(preventWindowScroll ?? isOpen);
|
usePreventWindowScroll(preventWindowScroll ?? isOpen);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
export const usePreventWindowScroll = (isPrevented?: boolean) => {
|
export const usePreventWindowScroll = (isPrevented?: boolean) => {
|
||||||
const preventScroll = useCallback((isPrevented: boolean) => {
|
const preventScroll = useCallback((isPrevented: boolean) => {
|
||||||
document.body.classList.toggle('scroll-prevented', isPrevented);
|
document.documentElement.classList.toggle('scroll-prevented', isPrevented);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -69,7 +69,7 @@ export const ResponsePage: FC = () => {
|
||||||
<th>Имя</th>
|
<th>Имя</th>
|
||||||
<th>М. н-с.</th>
|
<th>М. н-с.</th>
|
||||||
<th>М. стат.</th>
|
<th>М. стат.</th>
|
||||||
{/*<th>М. п.</th>*/}
|
<th>М. c.</th>
|
||||||
{/*<th>Рез.</th>*/}
|
{/*<th>Рез.</th>*/}
|
||||||
<th>Крат. сод.</th>
|
<th>Крат. сод.</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -88,6 +88,10 @@ export const ResponsePage: FC = () => {
|
||||||
{text.score.f.answer}
|
{text.score.f.answer}
|
||||||
{/*| <span style={{ color: getPercentageColor(0.99) }}>0.99</span>*/}
|
{/*| <span style={{ color: getPercentageColor(0.99) }}>0.99</span>*/}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{text.score.nearest.answer}
|
||||||
|
{/*| <span style={{ color: getPercentageColor(0.99) }}>0.99</span>*/}
|
||||||
|
</td>
|
||||||
{/*<td>*/}
|
{/*<td>*/}
|
||||||
{/* AA+ | <span style={{ color: getPercentageColor(0.95) }}>0.95</span>*/}
|
{/* AA+ | <span style={{ color: getPercentageColor(0.95) }}>0.95</span>*/}
|
||||||
{/*</td>*/}
|
{/*</td>*/}
|
||||||
|
@ -107,34 +111,6 @@ export const ResponsePage: FC = () => {
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -132,4 +132,22 @@
|
||||||
.TextPage__loader {
|
.TextPage__loader {
|
||||||
--loader-size: 96px;
|
--loader-size: 96px;
|
||||||
color: $color-background-secondary !important;
|
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 { useForm } from 'react-hook-form';
|
||||||
import { Heading, HeadingSize } from '../../components/Heading';
|
import { Heading, HeadingSize } from '../../components/Heading';
|
||||||
import { useUrlParam } from '../../hooks/useUrlParam';
|
import { useUrlParam } from '../../hooks/useUrlParam';
|
||||||
|
@ -7,10 +7,13 @@ import { ETextVariants, Text } from '../../components/Text';
|
||||||
import { Tooltip } from '../../components/Tooltip';
|
import { Tooltip } from '../../components/Tooltip';
|
||||||
import { Link } from '../../components/Link';
|
import { Link } from '../../components/Link';
|
||||||
import { useSingleTimeout } from '../../hooks/useSingleTimeout';
|
import { useSingleTimeout } from '../../hooks/useSingleTimeout';
|
||||||
import { ScoreType, useText } from '../../api/process';
|
import { DetailDescriptor, ScoreType, useText } from '../../api/process';
|
||||||
import { Loader } from '../../components/Loader';
|
import { Loader } from '../../components/Loader';
|
||||||
import { BACKEND_MEDIA_PORT, BACKEND_URL } from '../../config';
|
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 { getEntriesFromText } from './utils/getEntriesFromText';
|
||||||
|
import { getTextFromDetailed } from './utils/getTextFromDetailed';
|
||||||
import s from './TextPage.module.scss';
|
import s from './TextPage.module.scss';
|
||||||
|
|
||||||
export type TextFields = {
|
export type TextFields = {
|
||||||
|
@ -31,6 +34,7 @@ export const TextPage: FC = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const scoreType = watch('type');
|
const scoreType = watch('type');
|
||||||
|
const isDetailedScoreType = !(['bert', 'f'] as ScoreType[]).includes(scoreType);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: textEntity,
|
data: textEntity,
|
||||||
|
@ -42,13 +46,19 @@ export const TextPage: FC = () => {
|
||||||
config: {
|
config: {
|
||||||
enabled: !!textId,
|
enabled: !!textId,
|
||||||
refetchInterval: (data) =>
|
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(
|
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
|
const docxHref = textEntity?.description?.[scoreType]?.file
|
||||||
|
@ -64,11 +74,26 @@ export const TextPage: FC = () => {
|
||||||
const tipRef = useRef<HTMLDivElement>(null);
|
const tipRef = useRef<HTMLDivElement>(null);
|
||||||
const timeout = useSingleTimeout();
|
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(() => {
|
useEffect(() => {
|
||||||
if (!textRef.current) {
|
if (!textRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------ Обработка хинтов ------
|
||||||
|
|
||||||
const resetTip = () => {
|
const resetTip = () => {
|
||||||
if (tipRef.current) {
|
if (tipRef.current) {
|
||||||
tipRef.current.style.opacity = `0`;
|
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', () => {
|
item.addEventListener('mouseover', () => {
|
||||||
const rect = item.getBoundingClientRect();
|
const rect = item.getBoundingClientRect();
|
||||||
const value = Number(item.getAttribute('data-value'));
|
const value = Number(item.getAttribute('data-value'));
|
||||||
|
@ -104,8 +129,18 @@ export const TextPage: FC = () => {
|
||||||
|
|
||||||
window.addEventListener('scroll', resetTip);
|
window.addEventListener('scroll', resetTip);
|
||||||
window.addEventListener('touchmove', 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
|
// 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>*/}
|
{/*| Accuracy: <span style={{ color: getPercentageColor(0.71) }}>0.71</span>*/}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/*<Text component={'div'} className={s.TextPage__prop} variant={ETextVariants.PROGRAMMING_CODE_REGULAR}>*/}
|
<Text component={'div'} className={s.TextPage__prop} variant={ETextVariants.PROGRAMMING_CODE_REGULAR}>
|
||||||
{/* Результат по методу{' '}*/}
|
Результат по методу{' '}
|
||||||
{/* <Tooltip className={s.TextPage__tooltip} content={'Bert + Annoy'}>*/}
|
<Tooltip className={s.TextPage__tooltip} content={'Bert + Annoy'}>
|
||||||
{/* <span className={s.TextPage__underline}>похожести</span>*/}
|
<span className={s.TextPage__underline}>схожести</span>
|
||||||
{/* </Tooltip>*/}
|
</Tooltip>
|
||||||
{/* : АА+ | Accuracy: <span style={{ color: getPercentageColor(0.63) }}>0.63</span>*/}
|
: {textEntity.score.nearest.answer}
|
||||||
{/*</Text>*/}
|
{textEntity.score.nearest.metric && (
|
||||||
|
<>
|
||||||
|
| Точность:{' '}
|
||||||
|
<span style={{ color: getPercentageColor(textEntity.score.nearest.metric / 100) }}>
|
||||||
|
{(textEntity.score.nearest.metric / 100).toFixed(2)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={s.TextPage__summary}>
|
<div className={s.TextPage__summary}>
|
||||||
|
@ -177,7 +220,7 @@ export const TextPage: FC = () => {
|
||||||
<select className={s.TextPage__select} {...register('type')}>
|
<select className={s.TextPage__select} {...register('type')}>
|
||||||
<option value="bert">Нейросетевой</option>
|
<option value="bert">Нейросетевой</option>
|
||||||
<option value="f">Статистический</option>
|
<option value="f">Статистический</option>
|
||||||
{/*<option value="f">Схожести</option>*/}
|
<option value="nearest">Схожести</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
|
@ -199,9 +242,16 @@ export const TextPage: FC = () => {
|
||||||
Скачать PDF
|
Скачать PDF
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className={s.TextPage__fullText} dangerouslySetInnerHTML={{ __html: parsedText }} ref={textRef} />
|
{isDetailedScoreType ? (
|
||||||
|
<>
|
||||||
<div className={s.TextPage__textTip} ref={tipRef} />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -209,6 +259,29 @@ export const TextPage: FC = () => {
|
||||||
<Loader className={s.TextPage__loader} />
|
<Loader className={s.TextPage__loader} />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@ export const getColorFromValue = (value: number) => {
|
||||||
return `rgba(255, 255, 0, ${value > 0.1 ? value + 0.2 : 0})`;
|
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;
|
let copiedHtml = html;
|
||||||
const matches = Array.from(html.matchAll(/<span[^>]+>(.*?)<\/span>/gi));
|
const matches = Array.from(html.matchAll(/<span[^>]+>(.*?)<\/span>/gi));
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ export const getEntriesFromText = (html: string) => {
|
||||||
|
|
||||||
copiedHtml = copiedHtml.replace(
|
copiedHtml = copiedHtml.replace(
|
||||||
match[0],
|
match[0],
|
||||||
`<span data-value="${value}" style="background-color: ${getColorFromValue(
|
`<span class="hintText" data-value="${value}" style="background-color: ${getColorFromValue(
|
||||||
value
|
value
|
||||||
)}; /*padding: 0 2px; margin: 0 -2px;*/">${text}</span>`
|
)}; /*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