setup(frontend): rename frontend-react

This commit is contained in:
Nex Zhu
2025-11-20 09:41:20 +08:00
parent e40cdd1dee
commit 4fa5504697
195 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,286 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { SxProps } from '@mui/material';
import Box from '@mui/material/Box';
import EditIcon from '@mui/icons-material/Edit';
import TextField from '@mui/material/TextField';
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
import MarkdownBlock from '@/components/MarkdownBlock';
import { globalStorage } from '@/storage';
export interface IObjectNodeProps {
name: string;
content: string;
_ref?: React.RefObject<HTMLDivElement | HTMLElement>;
style?: SxProps;
editObjectName?: string;
stepId: string;
handleExpand?: () => void;
}
export default observer(
({
style = {},
name,
content,
_ref,
editObjectName,
stepId,
handleExpand,
}: IObjectNodeProps) => {
const inputStringRef = React.useRef<string>('');
const [edit, setEdit] = React.useState(false);
const [expand, setExpand] = React.useState(false);
const refDetail = React.useRef<HTMLDivElement>(null);
// 使用useEffect来更新detail容器的高度
React.useEffect(() => {
if (refDetail.current) {
refDetail.current.style.height = expand
? `${refDetail.current.scrollHeight}px`
: '0px';
}
if (handleExpand) {
let count = 0;
const intervalId = setInterval(() => {
handleExpand();
count++;
if (count >= 20) {
clearInterval(intervalId);
}
}, 10);
}
}, [expand]);
return (
<Box
sx={{
userSelect: 'none',
borderRadius: '8px',
background: '#F6F6F6',
padding: '10px',
border: '2px solid #E5E5E5',
fontSize: '14px',
position: 'relative',
...(stepId
? {
cursor: 'pointer',
transition: 'all 200ms ease-in-out',
'&:hover': {
border: '2px solid #0002',
backgroundImage: 'linear-gradient(0, #0000000A, #0000000A)',
},
}
: {}),
...style,
}}
ref={_ref}
onClick={() => {
if (stepId) {
globalStorage.setFocusingStepTaskId(stepId);
}
}}
>
<Box component="span" sx={{ fontWeight: 800 }}>
{name}
</Box>
{content && !editObjectName ? (
<Box
ref={refDetail}
sx={{
overflow: 'hidden',
transition: 'height 200ms ease-out', // 添加过渡效果
}}
>
{expand ? (
<MarkdownBlock
text={content}
style={{
marginTop: '5px',
borderRadius: '6px',
padding: '4px',
background: '#E6E6E6',
fontSize: '12px',
maxHeight: '240px',
overflowY: 'auto',
border: '1px solid #0003',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
marginBottom: '0',
}}
/>
) : (
<></>
)}
<Box
onClick={e => {
setExpand(v => !v);
e.stopPropagation();
}}
sx={{
position: 'absolute',
right: '18px',
bottom: '14px',
cursor: 'pointer',
userSelect: 'none',
height: '14px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#0002',
borderRadius: '8px',
marginLeft: '4px',
padding: '0 4px',
'&:hover': {
background: '#0003',
},
}}
>
{expand ? (
<UnfoldLessIcon
sx={{ fontSize: '16px', transform: 'rotate(90deg)' }}
/>
) : (
<MoreHorizIcon sx={{ fontSize: '16px' }} />
)}
</Box>
</Box>
) : (
<></>
)}
{editObjectName ? (
<Box sx={{ position: 'relative' }}>
{edit ? (
<>
<TextField
fullWidth
multiline
rows={1}
inputRef={ele => {
if (ele) {
ele.value = inputStringRef.current;
}
}}
onChange={event =>
(inputStringRef.current = event.target.value)
}
size="small"
sx={{
fontSize: '12px',
paddingTop: '10px',
paddingBottom: '10px',
borderRadius: '10px',
border: 'none !important',
borderBottom: 'none !important',
'&::before': {
borderBottom: 'none !important',
border: 'none !important',
},
'& > .MuiInputBase-root': {
border: 'none',
background: '#0001',
'& > .MuiOutlinedInput-notchedOutline': {
border: 'none !important',
},
},
}}
/>
<Box
onClick={() => {
setEdit(false);
globalStorage.form.inputs[editObjectName] =
inputStringRef.current;
}}
sx={{
position: 'absolute',
right: '8px',
bottom: '2px',
cursor: 'pointer',
userSelect: 'none',
height: '18px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backdropFilter: 'contrast(0.6)',
borderRadius: '3px',
marginLeft: '4px',
}}
>
<CheckIcon sx={{ fontSize: '18px', color: '#1d7d09' }} />
</Box>
<Box
onClick={() => setEdit(false)}
sx={{
position: 'absolute',
right: '34px',
bottom: '2px',
cursor: 'pointer',
userSelect: 'none',
height: '18px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backdropFilter: 'contrast(0.6)',
borderRadius: '3px',
marginLeft: '4px',
}}
>
<CloseIcon sx={{ fontSize: '18px', color: '#8e0707' }} />
</Box>
</>
) : (
<>
<MarkdownBlock
text={globalStorage.form.inputs[editObjectName]}
style={{
marginTop: '5px',
borderRadius: '6px',
padding: '4px',
background: '#E6E6E6',
fontSize: '12px',
maxHeight: '240px',
overflowY: 'auto',
border: '1px solid #0003',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
marginBottom: '0',
}}
/>
<Box
onClick={() => {
inputStringRef.current =
globalStorage.form.inputs[editObjectName];
setEdit(true);
}}
sx={{
position: 'absolute',
right: '6px',
bottom: '8px',
cursor: 'pointer',
userSelect: 'none',
height: '14px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '3px',
marginLeft: '4px',
opacity: 0.5,
transition: 'opacity 200ms ease-out',
'&:hover': {
opacity: 1,
},
}}
>
<EditIcon sx={{ fontSize: '14px' }} />
</Box>
</>
)}
</Box>
) : (
<></>
)}
</Box>
);
},
);

