feat(web): add warning dot indicator on LLM panel field labels synced with checklist

Store checklist items in zustand WorkflowStore so both the checklist UI
and node panels share a single source of truth. The LLM panel reads from
the store to show a Figma-aligned warning dot (absolute-positioned, no
layout shift) on the MODEL field label when the node has checklist warnings.
This commit is contained in:
yyh
2026-03-09 16:37:47 +08:00
parent 2d979e2cec
commit 694ca840e1
5 changed files with 18 additions and 11 deletions

View File

@ -104,6 +104,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const appMode = useAppStore.getState().appDetail?.mode
const shouldCheckStartNode = appMode === AppModeEnum.WORKFLOW || appMode === AppModeEnum.ADVANCED_CHAT
const modelProviders = useProviderContextSelector(s => s.modelProviders)
const workflowStore = useWorkflowStore()
const map = useNodesAvailableVarList(nodes)
const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)
@ -261,8 +262,9 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
}
})
workflowStore.setState({ checklistItems: list })
return list
}, [nodes, edges, shouldCheckStartNode, nodesExtraData, buildInTools, customTools, workflowTools, language, dataSourceList, triggerPlugins, getToolIcon, strategyProviders, getCheckData, t, map, modelProviders])
}, [nodes, edges, shouldCheckStartNode, nodesExtraData, buildInTools, customTools, workflowTools, language, dataSourceList, triggerPlugins, getToolIcon, strategyProviders, getCheckData, t, map, modelProviders, workflowStore])
return needWarningNodes
}

View File

@ -18,6 +18,7 @@ type Props = {
operations?: React.JSX.Element
inline?: boolean
required?: boolean
warningDot?: boolean
}
const Field: FC<Props> = ({
@ -30,6 +31,7 @@ const Field: FC<Props> = ({
inline,
supportFold,
required,
warningDot,
}) => {
const [fold, {
toggle: toggleFold,
@ -41,7 +43,10 @@ const Field: FC<Props> = ({
className={cn('flex items-center justify-between', supportFold && 'cursor-pointer')}
>
<div className="flex h-6 items-center">
<div className={cn(isSubTitle ? 'system-xs-medium-uppercase text-text-tertiary' : 'system-sm-semibold-uppercase text-text-secondary')}>
<div className={cn('relative', isSubTitle ? 'text-text-tertiary system-xs-medium-uppercase' : 'text-text-secondary system-sm-semibold-uppercase')}>
{warningDot && (
<span className="absolute -left-[9px] top-1/2 size-[5px] -translate-y-1/2 rounded-full bg-text-warning-secondary" />
)}
{title}
{' '}
{required && <span className="text-text-destructive">*</span>}

View File

@ -15,6 +15,7 @@ import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/compo
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
import { useStore } from '@/app/components/workflow/store'
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
import ConfigVision from '../_base/components/config-vision'
import MemoryConfig from '../_base/components/memory-config'
@ -31,6 +32,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
data,
}) => {
const { t } = useTranslation()
const hasChecklistWarning = useStore(s => s.checklistItems.some(item => item.id === id))
const {
readOnly,
inputs,
@ -102,6 +104,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
<Field
title={t(`${i18nPrefix}.model`, { ns: 'workflow' })}
required
warningDot={hasChecklistWarning}
>
<ModelParameterModal
popupClassName="!w-[387px]"
@ -264,8 +267,8 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
noDecoration
popupContent={(
<div className="w-[232px] rounded-xl border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-4 py-3.5 shadow-lg backdrop-blur-[5px]">
<div className="title-xs-semi-bold text-text-primary">{t('structOutput.modelNotSupported', { ns: 'app' })}</div>
<div className="body-xs-regular mt-1 text-text-secondary">{t('structOutput.modelNotSupportedTip', { ns: 'app' })}</div>
<div className="text-text-primary title-xs-semi-bold">{t('structOutput.modelNotSupported', { ns: 'app' })}</div>
<div className="mt-1 text-text-secondary body-xs-regular">{t('structOutput.modelNotSupportedTip', { ns: 'app' })}</div>
</div>
)}
>
@ -274,7 +277,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
</div>
</Tooltip>
)}
<div className="system-xs-medium-uppercase mr-0.5 text-text-tertiary">{t('structOutput.structured', { ns: 'app' })}</div>
<div className="mr-0.5 text-text-tertiary system-xs-medium-uppercase">{t('structOutput.structured', { ns: 'app' })}</div>
<Tooltip popupContent={
<div className="max-w-[150px]">{t('structOutput.structuredTip', { ns: 'app' })}</div>
}

View File

@ -1,4 +1,5 @@
import type { StateCreator } from 'zustand'
import type { ChecklistItem } from '@/app/components/workflow/hooks/use-checklist'
import type {
VariableAssignerNodeType,
} from '@/app/components/workflow/nodes/variable-assigner/types'
@ -10,6 +11,7 @@ import type {
} from '@/types/workflow'
export type NodeSliceShape = {
checklistItems: ChecklistItem[]
showSingleRunPanel: boolean
setShowSingleRunPanel: (showSingleRunPanel: boolean) => void
nodeAnimation: boolean
@ -56,6 +58,7 @@ export type NodeSliceShape = {
}
export const createNodeSlice: StateCreator<NodeSliceShape> = set => ({
checklistItems: [],
showSingleRunPanel: false,
setShowSingleRunPanel: showSingleRunPanel => set(() => ({ showSingleRunPanel })),
nodeAnimation: false,

View File

@ -6678,9 +6678,6 @@
"app/components/workflow/nodes/_base/components/field.tsx": {
"no-restricted-imports": {
"count": 1
},
"tailwindcss/enforce-consistent-class-order": {
"count": 2
}
},
"app/components/workflow/nodes/_base/components/file-type-item.tsx": {
@ -7937,9 +7934,6 @@
"app/components/workflow/nodes/llm/panel.tsx": {
"no-restricted-imports": {
"count": 1
},
"tailwindcss/enforce-consistent-class-order": {
"count": 3
}
},
"app/components/workflow/nodes/llm/use-config.ts": {