V4.12.3 features (#5595)

* refactor: remove ModelProviderIdType and update related types (#5549)

* perf: model provider

* fix eval create split (#5570)

* git rebase --continuedoc

* add more variable types (#5540)

* variable types

* password

* time picker

* internal var

* file

* fix-test

* time select default value & range

* password & type render

* fix

* fix build

* fix

* move method

* split date select

* icon

* perf: variable code

* prompt editor add markdown plugin (#5556)

* editor markdown

* fix build

* pnpm lock

* add props

* update code

* fix list

* editor ui

* fix variable reset (#5586)

* perf: variables type code

* customize lexical indent (#5588)

* perf: multiple selector

* perf: tab plugin

* doc

* refactor: update workflow constants to use ToolTypeEnum (#5491)

* refactor: replace FlowNodeTemplateTypeEnum with string literals in workflow templates

* perf: tool type

---------

Co-authored-by: archer <545436317@qq.com>

* update doc

* fix: make table's row more natural while dragging it (#5596)

* feat: add APIGetTemplate function and refactor template fetching logic (#5498)

* feat: add APIGetTemplate function and refactor template fetching logic

* chore: adjust the code

* chore: update sdk

---------

Co-authored-by: FinleyGe <m13203533462@163.com>

* perf init system

* doc

* remove log

* remove i18n

* perf: variables render

---------

Co-authored-by: Ctrlz <143257420+ctrlz526@users.noreply.github.com>
Co-authored-by: heheer <heheer@sealos.io>
Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com>
Co-authored-by: FinleyGe <m13203533462@163.com>
This commit is contained in:
Archer
2025-09-07 14:41:48 +08:00
committed by GitHub
parent c747fc03ad
commit 3f9b0fa1d4
166 changed files with 3407 additions and 11604 deletions

View File

@@ -0,0 +1,106 @@
import React, { useState, useMemo, useRef, useEffect } from 'react';
import type { BoxProps } from '@chakra-ui/react';
import { Box, Card, Flex, useOutsideClick } from '@chakra-ui/react';
import { format } from 'date-fns';
import type { Matcher } from 'react-day-picker';
import { DayPicker } from 'react-day-picker';
import 'react-day-picker/dist/style.css';
import zhCN from 'date-fns/locale/zh-CN';
import MyIcon from '../Icon';
const DateTimePicker = ({
onChange,
popPosition = 'bottom',
defaultDate = new Date(),
selectedDateTime,
disabled,
...props
}: {
onChange?: (dateTime: Date) => void;
popPosition?: 'bottom' | 'top';
defaultDate?: Date;
selectedDateTime?: Date;
disabled?: Matcher[];
} & Omit<BoxProps, 'onChange'>) => {
const OutRangeRef = useRef(null);
const [selectedDate, setSelectedDate] = useState<Date | undefined>(
selectedDateTime || defaultDate
);
const [showSelected, setShowSelected] = useState(false);
useEffect(() => {
if (selectedDateTime) {
setSelectedDate(selectedDateTime);
}
}, [selectedDateTime]);
const formatSelected = useMemo(() => {
if (selectedDate) {
const dateStr = format(selectedDate, 'y/MM/dd');
return dateStr;
}
return format(new Date(), 'y/MM/dd');
}, [selectedDate]);
useOutsideClick({
ref: OutRangeRef,
handler: () => {
setShowSelected(false);
}
});
return (
<Box position={'relative'} ref={OutRangeRef}>
<Flex
border={'base'}
px={3}
pr={3}
py={1}
borderRadius={'sm'}
cursor={'pointer'}
bg={'myGray.50'}
fontSize={'sm'}
onClick={() => setShowSelected(true)}
alignItems={'center'}
{...props}
>
<Box color={'myGray.600'} fontWeight={'400'} flex={1}>
{formatSelected}
</Box>
<MyIcon ml={2} name={'date'} w={'16px'} color={'myGray.600'} />
</Flex>
{showSelected && (
<Card
position={'absolute'}
zIndex={1}
css={{
'--rdp-background-color': '#d6e8ff',
'--rdp-accent-color': '#0000ff'
}}
{...(popPosition === 'top'
? {
bottom: '40px'
}
: {})}
>
<DayPicker
locale={zhCN}
mode="single"
defaultMonth={selectedDate}
selected={selectedDate}
disabled={disabled}
onSelect={(date) => {
if (date) {
setSelectedDate(date);
onChange?.(date);
setShowSelected(false);
}
}}
/>
</Card>
)}
</Box>
);
};
export default DateTimePicker;

View File

@@ -271,14 +271,25 @@ export const iconPaths = {
'core/workflow/inputType/array': () => import('./icons/core/workflow/inputType/array.svg'),
'core/workflow/inputType/customVariable': () =>
import('./icons/core/workflow/inputType/customVariable.svg'),
'core/workflow/inputType/dataset': () => import('./icons/core/workflow/inputType/dataset.svg'),
'core/workflow/inputType/timePointSelect': () =>
import('./icons/core/workflow/inputType/timePointSelect.svg'),
'core/workflow/inputType/timeRangeSelect': () =>
import('./icons/core/workflow/inputType/timeRangeSelect.svg'),
'core/workflow/inputType/dynamic': () => import('./icons/core/workflow/inputType/dynamic.svg'),
'core/workflow/inputType/external': () => import('./icons/core/workflow/inputType/external.svg'),
'core/workflow/inputType/file': () => import('./icons/core/workflow/inputType/file.svg'),
'core/workflow/inputType/input': () => import('./icons/core/workflow/inputType/input.svg'),
'core/workflow/inputType/internal': () => import('./icons/core/workflow/inputType/internal.svg'),
'core/workflow/inputType/jsonEditor': () =>
import('./icons/core/workflow/inputType/jsonEditor.svg'),
'core/workflow/inputType/model': () => import('./icons/core/workflow/inputType/model.svg'),
'core/workflow/inputType/multipleSelect': () =>
import('./icons/core/workflow/inputType/multipleSelect.svg'),
'core/workflow/inputType/numberInput': () =>
import('./icons/core/workflow/inputType/numberInput.svg'),
'core/workflow/inputType/option': () => import('./icons/core/workflow/inputType/option.svg'),
'core/workflow/inputType/password': () => import('./icons/core/workflow/inputType/password.svg'),
'core/workflow/inputType/reference': () =>
import('./icons/core/workflow/inputType/reference.svg'),
'core/workflow/inputType/select': () => import('./icons/core/workflow/inputType/select.svg'),
@@ -419,6 +430,7 @@ export const iconPaths = {
'model/cloudflare': () => import('./icons/model/cloudflare.svg'),
'model/cohere': () => import('./icons/model/cohere.svg'),
'model/coze': () => import('./icons/model/coze.svg'),
'model/huggingface': () => import('./icons/model/huggingface.svg'),
more: () => import('./icons/more.svg'),
moreLine: () => import('./icons/moreLine.svg'),
optimizer: () => import('./icons/optimizer.svg'),

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path d="M17.596 5.17271V4.75465L17.5955 4.71549C17.5505 2.77985 14.1671 1.66669 10.0001 1.66669C5.83276 1.66669 2.44882 2.76409 2.40417 4.71549V5.17271H2.40791C2.40542 5.20487 2.40417 5.23731 2.40417 5.27002C2.40417 6.73999 5.10674 7.93164 10.0001 7.93164C14.8934 7.93164 17.596 6.73999 17.596 5.27002C17.596 5.23732 17.5947 5.20488 17.5922 5.17271H17.596Z"/>
<path d="M2.40417 7.46899V10.4345C2.404 10.4415 2.40405 10.4485 2.40411 10.4554L2.40417 10.466C2.40417 11.9342 5.10674 13.1244 10.0001 13.1244C14.8934 13.1244 17.596 11.9342 17.596 10.466C17.596 10.4555 17.5958 10.445 17.5956 10.4345L17.596 7.46899H17.5437C17.1466 8.80744 14.4994 9.84077 10.0001 9.84077C5.50068 9.84077 2.85352 8.80744 2.45645 7.46899H2.40417Z"/>
<path d="M2.40417 12.3715V15.3524H2.40774C2.51867 17.0083 5.20952 18.3334 10.0001 18.3334C14.7906 18.3334 17.4815 17.0083 17.5924 15.3524H17.596V12.3715C17.596 13.9683 14.8934 15.2627 10.0001 15.2627C5.10674 15.2627 2.40417 13.9683 2.40417 12.3715Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path d="M16.6723 15.5599C17.2705 15.56 17.7555 16.0448 17.7555 16.6431C17.7554 17.2413 17.2705 17.7262 16.6723 17.7262H3.32756C2.72934 17.7262 2.24445 17.2413 2.24438 16.6431C2.24438 16.0448 2.7293 15.56 3.32756 15.5599H16.6723ZM7.31681 3.30322C8.8057 3.30329 10.1139 4.27969 10.5427 5.69987L11.5819 4.48405C12.2224 3.7344 13.1594 3.30333 14.1454 3.30322H14.6833C15.3487 3.30323 15.8877 3.8423 15.8878 4.50765C15.8878 5.17304 15.3487 5.71207 14.6833 5.71208H14.1454C13.8642 5.71218 13.5973 5.83601 13.4146 6.0498L11.3272 8.49202L11.6755 9.73633C11.7919 10.1515 12.1705 10.4385 12.6016 10.4386H13.5058L13.6295 10.4451C14.2366 10.5071 14.7108 11.0197 14.711 11.6431C14.711 12.2667 14.2367 12.7798 13.6295 12.8418L13.5058 12.8483H12.6016C11.1954 12.8482 9.9499 11.9769 9.45467 10.6836L8.61564 11.6667C7.97512 12.4164 7.03824 12.8483 6.05216 12.8483H5.31567L5.19279 12.8418C4.58539 12.78 4.11125 12.2668 4.11125 11.6431C4.11148 11.0196 4.58554 10.507 5.19279 10.4451L5.31567 10.4386H6.05216C6.33331 10.4386 6.60025 10.3154 6.78296 10.1017L8.6604 7.90365L8.24373 6.4152C8.12742 5.99992 7.74807 5.71214 7.31681 5.71208H6.26945C5.60406 5.71208 5.06502 5.17304 5.06502 4.50765C5.06508 3.8423 5.60409 3.30322 6.26945 3.30322H7.31681Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,5 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 13" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M2.1 1.93982C1.21634 1.93982 0.5 2.65616 0.5 3.53982V9.46026C0.5 10.3439 1.21635 11.0603 2.1 11.0603H9.89076C10.7744 11.0603 11.4908 10.3439 11.4908 9.46026V3.53982C11.4908 2.65616 10.7744 1.93982 9.89076 1.93982H2.1ZM2.51667 4.54432C2.51667 4.26817 2.74053 4.04432 3.01667 4.04432H4.51156C4.7877 4.04432 5.01156 4.26817 5.01156 4.54432C5.01156 4.82046 4.7877 5.04432 4.51156 5.04432H4.21412V7.96631H4.51156C4.7877 7.96631 5.01156 8.19017 5.01156 8.46631C5.01156 8.74245 4.7877 8.96631 4.51156 8.96631H3.01667C2.74053 8.96631 2.51667 8.74245 2.51667 8.46631C2.51667 8.19017 2.74053 7.96631 3.01667 7.96631H3.31412L3.31412 5.04432H3.01667C2.74053 5.04432 2.51667 4.82046 2.51667 4.54432ZM6.63225 8.11713C6.43699 7.92186 6.1204 7.92186 5.92514 8.11713C5.72988 8.31239 5.72988 8.62897 5.92514 8.82423L5.92868 8.82777C6.12394 9.02303 6.44052 9.02303 6.63578 8.82777C6.83105 8.63251 6.83105 8.31592 6.63578 8.12066L6.63225 8.11713ZM8.39262 8.11713C8.19736 7.92186 7.88078 7.92186 7.68552 8.11713C7.49025 8.31239 7.49025 8.62897 7.68552 8.82423L7.68905 8.82777C7.88431 9.02303 8.2009 9.02303 8.39616 8.82777C8.59142 8.63251 8.59142 8.31592 8.39616 8.12066L8.39262 8.11713Z"
/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 16" >
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.242676 2.98988C0.242676 1.51712 1.43658 0.323212 2.90934 0.323212H15.0908C16.5636 0.323212 17.7575 1.51712 17.7575 2.98988V13.0101C17.7575 14.4829 16.5636 15.6768 15.0908 15.6768H2.90934C1.43659 15.6768 0.242676 14.4828 0.242676 13.0101V2.98988ZM3.95217 4.0327V4.63745C3.95217 4.77104 3.84386 4.87935 3.71027 4.87935H2.98457C2.85098 4.87935 2.74268 4.77104 2.74268 4.63745V3.06511C2.74268 2.93151 2.85098 2.82321 2.98457 2.82321H8.54823C8.68183 2.82321 8.79013 2.93151 8.79013 3.06511V4.63745C8.79013 4.77104 8.68183 4.87935 8.54823 4.87935H7.82254C7.68894 4.87935 7.58064 4.77104 7.58064 4.63745V4.0327H6.37115V7.68837H7.07026C7.20385 7.68837 7.31215 7.79667 7.31215 7.93027V8.65596C7.31215 8.78956 7.20385 8.89786 7.07026 8.89786H5.7678L5.7664 8.89786L5.765 8.89786H4.46256C4.32896 8.89786 4.22066 8.78956 4.22066 8.65596V7.93027C4.22066 7.79667 4.32896 7.68837 4.46256 7.68837H5.16166V4.0327H3.95217ZM13.4238 13.8287C13.4238 13.7819 13.4418 13.7369 13.4742 13.7031L15.7042 11.3773C15.7466 11.3332 15.8051 11.3082 15.8663 11.3082C15.9903 11.3082 16.0908 11.4087 16.0908 11.5327V13.2101C16.0908 13.4901 16.0908 13.6301 16.0363 13.7371C15.9884 13.8312 15.9119 13.9077 15.8178 13.9556C15.7109 14.0101 15.5708 14.0101 15.2908 14.0101H13.6052C13.505 14.0101 13.4238 13.9289 13.4238 13.8287Z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 16" fill="none">
<path d="M1.74992 0.499573C2.34817 0.499578 2.833 0.984513 2.83309 1.58274V14.4172C2.833 15.0154 2.34817 15.5004 1.74992 15.5004C1.15166 15.5004 0.666836 15.0154 0.666748 14.4172V1.58274C0.666835 0.984509 1.15166 0.499573 1.74992 0.499573Z" />
<path d="M16.5839 0.499573C17.182 0.499743 17.667 0.984626 17.6671 1.58274V14.4172C17.667 15.0154 17.182 15.5002 16.5839 15.5004C15.9856 15.5004 15.5 15.0155 15.4999 14.4172V1.58274C15.5 0.984521 15.9857 0.499573 16.5839 0.499573Z" />
<path d="M7.19181 3.65143C8.28863 3.65161 9.2518 4.38273 9.54614 5.43935L9.65682 5.83893L10.7856 4.51243C11.2498 3.96627 11.9307 3.65148 12.6475 3.65143H13.4109C14.0091 3.65149 14.494 4.13638 14.4941 4.7346C14.4939 5.33277 14.0091 5.81771 13.4109 5.81777H12.6475C12.5665 5.81782 12.4893 5.8537 12.4368 5.91543L10.3591 8.35927L10.8018 9.94863C10.8353 10.0682 10.9446 10.1512 11.0688 10.1513H12.344L12.4547 10.157C13.0007 10.2126 13.427 10.6738 13.4272 11.2344C13.427 11.7951 13.0007 12.2571 12.4547 12.3127L12.344 12.3176H11.0688C9.97195 12.3176 9.00896 11.587 8.71444 10.5305L8.66479 10.3531L7.72729 11.4566C7.26306 12.0027 6.5821 12.3176 5.86532 12.3176H4.92212L4.81144 12.3127C4.26532 12.2572 3.83906 11.7952 3.83895 11.2344C3.8391 10.6737 4.26538 10.2125 4.81144 10.157L4.92212 10.1513H5.86532C5.94641 10.1512 6.02352 10.1154 6.07609 10.0536L7.9633 7.83274L7.45874 6.02041C7.4254 5.90083 7.31592 5.81795 7.19181 5.81777H5.78638C5.18817 5.81774 4.70333 5.33278 4.70321 4.7346C4.70327 4.13637 5.18813 3.65146 5.78638 3.65143H7.19181Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
<path d="M13.2734 4.04181H14.2084C15.9343 4.04181 17.3334 5.44092 17.3334 7.16678V11.3334C17.3334 13.0593 15.9343 14.4584 14.2084 14.4584H3.79175C2.06587 14.4584 0.666748 13.0593 0.666748 11.3334V7.16678C0.666748 5.44092 2.06587 4.04181 3.79175 4.04181H4.72675L4.34616 1.75845C4.26104 1.24775 4.60608 0.764714 5.11679 0.679589C5.6275 0.594465 6.11054 0.939504 6.19566 1.45021L6.61233 3.95018C6.61746 3.98085 6.621 4.01143 6.62308 4.04181H11.3771C11.3792 4.01139 11.3827 3.98085 11.3878 3.95018L11.8045 1.45021C11.8896 0.939504 12.3727 0.594465 12.8834 0.679589C13.3941 0.764714 13.7391 1.24775 13.654 1.75845L13.2734 4.04181ZM4.75008 8.41677V9.25009C4.75008 9.76784 5.16983 10.1876 5.68758 10.1876C6.20533 10.1876 6.62508 9.76784 6.62508 9.25009V8.41677C6.62508 7.89902 6.20533 7.47928 5.68758 7.47928C5.16983 7.47928 4.75008 7.89902 4.75008 8.41677ZM11.3751 8.41677V9.25009C11.3751 9.76784 11.7948 10.1876 12.3126 10.1876C12.8303 10.1876 13.2501 9.76784 13.2501 9.25009V8.41677C13.2501 7.89902 12.8303 7.47928 12.3126 7.47928C11.7948 7.47928 11.3751 7.89902 11.3751 8.41677ZM4.83341 17.3334C4.31566 17.3334 3.89591 16.9136 3.89591 16.3959C3.89591 15.8781 4.31566 15.4584 4.83341 15.4584H13.1667C13.6845 15.4584 14.1042 15.8781 14.1042 16.3959C14.1042 16.9136 13.6845 17.3334 13.1667 17.3334H4.83341Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9169 1.66666C16.7577 1.66688 18.2502 3.15918 18.2502 4.99999V15C18.2502 16.8409 16.757 18.3333 14.9161 18.3333H4.9161C3.07533 18.3331 1.58276 16.8408 1.58276 15V4.99999C1.58276 3.15904 3.07596 1.66666 4.91691 1.66666H14.9169ZM14.9324 6.42821C14.6135 6.10946 14.0964 6.1094 13.7776 6.42821L8.36662 11.8392L6.22225 9.69481C5.90338 9.37594 5.38637 9.37601 5.06746 9.69481C4.74856 10.0137 4.74856 10.5307 5.06746 10.8496L7.78963 13.5718C8.10856 13.8904 8.6256 13.8906 8.94442 13.5718L14.9324 7.583C15.2512 7.26409 15.2513 6.7471 14.9324 6.42821Z" />
</svg>

After

Width:  |  Height:  |  Size: 680 B

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 21">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.00033 2.22217C3.15938 2.22217 1.66699 3.71455 1.66699 5.5555V15.5555C1.66699 17.3965 3.15938 18.8888 5.00033 18.8888H15.0003C16.8413 18.8888 18.3337 17.3965 18.3337 15.5555V5.5555C18.3337 3.71455 16.8413 2.22217 15.0003 2.22217H5.00033ZM8.52689 7.35494C8.81978 7.06204 8.81978 6.58717 8.52689 6.29428C8.23399 6.00138 7.75912 6.00138 7.46623 6.29427L5.8429 7.9176L5.24853 7.32322C4.95563 7.03033 4.48076 7.03033 4.18787 7.32322C3.89497 7.61612 3.89497 8.09099 4.18786 8.38388L5.31032 9.50633C5.4569 9.65292 5.64907 9.72614 5.84119 9.726C6.03445 9.72702 6.22803 9.6538 6.37548 9.50634L8.52689 7.35494ZM16.0325 7.9003C16.0325 8.31452 15.6967 8.6503 15.2825 8.6503H10.7764C10.3622 8.6503 10.0264 8.31452 10.0264 7.9003C10.0264 7.48609 10.3622 7.1503 10.7764 7.1503L15.2825 7.1503C15.6967 7.1503 16.0325 7.48609 16.0325 7.9003ZM6.12591 15.3471C6.95434 15.3471 7.62591 14.6755 7.62591 13.8471C7.62591 13.0186 6.95434 12.3471 6.12591 12.3471C5.29748 12.3471 4.62591 13.0186 4.62591 13.8471C4.62591 14.6755 5.29748 15.3471 6.12591 15.3471ZM16.0325 13.8471C16.0325 14.2613 15.6967 14.5971 15.2825 14.5971H10.7764C10.3622 14.5971 10.0264 14.2613 10.0264 13.8471C10.0264 13.4329 10.3622 13.0971 10.7764 13.0971H15.2825C15.6967 13.0971 16.0325 13.4329 16.0325 13.8471Z" />
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.9161 1.7814C14.5184 1.7814 18.2502 5.51242 18.2502 10.1147C18.2502 14.7171 14.5185 18.4481 9.9161 18.4481C5.31389 18.4479 1.58276 14.717 1.58276 10.1147C1.58285 5.51255 5.31394 1.78161 9.9161 1.7814ZM14.9324 6.42821C14.6135 6.10946 14.0964 6.1094 13.7776 6.42821L8.36662 11.8392L6.22225 9.69481C5.90338 9.37594 5.38637 9.37601 5.06746 9.69481C4.74856 10.0137 4.74856 10.5307 5.06746 10.8496L7.78963 13.5718C8.10856 13.8904 8.6256 13.8906 8.94442 13.5718L14.9324 7.583C15.2512 7.26409 15.2513 6.7471 14.9324 6.42821Z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 653 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 20" fill="none">
<path d="M4.57143 5.625C4.57143 3.76087 6.10629 2.25 8 2.25C9.89371 2.25 11.4286 3.76087 11.4286 5.625C11.4286 6.02212 11.3463 6.39675 11.2183 6.75H13.7143V5.625C13.7143 2.51888 11.1554 0 8 0C4.84343 0 2.28571 2.51775 2.28571 5.625V6.75C2.28571 6.75 3.32457 6.75 4.78171 6.75C4.65371 6.39562 4.57143 6.021 4.57143 5.625ZM13.7143 6.75H2.28571C1.02286 6.75 0 7.758 0 9V15.75C0 16.9931 1.02286 18 2.28571 18H13.7143C14.9771 18 16 16.992 16 15.75V9C16 7.758 14.9771 6.75 13.7143 6.75ZM9.14286 13.1873V14.625C9.14286 15.246 8.63086 15.75 8 15.75C7.36914 15.75 6.85714 15.246 6.85714 14.625V13.1884C6.17714 12.798 5.71429 12.0802 5.71429 11.25C5.71429 10.0069 6.73714 9 8 9C9.26286 9 10.2857 10.0069 10.2857 11.25C10.2857 12.0802 9.82286 12.798 9.14286 13.1873Z" />
</svg>

After

Width:  |  Height:  |  Size: 841 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" >
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99992 0.41684C13.7404 0.41684 17.5831 4.25957 17.5831 9.00001C17.5831 13.7405 13.7404 17.5832 8.99992 17.5832C4.25948 17.5832 0.416748 13.7404 0.416748 9.00001C0.416754 4.25957 4.25948 0.416845 8.99992 0.41684ZM8.99992 3.51824C8.53977 3.51824 8.16674 3.89146 8.16658 4.35157V9.00001C8.16659 9.31565 8.34488 9.60429 8.6272 9.74545L11.7262 11.2949C12.1378 11.5006 12.6385 11.3338 12.8443 10.9222C13.0501 10.5106 12.8831 10.0099 12.4716 9.80405L9.83325 8.48487V4.35157C9.8331 3.89146 9.46006 3.51824 8.99992 3.51824Z" />
</svg>

After

Width:  |  Height:  |  Size: 640 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
<path d="M9 0.419922C13.7387 0.41993 17.5801 4.26129 17.5801 9C17.5801 13.7387 13.7387 17.5801 9 17.5801C4.26126 17.5801 0.419922 13.7387 0.419922 9C0.419947 4.26128 4.26128 0.419922 9 0.419922ZM9 2.08691C5.18175 2.08691 2.08596 5.18176 2.08594 9C2.08594 12.8183 5.18174 15.9141 9 15.9141C12.8183 15.9141 15.9131 12.8183 15.9131 9C15.9131 5.18176 12.8182 2.08692 9 2.08691ZM9.39941 4.01562C10.1815 4.07832 10.9401 4.32451 11.6123 4.73633C12.3985 5.21813 13.0364 5.90886 13.4551 6.73047C13.8736 7.55199 14.0567 8.47343 13.9844 9.39258C13.9225 10.1785 13.6756 10.9369 13.2666 11.6064C13.1514 11.7946 12.9003 11.8337 12.7217 11.7041L9.16504 9.12012C9.06145 9.04486 9 8.92394 9 8.7959V4.40039C9 4.17956 9.17932 3.99813 9.39941 4.01562Z" />
</svg>

After

Width:  |  Height:  |  Size: 817 B

View File

@@ -0,0 +1,15 @@
<svg t="1710841272884" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2684"
width="128" height="128">
<path
d="M511.968 959.936c298.688 0 447.968-200.576 447.968-448 0-247.36-149.28-447.936-448-447.936C213.28 64 64 264.576 64 511.968c0 247.392 149.248 447.968 447.968 447.968z"
fill="#FFB02E" p-id="2685"></path>
<path
d="M103.936 586.912a31.936 31.936 0 0 0-7.584 25.568 32 32 0 0 0-37.152 51.84l9.344 8a32 32 0 0 0-24.992 56.256l63.52 52.928-4.032-1.984a35.712 35.712 0 0 0-36.672 60.896C107.712 869.76 163.008 908.64 192 928c48 32 102.72 42.944 160 0 32-24 72.48-97.984 29.92-171.712-8.064-13.952-15.296-28.64-18.304-44.48-13.152-69.76-32.8-141.216-75.616-119.808-23.2 11.584-21.184 31.584-18.304 60 1.088 10.784 2.304 22.784 2.304 36l-2.56 1.28-120.384-105.376a32 32 0 0 0-45.12 3.04zM920.096 586.912c6.368 7.296 8.832 16.64 7.584 25.568a32 32 0 0 1 37.12 51.84l-9.344 8a32 32 0 0 1 25.024 56.256l-63.52 52.928 4.032-1.984a35.712 35.712 0 0 1 36.672 60.896C916.32 869.76 861.024 908.64 832 928c-48 32-102.752 42.944-160 0-32-24-72.48-97.984-29.92-171.712 8.064-13.952 15.296-28.64 18.304-44.48 13.152-69.76 32.8-141.216 75.616-119.808 23.2 11.584 21.184 31.584 18.304 60-1.088 10.784-2.304 22.784-2.304 36l2.56 1.28 120.384-105.376a32 32 0 0 1 45.12 3.04z"
fill="#FF822D" p-id="2686"></path>
<path
d="M224 464c0 44.16-28.64 80-64 80s-64-35.84-64-80 28.64-80 64-80 64 35.84 64 80zM928 464c0 44.16-28.64 80-64 80s-64-35.84-64-80 28.64-80 64-80 64 35.84 64 80z"
fill="#FF6723" p-id="2687"></path>
<path
d="M299.168 333.184c-6.72 7.296-10.24 17.024-11.744 24.928a32 32 0 0 1-62.848-12.224c2.848-14.592 9.92-36.896 27.456-55.968C270.496 269.792 298.112 256 336 256c38.24 0 65.984 14.464 84.352 34.624 17.408 19.104 24.64 41.344 27.2 55.904a32 32 0 0 1-63.072 10.944 49.472 49.472 0 0 0-11.456-23.744C367.04 327.104 356.544 320 336 320c-20.896 0-31.104 6.944-36.832 13.184zM651.2 333.184c-6.72 7.296-10.24 17.024-11.776 24.928a32 32 0 0 1-62.816-12.224c2.816-14.592 9.92-36.896 27.424-55.968C622.496 269.792 650.112 256 688 256c38.272 0 65.984 14.464 84.352 34.624 17.408 19.104 24.64 41.344 27.2 55.904a32 32 0 0 1-63.072 10.944 49.44 49.44 0 0 0-11.456-23.744C719.04 327.104 708.544 320 688 320c-20.896 0-31.072 6.944-36.8 13.184zM313.6 492.8a32 32 0 1 0-51.2 38.4c22.464 29.952 96.256 92.8 249.6 92.8s227.136-62.848 249.6-92.8a32 32 0 0 0-51.2-38.4c-9.536 12.704-63.744 67.2-198.4 67.2s-188.864-54.496-198.4-67.2z"
fill="#402A32" p-id="2688"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -26,7 +26,6 @@ import { textToEditorState } from '../../Textarea/PromptEditor/utils';
import { SingleLinePlugin } from '../../Textarea/PromptEditor/plugins/SingleLinePlugin';
import OnBlurPlugin from '../../Textarea/PromptEditor/plugins/OnBlurPlugin';
import VariablePlugin from '../../Textarea/PromptEditor/plugins/VariablePlugin';
import VariablePickerPlugin from '../../Textarea/PromptEditor/plugins/VariablePickerPlugin';
import FocusPlugin from '../../Textarea/PromptEditor/plugins/FocusPlugin';
import VariableLabelPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin';
import { VariableLabelNode } from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin/node';

View File

@@ -1,12 +1,12 @@
import React from 'react';
import type { EditorState, LexicalEditor } from 'lexical';
import { useCallback } from 'react';
import { editorStateToText } from '../../Textarea/PromptEditor/utils';
import {
type EditorVariableLabelPickerType,
type EditorVariablePickerType
} from '../../Textarea/PromptEditor/type';
import Editor from './Editor';
import { editorStateToText } from '../../Textarea/PromptEditor/utils';
const HttpInput = ({
variables = [],

View File

@@ -18,10 +18,20 @@ type Props = Omit<NumberInputProps, 'onChange' | 'onBlur'> & {
register?: UseFormRegister<any>;
name?: string;
inputFieldProps?: NumberInputFieldProps;
hideStepper?: boolean;
};
const MyNumberInput = (props: Props) => {
const { register, name, onChange, onBlur, placeholder, inputFieldProps, ...restProps } = props;
const {
register,
name,
onChange,
onBlur,
placeholder,
inputFieldProps,
hideStepper = false,
...restProps
} = props;
return (
<NumberInput
@@ -91,14 +101,16 @@ const MyNumberInput = (props: Props) => {
: {})}
{...inputFieldProps}
/>
<NumberInputStepper>
<NumberIncrementStepper>
<MyIcon name={'core/chat/chevronUp'} width={'12px'} />
</NumberIncrementStepper>
<NumberDecrementStepper>
<MyIcon name={'core/chat/chevronDown'} width={'12px'} />
</NumberDecrementStepper>
</NumberInputStepper>
{!hideStepper && (
<NumberInputStepper>
<NumberIncrementStepper>
<MyIcon name={'core/chat/chevronUp'} width={'12px'} />
</NumberIncrementStepper>
<NumberDecrementStepper>
<MyIcon name={'core/chat/chevronDown'} width={'12px'} />
</NumberDecrementStepper>
</NumberInputStepper>
)}
</NumberInput>
);
};

View File

@@ -22,6 +22,7 @@ import type { useScrollPagination } from '../../../hooks/useScrollPagination';
import MyDivider from '../MyDivider';
import { shadowLight } from '../../../styles/theme';
import { isArray } from 'lodash';
import { useMount } from 'ahooks';
const menuItemStyles: MenuItemProps = {
borderRadius: 'sm',
@@ -83,7 +84,6 @@ const MultipleSelect = <T = any,>({
tagStyle,
...props
}: SelectProps<T>) => {
const ref = useRef<HTMLButtonElement>(null);
const SearchInputRef = useRef<HTMLInputElement>(null);
const tagsContainerRef = useRef<HTMLDivElement>(null);
@@ -116,7 +116,7 @@ const MultipleSelect = <T = any,>({
onSelect(newValue);
}
},
[inputValue, value, isSelectAll, onSelect]
[inputValue, value, onSelect]
);
useEffect(() => {
if (!isOpen) {
@@ -124,40 +124,6 @@ const MultipleSelect = <T = any,>({
}
}, [isOpen]);
useEffect(() => {
const getWidth = (w: any) =>
typeof w === 'number' ? w : typeof w === 'string' ? parseInt(w) : 0;
const totalWidth = getWidth(props.w) || 200;
const tagWidth = getWidth(tagStyle?.w) || 60;
const formLabelWidth = formLabel ? formLabel.length * 8 + 20 : 0;
const availableWidth = totalWidth - formLabelWidth - 40;
const overflowWidth = 30;
if (availableWidth <= 0) {
setVisibleItems(selectedItems.length > 0 ? [selectedItems[0]] : []);
setOverflowItems(selectedItems.slice(1));
return;
}
const { count } = selectedItems.reduce(
(acc, item, i) => {
const remain = selectedItems.length - i - 1;
const needOverflow = remain > 0 ? overflowWidth : 0;
if (acc.used + tagWidth + needOverflow <= availableWidth) {
return {
used: acc.used + tagWidth,
count: i + 1
};
}
return acc;
},
{ used: 0, count: 0 }
);
setVisibleItems(selectedItems.slice(0, count));
setOverflowItems(selectedItems.slice(count));
}, [selectedItems, isOpen, props.w, tagStyle, formLabel]);
const onclickItem = useCallback(
(val: T) => {
if (isSelectAll) {
@@ -172,15 +138,128 @@ const MultipleSelect = <T = any,>({
onSelect([...value, val]);
}
},
[value, isSelectAll, onSelect, setIsSelectAll]
[isSelectAll, value, onSelect, list, setIsSelectAll]
);
const onSelectAll = useCallback(() => {
const hasSelected = isSelectAll || value.length > 0;
onSelect(hasSelected ? [] : list.map((item) => item.value));
onSelect(isSelectAll ? [] : list.map((item) => item.value));
setIsSelectAll?.((state) => !state);
}, [value, list, setIsSelectAll, onSelect]);
}, [isSelectAll, onSelect, list, setIsSelectAll]);
// 动态长度计算器 - 计算一行能展示多少个tag剩余用+n表示
const calculateLayout = useCallback(() => {
if (!tagsContainerRef.current || selectedItems.length === 0) {
setVisibleItems(selectedItems);
setOverflowItems([]);
return;
}
const containerWidth = tagsContainerRef.current.offsetWidth;
const tagGap = 4; // tag之间的gap
const overflowIndicatorWidth = 30; // "+n" 宽度
const formLabelWidth = formLabel ? formLabel.length * 8 + 20 : 0;
// 实际可用宽度
const availableWidth = containerWidth - formLabelWidth - 10;
// 如果只有一个项目,直接显示
if (selectedItems.length === 1) {
setVisibleItems(selectedItems);
setOverflowItems([]);
return;
}
// 创建临时元素来测量每个tag的实际宽度
const measureTagWidth = (item: any): number => {
// 如果有tagStyle.w优先使用
if (tagStyle?.w) {
return typeof tagStyle.w === 'number' ? tagStyle.w : parseInt(String(tagStyle.w)) || 60;
}
// 否则根据文本长度估算(更精确)
const text = String(item.label || item.value);
const baseWidth = 16; // 基础padding
const charWidth = 8; // 每个字符约8px
const closeIconWidth = closeable ? 20 : 0; // 关闭按钮宽度
return baseWidth + text.length * charWidth + closeIconWidth;
};
// 确保至少显示1个tag
const firstTagWidth = measureTagWidth(selectedItems[0]);
// 如果连第一个tag都放不下也要强制显示
if (availableWidth < firstTagWidth) {
setVisibleItems([selectedItems[0]]);
setOverflowItems(selectedItems.slice(1));
return;
}
// 精确计算每个tag的宽度
let usedWidth = 0;
let visibleCount = 0;
for (let i = 0; i < selectedItems.length; i++) {
const currentTagWidth = measureTagWidth(selectedItems[i]);
const currentGap = i > 0 ? tagGap : 0;
const remainingItems = selectedItems.length - i - 1;
const needsOverflow = remainingItems > 0;
const overflowSpace = needsOverflow ? overflowIndicatorWidth + tagGap : 0;
const totalNeeded = usedWidth + currentTagWidth + currentGap + overflowSpace;
if (totalNeeded <= availableWidth) {
usedWidth += currentTagWidth + currentGap;
visibleCount = i + 1;
} else {
break;
}
}
// 保证至少显示1个tag
if (visibleCount === 0) {
visibleCount = 1;
}
setVisibleItems(selectedItems.slice(0, visibleCount));
setOverflowItems(selectedItems.slice(visibleCount));
}, [closeable, formLabel, selectedItems, tagStyle?.w]);
// 动态监听容器宽度变化并重新计算布局
useEffect(() => {
if (!tagsContainerRef.current) return;
// 创建 ResizeObserver 监听容器宽度变化
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
// 当容器宽度发生变化时,触发重新计算
requestAnimationFrame(() => {
calculateLayout();
});
}
});
// 开始监听容器
resizeObserver.observe(tagsContainerRef.current);
// 初始计算
requestAnimationFrame(() => {
calculateLayout();
});
// 清理监听器
return () => {
resizeObserver.disconnect();
};
}, [calculateLayout]);
// 当选中项目、样式等发生变化时重新计算
useEffect(() => {
requestAnimationFrame(() => {
calculateLayout();
});
}, [calculateLayout]);
const ListRender = useMemo(() => {
return (
@@ -215,7 +294,7 @@ const MultipleSelect = <T = any,>({
})}
</>
);
}, [value, list, isSelectAll]);
}, [list, isSelectAll, value, onclickItem]);
return (
<Box h={'100%'} w={'100%'}>
@@ -230,7 +309,6 @@ const MultipleSelect = <T = any,>({
>
<MenuButton
as={Flex}
ref={ref}
px={3}
alignItems={'center'}
borderRadius={'md'}

View File

@@ -9,20 +9,26 @@
import { useMemo, useState, useTransition } from 'react';
import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
import { ListPlugin } from '@lexical/react/LexicalListPlugin';
import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
import { ListItemNode, ListNode } from '@lexical/list';
import { CodeHighlightNode, CodeNode } from '@lexical/code';
import VariableLabelPickerPlugin from './plugins/VariableLabelPickerPlugin';
import ListDisplayFixPlugin from './plugins/ListDisplayFixPlugin';
import { Box, Flex } from '@chakra-ui/react';
import styles from './index.module.scss';
import VariablePlugin from './plugins/VariablePlugin';
import { VariableNode } from './plugins/VariablePlugin/node';
import type { EditorState, LexicalEditor } from 'lexical';
import OnBlurPlugin from './plugins/OnBlurPlugin';
import MyIcon from '../../Icon';
import type { FormPropsType } from './type.d';
import { type EditorVariableLabelPickerType, type EditorVariablePickerType } from './type.d';
import type { FormPropsType } from './type';
import { type EditorVariableLabelPickerType, type EditorVariablePickerType } from './type';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import FocusPlugin from './plugins/FocusPlugin';
import { textToEditorState } from './utils';
@@ -31,8 +37,38 @@ import { VariableLabelNode } from './plugins/VariableLabelPlugin/node';
import VariableLabelPlugin from './plugins/VariableLabelPlugin';
import { useDeepCompareEffect } from 'ahooks';
import VariablePickerPlugin from './plugins/VariablePickerPlugin';
import MarkdownPlugin from './plugins/MarkdownPlugin';
import MyIcon from '../../Icon';
import TabToSpacesPlugin from './plugins/TabToSpacesPlugin';
import ListExitPlugin from './plugins/ListExitPlugin';
const Placeholder = ({ children }: { children: React.ReactNode }) => (
<Box
position={'absolute'}
top={0}
left={0}
right={0}
bottom={0}
py={3}
px={3.5}
pointerEvents={'none'}
overflow={'hidden'}
>
<Box
color={'myGray.400'}
fontSize={'mini'}
userSelect={'none'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
h={'100%'}
>
{children}
</Box>
</Box>
);
export type EditorProps = {
isRichText?: boolean;
variables?: EditorVariablePickerType[];
variableLabels?: EditorVariableLabelPickerType[];
value?: string;
@@ -50,6 +86,7 @@ export type EditorProps = {
};
export default function Editor({
isRichText = false,
minH = 200,
maxH = 400,
maxLength,
@@ -71,7 +108,7 @@ export default function Editor({
onOpenModal?: () => void;
onChange: (editorState: EditorState, editor: LexicalEditor) => void;
onChangeText?: ((text: string) => void) | undefined;
onBlur: (editor: LexicalEditor) => void;
onBlur?: (editor: LexicalEditor) => void;
}) {
const [key, setKey] = useState(getNanoid(6));
const [_, startSts] = useTransition();
@@ -79,8 +116,17 @@ export default function Editor({
const [scrollHeight, setScrollHeight] = useState(0);
const initialConfig = {
namespace: 'promptEditor',
nodes: [VariableNode, VariableLabelNode],
namespace: isRichText ? 'richPromptEditor' : 'promptEditor',
nodes: [
VariableNode,
VariableLabelNode,
HeadingNode,
ListNode,
ListItemNode,
QuoteNode,
CodeNode,
CodeHighlightNode
],
editorState: textToEditorState(value),
onError: (error: Error) => {
throw error;
@@ -125,59 +171,75 @@ export default function Editor({
borderRadius={'md'}
>
<LexicalComposer initialConfig={initialConfig} key={key}>
<PlainTextPlugin
contentEditable={
<ContentEditable
className={isInvalid ? styles.contentEditable_invalid : styles.contentEditable}
style={{
minHeight: `${minH}px`,
maxHeight: `${maxH}px`
}}
/>
}
placeholder={
<Box
position={'absolute'}
top={0}
left={0}
right={0}
bottom={0}
py={3}
px={3.5}
pointerEvents={'none'}
overflow={'hidden'}
>
<Box
color={'myGray.400'}
fontSize={'mini'}
userSelect={'none'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
h={'100%'}
>
{placeholder}
</Box>
</Box>
}
ErrorBoundary={LexicalErrorBoundary}
/>
<HistoryPlugin />
<MaxLengthPlugin maxLength={maxLength || 999999} />
<FocusPlugin focus={focus} setFocus={setFocus} />
<OnChangePlugin
onChange={(editorState, editor) => {
const rootElement = editor.getRootElement();
setScrollHeight(rootElement?.scrollHeight || 0);
startSts(() => {
onChange?.(editorState, editor);
});
}}
/>
<VariableLabelPlugin variables={variableLabels} />
<VariablePlugin variables={variables} />
<VariableLabelPickerPlugin variables={variableLabels} isFocus={focus} />
<VariablePickerPlugin variables={variableLabels.length > 0 ? [] : variables} />
<OnBlurPlugin onBlur={onBlur} />
{/* Text type */}
{isRichText ? (
<RichTextPlugin
contentEditable={
<ContentEditable
className={`${isInvalid ? styles.contentEditable_invalid : styles.contentEditable} ${styles.richText}`}
style={{
minHeight: `${minH}px`,
maxHeight: `${maxH}px`
}}
onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)}
/>
}
placeholder={<Placeholder>{placeholder}</Placeholder>}
ErrorBoundary={LexicalErrorBoundary}
/>
) : (
<PlainTextPlugin
contentEditable={
<ContentEditable
className={isInvalid ? styles.contentEditable_invalid : styles.contentEditable}
style={{
minHeight: `${minH}px`,
maxHeight: `${maxH}px`
}}
/>
}
placeholder={<Placeholder>{placeholder}</Placeholder>}
ErrorBoundary={LexicalErrorBoundary}
/>
)}
{/* Basic Plugin */}
<>
<HistoryPlugin />
<MaxLengthPlugin maxLength={maxLength || 999999} />
<FocusPlugin focus={focus} setFocus={setFocus} />
<VariablePlugin variables={variables} />
{variableLabels.length > 0 && (
<>
<VariableLabelPlugin variables={variableLabels} />
<VariableLabelPickerPlugin variables={variableLabels} isFocus={focus} />
</>
)}
{variableLabels.length > 0 && <VariablePickerPlugin variables={variables} />}
<OnBlurPlugin onBlur={onBlur} />
<ListDisplayFixPlugin />
<OnChangePlugin
onChange={(editorState, editor) => {
const rootElement = editor.getRootElement();
setScrollHeight(rootElement?.scrollHeight || 0);
startSts(() => {
onChange?.(editorState, editor);
});
}}
/>
{isRichText && (
<>
{/* <ListPlugin />
<CheckListPlugin />
<ListExitPlugin /> */}
<TabToSpacesPlugin />
{/* <MarkdownPlugin /> */}
</>
)}
</>
</LexicalComposer>
{onChangeText &&

View File

@@ -76,3 +76,14 @@
color: var(--chakra-colors-primary-600);
padding: 0 2px;
}
.richText {
ul,
ol {
padding-left: 16px;
li::marker {
color: var(--chakra-colors-primary-600);
}
}
}

View File

@@ -1,13 +1,12 @@
import { Box, Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react';
import React, { useMemo } from 'react';
import React, { useMemo, useCallback } from 'react';
import { editorStateToText } from './utils';
import type { EditorProps } from './Editor';
import Editor from './Editor';
import MyModal from '../../MyModal';
import { useTranslation } from 'next-i18next';
import type { EditorState, LexicalEditor } from 'lexical';
import type { FormPropsType } from './type.d';
import { useCallback } from 'react';
import type { FormPropsType } from './type';
const PromptEditor = ({
showOpenModal = true,
@@ -34,18 +33,27 @@ const PromptEditor = ({
},
[onChange]
);
const onBlurInput = useCallback(
(editor: LexicalEditor) => {
const text = editorStateToText(editor);
onBlur?.(text);
if (onBlur) {
const text = editorStateToText(editor);
onBlur(text);
}
},
[onBlur]
);
const formattedValue = useMemo(() => {
if (typeof value === 'object') {
return JSON.stringify(value);
}
return value;
if (value === undefined || value === null) {
return '';
}
return String(value || '');
}, [value]);
return (
@@ -74,6 +82,7 @@ const PromptEditor = ({
/>
)}
</Box>
<MyModal
isOpen={isOpen}
onClose={onClose}
@@ -102,4 +111,5 @@ const PromptEditor = ({
</>
);
};
export default React.memo(PromptEditor);

View File

@@ -1,93 +0,0 @@
import { Box, Flex } from '@chakra-ui/react';
import { type EditorVariablePickerType } from '../../type';
import MyIcon from '../../../../Icon';
import React, { useCallback, useEffect } from 'react';
export default function DropDownMenu({
variables,
setDropdownValue
}: {
variables: EditorVariablePickerType[];
setDropdownValue?: (value: string) => void;
}) {
const [highlightedIndex, setHighlightedIndex] = React.useState(0);
const handleKeyDown = useCallback(
(event: any) => {
if (event.keyCode === 38) {
setHighlightedIndex((prevIndex) => Math.max(prevIndex - 1, 0));
} else if (event.keyCode === 40) {
setHighlightedIndex((prevIndex) => Math.min(prevIndex + 1, variables.length - 1));
} else if (event.keyCode === 13 && variables[highlightedIndex]?.key) {
setDropdownValue?.(variables[highlightedIndex].key);
}
},
[highlightedIndex, variables]
);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [handleKeyDown]);
return variables.length ? (
<Box
bg={'white'}
boxShadow={'lg'}
borderWidth={'1px'}
borderColor={'borderColor.base'}
p={2}
borderRadius={'md'}
position={'absolute'}
top={'100%'}
w={'auto'}
zIndex={99999}
maxH={'300px'}
overflow={'auto'}
className="nowheel"
>
{variables.map((item, index) => (
<Flex
alignItems={'center'}
as={'li'}
key={item.key}
px={4}
py={2}
borderRadius={'sm'}
cursor={'pointer'}
maxH={'300px'}
overflow={'auto'}
_notLast={{
mb: 2
}}
{...(highlightedIndex === index
? {
bg: 'primary.50',
color: 'primary.600'
}
: {
bg: 'white',
color: 'myGray.600'
})}
onMouseDown={(e) => {
e.preventDefault();
setDropdownValue?.(item.key);
}}
onMouseEnter={() => {
setHighlightedIndex(index);
}}
>
<MyIcon name={(item.icon as any) || 'core/modules/variable'} w={'14px'} />
<Box ml={2} fontSize={'sm'}>
{item.key}
{item.key !== item.label && `(${item.label})`}
</Box>
</Flex>
))}
</Box>
) : null;
}

View File

@@ -0,0 +1,66 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect } from 'react';
export default function ListDisplayFixPlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext();
useEffect(() => {
const fixListDisplay = () => {
const rootElement = editor.getRootElement();
if (!rootElement) return;
const allListItems = rootElement.querySelectorAll('li');
allListItems.forEach((li) => {
const htmlLi = li as HTMLLIElement;
// Check if this li only contains a sublist without direct text content
const hasDirectText = Array.from(htmlLi.childNodes).some((node) => {
return node.nodeType === Node.TEXT_NODE && node.textContent?.trim();
});
const hasSpan = htmlLi.querySelector(':scope > span');
const hasOnlySublist =
htmlLi.children.length === 1 &&
(htmlLi.children[0].tagName === 'UL' || htmlLi.children[0].tagName === 'OL');
// If this li only contains a sublist without text content, hide its marker
if (!hasDirectText && !hasSpan && hasOnlySublist) {
// Only hide the marker, don't adjust position, let CSS handle indentation
htmlLi.style.listStyle = 'none';
htmlLi.style.paddingLeft = '0';
htmlLi.style.marginLeft = '0';
// Keep normal indentation for sublists
const sublist = htmlLi.children[0] as HTMLElement;
sublist.style.marginTop = '0';
sublist.style.marginBottom = '0';
// Don't modify marginLeft and paddingLeft, let CSS handle it
} else {
htmlLi.style.listStyle = '';
htmlLi.style.paddingLeft = '';
htmlLi.style.marginLeft = '';
if (
htmlLi.children[0] &&
(htmlLi.children[0].tagName === 'UL' || htmlLi.children[0].tagName === 'OL')
) {
const sublist = htmlLi.children[0] as HTMLElement;
sublist.style.marginTop = '';
sublist.style.marginBottom = '';
}
}
});
};
const removeListener = editor.registerUpdateListener(() => {
setTimeout(fixListDisplay, 10);
});
setTimeout(fixListDisplay, 10);
return removeListener;
}, [editor]);
return null;
}

View File

@@ -0,0 +1,135 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect } from 'react';
import {
$getSelection,
$isRangeSelection,
COMMAND_PRIORITY_HIGH,
KEY_ENTER_COMMAND,
KEY_BACKSPACE_COMMAND,
$createParagraphNode
} from 'lexical';
import { $isListItemNode, $isListNode } from '@lexical/list';
export default function ListExitPlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext();
useEffect(() => {
const handleEnterKey = () => {
let handled = false;
editor.update(() => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return;
}
const anchorNode = selection.anchor.getNode();
const listItemNode = anchorNode.getParent();
if ($isListItemNode(listItemNode)) {
// Check if the list item is empty
const textContent = listItemNode.getTextContent().trim();
if (textContent === '') {
// Remove the empty list item and exit list mode
const listNode = listItemNode.getParent();
if ($isListNode(listNode)) {
// If this is the only item in the list, remove the entire list
if (listNode.getChildrenSize() === 1) {
listNode.remove();
} else {
// Remove just this list item
listItemNode.remove();
}
// Insert a paragraph after the list to exit list mode
const paragraph = $createParagraphNode();
if (listNode && !listNode.isAttached()) {
// If we removed the entire list, replace it with a paragraph
listNode.getParent()?.append(paragraph);
} else {
// Insert paragraph after the list
listNode?.insertAfter(paragraph);
}
paragraph.select();
handled = true;
}
}
}
});
return handled;
};
const handleBackspaceKey = (event: KeyboardEvent) => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
const anchorNode = selection.anchor.getNode();
const listItemNode = anchorNode.getParent();
if ($isListItemNode(listItemNode)) {
// Check if cursor is at the beginning of an empty list item
const textContent = listItemNode.getTextContent().trim();
const cursorOffset = selection.anchor.offset;
// Only handle empty list items with cursor at the beginning
if (textContent === '' && cursorOffset === 0) {
// Prevent default backspace behavior
event.preventDefault();
event.stopPropagation();
editor.update(() => {
const listNode = listItemNode.getParent();
if ($isListNode(listNode)) {
// Create a new paragraph
const paragraph = $createParagraphNode();
// Always insert after the current list item and remove it
// This ensures the paragraph appears at the current position
listItemNode.insertAfter(paragraph);
listItemNode.remove();
// If the list is now empty, remove it
if (listNode.getChildrenSize() === 0) {
listNode.remove();
}
// Focus the new paragraph
paragraph.select();
}
});
return true;
}
}
return false;
};
// Register the keyboard event handlers
const removeEnterListener = editor.registerCommand(
KEY_ENTER_COMMAND,
handleEnterKey,
COMMAND_PRIORITY_HIGH
);
const removeBackspaceListener = editor.registerCommand(
KEY_BACKSPACE_COMMAND,
handleBackspaceKey,
COMMAND_PRIORITY_HIGH
);
return () => {
removeEnterListener();
removeBackspaceListener();
};
}, [editor]);
return null;
}

View File

@@ -0,0 +1,8 @@
import type { JSX } from 'react';
import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin';
import * as React from 'react';
import { RICH_PROMPT_TRANSFORMERS } from '../MarkdownTransformers';
export default function MarkdownPlugin(): JSX.Element {
return <MarkdownShortcutPlugin transformers={RICH_PROMPT_TRANSFORMERS} />;
}

View File

@@ -0,0 +1,12 @@
import {
CHECK_LIST,
ELEMENT_TRANSFORMERS,
TEXT_FORMAT_TRANSFORMERS,
type Transformer
} from '@lexical/markdown';
export const RICH_PROMPT_TRANSFORMERS: Array<Transformer> = [
CHECK_LIST,
...ELEMENT_TRANSFORMERS,
...TEXT_FORMAT_TRANSFORMERS
];

View File

@@ -0,0 +1,216 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import {
KEY_TAB_COMMAND,
COMMAND_PRIORITY_EDITOR,
$getSelection,
$isRangeSelection,
$isTextNode
} from 'lexical';
import { $createTextNode } from 'lexical';
import { $isListNode, $isListItemNode } from '@lexical/list';
import { useEffect } from 'react';
export default function TabToSpacesPlugin(): null {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerCommand(
KEY_TAB_COMMAND,
(event) => {
try {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
// Check if we're in a list context
let isInList = false;
try {
const nodes = selection.getNodes();
isInList = nodes.some((node) => {
// Check if current node or any of its ancestors is a list or list item
let currentNode = node;
while (currentNode) {
try {
if ($isListNode(currentNode) || $isListItemNode(currentNode)) {
return true;
}
// @ts-ignore
currentNode = currentNode.getParent();
} catch (e) {
// If node is no longer valid, break the loop
break;
}
}
return false;
});
} catch (e) {
// If we can't get nodes safely, assume we're not in a list
isInList = false;
}
// If we're in a list, let the built-in list indentation handle it
if (isInList) {
return false;
}
// Only handle tab for non-list contexts
event.preventDefault();
const isShiftTab = event.shiftKey;
// Handle Shift+Tab (outdent)
if (isShiftTab) {
if (!selection.isCollapsed()) {
// For selected text, remove 4 spaces from the beginning of each line
try {
const selectedText = selection.getTextContent();
const lines = selectedText.split('\n');
const outdentedText = lines
.map((line) => {
// Remove up to 4 spaces from the beginning of the line
if (line.startsWith(' ')) {
return line.slice(4);
} else if (line.startsWith(' ')) {
return line.slice(3);
} else if (line.startsWith(' ')) {
return line.slice(2);
} else if (line.startsWith(' ')) {
return line.slice(1);
}
return line;
})
.join('\n');
// Insert the outdented text and let Lexical handle cursor positioning
selection.insertText(outdentedText);
// Schedule selection restoration in the next update cycle
setTimeout(() => {
editor.update(() => {
const currentSelection = $getSelection();
if ($isRangeSelection(currentSelection) && !currentSelection.isCollapsed()) {
// Selection is already maintained, do nothing
return;
}
// If selection was lost, try to select the inserted text
if ($isRangeSelection(currentSelection)) {
const currentOffset = currentSelection.anchor.offset;
const selectionStart = Math.max(0, currentOffset - outdentedText.length);
currentSelection.anchor.set(
currentSelection.anchor.key,
selectionStart,
'text'
);
currentSelection.focus.set(currentSelection.focus.key, currentOffset, 'text');
}
});
}, 0);
return true;
} catch (e) {
// If operation fails, do nothing
return true;
}
} else {
// For cursor position, try to remove spaces before cursor
try {
const anchorNode = selection.anchor.getNode();
const anchorOffset = selection.anchor.offset;
if ($isTextNode(anchorNode)) {
const textContent = anchorNode.getTextContent();
const beforeCursor = textContent.slice(0, anchorOffset);
const afterCursor = textContent.slice(anchorOffset);
// Check if there are spaces before cursor to remove
let spacesToRemove = 0;
for (let i = beforeCursor.length - 1; i >= 0 && spacesToRemove < 4; i--) {
if (beforeCursor[i] === ' ') {
spacesToRemove++;
} else {
break;
}
}
if (spacesToRemove > 0) {
const newTextContent =
beforeCursor.slice(0, beforeCursor.length - spacesToRemove) + afterCursor;
anchorNode.setTextContent(newTextContent);
selection.anchor.set(
anchorNode.getKey(),
anchorOffset - spacesToRemove,
'text'
);
selection.focus.set(anchorNode.getKey(), anchorOffset - spacesToRemove, 'text');
}
}
return true;
} catch (e) {
return true;
}
}
} else {
// Handle regular Tab (indent)
if (!selection.isCollapsed()) {
try {
const selectedText = selection.getTextContent();
const lines = selectedText.split('\n');
const indentedText = lines.map((line) => ' ' + line).join('\n');
// Insert the indented text and let Lexical handle cursor positioning
selection.insertText(indentedText);
// Schedule selection restoration in the next update cycle
setTimeout(() => {
editor.update(() => {
const currentSelection = $getSelection();
if ($isRangeSelection(currentSelection) && !currentSelection.isCollapsed()) {
// Selection is already maintained, do nothing
return;
}
// If selection was lost, try to select the inserted text
if ($isRangeSelection(currentSelection)) {
const currentOffset = currentSelection.anchor.offset;
const selectionStart = Math.max(0, currentOffset - indentedText.length);
currentSelection.anchor.set(
currentSelection.anchor.key,
selectionStart,
'text'
);
currentSelection.focus.set(currentSelection.focus.key, currentOffset, 'text');
}
});
}, 0);
return true;
} catch (e) {
// If selection operation fails, fall back to simple space insertion
const textNode = $createTextNode(' ');
selection.insertNodes([textNode]);
return true;
}
} else {
// For cursor position (no selection), insert 4 spaces
const textNode = $createTextNode(' '); // 4 spaces
selection.insertNodes([textNode]);
return true;
}
}
} catch (e) {
// If anything fails, just let the default behavior handle it
console.warn('TabToSpacesPlugin error:', e);
return false;
}
},
COMMAND_PRIORITY_EDITOR
);
}, [editor]);
return null;
}

View File

@@ -8,7 +8,7 @@
import type { DecoratorNode, Klass, LexicalEditor, LexicalNode } from 'lexical';
import type { EntityMatch } from '@lexical/text';
import { $createTextNode, $getRoot, $isTextNode, TextNode } from 'lexical';
import { $createTextNode, $isTextNode, TextNode } from 'lexical';
import { useCallback } from 'react';
import type { VariableLabelNode } from './plugins/VariableLabelPlugin/node';
import type { VariableNode } from './plugins/VariablePlugin/node';
@@ -209,36 +209,9 @@ export function textToEditorState(text = '') {
});
}
export function editorStateToText(editor: LexicalEditor) {
const editorStateTextString: string[] = [];
const paragraphs = editor.getEditorState().toJSON().root.children;
paragraphs.forEach((paragraph: any) => {
const children = paragraph.children;
const paragraphText: string[] = [];
children.forEach((child: any) => {
if (child.type === 'linebreak') {
paragraphText.push(`
`);
} else if (child.text) {
paragraphText.push(child.text);
} else if (child.type === 'variableLabel') {
paragraphText.push(child.variableKey);
} else if (child.type === 'Variable') {
paragraphText.push(child.variableKey);
}
});
editorStateTextString.push(paragraphText.join(''));
});
return editorStateTextString.join(`
`);
}
const varRegex = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
export const getVars = (value: string) => {
if (!value) return [];
// .filter((item) => {
// return ![CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT].includes(item)
// })
const keys =
value
.match(varRegex)
@@ -292,3 +265,23 @@ export function useBasicTypeaheadTriggerMatch(
[maxLength, minLength, trigger]
);
}
export function editorStateToText(editor: LexicalEditor) {
const editorStateTextString: string[] = [];
const paragraphs = editor.getEditorState().toJSON().root.children;
paragraphs.forEach((paragraph: any) => {
const children = paragraph.children || [];
const paragraphText: string[] = [];
children.forEach((child: any) => {
if (child.type === 'linebreak') {
paragraphText.push('\n');
} else if (child.text) {
paragraphText.push(child.text);
} else if (child.type === 'variableLabel' || child.type === 'Variable') {
paragraphText.push(child.variableKey);
}
});
editorStateTextString.push(paragraphText.join(''));
});
return editorStateTextString.join('\n');
}