feat: Added UI functions related to data-flow knowledge base #3221 (#10038)

### What problem does this PR solve?

feat: Added UI functions related to data-flow knowledge base #3221

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
chanx
2025-09-11 09:51:18 +08:00
committed by GitHub
parent df8d31451b
commit 8a09f07186
64 changed files with 5079 additions and 81 deletions

View File

@ -0,0 +1,57 @@
// src/pages/dataset/file-logs/file-status-badge.tsx
import { FC } from 'react';
interface StatusBadgeProps {
status: 'Success' | 'Failed' | 'Running' | 'Pending';
}
const FileStatusBadge: FC<StatusBadgeProps> = ({ status }) => {
const getStatusColor = () => {
// #3ba05c → rgb(59, 160, 92) // state-success
// #d8494b → rgb(216, 73, 75) // state-error
// #00beb4 → rgb(0, 190, 180) // accent-primary
// #faad14 → rgb(250, 173, 20) // state-warning
switch (status) {
case 'Success':
return `bg-[rgba(59,160,92,0.1)] text-state-success`;
case 'Failed':
return `bg-[rgba(216,73,75,0.1)] text-state-error`;
case 'Running':
return `bg-[rgba(0,190,180,0.1)] text-accent-primary`;
case 'Pending':
return `bg-[rgba(250,173,20,0.1)] text-state-warning`;
default:
return 'bg-gray-500/10 text-white';
}
};
const getBgStatusColor = () => {
// #3ba05c → rgb(59, 160, 92) // state-success
// #d8494b → rgb(216, 73, 75) // state-error
// #00beb4 → rgb(0, 190, 180) // accent-primary
// #faad14 → rgb(250, 173, 20) // state-warning
switch (status) {
case 'Success':
return `bg-[rgba(59,160,92,1)] text-state-success`;
case 'Failed':
return `bg-[rgba(216,73,75,1)] text-state-error`;
case 'Running':
return `bg-[rgba(0,190,180,1)] text-accent-primary`;
case 'Pending':
return `bg-[rgba(250,173,20,1)] text-state-warning`;
default:
return 'bg-gray-500/10 text-white';
}
};
return (
<span
className={`inline-flex items-center w-[75px] px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(0.1)}`}
>
<div className={`w-1 h-1 mr-1 rounded-full ${getBgStatusColor()}`}></div>
{status}
</span>
);
};
export default FileStatusBadge;

View File

