diff --git a/.github/workflows/stable-release.yaml b/.github/workflows/stable-release.yaml new file mode 100644 index 00000000..f456c7d2 --- /dev/null +++ b/.github/workflows/stable-release.yaml @@ -0,0 +1,167 @@ + +name: "Release To HF" + +on: + workflow_dispatch: + inputs: + git_tag: + description: 'Git Branch Tag' + required: true + type: string + default: "main" + cu: + description: 'CUDA Version' + required: true + type: string + default: "124" + python_minor: + description: 'Python Minor Version' + required: true + type: string + default: "10" + python_patch: + description: 'Python Patch Version' + required: true + type: string + default: "10" + version: + description: 'Release Version Tag' + required: true + type: string + default: "beta" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + ref: ${{ inputs.git_tag }} + + - uses: pnpm/action-setup@v2 + with: + version: 9 + run_install: false + + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "pnpm" + cache-dependency-path: "web/pnpm-lock.yaml" + + - working-directory: "./web" + run: pnpm install + + - working-directory: "./web" + run: pnpm turbo run build --filter=web + + - uses: actions/upload-artifact@v3 + with: + name: web-build + path: web/apps/web/dist + + package_shellagent_windows: + permissions: + contents: "write" + packages: "write" + pull-requests: "read" + needs: build + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_tag }} + fetch-depth: 0 + persist-credentials: false + submodules: recursive + - uses: actions/setup-python@v5 + with: + python-version: 3.${{ inputs.python_minor }}.${{ inputs.python_patch }} + + - name: Download web-build artifact + uses: actions/download-artifact@v3 + with: + name: web-build + path: ./servers/web-build + + - shell: bash + run: | + cd .. + cp -r ShellAgent ShellAgent_copy + + curl -L https://huggingface.co/XuminYu/git_embed/resolve/main/git.7z?download=true -o git.7z + "C:\Program Files\7-Zip\7z.exe" x git.7z -ogit + + curl https://www.python.org/ftp/python/3.${{ inputs.python_minor }}.${{ inputs.python_patch }}/python-3.${{ inputs.python_minor }}.${{ inputs.python_patch }}-embed-amd64.zip -o python_embeded.zip + unzip python_embeded.zip -d python_embeded + + cd python_embeded + + echo 'import site' >> ./python3${{ inputs.python_minor }}._pth + curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py + ./python.exe get-pip.py + cp -r ../ShellAgent/.ci/python_dependencies_wins/include ./include + cp -r ../ShellAgent/.ci/python_dependencies_wins/Library ./Library + cp -r ../ShellAgent/.ci/python_dependencies_wins/libs ./libs + ./python.exe -s -m pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu${{ inputs.cu }} + ./python.exe -s -m pip install --use-pep517 pygit2 Cython scikit_build_core enscons pytoml cmake==3.21 + ./python.exe -s -m pip install --use-pep517 -r ../ShellAgent/requirements.txt + ./python.exe -s -m pip install onnx==1.16.1 flask PyExecJS docx2txt fuzzywuzzy git cloudpickle + sed -i '1i../ShellAgent' ./python3${{ inputs.python_minor }}._pth + + cd .. + + mkdir ShellAgent_windows_portable + mv python_embeded ShellAgent_windows_portable + mv git ShellAgent_windows_portable + mv ShellAgent_copy ShellAgent_windows_portable/ShellAgent + + cd ShellAgent_windows_portable + cp -r ShellAgent/.ci/windows_base_files/* ./ + + cd ShellAgent + + mkdir -p models/insightface/models + ls models/insightface/ + curl -L https://huggingface.co/MonsterMMORPG/tools/resolve/main/antelopev2.zip -o models/insightface/models/antelopev2.zip + ls models/insightface/models/ + unzip models/insightface/models/antelopev2.zip -d models/insightface/models/ + + mkdir -p models/vae_approx + ls models + git clone --depth 1 https://github.com/comfyanonymous/taesd + cp taesd/*.pth models/vae_approx + + ../python_embeded/python.exe -m pip install -e . + ../python_embeded/python.exe -m pip show proconfig + # ../python_embeded/python.exe servers/main.py + + cd .. + cd .. + + "C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma2 -mx=8 -mfb=64 -md=32m -ms=on -mf=BCJ2 ShellAgent_windows_portable.7z ShellAgent_windows_portable/* + mv ShellAgent_windows_portable.7z ShellAgent/ShellAgent_windows_portable.7z + + ls + + - name: Install Hugging Face Hub SDK + run: pip install huggingface_hub + + - name: Upload binaries to Hugging Face + run: | + from huggingface_hub import HfApi + api = HfApi() + + # Set repository and file paths + repo_id = "myshell-ai/ShellAgent" + file_path = "ShellAgent_windows_portable.7z" + + # Upload the file to Hugging Face repository + api.upload_file( + path_or_fileobj=file_path, + path_in_repo="ShellAgent_${{ inputs.version }}.7z", + repo_id=repo_id, + repo_type="model", # or 'dataset' depending on your use case + token="${{ secrets.HF_TOKEN }}" + ) + shell: python 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) => {