View File

@@ -0,0 +1,289 @@
import React from 'react';
import Box from '@mui/material/Box';
import { observer } from 'mobx-react-lite';
import { globalStorage } from '@/storage';
import { ActionType, getAgentActionStyle } from '@/storage/plan';
interface ILine<T = string> {
type: T;
from: string;
to: string;
}
interface RehearsalSvgProps {
cardRefMap: Map<string, React.RefObject<HTMLElement | HTMLDivElement>>;
renderCount: number;
objectStepOrder: string[];
importantLines: ILine<ActionType>[];
actionIsHovered: string | undefined;
}
const getIOLineHeight = (nodeOrder: string[], lines: ILine[]) => {
const edgeHeightIndexMap_ = new Map<number, number[][]>();
const compareFunction = (a: ILine, b: ILine): number => {
const [afrom, ato] = [a.from, a.to];
const [bfrom, bto] = [b.from, b.to];
const afromPos = nodeOrder.indexOf(afrom);
const bfromPos = nodeOrder.indexOf(bfrom);
const atoPos = nodeOrder.indexOf(ato);
const btoPos = nodeOrder.indexOf(bto);
// 如果最小位置相同,则比较位置之间的距离
const aDistance = atoPos - afromPos;
const bDistance = btoPos - bfromPos;
if (aDistance !== bDistance) {
return aDistance - bDistance;
} else {
return afromPos - bfromPos;
}
};
lines.sort(compareFunction);
const isCrossOver = (ptPair: number[], ptList: number[][]) => {
for (const pt of ptList) {
if (pt[1] <= ptPair[0] || pt[0] >= ptPair[1]) {
continue;
}
return true;
}
return false;
};
lines.forEach(line => {
const fromIndex = nodeOrder.indexOf(line.from);
const toIndex = nodeOrder.indexOf(line.to);
let h = 1;
while (
isCrossOver([fromIndex, toIndex], edgeHeightIndexMap_.get(h) || [])
) {
h += 1;
}
edgeHeightIndexMap_.set(h, [
...(edgeHeightIndexMap_.get(h) || []),
[fromIndex, toIndex],
]);
});
const edgeHeightMap_ = new Map<string, number>();
edgeHeightIndexMap_.forEach((pairList, height) => {
// 遍历当前条目的数组将数字替换为数组b中对应的名称
pairList.forEach(pair => {
edgeHeightMap_.set(pair.map(index => nodeOrder[index]).join('.'), height);
});
});
return edgeHeightMap_;
};
const getOffset = (child: HTMLElement, parent: HTMLElement) => {
const parentRect = parent.getBoundingClientRect();
const childRect = child.getBoundingClientRect();
// 计算相对位置
return new DOMRect(
childRect.left - parentRect.left,
childRect.top - parentRect.top,
childRect.width,
childRect.height,
);
};
const calcCurve = (fromCard: DOMRect, toCard: DOMRect, height: number) => {
// calc bezier curve
// from [fromCard.left, fromCard.top+0.5*fromCard.height]
// to [toCard.left, toCard.top+0.5*toCard.height
const h = fromCard.left * height;
return `M ${fromCard.left + fromCard.width},${
fromCard.top + 0.5 * fromCard.height
}
C ${fromCard.left + fromCard.width + 1.5 * h},${
fromCard.top + 0.5 * fromCard.height
},
${toCard.left + toCard.width + 1.5 * h},${toCard.top + 0.5 * toCard.height},
${toCard.left + toCard.width},${toCard.top + 0.5 * toCard.height}`;
};
const calcPath = (objectCard: DOMRect, stepCard: DOMRect, height: number) => {
// console.log('calcPath', fromCard, toCard, height);
const fromCard = objectCard.top < stepCard.top ? objectCard : stepCard;
const toCard = objectCard.top < stepCard.top ? stepCard : objectCard;
const h = fromCard.left * height;
const ptList = [
{ x: fromCard.left, y: fromCard.top + fromCard.height - 10 },
{ x: fromCard.left - h, y: fromCard.top + fromCard.height + 10 - 10 },
{ x: toCard.left - h, y: toCard.top + 0 * toCard.height - 10 + 10 },
{ x: toCard.left, y: toCard.top + 0 * toCard.height + 10 },
];
const path = [
`M ${ptList[0].x},${ptList[0].y}`,
`L ${ptList[1].x},${ptList[1].y}`,
`L ${ptList[2].x},${ptList[2].y}`,
`L ${ptList[3].x},${ptList[3].y}`,
].join(' ');
return path;
};
export default observer(
({
cardRefMap,
renderCount,
objectStepOrder,
importantLines,
actionIsHovered,
}: RehearsalSvgProps) => {
const IOLines = globalStorage.renderingIOLines;
const edgeHeightMap = React.useMemo(
() => getIOLineHeight(objectStepOrder, IOLines),
[objectStepOrder, IOLines],
);
const [ioLineRects, setIOLineRects] = React.useState<
[ILine, DOMRect, DOMRect][]
>([]);
const [importantLineRects, setImportantLineRects] = React.useState<
[ILine, DOMRect, DOMRect][]
>([]);
const refreshCurrentIdRef = React.useRef(-1);
React.useEffect(() => {
refreshCurrentIdRef.current = (refreshCurrentIdRef.current + 1) % 100000;
const currentId = refreshCurrentIdRef.current;
const sleep = (time: number) =>
new Promise(resolve => setTimeout(resolve, time));
(async () => {
let ioAllReady = false;
let importantAllReady = false;
const ioLineRects: [ILine, DOMRect, DOMRect][] = [];
const importantLineRects: [ILine, DOMRect, DOMRect][] = [];
while (true) {
if (refreshCurrentIdRef.current !== currentId) {
return;
}
const rootElement = cardRefMap.get('root')?.current;
if (!rootElement) {
await sleep(5);
continue;
}
if (!ioAllReady) {
ioAllReady = true;
for (const line of IOLines) {
const fromElement = cardRefMap.get(line.from)?.current;
const toElement = cardRefMap.get(line.to)?.current;
if (fromElement && toElement) {
ioLineRects.push([
line,
getOffset(fromElement, rootElement),
getOffset(toElement, rootElement),
]);
} else {
ioAllReady = false;
continue;
}
}
if (!ioAllReady) {
ioLineRects.length = 0;
await sleep(5);
continue;
}
}
if (!importantAllReady) {
importantAllReady = true;
for (const line of importantLines) {
const fromElement = cardRefMap.get(line.from)?.current;
const toElement = cardRefMap.get(line.to)?.current;
if (fromElement && toElement) {
importantLineRects.push([
line,
getOffset(fromElement, rootElement),
getOffset(toElement, rootElement),
]);
} else {
importantAllReady = false;
break;
}
}
if (!importantAllReady) {
importantLineRects.length = 0;
await sleep(5);
continue;
}
}
break;
}
setIOLineRects(ioLineRects);
setImportantLineRects(importantLineRects);
})();
}, [edgeHeightMap, renderCount, cardRefMap]);
const ioLinesEle = React.useMemo(
() =>
ioLineRects.map(([line, from, to]) => {
const key = `${line.from}.${line.to}`;
const height = edgeHeightMap.get(key) || 0;
return (
<path
key={`Rehearsal.IOLine.${key}`}
fill="none"
strokeWidth="3"
stroke={line.type === 'output' ? '#FFCA8C' : '#B9DCB0'}
d={calcPath(
from,
to,
height / (Math.max(...edgeHeightMap.values()) + 1),
)}
/>
);
}),
[ioLineRects],
);
const importantLinesEle = React.useMemo(
() =>
importantLineRects
.sort((a, b) => {
// eslint-disable-next-line no-nested-ternary
return a[0].to === b[0].to
? 0
: a[0].to === actionIsHovered
? 1
: -1;
})
.map(([line, from, to]) => {
const key = `${line.from}.${line.to}`;
return (
<path
key={`Rehearsal.ImportantLine.${key}`}
fill="none"
strokeWidth="3"
stroke={
(getAgentActionStyle(line.type as any) as any).borderColor
}
d={calcCurve(from, to, 0.5)}
strokeOpacity={actionIsHovered === line.to ? 1 : 0.2}
// style={{
// ...(actionIsHovered === line.to
// ? { filter: 'brightness(110%) saturate(100%)' }
// : { filter: 'brightness(100%) saturate(20%)' }),
// }}
/>
);
}),
[importantLineRects, actionIsHovered],
);
const height = cardRefMap
.get('root')
?.current?.querySelector?.('.contents-stack')?.scrollHeight;
return (
<Box
component="svg"
sx={{
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: height ? `${height}px` : '100%',
zIndex: 999,
pointerEvents: 'none',
}}
>
<g>{ioLinesEle}</g>
<g>{importantLinesEle}</g>
</Box>
);
},
);

