From db820a8ff8bad02010561626d5d2ccc9ba3a5cfe Mon Sep 17 00:00:00 2001 From: ikun97 Date: Fri, 20 Sep 2024 18:41:38 +0800 Subject: [PATCH] Dev opti (#15) Co-authored-by: shanexi Co-authored-by: joe --- .github/workflows/sync-open-source-main.yaml | 47 +++++++++++++++++++ web/.husky/pre-push | 19 ++++++++ .../workflow/import-modal/index.tsx | 23 ++------- .../components/workflow/node-card/index.tsx | 25 ++++++++-- .../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 ++ 11 files changed, 177 insertions(+), 34 deletions(-) create mode 100644 .github/workflows/sync-open-source-main.yaml create mode 100644 web/.husky/pre-push diff --git a/.github/workflows/sync-open-source-main.yaml b/.github/workflows/sync-open-source-main.yaml new file mode 100644 index 00000000..06bfa3cc --- /dev/null +++ b/.github/workflows/sync-open-source-main.yaml @@ -0,0 +1,47 @@ +name: Sync open-source main + +on: + push: + branches: + - main + paths: + - "web/apps/web/**" + - "web/packages/**" + - ".github/**" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Get last commit author email + id: get_email + run: echo "COMMIT_AUTHOR_EMAIL=$(git log -n 1 --pretty=format:%ae)" >> $GITHUB_ENV + + - name: Test author email + run: echo "The commit author email is $COMMIT_AUTHOR_EMAIL" + + - name: Get last commit author name + id: get_name + run: echo "COMMIT_AUTHOR_NAME=$(git log -n 1 --pretty=format:%an)" >> $GITHUB_ENV + + - name: Test author name + run: echo "The commit author name is $COMMIT_AUTHOR_NAME" + + - uses: cpina/github-action-push-to-another-repository@main + env: + SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }} + with: + source-directory: web + target-directory: web + destination-github-username: "myshell-ai" + destination-repository-name: "open-source" + target-branch: main + create-target-branch-if-needed: false + user-name: ${{ env.COMMIT_AUTHOR_NAME }} + user-email: ${{ env.COMMIT_AUTHOR_EMAIL }} + + - name: Test get variable exported by push-to-another-repository + run: echo $DESTINATION_CLONED_DIRECTORY diff --git a/web/.husky/pre-push b/web/.husky/pre-push new file mode 100644 index 00000000..7a7fb842 --- /dev/null +++ b/web/.husky/pre-push @@ -0,0 +1,19 @@ +#!/bin/sh +# . "$(dirname "$0")/_/husky.sh" + + +echo "===\n>> Pre-push Hook: Checking branch name..." + +cd web + +BRANCH=`git rev-parse --abbrev-ref HEAD` +PROTECTED_BRANCHES="^(main|test)" + +if [[ "$BRANCH" =~ $PROTECTED_BRANCHES ]] +then + echo "\n🚫 Cannot push to remote $BRANCH branch, please create your own branch and use PR." +fi + +echo ">> Finish checking branch name.\n===" + +exit 0 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..8c13ab4a 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,17 +60,20 @@ const NodeCard: React.FC< {isUndefined ? (
- Undefined Widget + Missing Widget + {/* Missing Model */} +
- Replace it with installed widgets, or contact us to add it to - the standard environment. + Replace it with installed widgets/models, or contact us to add + it to the standard environment.
) : null} 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) => {