dify
This commit is contained in:
18
dify/web/app/components/datasets/create/step-two/escape.ts
Normal file
18
dify/web/app/components/datasets/create/step-two/escape.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
function escape(input: string): string {
|
||||
if (!input || typeof input !== 'string')
|
||||
return ''
|
||||
|
||||
const res = input
|
||||
// .replaceAll('\\', '\\\\') // This would add too many backslashes
|
||||
.replaceAll('\0', '\\0')
|
||||
.replaceAll('\b', '\\b')
|
||||
.replaceAll('\f', '\\f')
|
||||
.replaceAll('\n', '\\n')
|
||||
.replaceAll('\r', '\\r')
|
||||
.replaceAll('\t', '\\t')
|
||||
.replaceAll('\v', '\\v')
|
||||
.replaceAll('\'', '\\\'')
|
||||
return res
|
||||
}
|
||||
|
||||
export default escape
|
||||
@@ -0,0 +1,413 @@
|
||||
.pageHeader {
|
||||
@apply px-16 flex justify-between items-center;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-top: 42px;
|
||||
padding-bottom: 12px;
|
||||
background-color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
color: #101828;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.segmentationItem {
|
||||
min-height: 68px;
|
||||
}
|
||||
|
||||
.indexItem {
|
||||
min-height: 126px;
|
||||
}
|
||||
|
||||
.indexItem .disableMask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 12px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.indexItem .warningTip {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 8px 20px 8px 40px;
|
||||
background: #FFFAEB;
|
||||
border-top: 0.5px solid #FEF0C7;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #344054;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.indexItem .warningTip::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
left: 20px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: center no-repeat url(../assets/alert-triangle.svg);
|
||||
background-size: 12px;
|
||||
}
|
||||
|
||||
.indexItem .warningTip .click {
|
||||
color: #155EEF;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.indexItem.disabled:hover {
|
||||
background-color: #fcfcfd;
|
||||
border-color: #f2f4f7;
|
||||
box-shadow: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.indexItem.disabled:hover .radio {
|
||||
@apply w-4 h-4 border-[2px] border-gray-200 rounded-full;
|
||||
}
|
||||
|
||||
.radioItem {
|
||||
@apply relative mb-2 rounded-xl border border-components-option-card-option-border cursor-pointer bg-components-option-card-option-bg;
|
||||
}
|
||||
|
||||
.radioItem.segmentationItem.custom {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.radioItem.segmentationItem.custom .typeHeader {
|
||||
/* height: 65px; */
|
||||
}
|
||||
|
||||
.radioItem.indexItem .typeHeader {
|
||||
@apply py-4 pr-5;
|
||||
}
|
||||
|
||||
.radioItem.indexItem.active .typeHeader {
|
||||
padding: 15.5px 19.5px 15.5px 63.5px;
|
||||
}
|
||||
|
||||
.radioItem.indexItem .radio {
|
||||
top: 16px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.radioItem.indexItem.active .radio {
|
||||
top: 16px;
|
||||
right: 19.5px;
|
||||
}
|
||||
|
||||
.radioItem.indexItem .typeHeader .title {
|
||||
@apply pb-1;
|
||||
}
|
||||
|
||||
.radioItem .typeIcon {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
left: 20px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: #EEF4FF center no-repeat;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.typeIcon.auto {
|
||||
background-color: #F5F3FF;
|
||||
background-image: url(../assets/zap-fast.svg);
|
||||
}
|
||||
|
||||
.typeIcon.customize {
|
||||
background-image: url(../assets/sliders-02.svg);
|
||||
}
|
||||
|
||||
.typeIcon.qualified {
|
||||
background-color: #FFF6ED;
|
||||
background-image: url(../assets/star-07.svg);
|
||||
}
|
||||
|
||||
.typeIcon.economical {
|
||||
background-image: url(../assets/piggy-bank-mod.svg);
|
||||
}
|
||||
|
||||
.radioItem .radio {
|
||||
@apply w-4 h-4 border-[2px] border-gray-200 rounded-full;
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.radioItem:hover {
|
||||
background-color: #ffffff;
|
||||
border-color: #B2CCFF;
|
||||
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
||||
}
|
||||
|
||||
.radioItem:hover .radio {
|
||||
border-color: #155eef;
|
||||
}
|
||||
|
||||
.radioItem.active {
|
||||
border-width: 1.5px;
|
||||
border-color: #528BFF;
|
||||
box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
|
||||
}
|
||||
|
||||
.radioItem.active .radio {
|
||||
top: 25.5px;
|
||||
right: 19.5px;
|
||||
border-width: 5px;
|
||||
border-color: #155EEF;
|
||||
}
|
||||
|
||||
.radioItem.active:hover {
|
||||
border-width: 1.5px;
|
||||
border-color: #528BFF;
|
||||
box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
|
||||
}
|
||||
|
||||
.radioItem.active .typeIcon {
|
||||
top: 17.5px;
|
||||
left: 19.5px;
|
||||
}
|
||||
|
||||
.radioItem.active .typeHeader {
|
||||
padding: 11.5px 63.5px;
|
||||
}
|
||||
|
||||
.typeHeader {
|
||||
@apply flex flex-col px-16 py-3 justify-center;
|
||||
}
|
||||
|
||||
.typeHeader .title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 2px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.typeHeader .tip {
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.recommendTag {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 6px;
|
||||
margin-left: 4px;
|
||||
border: 1px solid #E0EAFF;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #444CE7;
|
||||
}
|
||||
|
||||
.typeFormBody {
|
||||
@apply px-16;
|
||||
border-top: 1px solid #F2F4F7;
|
||||
}
|
||||
|
||||
.formRow {
|
||||
@apply flex justify-between mt-6;
|
||||
}
|
||||
|
||||
.formRow .label {
|
||||
@apply mb-2 p-0;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.ruleItem {
|
||||
@apply flex items-center py-1.5;
|
||||
}
|
||||
|
||||
.formFooter {
|
||||
padding: 16px 0 28px;
|
||||
}
|
||||
|
||||
.formFooter .button {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply inline-flex h-9 w-full py-1 px-2 pr-14 rounded-lg text-xs leading-normal;
|
||||
@apply bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none focus:bg-white placeholder:text-gray-400;
|
||||
}
|
||||
|
||||
.source {
|
||||
@apply flex justify-between items-center mt-8 px-6 py-4 rounded-xl bg-gray-50 border border-gray-100;
|
||||
}
|
||||
|
||||
.source .divider {
|
||||
@apply shrink-0 mx-4 w-px bg-gray-200;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
.fileIcon {
|
||||
@apply inline-flex mr-1 w-6 h-6 bg-center bg-no-repeat;
|
||||
background-image: url(../assets/pdf.svg);
|
||||
background-size: 24px;
|
||||
}
|
||||
|
||||
.fileIcon.pdf {
|
||||
background-image: url(../assets/pdf.svg);
|
||||
}
|
||||
|
||||
.fileIcon.csv {
|
||||
background-image: url(../assets/csv.svg);
|
||||
}
|
||||
|
||||
.fileIcon.doc {
|
||||
background-image: url(../assets/doc.svg);
|
||||
}
|
||||
|
||||
.fileIcon.docx {
|
||||
background-image: url(../assets/docx.svg);
|
||||
}
|
||||
|
||||
.fileIcon.xlsx,
|
||||
.fileIcon.xls {
|
||||
background-image: url(../assets/xlsx.svg);
|
||||
}
|
||||
|
||||
.fileIcon.html,
|
||||
.fileIcon.htm {
|
||||
background-image: url(../assets/html.svg);
|
||||
}
|
||||
|
||||
.fileIcon.md,
|
||||
.fileIcon.markdown {
|
||||
background-image: url(../assets/md.svg);
|
||||
}
|
||||
|
||||
.fileIcon.txt {
|
||||
background-image: url(../assets/txt.svg);
|
||||
}
|
||||
|
||||
.fileIcon.json {
|
||||
background-image: url(../assets/json.svg);
|
||||
}
|
||||
|
||||
.sourceContent {
|
||||
width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.sourceCount {
|
||||
@apply shrink-0 ml-1;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.segmentCount {
|
||||
flex: 1 1 30%;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
@apply mx-3 w-px h-4 bg-gray-200;
|
||||
}
|
||||
|
||||
.calculating {
|
||||
color: #98A2B3;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.sideTip {
|
||||
@apply flex flex-col items-center shrink-0;
|
||||
padding-top: 108px;
|
||||
width: 524px;
|
||||
border-left: 0.5px solid #F2F4F7;
|
||||
}
|
||||
|
||||
.tipCard {
|
||||
@apply flex flex-col items-start p-6;
|
||||
width: 320px;
|
||||
background-color: #F9FAFB;
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.tipCard .icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #EAECF0;
|
||||
border-radius: 6px;
|
||||
background: center no-repeat url(../assets/book-open-01.svg);
|
||||
background-size: 16px;
|
||||
}
|
||||
|
||||
.tipCard .title {
|
||||
margin: 12px 0;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #344054;
|
||||
}
|
||||
|
||||
.tipCard .content {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #344054;
|
||||
}
|
||||
|
||||
.previewWrap {
|
||||
flex-shrink: 0;
|
||||
width: 524px;
|
||||
}
|
||||
|
||||
.previewWrap.isMobile {
|
||||
max-width: 524px;
|
||||
}
|
||||
|
||||
/*
|
||||
* `fixed` must under `previewHeader` because of style override would not work
|
||||
*/
|
||||
.fixed {
|
||||
padding-top: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-bottom: 0.5px solid #EAECF0;
|
||||
backdrop-filter: blur(4px);
|
||||
animation: fix 0.5s;
|
||||
}
|
||||
|
||||
@keyframes fix {
|
||||
from {
|
||||
padding-top: 42px;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
to {
|
||||
padding-top: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
1206
dify/web/app/components/datasets/create/step-two/index.tsx
Normal file
1206
dify/web/app/components/datasets/create/step-two/index.tsx
Normal file
File diff suppressed because it is too large
Load Diff
79
dify/web/app/components/datasets/create/step-two/inputs.tsx
Normal file
79
dify/web/app/components/datasets/create/step-two/inputs.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { FC, PropsWithChildren, ReactNode } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { InputProps } from '@/app/components/base/input'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import type { InputNumberProps } from '@/app/components/base/input-number'
|
||||
import { InputNumber } from '@/app/components/base/input-number'
|
||||
|
||||
const TextLabel: FC<PropsWithChildren> = (props) => {
|
||||
return <label className='text-xs font-semibold leading-none text-text-secondary'>{props.children}</label>
|
||||
}
|
||||
|
||||
const FormField: FC<PropsWithChildren<{ label: ReactNode }>> = (props) => {
|
||||
return <div className='flex-1 space-y-2'>
|
||||
<TextLabel>{props.label}</TextLabel>
|
||||
{props.children}
|
||||
</div>
|
||||
}
|
||||
|
||||
export const DelimiterInput: FC<InputProps & { tooltip?: string }> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
return <FormField label={<div className='mb-1 flex items-center'>
|
||||
<span className='system-sm-semibold mr-0.5'>{t('datasetCreation.stepTwo.separator')}</span>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='max-w-[200px]'>
|
||||
{props.tooltip || t('datasetCreation.stepTwo.separatorTip')}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>}>
|
||||
<Input
|
||||
type="text"
|
||||
className='h-9'
|
||||
placeholder={t('datasetCreation.stepTwo.separatorPlaceholder')!}
|
||||
{...props}
|
||||
/>
|
||||
</FormField>
|
||||
}
|
||||
|
||||
export const MaxLengthInput: FC<InputNumberProps> = (props) => {
|
||||
const maxValue = Number.parseInt(globalThis.document?.body?.getAttribute('data-public-indexing-max-segmentation-tokens-length') || '4000', 10)
|
||||
|
||||
const { t } = useTranslation()
|
||||
return <FormField label={<div className='system-sm-semibold mb-1'>
|
||||
{t('datasetCreation.stepTwo.maxLength')}
|
||||
</div>}>
|
||||
<InputNumber
|
||||
type="number"
|
||||
size='large'
|
||||
placeholder={`≤ ${maxValue}`}
|
||||
max={maxValue}
|
||||
min={1}
|
||||
{...props}
|
||||
/>
|
||||
</FormField>
|
||||
}
|
||||
|
||||
export const OverlapInput: FC<InputNumberProps> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
return <FormField label={<div className='mb-1 flex items-center'>
|
||||
<span className='system-sm-semibold'>{t('datasetCreation.stepTwo.overlap')}</span>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='max-w-[200px]'>
|
||||
{t('datasetCreation.stepTwo.overlapTip')}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>}>
|
||||
<InputNumber
|
||||
type="number"
|
||||
size='large'
|
||||
placeholder={t('datasetCreation.stepTwo.overlap') || ''}
|
||||
min={1}
|
||||
{...props}
|
||||
/>
|
||||
</FormField>
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import Popover from '@/app/components/base/popover'
|
||||
import { languages } from '@/i18n-config/language'
|
||||
|
||||
export type ILanguageSelectProps = {
|
||||
currentLanguage: string
|
||||
onSelect: (language: string) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const LanguageSelect: FC<ILanguageSelectProps> = ({
|
||||
currentLanguage,
|
||||
onSelect,
|
||||
disabled,
|
||||
}) => {
|
||||
return (
|
||||
<Popover
|
||||
manualClose
|
||||
trigger='click'
|
||||
disabled={disabled}
|
||||
popupClassName='z-20'
|
||||
htmlContent={
|
||||
<div className='w-full p-1'>
|
||||
{languages.filter(language => language.supported).map(({ prompt_name }) => (
|
||||
<div
|
||||
key={prompt_name}
|
||||
className='inline-flex w-full cursor-pointer items-center justify-between rounded-lg px-3 py-2 hover:bg-state-base-hover'
|
||||
onClick={() => onSelect(prompt_name)}
|
||||
>
|
||||
<span className='system-sm-medium text-text-secondary'>{prompt_name}</span>
|
||||
{(currentLanguage === prompt_name) && <RiCheckLine className='size-4 text-text-accent' />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
btnElement={
|
||||
<div className={cn('inline-flex items-center gap-x-[1px]', disabled && 'cursor-not-allowed')}>
|
||||
<span className={cn(
|
||||
'system-xs-semibold px-[3px] text-components-button-tertiary-text',
|
||||
disabled ? 'text-components-button-tertiary-text-disabled' : '',
|
||||
)}>
|
||||
{currentLanguage}
|
||||
</span>
|
||||
<RiArrowDownSLine className={cn(
|
||||
'size-3.5 text-components-button-tertiary-text',
|
||||
disabled ? 'text-components-button-tertiary-text-disabled' : '',
|
||||
)} />
|
||||
</div>
|
||||
}
|
||||
btnClassName={() => cn(
|
||||
'!hover:bg-components-button-tertiary-bg !mx-1 rounded-md !border-0 !bg-components-button-tertiary-bg !px-1.5 !py-1',
|
||||
disabled ? 'bg-components-button-tertiary-bg-disabled' : '',
|
||||
)}
|
||||
className='!left-1 !z-20 h-fit !w-[140px] !translate-x-0'
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(LanguageSelect)
|
||||
108
dify/web/app/components/datasets/create/step-two/option-card.tsx
Normal file
108
dify/web/app/components/datasets/create/step-two/option-card.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import type { ComponentProps, FC, ReactNode } from 'react'
|
||||
import Image from 'next/image'
|
||||
import classNames from '@/utils/classnames'
|
||||
|
||||
const TriangleArrow: FC<ComponentProps<'svg'>> = props => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="11" viewBox="0 0 24 11" fill="none" {...props}>
|
||||
<path d="M9.87868 1.12132C11.0503 -0.0502525 12.9497 -0.0502525 14.1213 1.12132L23.3137 10.3137H0.686292L9.87868 1.12132Z" fill="currentColor" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
type OptionCardHeaderProps = {
|
||||
icon: ReactNode
|
||||
title: ReactNode
|
||||
description: string
|
||||
isActive?: boolean
|
||||
activeClassName?: string
|
||||
effectImg?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export const OptionCardHeader: FC<OptionCardHeaderProps> = (props) => {
|
||||
const { icon, title, description, isActive, activeClassName, effectImg, disabled } = props
|
||||
return <div className={classNames(
|
||||
'relative flex h-full overflow-hidden rounded-t-xl',
|
||||
isActive && activeClassName,
|
||||
!disabled && 'cursor-pointer',
|
||||
)}>
|
||||
<div className='relative flex size-14 items-center justify-center overflow-hidden'>
|
||||
{isActive && effectImg && <Image src={effectImg} className='absolute left-0 top-0 h-full w-full' alt='' width={56} height={56} />}
|
||||
<div className='p-1'>
|
||||
<div className='flex size-8 justify-center rounded-lg border border-components-panel-border-subtle bg-background-default-dodge p-1.5 shadow-md'>
|
||||
{icon}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TriangleArrow
|
||||
className={classNames('absolute -bottom-1.5 left-4 text-transparent', isActive && 'text-components-panel-bg')}
|
||||
/>
|
||||
<div className='flex-1 space-y-0.5 py-3 pr-4'>
|
||||
<div className='system-md-semibold text-text-secondary'>{title}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
type OptionCardProps = {
|
||||
icon: ReactNode
|
||||
className?: string
|
||||
activeHeaderClassName?: string
|
||||
title: ReactNode
|
||||
description: string
|
||||
isActive?: boolean
|
||||
actions?: ReactNode
|
||||
effectImg?: string
|
||||
onSwitched?: () => void
|
||||
noHighlight?: boolean
|
||||
disabled?: boolean
|
||||
} & Omit<ComponentProps<'div'>, 'title' | 'onClick'>
|
||||
|
||||
export const OptionCard: FC<OptionCardProps> = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
},
|
||||
) => {
|
||||
const { icon, className, title, description, isActive, children, actions, activeHeaderClassName, style, effectImg, onSwitched, noHighlight, disabled, ...rest } = props
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-xl bg-components-option-card-option-bg shadow-xs',
|
||||
(isActive && !noHighlight)
|
||||
? 'border-[1.5px] border-components-option-card-option-selected-border'
|
||||
: 'border border-components-option-card-option-border',
|
||||
disabled && 'pointer-events-none opacity-50',
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
...style,
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!isActive && !disabled)
|
||||
onSwitched?.()
|
||||
}}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
>
|
||||
<OptionCardHeader
|
||||
icon={icon}
|
||||
title={title}
|
||||
description={description}
|
||||
isActive={isActive && !noHighlight}
|
||||
activeClassName={activeHeaderClassName}
|
||||
effectImg={effectImg}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{/** Body */}
|
||||
{isActive && (children || actions) && <div className='rounded-b-xl bg-components-panel-bg px-4 py-3'>
|
||||
{children}
|
||||
{actions && <div className='mt-4 flex gap-2'>
|
||||
{actions}
|
||||
</div>
|
||||
}
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
OptionCard.displayName = 'OptionCard'
|
||||
@@ -0,0 +1,78 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export type IPreviewItemProps = {
|
||||
type: string
|
||||
index: number
|
||||
content?: string
|
||||
qa?: {
|
||||
answer: string
|
||||
question: string
|
||||
}
|
||||
}
|
||||
|
||||
export enum PreviewType {
|
||||
TEXT = 'text',
|
||||
QA = 'QA',
|
||||
}
|
||||
|
||||
const sharpIcon = (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.74999 1.5L3.24999 10.5M8.74998 1.5L7.24998 10.5M10.25 4H1.75M9.75 8H1.25" stroke="#98A2B3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const textIcon = (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 3.5H8M6 3.5V8.5M3.9 10.5H8.1C8.94008 10.5 9.36012 10.5 9.68099 10.3365C9.96323 10.1927 10.1927 9.96323 10.3365 9.68099C10.5 9.36012 10.5 8.94008 10.5 8.1V3.9C10.5 3.05992 10.5 2.63988 10.3365 2.31901C10.1927 2.03677 9.96323 1.8073 9.68099 1.66349C9.36012 1.5 8.94008 1.5 8.1 1.5H3.9C3.05992 1.5 2.63988 1.5 2.31901 1.66349C2.03677 1.8073 1.8073 2.03677 1.66349 2.31901C1.5 2.63988 1.5 3.05992 1.5 3.9V8.1C1.5 8.94008 1.5 9.36012 1.66349 9.68099C1.8073 9.96323 2.03677 10.1927 2.31901 10.3365C2.63988 10.5 3.05992 10.5 3.9 10.5Z" stroke="#667085" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
|
||||
)
|
||||
|
||||
const PreviewItem: FC<IPreviewItemProps> = ({
|
||||
type = PreviewType.TEXT,
|
||||
index,
|
||||
content,
|
||||
qa,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const charNums = type === PreviewType.TEXT
|
||||
? (content || '').length
|
||||
: (qa?.answer || '').length + (qa?.question || '').length
|
||||
const formattedIndex = (() => String(index).padStart(3, '0'))()
|
||||
|
||||
return (
|
||||
<div className='rounded-xl bg-gray-50 p-4'>
|
||||
<div className='flex h-5 items-center justify-between text-xs text-gray-500'>
|
||||
<div className='box-border flex h-[18px] items-center space-x-1 rounded-md border border-gray-200 pl-1 pr-1.5 font-medium italic'>
|
||||
{sharpIcon}
|
||||
<span>{formattedIndex}</span>
|
||||
</div>
|
||||
<div className='flex items-center space-x-1'>
|
||||
{textIcon}
|
||||
<span>{charNums} {t('datasetCreation.stepTwo.characters')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-2 line-clamp-6 max-h-[120px] overflow-hidden text-sm text-gray-800'>
|
||||
{type === PreviewType.TEXT && (
|
||||
<div style={{ whiteSpace: 'pre-line' }}>{content}</div>
|
||||
)}
|
||||
{type === PreviewType.QA && (
|
||||
<div style={{ whiteSpace: 'pre-line' }}>
|
||||
<div className='flex'>
|
||||
<div className='text-medium mr-2 shrink-0 text-gray-400'>Q</div>
|
||||
<div style={{ whiteSpace: 'pre-line' }}>{qa?.question}</div>
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<div className='text-medium mr-2 shrink-0 text-gray-400'>A</div>
|
||||
<div style={{ whiteSpace: 'pre-line' }}>{qa?.answer}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(PreviewItem)
|
||||
54
dify/web/app/components/datasets/create/step-two/unescape.ts
Normal file
54
dify/web/app/components/datasets/create/step-two/unescape.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// https://github.com/iamakulov/unescape-js/blob/master/src/index.js
|
||||
|
||||
/**
|
||||
* \\ - matches the backslash which indicates the beginning of an escape sequence
|
||||
* (
|
||||
* u\{([0-9A-Fa-f]+)\} - first alternative; matches the variable-length hexadecimal escape sequence (\u{ABCD0})
|
||||
* |
|
||||
* u([0-9A-Fa-f]{4}) - second alternative; matches the 4-digit hexadecimal escape sequence (\uABCD)
|
||||
* |
|
||||
* x([0-9A-Fa-f]{2}) - third alternative; matches the 2-digit hexadecimal escape sequence (\xA5)
|
||||
* |
|
||||
* ([1-7][0-7]{0,2}|[0-7]{2,3}) - fourth alternative; matches the up-to-3-digit octal escape sequence (\5 or \512)
|
||||
* |
|
||||
* (['"tbrnfv0\\]) - fifth alternative; matches the special escape characters (\t, \n and so on)
|
||||
* |
|
||||
* \U([0-9A-Fa-f]+) - sixth alternative; matches the 8-digit hexadecimal escape sequence used by python (\U0001F3B5)
|
||||
* )
|
||||
*/
|
||||
const jsEscapeRegex = /\\(u\{([0-9A-Fa-f]+)\}|u([0-9A-Fa-f]{4})|x([0-9A-Fa-f]{2})|([1-7][0-7]{0,2}|[0-7]{2,3})|(['"tbrnfv0\\]))|\\U([0-9A-Fa-f]{8})/g
|
||||
|
||||
const usualEscapeSequences: Record<string, string> = {
|
||||
'0': '\0',
|
||||
'b': '\b',
|
||||
'f': '\f',
|
||||
'n': '\n',
|
||||
'r': '\r',
|
||||
't': '\t',
|
||||
'v': '\v',
|
||||
'\'': '\'',
|
||||
'"': '"',
|
||||
'\\': '\\',
|
||||
}
|
||||
|
||||
const fromHex = (str: string) => String.fromCodePoint(Number.parseInt(str, 16))
|
||||
const fromOct = (str: string) => String.fromCodePoint(Number.parseInt(str, 8))
|
||||
|
||||
const unescape = (str: string) => {
|
||||
return str.replace(jsEscapeRegex, (_, __, varHex, longHex, shortHex, octal, specialCharacter, python) => {
|
||||
if (varHex !== undefined)
|
||||
return fromHex(varHex)
|
||||
else if (longHex !== undefined)
|
||||
return fromHex(longHex)
|
||||
else if (shortHex !== undefined)
|
||||
return fromHex(shortHex)
|
||||
else if (octal !== undefined)
|
||||
return fromOct(octal)
|
||||
else if (python !== undefined)
|
||||
return fromHex(python)
|
||||
else
|
||||
return usualEscapeSequences[specialCharacter]
|
||||
})
|
||||
}
|
||||
|
||||
export default unescape
|
||||
Reference in New Issue
Block a user