View File

@@ -0,0 +1,170 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { Divider, SxProps } from '@mui/material';
import Box from '@mui/material/Box';
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import MarkdownBlock from '@/components/MarkdownBlock';
import { globalStorage } from '@/storage';
import type { IExecuteStepHistoryItem } from '@/apis/execute-plan';
import AgentIcon from '@/components/AgentIcon';
import { getAgentActionStyle } from '@/storage/plan';
export interface IStepHistoryItemProps {
item: IExecuteStepHistoryItem;
actionRef: React.RefObject<HTMLDivElement | HTMLElement>;
style?: SxProps;
hoverCallback: (isHovered: boolean) => void;
handleExpand?: () => void;
}
export default observer(
({
item,
actionRef,
hoverCallback,
handleExpand,
style = {},
}: IStepHistoryItemProps) => {
const [expand, setExpand] = React.useState(false);
const refDetail = React.useRef<HTMLDivElement>(null);
// 使用useEffect来更新detail容器的高度
React.useEffect(() => {
if (refDetail.current) {
refDetail.current.style.height = expand
? `${refDetail.current.scrollHeight}px`
: '0px';
}
if (handleExpand) {
let count = 0;
const intervalId = setInterval(() => {
handleExpand();
count++;
if (count >= 20) {
clearInterval(intervalId);
}
}, 10);
}
}, [expand]);
const s = { ...getAgentActionStyle(item.type), ...style } as SxProps;
React.useEffect(() => {
console.log(item);
}, [item]);
return (
<Box
ref={actionRef}
className="step-history-item"
sx={{
userSelect: 'none',
borderRadius: '10px',
padding: '4px',
fontSize: '14px',
position: 'relative',
marginTop: '4px',
backgroundColor: (s as any).backgroundColor,
border: `2px solid ${(s as any).borderColor}`,
}}
onMouseOver={() => hoverCallback(true)}
onMouseOut={() => hoverCallback(false)}
>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<AgentIcon
name={globalStorage.agentIconMap.get(item.agent)}
style={{ height: '36px', width: 'auto', margin: '0px' }}
tooltipInfo={globalStorage.agentMap.get(item.agent)}
/>
<Box component="span" sx={{ fontWeight: 500, marginLeft: '4px' }}>
{item.agent}
</Box>
<Box component="span" sx={{ fontWeight: 400 }}>
: {item.type}
</Box>
</Box>
{item.result ? (
<Box
ref={refDetail}
sx={{
overflow: 'hidden',
transition: 'height 200ms ease-out', // 添加过渡效果
}}
>
{expand ? (
<>
<Divider
sx={{
margin: '4px 0px',
borderBottom: '2px dashed', // 设置为虚线
borderColor: '#0003',
}}
/>
<Box
sx={{
marginLeft: '6px',
marginBottom: '4px',
color: '#0009',
fontWeight: 400,
}}
>
{item.description}
</Box>
<MarkdownBlock
text={item.result}
style={{
marginTop: '5px',
borderRadius: '10px',
padding: '6px',
background: '#FFF9',
fontSize: '12px',
maxHeight: '240px',
overflowY: 'auto',
border: '1px solid #0003',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
marginBottom: '5px',
}}
/>
</>
) : (
<></>
)}
<Box
onClick={e => {
setExpand(v => !v);
e.stopPropagation();
}}
sx={{
position: 'absolute',
right: '8px',
// bottom: '12px',
top: '24px',
cursor: 'pointer',
userSelect: 'none',
height: '14px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#0002',
borderRadius: '8px',
marginLeft: '4px',
padding: '0 4px',
'&:hover': {
background: '#0003',
},
}}
>
{expand ? (
<UnfoldLessIcon
sx={{ fontSize: '16px', transform: 'rotate(90deg)' }}
/>
) : (
<MoreHorizIcon sx={{ fontSize: '16px' }} />
)}
</Box>
</Box>
) : (
<></>
)}
</Box>
);
},
);

