From 3444d7cff4ee1d960381d66332ad2d1029ba396a Mon Sep 17 00:00:00 2001 From: ikun97 Date: Fri, 20 Sep 2024 19:58:50 +0800 Subject: [PATCH] Dev missing item tips (#17) --- .../workflow/import-modal/index.tsx | 23 ++++-------------- .../components/workflow/node-card/index.tsx | 19 ++++++++++++--- .../components/workflow/node-form/index.tsx | 15 +++++++++++- .../workflow/nodes/widget-node/index.tsx | 24 +++++++++++++++++-- .../src/stores/workflow/schema-provider.tsx | 10 ++++---- .../src/stores/workflow/workflow-store.tsx | 20 +++++++++++++++- .../src/components/control/index.tsx | 19 ++++++++++++++- .../src/components/provider/index.tsx | 6 ++++- web/packages/form-engine/src/index.tsx | 3 +++ 9 files changed, 107 insertions(+), 32 deletions(-) diff --git a/web/apps/web/src/components/workflow/import-modal/index.tsx b/web/apps/web/src/components/workflow/import-modal/index.tsx index c5b0745c..98076943 100644 --- a/web/apps/web/src/components/workflow/import-modal/index.tsx +++ b/web/apps/web/src/components/workflow/import-modal/index.tsx @@ -39,7 +39,6 @@ const ImportModal: React.FC<{ onOpenChange: (open: boolean) => void; }> = ({ open, onOpenChange }) => { const [openTips, setOpenTips] = useBoolean(false); - const [isExpand, isExpandAction] = useBoolean(false); const [fileUrl, setFileUrl] = useState(); const [workflow, setWorkflow] = useState(); const { existedInfo, importWorkflow, setExistedInfo } = useWorkflowStore( @@ -204,31 +203,17 @@ const ImportModal: React.FC<{ - Warning + Missing Items The following items are missing.
- Replace it with installed models/widgets, or contact us to add - it to the standard environment. + If this workflow would run in MyShell, please replace the + missing models/widgets with similar installed ones, or contact + us to add them to the standard environment.
-
isExpandAction.toggle()}> - - {isExpand ? 'Show Less' : 'View Detail'} - - -
{!isEmpty(existedInfo.non_existed_models) ? (
diff --git a/web/apps/web/src/components/workflow/node-card/index.tsx b/web/apps/web/src/components/workflow/node-card/index.tsx index 5b005804..9053d6ad 100644 --- a/web/apps/web/src/components/workflow/node-card/index.tsx +++ b/web/apps/web/src/components/workflow/node-card/index.tsx @@ -14,9 +14,19 @@ const NodeCard: React.FC< NodeData & { children: React.ReactNode; selected?: boolean; + has_error?: boolean; mode?: string; } -> = ({ id, children, runtime_data, selected, name, display_name, mode }) => { +> = ({ + id, + children, + runtime_data, + selected, + name, + display_name, + mode, + has_error, +}) => { const selectRef = useRef(null); const active = useHover(selectRef); const isUndefined = mode === 'undefined'; @@ -35,7 +45,9 @@ const NodeCard: React.FC< }, { '!border-critical': - runtime_data?.node_status === NodeStatusEnum.failed || isUndefined, + runtime_data?.node_status === NodeStatusEnum.failed || + isUndefined || + has_error, }, { '!border-brand': runtime_data?.node_status === NodeStatusEnum.start, @@ -48,13 +60,14 @@ const NodeCard: React.FC< {isUndefined ? (
- Undefined Widget + Missing Widget Replace it with installed widgets, or contact us to add it to diff --git a/web/apps/web/src/components/workflow/node-form/index.tsx b/web/apps/web/src/components/workflow/node-form/index.tsx index 596f2c01..85120a00 100644 --- a/web/apps/web/src/components/workflow/node-form/index.tsx +++ b/web/apps/web/src/components/workflow/node-form/index.tsx @@ -44,10 +44,22 @@ interface NodeFormProps { loading?: boolean; modeMap?: Record; onModeChange?: (name: string, mode: TFieldMode) => void; + onStatusChange?: (status: { [key: string]: string }) => void; } const NodeForm = forwardRef( - ({ values, onChange, schema, loading, onModeChange, modeMap }, ref) => { + ( + { + values, + onChange, + schema, + loading, + onModeChange, + onStatusChange, + modeMap, + }, + ref, + ) => { const { schema: formSchema, formKey } = useSchemaContext(state => ({ schema: state.schema, formKey: state.formKey, @@ -78,6 +90,7 @@ const NodeForm = forwardRef( schema={currentSchema} modeMap={modeMap} onModeChange={onModeChange} + onStatusChange={onStatusChange} components={{ Input, Select, diff --git a/web/apps/web/src/components/workflow/nodes/widget-node/index.tsx b/web/apps/web/src/components/workflow/nodes/widget-node/index.tsx index 1f4e1693..e59df964 100644 --- a/web/apps/web/src/components/workflow/nodes/widget-node/index.tsx +++ b/web/apps/web/src/components/workflow/nodes/widget-node/index.tsx @@ -6,7 +6,13 @@ import { import { TValues, TFieldMode } from '@shellagent/form-engine'; import { FormRef } from '@shellagent/ui'; import { useKeyPress } from 'ahooks'; -import React, { useCallback, useRef, useEffect } from 'react'; +import React, { + useCallback, + useRef, + useEffect, + useState, + useMemo, +} from 'react'; import { useShallow } from 'zustand/react/shallow'; import { useWorkflowState } from '@/stores/workflow/use-workflow-state'; @@ -21,6 +27,7 @@ import { useDuplicateState } from './hook/use-duplicate-state'; import { EventType, useEventEmitter } from '../../emitter'; import NodeCard from '../../node-card'; import NodeForm from '../../node-form'; +import { some } from 'lodash-es'; const WidgetNode: React.FC> = ({ id, @@ -29,6 +36,7 @@ const WidgetNode: React.FC> = ({ }) => { const formRef = useRef(null); const nodeRef = useRef(null); + const statusRef = useRef({}); const { duplicateState } = useDuplicateState(id); const { onDelNode } = useReactFlowStore(state => ({ onDelNode: state.onDelNode, @@ -37,6 +45,8 @@ const WidgetNode: React.FC> = ({ setCurrentCopyId: state.setCurrentCopyId, })); + const [hasError, setHasError] = useState(false); + const { setNodeData, nodeData, @@ -144,14 +154,24 @@ const WidgetNode: React.FC> = ({ } }); + const onStatusChange = (obj: { [key: string]: string }) => { + statusRef.current = { ...statusRef.current, ...obj }; + setHasError(some(statusRef.current, value => value === 'missOption')); + }; + return ( - + value === 'missOption')} + {...data}> diff --git a/web/apps/web/src/stores/workflow/schema-provider.tsx b/web/apps/web/src/stores/workflow/schema-provider.tsx index 3723f4a3..24e3e3b4 100644 --- a/web/apps/web/src/stores/workflow/schema-provider.tsx +++ b/web/apps/web/src/stores/workflow/schema-provider.tsx @@ -52,14 +52,16 @@ export const useSchemaContext = ( export interface SchemaProviderProps { id: string; - name: string | undefined; - display_name: string | undefined; + name?: string; + mode?: string; + display_name?: string; children: React.ReactNode | React.ReactNode[]; output?: Record; } export const SchemaProvider: React.FC = ({ name = '', + mode, display_name = '', id, children, @@ -122,7 +124,7 @@ export const SchemaProvider: React.FC = ({ memoized.schema = endSchema; } else if (!isEmpty(currentWidgetSchema)) { const schema = getSchemaByWidget({ - name: display_name, + name: mode === 'undefined' ? name : display_name, inputSchema: currentWidgetSchema?.input_schema, outputSchema: currentWidgetSchema?.output_schema, fieldsModeMap: currentFieldsModeMap, @@ -142,7 +144,7 @@ export const SchemaProvider: React.FC = ({ outputs: memoized.outputRefTypes, }); return memoized; - }, [id, name, display_name, currentWidgetSchema, currentFieldsModeMap]); + }, [id, mode, name, display_name, currentWidgetSchema, currentFieldsModeMap]); const formKey = useMemo(() => { return `${JSON.stringify({ diff --git a/web/apps/web/src/stores/workflow/workflow-store.tsx b/web/apps/web/src/stores/workflow/workflow-store.tsx index 52c8f726..452498c5 100644 --- a/web/apps/web/src/stores/workflow/workflow-store.tsx +++ b/web/apps/web/src/stores/workflow/workflow-store.tsx @@ -395,7 +395,6 @@ export const createWorkflowStore = () => { ); } if (type === EventStatusEnum.workflow_end) { - get().flowInstance?.fitView(); set( produce(state => { state.loading.workflowRunning = false; @@ -453,11 +452,30 @@ export const createWorkflowStore = () => { return nodes; }); if (data?.node_status === NodeStatusEnum.failed) { + get().flowInstance?.fitView({ + minZoom: 0.8, + maxZoom: 1.5, + nodes: [{ id: data?.node_id as string }], + padding: 400, + }); + // 设置为选中状态,放到最上层 + get().flowInstance?.setNodes(nodes => + nodes.map(node => + node.id === data?.node_id + ? { + ...node, + selected: true, + } + : node, + ), + ); set( produce(state => { state.loading.workflowRunning = false; }), ); + } else { + get().flowInstance?.fitView(); } } }, diff --git a/web/packages/form-engine/src/components/control/index.tsx b/web/packages/form-engine/src/components/control/index.tsx index 6a5a9e3a..0ef87223 100644 --- a/web/packages/form-engine/src/components/control/index.tsx +++ b/web/packages/form-engine/src/components/control/index.tsx @@ -14,6 +14,7 @@ import { TooltipContent, IconButton, Switch, + Text, } from '@shellagent/ui'; import { isEmpty, omit } from 'lodash-es'; import React, { useMemo, useRef, useState } from 'react'; @@ -37,7 +38,7 @@ type Mode = 'raw' | 'ui' | 'ref'; */ const Control: React.FC = props => { const { name } = props; - const { fields, components, remove, modeMap, onModeChange } = + const { fields, components, remove, modeMap, onModeChange, onStatusChange } = useFormEngineContext(); const { setValue, getValues } = useFormContext(); const { schema, state, parent } = fields[name] || {}; @@ -284,11 +285,27 @@ const Control: React.FC = props => { if (xOnChangePropsName) { newField[xOnChangePropsName] = field.onChange; } + const missOption = + xComponentProps?.options?.length && + newField[valuePropsName] && + !xComponentProps?.options?.find( + (item: { value: string }) => + item.value === (newField[valuePropsName] as unknown as string), + ); + if (missOption !== undefined) { + onStatusChange?.({ [name]: missOption ? 'missOption' : '' }); + } const FormItemWithDesc = (!checked && xSwithable) || xHiddenControl ? null : (
{renderFormItem()} {fieldState.error ? : null} + {missOption ? ( + + {newField[valuePropsName] as unknown as string} is + undefined. + + ) : null}
); diff --git a/web/packages/form-engine/src/components/provider/index.tsx b/web/packages/form-engine/src/components/provider/index.tsx index 5e302500..b04194ce 100644 --- a/web/packages/form-engine/src/components/provider/index.tsx +++ b/web/packages/form-engine/src/components/provider/index.tsx @@ -26,6 +26,7 @@ const FormEngineContext = createContext<{ reorder: (path: TPath, startIndex: number, endIndex: number) => void; modeMap?: Record; onModeChange?: (name: string, mode: TFieldMode) => void; + onStatusChange?: (obj: { [key: string]: string }) => void; }>({ components: {}, fields: {}, @@ -42,6 +43,7 @@ export interface IFormEngineProviderProps { layout?: 'Horizontal' | 'Vertical'; modeMap?: Record; onModeChange?: (name: string, mode: TFieldMode) => void; + onStatusChange?: (obj: { [key: string]: string }) => void; children: React.ReactNode | React.ReactNode[]; } @@ -52,7 +54,8 @@ export const useFormEngineContext = () => { const Counter: { [path: string]: number } = {}; export const FormEngineProvider: React.FC = props => { - const { children, fields, components, parent, layout } = props; + const { children, fields, components, parent, layout, onStatusChange } = + props; const { getValues, setValue } = useFormContext(); const [modeMap, setModeMap] = useImmer(props.modeMap || {}); @@ -178,6 +181,7 @@ export const FormEngineProvider: React.FC = props => { reorder, modeMap, onModeChange, + onStatusChange, }}> {children} diff --git a/web/packages/form-engine/src/index.tsx b/web/packages/form-engine/src/index.tsx index 6ebc82d3..ce0751fe 100644 --- a/web/packages/form-engine/src/index.tsx +++ b/web/packages/form-engine/src/index.tsx @@ -37,6 +37,7 @@ export interface IFormEngineProps { children?: React.ReactNode; modeMap?: Record; onModeChange?: (name: string, mode: TFieldMode) => void; + onStatusChange?: (obj: { [key: string]: string }) => void; } const FormEngine = forwardRef((props, ref) => { @@ -52,6 +53,7 @@ const FormEngine = forwardRef((props, ref) => { onModeChange, onChange, onSubmit, + onStatusChange, } = props; const [fields, setFields] = useState(createFields(schema, values)); @@ -107,6 +109,7 @@ const FormEngine = forwardRef((props, ref) => {