mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-23 23:16:58 +08:00
### 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:
57
web/src/components/file-status-badge.tsx
Normal file
57
web/src/components/file-status-badge.tsx
Normal 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;
|
||||
@ -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,
|
||||
};
|
||||
|
||||
41
web/src/components/spotlight.tsx
Normal file
41
web/src/components/spotlight.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user