View File

@@ -0,0 +1,86 @@
import { observer } from 'mobx-react-lite';
import { SxProps } from '@mui/material';
import Box from '@mui/material/Box';
import React from 'react';
import StepHistoryItem from './StepHistoryItem';
import type { IExecuteStepHistoryItem } from '@/apis/execute-plan';
import { globalStorage } from '@/storage';
export interface IStepNodeProps {
name: string;
history: IExecuteStepHistoryItem[];
touchRef: (
id: string,
existingRef?: React.RefObject<HTMLElement>,
) => React.RefObject<HTMLElement>;
_ref?: React.RefObject<HTMLDivElement | HTMLElement>;
style?: SxProps;
id: string;
actionHoverCallback: (actionName: string | undefined) => void;
handleExpand?: () => void;
}
export default observer(
({
style = {},
name,
history,
_ref,
touchRef,
id,
actionHoverCallback,
handleExpand,
}: IStepNodeProps) => {
return (
<Box
sx={{
userSelect: 'none',
borderRadius: '12px',
background: '#F6F6F6',
padding: '10px',
border: '2px solid #E5E5E5',
fontSize: '14px',
position: 'relative',
display: 'flex',
flexDirection: 'column',
cursor: 'pointer',
transition: 'all 200ms ease-in-out',
'&:hover': {
border: '2px solid #0002',
backgroundImage: 'linear-gradient(0, #0000000A, #0000000A)',
},
'& > .step-history-item:first-of-type': {
marginTop: '10px',
},
...style,
}}
ref={_ref}
onClick={() => {
if (id) {
globalStorage.setFocusingStepTaskId(id);
}
}}
>
<Box component="span" sx={{ fontWeight: 800 }}>
{name}
</Box>
{history.map((item, index) => (
<StepHistoryItem
// eslint-disable-next-line react/no-array-index-key
key={`${item.id}.${item.agent}.${index}`}
item={item}
actionRef={touchRef(`Action.${name}.${item.id}`)!}
hoverCallback={(isHovered: boolean) => {
// console.log(isHovered, 'hover', `action.${name}.${item.id}`);
actionHoverCallback(
isHovered ? `Action.${name}.${item.id}` : undefined,
);
}}
handleExpand={handleExpand}
/>
))}
</Box>
);
},
);