@ -1,6 +1,7 @@
'use client';
import { cn } from '@/lib/utils';
import { parseColorToRGBA } from '@/utils/common-util';
import { Slot } from '@radix-ui/react-slot';
import * as React from 'react';
@ -197,7 +198,208 @@ function TimelineTitle({
);
}
interface TimelineIndicatorNodeProps {
nodeSize?: string | number;
iconColor?: string;
lineColor?: string;
textColor?: string;
indicatorBgColor?: string;
indicatorBorderColor?: string;
}
interface TimelineNode
extends Omit<
React.HTMLAttributes<HTMLDivElement>,
'id' | 'title' | 'content'
>,
TimelineIndicatorNodeProps {
id: string | number;
title?: React.ReactNode;
content?: React.ReactNode;
date?: React.ReactNode;
icon?: React.ReactNode;
completed?: boolean;
clickable?: boolean;
activeStyle?: TimelineIndicatorNodeProps;
}
interface CustomTimelineProps extends React.HTMLAttributes<HTMLDivElement> {
nodes: TimelineNode[];
activeStep?: number;
nodeSize?: string | number;
onStepChange?: (step: number, id: string | number) => void;
orientation?: 'horizontal' | 'vertical';
lineStyle?: 'solid' | 'dashed';
lineColor?: string;
indicatorColor?: string;
defaultValue?: number;
activeStyle?: TimelineIndicatorNodeProps;
}
const CustomTimeline = ({
nodes,
activeStep,
nodeSize = 12,
onStepChange,
orientation = 'horizontal',
lineStyle = 'solid',
lineColor = 'var(--text-secondary)',
indicatorColor = 'var(--accent-primary)',
defaultValue = 1,
className,
activeStyle,
...props
}: CustomTimelineProps) => {
const [internalActiveStep, setInternalActiveStep] =
React.useState(defaultValue);
const _lineColor = `rgb(${parseColorToRGBA(lineColor)})`;
console.log(lineColor, _lineColor);
const currentActiveStep = activeStep ?? internalActiveStep;
const handleStepChange = (step: number, id: string | number) => {
if (activeStep === undefined) {
setInternalActiveStep(step);
}
onStepChange?.(step, id);
};
const [r, g, b] = parseColorToRGBA(indicatorColor);
return (
<Timeline
value={currentActiveStep}
onValueChange={(step) => handleStepChange(step, nodes[step - 1]?.id)}
orientation={orientation}
className={className}
{...props}
>
{nodes.map((node, index) => {
const step = index + 1;
const isCompleted = node.completed ?? step <= currentActiveStep;
const isActive = step === currentActiveStep;
const isClickable = node.clickable ?? true;
const _activeStyle = node.activeStyle ?? (activeStyle || {});
const _nodeSizeTemp =
isActive && _activeStyle?.nodeSize
? _activeStyle?.nodeSize
: node.nodeSize ?? nodeSize;
const _nodeSize =
typeof _nodeSizeTemp === 'number'
? `${_nodeSizeTemp}px`
: _nodeSizeTemp;
console.log('icon-size', nodeSize, node.nodeSize, _nodeSize);
// const activeStyle = _activeStyle || {};
return (
<TimelineItem
key={node.id}
step={step}
className={cn(
node.className,
isClickable &&
'cursor-pointer hover:opacity-80 transition-opacity',
isCompleted && 'data-[completed]:data-completed/timeline-item',
isActive && 'relative z-10',
)}
onClick={() => isClickable && handleStepChange(step, node.id)}
>
<TimelineSeparator
className={cn(
'group-data-[orientation=horizontal]/timeline:-top-6 group-data-[orientation=horizontal]/timeline:h-0.1 group-data-[orientation=horizontal]/timeline:-translate-y-1/2',
'group-data-[orientation=vertical]/timeline:-left-6 group-data-[orientation=vertical]/timeline:w-0.1 group-data-[orientation=vertical]/timeline:-translate-x-1/2 ',
// `group-data-[orientation=horizontal]/timeline:w-[calc(100%-0.5rem-1rem)] group-data-[orientation=vertical]/timeline:h-[calc(100%-1rem-1rem)] group-data-[orientation=vertical]/timeline:translate-y-7 group-data-[orientation=horizontal]/timeline:translate-x-7`,
)}
style={{
border:
lineStyle === 'dashed'
? `1px dashed ${isActive ? _activeStyle.lineColor || _lineColor : _lineColor}`
: lineStyle === 'solid'
? `1px solid ${isActive ? _activeStyle.lineColor || _lineColor : _lineColor}`
: 'none',
backgroundColor: 'transparent',
width:
orientation === 'horizontal'
? `calc(100% - ${_nodeSize} - 2px - 0.1rem)`
: '1px',
height:
orientation === 'vertical'
? `calc(100% - ${_nodeSize} - 2px - 0.1rem)`
: '1px',
transform: `translate(${
orientation === 'horizontal' ? `${_nodeSize}` : '0'
}, ${orientation === 'vertical' ? `${_nodeSize}` : '0'})`,
}}
/>
<TimelineIndicator
className={cn(
'flex items-center justify-center p-1',
isCompleted && 'bg-primary border-primary',
!isCompleted && 'border-text-secondary bg-bg-base',
)}
style={{
width: _nodeSize,
height: _nodeSize,
borderColor: isActive
? _activeStyle.indicatorBorderColor || indicatorColor
: isCompleted
? indicatorColor
: '',
// backgroundColor: isActive
// ? _activeStyle.indicatorBgColor || indicatorColor
// : isCompleted
// ? indicatorColor
// : '',
backgroundColor: isActive
? _activeStyle.indicatorBgColor ||
`rgba(${r}, ${g}, ${b}, 0.1)`
: isCompleted
? `rgba(${r}, ${g}, ${b}, 0.1)`
: '',
}}
>
{node.icon && (
<div
className={cn(
'text-current',
`w-[${_nodeSize}] h-[${_nodeSize}]`,
isActive &&
`text-primary w-[${_activeStyle.nodeSize || _nodeSize}] h-[${_activeStyle.nodeSize || _nodeSize}]`,
)}
style={{
color: isActive ? _activeStyle.iconColor : undefined,
}}
>
{node.icon}
</div>
)}
</TimelineIndicator>
<TimelineHeader>
{node.date && <TimelineDate>{node.date}</TimelineDate>}
<TimelineTitle
className={cn(
'text-sm font-medium',
isActive && _activeStyle.textColor
? `text-${_activeStyle.textColor}`
: '',
)}
style={{
color: isActive ? _activeStyle.textColor : undefined,
}}
>
{node.title}
</TimelineTitle>
</TimelineHeader>
{node.content && <TimelineContent>{node.content}</TimelineContent>}
</TimelineItem>
);
})}
</Timeline>
);
};
CustomTimeline.displayName = 'CustomTimeline';
export {
CustomTimeline,
Timeline,
TimelineContent,
TimelineDate,
@ -206,4 +408,5 @@ export {
TimelineItem,
TimelineSeparator,
TimelineTitle,
type TimelineNode,
};

View File

@ -0,0 +1,41 @@
import { useIsDarkTheme } from '@/components/theme-provider';
import React from 'react';
interface SpotlightProps {
className?: string;
opcity?: number;
coverage?: number;
}
/**
*
* @param opcity 0~1 default 0.5
* @param coverage 0~100 default 60
* @returns
*/
const Spotlight: React.FC<SpotlightProps> = ({
className,
opcity = 0.5,
coverage = 60,
}) => {
const isDark = useIsDarkTheme();
const rgb = isDark ? '255, 255, 255' : '194, 221, 243';
return (
<div
className={`absolute inset-0 opacity-80 ${className} rounded-lg`}
style={{
backdropFilter: 'blur(30px)',
zIndex: -1,
}}
>
<div
className="absolute inset-0"
style={{
background: `radial-gradient(circle at 50% 190%, rgba(${rgb},${opcity}) 0%, rgba(${rgb},0) ${coverage}%)`,
pointerEvents: 'none',
}}
></div>
</div>
);
};
export default Spotlight;