mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 18:27:19 +08:00
fix(web): style of default metric
This commit is contained in:
parent
2607eb8d32
commit
b420298398
@ -9,7 +9,8 @@ import {
|
||||
} from '@langgenius/dify-ui/popover'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getNodeVisual, getToneClasses } from '@/app/components/evaluation/components/metric-selector/utils'
|
||||
import { getEvaluationNodeBlockType } from '@/app/components/evaluation/components/metric-selector/utils'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
|
||||
type EvaluationCellProps = {
|
||||
evaluation: EvaluationLogItem[]
|
||||
@ -60,37 +61,34 @@ const EvaluationCell = ({
|
||||
popupClassName="w-[320px] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border p-0 shadow-[0px_12px_16px_-4px_rgba(9,9,11,0.08),0px_4px_6px_-2px_rgba(9,9,11,0.03)]"
|
||||
>
|
||||
<div data-testid="workflow-log-evaluation-popover" className="max-h-[320px] overflow-y-auto bg-components-panel-bg">
|
||||
{evaluation.map((item, index) => {
|
||||
const nodeVisual = item.nodeInfo ? getNodeVisual(item.nodeInfo) : null
|
||||
const nodeToneClasses = nodeVisual ? getToneClasses(nodeVisual.tone) : null
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`${item.name}-${index}`}
|
||||
className={cn(
|
||||
'grid grid-cols-[minmax(0,1fr)_auto] gap-3 px-4 py-3',
|
||||
index !== evaluation.length - 1 && 'border-b border-divider-subtle',
|
||||
{evaluation.map((item, index) => (
|
||||
<div
|
||||
key={item.nodeInfo ? `${item.name}-${item.nodeInfo.node_id}` : item.name}
|
||||
className={cn(
|
||||
'grid grid-cols-[minmax(0,1fr)_auto] gap-3 px-4 py-3',
|
||||
index !== evaluation.length - 1 && 'border-b border-divider-subtle',
|
||||
)}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate system-sm-medium text-text-secondary">{item.name}</div>
|
||||
{item.nodeInfo && (
|
||||
<div className="mt-1 flex min-w-0 items-center gap-1.5">
|
||||
<BlockIcon
|
||||
type={getEvaluationNodeBlockType(item.nodeInfo)}
|
||||
size="xs"
|
||||
className="h-[18px] w-[18px] shrink-0"
|
||||
/>
|
||||
<span className="truncate system-xs-regular text-text-tertiary">
|
||||
{item.nodeInfo.title}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate system-sm-medium text-text-secondary">{item.name}</div>
|
||||
{item.nodeInfo && nodeVisual && nodeToneClasses && (
|
||||
<div className="mt-1 flex min-w-0 items-center gap-1.5">
|
||||
<div className={cn('flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-md border-[0.45px] border-divider-subtle shadow-xs shadow-shadow-shadow-3', nodeToneClasses.solid)}>
|
||||
<span aria-hidden="true" className={cn(nodeVisual.icon, 'h-3.5 w-3.5')} />
|
||||
</div>
|
||||
<span className="truncate system-xs-regular text-text-tertiary">
|
||||
{item.nodeInfo.title}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-[120px] text-right system-sm-regular wrap-break-word text-text-secondary">
|
||||
{formatEvaluationValue(item.value)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="max-w-[120px] text-right system-sm-regular wrap-break-word text-text-secondary">
|
||||
{formatEvaluationValue(item.value)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@ -12,8 +12,9 @@ import {
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import { useEvaluationStore } from '../../store'
|
||||
import { dedupeNodeInfoList, getMetricVisual, getNodeVisual, getToneClasses } from '../metric-selector/utils'
|
||||
import { dedupeNodeInfoList, getEvaluationNodeBlockType, getMetricVisual, getToneClasses } from '../metric-selector/utils'
|
||||
|
||||
type BuiltinMetricCardProps = EvaluationResourceProps & {
|
||||
metric: EvaluationMetric
|
||||
@ -41,7 +42,7 @@ const BuiltinMetricCard = ({
|
||||
|
||||
return (
|
||||
<div className="group overflow-hidden rounded-xl border border-components-panel-border hover:bg-background-section">
|
||||
<div className="flex items-center justify-between gap-3 px-3 pt-3 pb-1">
|
||||
<div className={cn('flex items-center justify-between gap-3 px-3 pt-3', isExpanded ? 'pb-1' : 'pb-3')}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex min-w-0 flex-1 items-center gap-2 px-1 text-left"
|
||||
@ -76,17 +77,16 @@ const BuiltinMetricCard = ({
|
||||
<div className="flex flex-wrap gap-1 px-3 pt-1 pb-3">
|
||||
{selectedNodeInfoList.length
|
||||
? selectedNodeInfoList.map((nodeInfo) => {
|
||||
const nodeVisual = getNodeVisual(nodeInfo)
|
||||
const nodeToneClasses = getToneClasses(nodeVisual.tone)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={nodeInfo.node_id}
|
||||
className="inline-flex min-w-[18px] items-center rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark p-1.5 shadow-xs"
|
||||
>
|
||||
<div className={cn('flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-md border-[0.45px] border-divider-subtle shadow-xs shadow-shadow-shadow-3', nodeToneClasses.solid)}>
|
||||
<span aria-hidden="true" className={cn(nodeVisual.icon, 'h-3.5 w-3.5')} />
|
||||
</div>
|
||||
<BlockIcon
|
||||
type={getEvaluationNodeBlockType(nodeInfo)}
|
||||
size="xs"
|
||||
className="h-[18px] w-[18px] shrink-0"
|
||||
/>
|
||||
<span className="px-1 system-xs-regular text-text-primary">{nodeInfo.title}</span>
|
||||
<button
|
||||
type="button"
|
||||
@ -99,7 +99,7 @@ const BuiltinMetricCard = ({
|
||||
selectedNodeInfoList.filter(item => item.node_id !== nodeInfo.node_id),
|
||||
)}
|
||||
>
|
||||
<span aria-hidden="true" className="i-ri-close-line h-3.5 w-3.5" />
|
||||
<span aria-hidden="true" className="i-custom-vender-solid-general-x-circle h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
@ -126,9 +126,6 @@ const BuiltinMetricCard = ({
|
||||
popupClassName="w-[252px] rounded-md border-[0.5px] border-components-panel-border py-1 shadow-[0px_12px_16px_-4px_rgba(9,9,11,0.08),0px_4px_6px_-2px_rgba(9,9,11,0.03)]"
|
||||
>
|
||||
{selectableNodeInfoList.map((nodeInfo) => {
|
||||
const nodeVisual = getNodeVisual(nodeInfo)
|
||||
const nodeToneClasses = getToneClasses(nodeVisual.tone)
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={nodeInfo.node_id}
|
||||
@ -141,9 +138,11 @@ const BuiltinMetricCard = ({
|
||||
)}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2.5 pr-1">
|
||||
<div className={cn('flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-md border-[0.45px] border-divider-subtle shadow-xs shadow-shadow-shadow-3', nodeToneClasses.solid)}>
|
||||
<span aria-hidden="true" className={cn(nodeVisual.icon, 'h-3.5 w-3.5')} />
|
||||
</div>
|
||||
<BlockIcon
|
||||
type={getEvaluationNodeBlockType(nodeInfo)}
|
||||
size="xs"
|
||||
className="h-[18px] w-[18px] shrink-0"
|
||||
/>
|
||||
<span className="truncate system-sm-medium text-text-secondary">{nodeInfo.title}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
|
||||
@ -5,9 +5,9 @@ type SelectorEmptyStateProps = {
|
||||
const EmptySearchStateIcon = () => {
|
||||
return (
|
||||
<div className="relative h-8 w-8 text-text-quaternary">
|
||||
<span aria-hidden="true" className="i-ri-search-line absolute bottom-0 right-0 h-6 w-6" />
|
||||
<span aria-hidden="true" className="absolute left-0 top-[9px] h-[2px] w-[7px] rounded-full bg-current opacity-80" />
|
||||
<span aria-hidden="true" className="absolute left-0 top-[16px] h-[2px] w-[4px] rounded-full bg-current opacity-80" />
|
||||
<span aria-hidden="true" className="absolute right-0 bottom-0 i-ri-search-line h-6 w-6" />
|
||||
<span aria-hidden="true" className="absolute top-[9px] left-0 h-[2px] w-[7px] rounded-full bg-current opacity-80" />
|
||||
<span aria-hidden="true" className="absolute top-[16px] left-0 h-[2px] w-[4px] rounded-full bg-current opacity-80" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -18,7 +18,7 @@ const SelectorEmptyState = ({
|
||||
return (
|
||||
<div className="flex h-full min-h-[524px] flex-col items-center justify-center gap-2 px-4 pb-20 text-center">
|
||||
<EmptySearchStateIcon />
|
||||
<div className="text-text-secondary system-sm-regular">{message}</div>
|
||||
<div className="system-sm-regular text-text-secondary">{message}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,7 +2,8 @@ import type { TFunction } from 'i18next'
|
||||
import type { EvaluationMetric } from '../../types'
|
||||
import type { MetricSelectorSection } from './types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { getMetricVisual, getNodeVisual, getToneClasses } from './utils'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import { getEvaluationNodeBlockType, getMetricVisual, getToneClasses } from './utils'
|
||||
|
||||
type SelectorMetricSectionProps = {
|
||||
section: MetricSelectorSection
|
||||
@ -56,7 +57,7 @@ const SelectorMetricSection = ({
|
||||
<span aria-hidden="true" className={cn(metricVisual.icon, 'h-3.5 w-3.5')} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="system-xs-medium-uppercase truncate text-text-secondary">{metric.label}</span>
|
||||
<span className="truncate system-xs-medium-uppercase text-text-secondary">{metric.label}</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={cn('i-ri-arrow-down-s-line h-4 w-4 text-text-quaternary transition-transform', !isExpanded && '-rotate-90')}
|
||||
@ -72,13 +73,11 @@ const SelectorMetricSection = ({
|
||||
{isExpanded && (
|
||||
<div className="px-1 py-1">
|
||||
{hasNoNodeInfo && (
|
||||
<div className="system-sm-regular px-3 pt-0.5 pb-2 text-text-tertiary">
|
||||
<div className="px-3 pt-0.5 pb-2 system-sm-regular text-text-tertiary">
|
||||
{t('metrics.noNodesInWorkflow')}
|
||||
</div>
|
||||
)}
|
||||
{shownNodes.map((nodeInfo) => {
|
||||
const nodeVisual = getNodeVisual(nodeInfo)
|
||||
const nodeToneClasses = getToneClasses(nodeVisual.tone)
|
||||
const isAdded = addedMetric
|
||||
? addedMetric.nodeInfoList?.length
|
||||
? selectedNodeIds.has(nodeInfo.node_id)
|
||||
@ -91,21 +90,23 @@ const SelectorMetricSection = ({
|
||||
data-testid={`evaluation-metric-node-${metric.id}-${nodeInfo.node_id}`}
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex w-full items-center gap-1 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-state-base-hover-alt',
|
||||
'flex w-full items-center gap-1 rounded-md px-3 py-1.5 text-left transition-colors hover:bg-state-base-hover-alt',
|
||||
isAdded && 'opacity-50',
|
||||
)}
|
||||
onClick={() => onToggleNodeSelection(metric.id, nodeInfo)}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2.5 pr-1">
|
||||
<div className={cn('flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-md border-[0.45px] border-divider-subtle shadow-xs shadow-shadow-shadow-3', nodeToneClasses.solid)}>
|
||||
<span aria-hidden="true" className={cn(nodeVisual.icon, 'h-3.5 w-3.5')} />
|
||||
</div>
|
||||
<BlockIcon
|
||||
type={getEvaluationNodeBlockType(nodeInfo)}
|
||||
size="xs"
|
||||
className="h-[18px] w-[18px] shrink-0"
|
||||
/>
|
||||
<span className="truncate text-[13px] leading-4 font-medium text-text-secondary">
|
||||
{nodeInfo.title}
|
||||
</span>
|
||||
</div>
|
||||
{isAdded && (
|
||||
<span className="system-xs-regular shrink-0 px-1 text-text-quaternary">{t('metrics.added')}</span>
|
||||
<span className="shrink-0 px-1 system-xs-regular text-text-quaternary">{t('metrics.added')}</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
@ -120,7 +121,7 @@ const SelectorMetricSection = ({
|
||||
<div className="flex items-center px-1 text-text-tertiary">
|
||||
<span aria-hidden="true" className={cn(isShowingAllNodes ? 'i-ri-subtract-line' : 'i-ri-more-line', 'h-4 w-4')} />
|
||||
</div>
|
||||
<span className="system-xs-regular truncate text-text-tertiary">
|
||||
<span className="truncate system-xs-regular text-text-tertiary">
|
||||
{isShowingAllNodes ? t('metrics.showLess') : t('metrics.showMore')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { ConditionMetricValueType, MetricOption } from '../../types'
|
||||
import type { MetricVisualTone } from './types'
|
||||
import type { EvaluationDefaultMetric, NodeInfo } from '@/types/evaluation'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { getDefaultMetricDescription } from '../../default-metric-descriptions'
|
||||
|
||||
const defaultConditionMetricValueType: ConditionMetricValueType = 'number'
|
||||
@ -70,17 +71,13 @@ export const getMetricVisual = (metricId: string): { icon: string, tone: MetricV
|
||||
return { icon: 'i-ri-checkbox-circle-line', tone: 'indigo' }
|
||||
}
|
||||
|
||||
export const getNodeVisual = (nodeInfo: NodeInfo): { icon: string, tone: MetricVisualTone } => {
|
||||
const normalizedType = nodeInfo.type.toLowerCase()
|
||||
const normalizedTitle = nodeInfo.title.toLowerCase()
|
||||
const workflowBlockTypeSet = new Set<string>(Object.values(BlockEnum))
|
||||
|
||||
if (normalizedType.includes('retriev') || normalizedTitle.includes('retriev') || normalizedTitle.includes('knowledge'))
|
||||
return { icon: 'i-ri-book-open-line', tone: 'green' }
|
||||
export const getEvaluationNodeBlockType = (nodeInfo: Pick<NodeInfo, 'type'>): BlockEnum => {
|
||||
if (workflowBlockTypeSet.has(nodeInfo.type))
|
||||
return nodeInfo.type as BlockEnum
|
||||
|
||||
if (normalizedType.includes('agent') || normalizedTitle.includes('agent'))
|
||||
return { icon: 'i-ri-user-star-line', tone: 'indigo' }
|
||||
|
||||
return { icon: 'i-ri-ai-generate-2', tone: 'indigo' }
|
||||
return BlockEnum.LLM
|
||||
}
|
||||
|
||||
export const getToneClasses = (tone: MetricVisualTone) => {
|
||||
|
||||
@ -110,7 +110,7 @@
|
||||
"metrics.groups.operations": "Operations",
|
||||
"metrics.groups.other": "Other",
|
||||
"metrics.groups.quality": "Quality",
|
||||
"metrics.noNodesInWorkflow": "No LLM nodes in this workflow",
|
||||
"metrics.noNodesInWorkflow": "No selectable nodes",
|
||||
"metrics.noResults": "No metrics or nodes were found",
|
||||
"metrics.nodesAll": "All nodes",
|
||||
"metrics.nodesLabel": "Node Scope",
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
"metrics.groups.operations": "运行",
|
||||
"metrics.groups.other": "其他",
|
||||
"metrics.groups.quality": "质量",
|
||||
"metrics.noNodesInWorkflow": "当前工作流中没有 LLM 节点",
|
||||
"metrics.noNodesInWorkflow": "没有可选节点",
|
||||
"metrics.noResults": "没有匹配的指标。",
|
||||
"metrics.nodesAll": "全部节点",
|
||||
"metrics.nodesLabel": "节点范围",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user