View File

@@ -0,0 +1,219 @@
import React from 'react';
import throttle from 'lodash/throttle';
import { SxProps } from '@mui/material';
import { observer } from 'mobx-react-lite';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import IconButton from '@mui/material/IconButton';
import RefreshIcon from '@mui/icons-material/Refresh';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import CircularProgress from '@mui/material/CircularProgress';
import ObjectNode from './ObjectNode';
import StepNode from './StepNode';
import RehearsalSvg from './RehearsalSvg';
import { globalStorage } from '@/storage';
import Title from '@/components/Title';
import { ExecuteNodeType } from '@/apis/execute-plan';
import { useResize } from '@/utils/resize-hook';
export default observer(({ style = {} }: { style?: SxProps }) => {
const [renderCount, setRenderCount] = React.useState(0);
const cardRefMap = React.useMemo(
() => new Map<string, React.RefObject<HTMLElement>>(),
[],
);
const touchRef = React.useCallback(
(id: string, existingRef?: React.RefObject<HTMLElement>) => {
if (existingRef) {
cardRefMap.set(id, existingRef);
return existingRef;
}
if (cardRefMap.has(id)) {
return cardRefMap.get(id)!;
} else {
cardRefMap.set(id, React.createRef<HTMLElement>());
return cardRefMap.get(id)!;
}
},
[cardRefMap],
);
const render = React.useMemo(
() =>
throttle(
() =>
// eslint-disable-next-line max-nested-callbacks
requestAnimationFrame(() => setRenderCount(old => (old + 1) % 100)),
5,
{
leading: false,
trailing: true,
},
),
[],
);
const stackRef = useResize(render);
const [actionIsHovered, setActionIsHovered] = React.useState<
string | undefined
>();
return (
<Box
sx={{
background: '#FFF',
border: '3px solid #E1E1E1',
display: 'flex',
overflow: 'hidden',
flexDirection: 'column',
width: '100%',
...style,
}}
>
<Box
sx={{
fontWeight: 600,
fontSize: '18px',
userSelect: 'none',
padding: '0 6px 0 0',
position: 'relative',
display: 'flex',
alignItems: 'center',
}}
>
<Title title="Execution Result" />
<Box
sx={{
flexGrow: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'end',
}}
>
{globalStorage.api.planExecuting ? (
<CircularProgress
sx={{
width: '12px !important',
height: '12px !important',
marginLeft: '6px',
}}
/>
) : (
<></>
)}
{globalStorage.api.planExecuting ? (
<></>
) : (
<>
<IconButton
disabled={!globalStorage.logManager.outdate}
size="small"
onClick={() => globalStorage.logManager.clearLog()}
sx={{
color: 'error.main',
'&:hover': {
color: 'error.dark',
},
}}
>
<RefreshIcon />
</IconButton>
<IconButton
disabled={!globalStorage.api.ready}
size="small"
onClick={() => globalStorage.executePlan(Infinity)}
sx={{
color: 'primary.main',
'&:hover': {
color: 'primary.dark',
},
}}
>
<PlayArrowIcon />
</IconButton>
</>
)}
</Box>
</Box>
<Box
sx={{
position: 'relative',
height: 0,
flexGrow: 1,
overflowY: 'auto',
overflowX: 'hidden',
padding: '6px 6px',
}}
onScroll={() => {
globalStorage.renderLines({ delay: 0, repeat: 2 });
}}
>
<Box sx={{ height: '100%', width: '100%' }}>
<Box sx={{ position: 'relative' }} ref={touchRef('root', stackRef)}>
<Stack
spacing="12px"
sx={{
position: 'absolute',
zIndex: 1,
marginLeft: '12px',
marginRight: '12px',
width: 'calc(100% - 24px)',
paddingBottom: '24px',
}}
className="contents-stack"
>
{Object.keys(globalStorage.form.inputs).map(name => (
<ObjectNode
key={`rehearsal.object.${name}`}
name={name}
content=""
stepId=""
editObjectName={name}
_ref={touchRef(`Object.${name}`)}
handleExpand={() => {
// render();
setRenderCount(old => (old + 1) % 100);
}}
/>
))}
{globalStorage.logManager.renderingLog.map(node =>
node.type === ExecuteNodeType.Step ? (
<StepNode
key={`rehearsal.step.${node.id}`}
id={node.stepId}
_ref={touchRef(`Step.${node.id}`, node.ref)}
name={node.id}
history={node.history}
touchRef={touchRef}
actionHoverCallback={setActionIsHovered}
handleExpand={() => {
// render();
setRenderCount(old => (old + 1) % 100);
}}
/>
) : (
<ObjectNode
key={`rehearsal.object.${node.id}`}
_ref={touchRef(`Object.${node.id}`, node.ref)}
name={node.id}
content={node.content}
stepId={node.stepId}
handleExpand={() => {
// render();
setRenderCount(old => (old + 1) % 100);
}}
/>
),
)}
</Stack>
<RehearsalSvg
renderCount={renderCount}
cardRefMap={cardRefMap}
actionIsHovered={actionIsHovered}
objectStepOrder={globalStorage.rehearsalSvgObjectOrder}
importantLines={globalStorage.rehearsalSvgLines}
/>
</Box>
</Box>
</Box>
</Box>
);
});