From e37690cef091983193da1b71a237b1f10d4d91fb Mon Sep 17 00:00:00 2001 From: tiancheng Date: Wed, 8 Jan 2025 20:41:42 +0800 Subject: [PATCH 01/42] type: proconfig for X bot --- web/packages/pro-config-type/src/block.ts | 13 +++++++++++-- web/packages/pro-config-type/src/common.ts | 5 +++-- web/packages/pro-config-type/src/transition.ts | 14 +++++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/web/packages/pro-config-type/src/block.ts b/web/packages/pro-config-type/src/block.ts index 8527cb78..19335882 100644 --- a/web/packages/pro-config-type/src/block.ts +++ b/web/packages/pro-config-type/src/block.ts @@ -8,9 +8,9 @@ import { BoolExpression, } from './variables'; import { RenderConfig } from './render'; -import { Transition, SpecialEvents } from './transition'; +import { Transition, SpecialEvents, TransitionCase } from './transition'; -type BlockType = 'automata' | 'state' | 'task' | 'workflow'; +type BlockType = 'automata' | 'state' | 'task' | 'workflow' | 'dispatcher'; interface Block { /** @@ -151,3 +151,12 @@ export interface Workflow extends ContainerBlock { }; blocks: BlockChildren; // 后续支持 Workflow } + +export interface Dispatcher extends Block { + type: 'dispatcher'; + properties?: { + cache?: boolean; + }; + blocks?: BlockChildren; + transitions: { ALWAYS: TransitionCase[] }; +} \ No newline at end of file diff --git a/web/packages/pro-config-type/src/common.ts b/web/packages/pro-config-type/src/common.ts index 7d305b13..fca8e804 100644 --- a/web/packages/pro-config-type/src/common.ts +++ b/web/packages/pro-config-type/src/common.ts @@ -1,3 +1,4 @@ +import {EventPrefix} from './transition'; /** * @example * "variable_name" @@ -15,8 +16,8 @@ export type ReservedKey = | 'transitions' | 'states' | 'context' - | 'payload'; -// TODO:blocks, result + | 'payload' + | 'blocks'; /** * User-defined keys should be: diff --git a/web/packages/pro-config-type/src/transition.ts b/web/packages/pro-config-type/src/transition.ts index 34346791..bd5694e5 100644 --- a/web/packages/pro-config-type/src/transition.ts +++ b/web/packages/pro-config-type/src/transition.ts @@ -5,9 +5,9 @@ import { Button } from '.'; /* State transitions */ export type TransitionCase = { + name?: string; /** Transist to the self if unspecified */ target?: TargetPath; - // TODO(@Boyn): override the value? /** Specify the value for the inputs of target state. It will override the default value of the input, but not the value of the input. */ target_inputs?: { [input_name: CustomKey]: Value; @@ -33,12 +33,20 @@ export type AutomataSpecialEvents = 'DONE'; /** * - 'CHAT' is triggered when user inputs in IM. * - 'ALWAYS' means the state will transist as soon as its condition is satisfied after executing tasks. + * - 'X.' means X(Twitter)-related events, including being mentioned and pinned tweet being replied. */ -export type StateSpecialEvents = 'CHAT' | 'ALWAYS'; +export type StateSpecialEvents = 'CHAT' | 'ALWAYS' | 'X.MENTIONED' | 'X.PINNED.REPLIED'; export type ActionEvents = 'COPY' | 'OPEN' | 'DOWNLOAD'; + +// TODO: Different payload for different typs +type PayloadMap = { + 'X.MENTIONED': {}; + 'X.PINNED.REPLIED': {}; +}; + export type SpecialEvents = | AutomataSpecialEvents | StateSpecialEvents - | ActionEvents; + | ActionEvents From 17a3db3362d7e2ae403d69983e233ffff50e83c5 Mon Sep 17 00:00:00 2001 From: kun Date: Wed, 8 Jan 2025 22:25:50 +0800 Subject: [PATCH 02/42] feat: delete manager --- web/apps/web/next.config.js | 6 +- web/apps/web/src/app/container.ts | 18 - .../app/plugins/comfyui/comfyui.model.ts | 55 +-- .../app/plugins/comfyui/services/index.tsx | 14 + .../app/plugins/comfyui/services/type.tsx | 1 + .../components/chat/app-builder-chat.model.ts | 18 +- .../common/list-footer-extra/index.tsx | 6 +- .../components/download/completed/index.tsx | 62 --- .../download/download-definitions.ts | 28 -- .../src/components/download/download.model.ts | 146 ------- .../components/download/downloading/index.tsx | 92 ---- .../web/src/components/download/index.tsx | 75 ---- web/apps/web/src/components/manager/index.tsx | 34 -- .../manager/manager-content/index.tsx | 62 --- .../manager/manager-content/manager-confg.ts | 1 - .../models/models-common.model.ts | 164 ------- .../models/models-installed.model.ts | 97 ----- .../models/models-installed.tsx | 209 --------- .../models/models-marketplace.model.ts | 85 ---- .../models/models-marketplace.tsx | 405 ------------------ .../widgets/widgets-common.model.ts | 246 ----------- .../widgets/widgets-installed.model.ts | 154 ------- .../widgets/widgets-installed.tsx | 128 ------ .../widgets/widgets-marketplace.model.ts | 42 -- .../widgets/widgets-marketplace.tsx | 396 ----------------- .../components/manager/manager-definitions.ts | 70 --- .../components/manager/manger-sider/index.tsx | 42 -- .../src/components/settings/settings.model.ts | 123 ++---- web/apps/web/src/services/app/index.tsx | 94 ++++ web/apps/web/src/services/app/message-type.ts | 1 + .../web/src/stores/global/global-provider.tsx | 2 - 31 files changed, 181 insertions(+), 2695 deletions(-) delete mode 100644 web/apps/web/src/components/download/completed/index.tsx delete mode 100644 web/apps/web/src/components/download/download-definitions.ts delete mode 100644 web/apps/web/src/components/download/download.model.ts delete mode 100644 web/apps/web/src/components/download/downloading/index.tsx delete mode 100644 web/apps/web/src/components/download/index.tsx delete mode 100644 web/apps/web/src/components/manager/index.tsx delete mode 100644 web/apps/web/src/components/manager/manager-content/index.tsx delete mode 100644 web/apps/web/src/components/manager/manager-content/manager-confg.ts delete mode 100644 web/apps/web/src/components/manager/manager-content/models/models-common.model.ts delete mode 100644 web/apps/web/src/components/manager/manager-content/models/models-installed.model.ts delete mode 100644 web/apps/web/src/components/manager/manager-content/models/models-installed.tsx delete mode 100644 web/apps/web/src/components/manager/manager-content/models/models-marketplace.model.ts delete mode 100644 web/apps/web/src/components/manager/manager-content/models/models-marketplace.tsx delete mode 100644 web/apps/web/src/components/manager/manager-content/widgets/widgets-common.model.ts delete mode 100644 web/apps/web/src/components/manager/manager-content/widgets/widgets-installed.model.ts delete mode 100644 web/apps/web/src/components/manager/manager-content/widgets/widgets-installed.tsx delete mode 100644 web/apps/web/src/components/manager/manager-content/widgets/widgets-marketplace.model.ts delete mode 100644 web/apps/web/src/components/manager/manager-content/widgets/widgets-marketplace.tsx delete mode 100644 web/apps/web/src/components/manager/manager-definitions.ts delete mode 100644 web/apps/web/src/components/manager/manger-sider/index.tsx diff --git a/web/apps/web/next.config.js b/web/apps/web/next.config.js index 2aca1ce3..34dfd9d6 100644 --- a/web/apps/web/next.config.js +++ b/web/apps/web/next.config.js @@ -25,7 +25,6 @@ module.exports = { ); const destFile = path.join(__dirname, 'public/fabric.js'); copyFileSync(srcFile, destFile); - console.log('copied fabric.js'); // Important: return the modified config config.resolve.alias = { @@ -54,7 +53,10 @@ module.exports = { return [ { source: '/api/:path*', - destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`, + destination: `${ + process.env.NEXT_PUBLIC_SHELL_API_URL || + process.env.NEXT_PUBLIC_API_URL + }/api/:path*`, }, { source: '/models_searcher/:path*', diff --git a/web/apps/web/src/app/container.ts b/web/apps/web/src/app/container.ts index c6e6933e..f2b97eef 100644 --- a/web/apps/web/src/app/container.ts +++ b/web/apps/web/src/app/container.ts @@ -11,14 +11,7 @@ import { ComfyUIModel } from '@/components/app/plugins/comfyui/comfyui.model'; import { AssistantModel } from '@/components/assistant/model'; import { FeedbackModel } from '@/components/feedback/model'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; -import { DownloadModel } from '@/components/download/download.model'; import { OpenImageCanvasModel } from '@/components/image-canvas/open-image-canvas.model'; -import { ModelsCommonModel } from '@/components/manager/manager-content/models/models-common.model'; -import { ModelsInstalledModel } from '@/components/manager/manager-content/models/models-installed.model'; -import { ModelsMarketplaceModel } from '@/components/manager/manager-content/models/models-marketplace.model'; -import { WidgetsCommonModel } from '@/components/manager/manager-content/widgets/widgets-common.model'; -import { WidgetsInstalledModel } from '@/components/manager/manager-content/widgets/widgets-installed.model'; -import { WidgetsMarketplaceModel } from '@/components/manager/manager-content/widgets/widgets-marketplace.model'; import { SettingsModel } from '@/components/settings/settings.model'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { FormEngineModel } from '@/utils/form-engine.model'; @@ -79,23 +72,12 @@ container return model; }); -// manager -container.bind(WidgetsCommonModel).toSelf().inSingletonScope(); -container.bind(WidgetsMarketplaceModel).toSelf().inSingletonScope(); -container.bind(WidgetsInstalledModel).toSelf().inSingletonScope(); -container.bind(ModelsCommonModel).toSelf().inSingletonScope(); -container.bind(ModelsMarketplaceModel).toSelf().inSingletonScope(); -container.bind(ModelsInstalledModel).toSelf().inSingletonScope(); - // settings container.bind(SettingsModel).toSelf().inSingletonScope(); // app builder chat container.bind(AppBuilderChatModel).toSelf().inSingletonScope(); -// download -container.bind(DownloadModel).toSelf().inSingletonScope(); - // image canvas container.bind('ImageCanvasModel').to(ImageCanvasModel).inSingletonScope(); container.bind(OpenImageCanvasModel).toSelf().inSingletonScope(); diff --git a/web/apps/web/src/components/app/plugins/comfyui/comfyui.model.ts b/web/apps/web/src/components/app/plugins/comfyui/comfyui.model.ts index 604fd3da..9b55b942 100644 --- a/web/apps/web/src/components/app/plugins/comfyui/comfyui.model.ts +++ b/web/apps/web/src/components/app/plugins/comfyui/comfyui.model.ts @@ -1,5 +1,4 @@ import { customSnakeCase } from '@shellagent/shared/utils'; -import axios from 'axios'; import { inject, injectable } from 'inversify'; import { isEmpty, merge, omit } from 'lodash-es'; import mitt from 'mitt'; @@ -12,7 +11,13 @@ import { LOCATION_TIP, MessageType, } from '@/components/app/plugins/comfyui/constant'; -import { getFile, saveComfy } from '@/components/app/plugins/comfyui/services'; +import { + checkJsonExist, + getFile, + saveComfy, + getCwdSvc, + updateDependency, +} from '@/components/app/plugins/comfyui/services'; import { GetFileResponse, type SaveRequest, @@ -194,18 +199,10 @@ export class ComfyUIModel { @action.bound async checkJsonExists(location: string) { try { - const res = await axios.post( - `/api/comfyui/check_json_exist`, - { - location, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - if (!res.data.success) { + const result = await checkJsonExist({ + location, + }); + if (!result.success) { return `The ShellAgent-extended ComfyUI JSON (.shellagent.json) doesn't exist`; } return undefined; @@ -217,13 +214,9 @@ export class ComfyUIModel { @action.bound async getCwd() { try { - const res = await axios.get(`/api/get_cwd`, { - headers: { - 'Content-Type': 'application/json', - }, - }); - if (typeof res.data.cwd === 'string') { - this.defaultLocation = pathJoin([res.data.cwd, 'data/comfy_workflow']); + const result = await getCwdSvc({}); + if (typeof result.cwd === 'string') { + this.defaultLocation = pathJoin([result.cwd, 'data/comfy_workflow']); } else { this.toast.error(`/api/get_cwd response is invalid`); } @@ -294,20 +287,12 @@ export class ComfyUIModel { @action.bound async duplicateComfyUIExtendedJson(location: string, location_new: string) { try { - await axios.post( - `/api/comfyui/update_dependency`, - { - location, - location_new, // this param will duplicate a json from location - missing_custom_nodes: [], - missing_models: {}, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); + await updateDependency({ + location, + location_new, // this param will duplicate a json from location + missing_custom_nodes: [], + missing_models: {}, + }); } catch (e) { this.toast.error((e as Error).message); throw e; diff --git a/web/apps/web/src/components/app/plugins/comfyui/services/index.tsx b/web/apps/web/src/components/app/plugins/comfyui/services/index.tsx index 94c64c9d..787b6576 100644 --- a/web/apps/web/src/components/app/plugins/comfyui/services/index.tsx +++ b/web/apps/web/src/components/app/plugins/comfyui/services/index.tsx @@ -1,4 +1,5 @@ import { APIFetch } from '@/services/base'; +import type { Fetcher } from 'swr'; import { GetFileRequest, @@ -37,3 +38,16 @@ export const updateDependency = (params: UpdateDependencyRequest) => { }, ); }; + +export const checkJsonExist = (params: { location: string }) => { + return APIFetch.post( + '/api/comfyui/check_json_exist', + { + body: params, + }, + ); +}; + +export const getCwdSvc: Fetcher<{ cwd: string }, {}> = () => { + return APIFetch.get('/api/get_cwd'); +}; diff --git a/web/apps/web/src/components/app/plugins/comfyui/services/type.tsx b/web/apps/web/src/components/app/plugins/comfyui/services/type.tsx index 0420e148..e5c6e1bb 100644 --- a/web/apps/web/src/components/app/plugins/comfyui/services/type.tsx +++ b/web/apps/web/src/components/app/plugins/comfyui/services/type.tsx @@ -141,6 +141,7 @@ export interface GetFileResponse { export interface UpdateDependencyRequest { comfy_workflow_id?: string; location?: string; + location_new?: string; missing_custom_nodes: Array<{ name: string; repo: string; diff --git a/web/apps/web/src/components/chat/app-builder-chat.model.ts b/web/apps/web/src/components/chat/app-builder-chat.model.ts index 428594d6..8c5b9c20 100644 --- a/web/apps/web/src/components/chat/app-builder-chat.model.ts +++ b/web/apps/web/src/components/chat/app-builder-chat.model.ts @@ -4,7 +4,6 @@ import { } from '@microsoft/fetch-event-source'; import { Automata } from '@shellagent/pro-config'; import { ChatNewModel } from '@shellagent/ui'; -import axios from 'axios'; import { inject, injectable } from 'inversify'; import { action, makeObservable, observable, runInAction } from 'mobx'; import { ButtonFnParams, IMLocalFile } from 'myshell-bundled-chat'; @@ -21,6 +20,7 @@ import { import type { ServerMessage } from '../../services/app/message-type'; import { EventStatusEnum, RunAppRequest } from '../../services/app/type'; import { ToastModel } from '../../utils/toast.model'; +import { initBot } from '@/services/app'; @injectable() export class AppBuilderChatModel { @@ -137,18 +137,14 @@ export class AppBuilderChatModel { this.isInitBotLoading = true; }); try { - const res = await axios.post( - `/api/app/init_bot`, - { - automata, - }, - { headers: { 'Content-Type': 'application/json' } }, - ); - if (res.data.session_id == null && res.data.message) { - this.emitter.emitter.emit('message.error', res.data.message); + const reuslt = await initBot({ + automata, + }); + if (reuslt.data.session_id == null && reuslt.data.message) { + this.emitter.emitter.emit('message.error', reuslt.data.message); return; } - this.session_id = res.data.session_id; + this.session_id = reuslt.data.session_id; this.openRunDrawer(); await this.chatNew.isReadyPromise; this.chatNew.clearMemory(); diff --git a/web/apps/web/src/components/common/list-footer-extra/index.tsx b/web/apps/web/src/components/common/list-footer-extra/index.tsx index 4a211e24..32609c79 100644 --- a/web/apps/web/src/components/common/list-footer-extra/index.tsx +++ b/web/apps/web/src/components/common/list-footer-extra/index.tsx @@ -1,9 +1,9 @@ -import { IconButton, Setting } from '@shellagent/ui'; import { QuestionMarkCircleIcon } from '@heroicons/react/24/outline'; +import { IconButton, Setting } from '@shellagent/ui'; import { useInjection } from 'inversify-react'; -import { SettingsModel } from '@/components/settings/settings.model'; import { FeedbackModel } from '@/components/feedback/model'; +import { SettingsModel } from '@/components/settings/settings.model'; export const ListFooterExtra = () => { const settingsModel = useInjection(SettingsModel); @@ -13,8 +13,6 @@ export const ListFooterExtra = () => { process.env.NEXT_PUBLIC_DISABLE_SETTING === 'yes' || process.env.NEXT_PUBLIC_API_URL?.includes('34.239.158.245'); - console.log('settingsDisabled: ', process.env.NEXT_PUBLIC_API_URL); - return (
{ - const [itemType, setItemType] = useState('models'); - const model = useInjection(DownloadModel); - - useEffect(() => { - if (itemType === 'models' && model.drawer.isOpen) { - model.getCompletedModels(); - } - }, [itemType, model.drawer.isOpen]); - - return ( -
-
- {/* */} - -
-
- {model.completedModels.map((item, idx) => ( - <> - {item.filename} -
-
-
- Form {item.source} -
- - {item.finish_time} - -
-
- - ))} -
-
- ); -}); diff --git a/web/apps/web/src/components/download/download-definitions.ts b/web/apps/web/src/components/download/download-definitions.ts deleted file mode 100644 index 5ff7d5b2..00000000 --- a/web/apps/web/src/components/download/download-definitions.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from 'zod'; - -// start-Download-Center-Models -export const url_download_center_list = - '/api/download-center/get_downloading_models'; - -export const DownloadingModelItem = z.object({ - model_id: z.string(), - model_url: z.string(), - cache_path: z.string(), - force: z.boolean().optional(), - filename: z.string(), - status: z.string().optional(), - error_status: z.literal('failed').optional(), -}); - -export const DownloadModelListRes = z.object({ - models: z.array(DownloadingModelItem), -}); - -// stop-Download-Center-Models - -// start-Model-Cancel -export const url_model_cancel = `/api/models/cancel_download`; -export const ModelCancelRequest = z.object({ - cache_path: z.string(), -}); -// stop-Model-Cancel diff --git a/web/apps/web/src/components/download/download.model.ts b/web/apps/web/src/components/download/download.model.ts deleted file mode 100644 index a78d4d93..00000000 --- a/web/apps/web/src/components/download/download.model.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { - EventSourceMessage, - fetchEventSource, -} from '@microsoft/fetch-event-source'; -import axios from 'axios'; -import { inject, injectable } from 'inversify'; -import { action, makeObservable, observable } from 'mobx'; -import { z } from 'zod'; - -import { - DownloadingModelItem, - DownloadModelListRes, - url_download_center_list, -} from '@/components/download/download-definitions'; -import { url_models_marketplace_install } from '@/components/manager/manager-definitions'; -import { ModalModel } from '@/utils/modal.model'; -import { ToastModel } from '@/utils/toast.model'; - -@injectable() -export class DownloadModel { - @observable downloadingModels: z.infer[] = []; - @observable completedModels: any[] = []; - @observable tab: 'downloading' | 'completed' = 'downloading'; - @observable progressMap = new Map(); - progressAbortCtrl = new AbortController(); - - constructor( - @inject(ToastModel) private emitter: ToastModel, - @inject(ModalModel) public drawer: ModalModel, - ) { - makeObservable(this); - } - - @action.bound - setTab(tab: 'downloading' | 'completed') { - this.tab = tab; - } - - async forceInstall(id: string) { - try { - await axios.post( - url_models_marketplace_install, - { id, force: true }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - } - - async getProgress() { - this.progressAbortCtrl.abort(); - this.progressAbortCtrl = new AbortController(); - fetchEventSource('/api/download-center/get_downloading_models_progress', { - method: 'POST', - mode: 'cors', - headers: { - method: 'POST', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}), - signal: this.progressAbortCtrl.signal, - openWhenHidden: true, - onerror: err => { - this.emitter.emitter.emit('message.error', err.message); - }, - onmessage: (ev: EventSourceMessage) => { - if (ev.event === 'download_start') { - this.getDownloadingModels(); - } - if (ev.event === 'download_end') { - this.getDownloadingModels(); - } - if (ev.event === 'downloading') { - try { - const data = JSON.parse(ev.data); - this.progressMap.set(data.input.model_id, data.output.progress); - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - } - }, - onopen: async () => { - // - }, - }); - } - - async getDownloadingModels() { - try { - const res = await axios.post( - url_download_center_list, - {}, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - this.downloadingModels = DownloadModelListRes.parse(res.data).models; - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - } - - async getCompletedModels() { - try { - const res = await axios.post( - '/api/download-center/get_completed_models', - {}, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - this.completedModels = res.data.models; - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - } - - async cancelDownload(cachePath: string) { - try { - await axios.post( - '/api/models/cancel_download', - { - cache_path: cachePath, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - this.emitter.emitter.emit('message.success', 'Successfully cancelled.'); - this.getDownloadingModels(); - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - } -} diff --git a/web/apps/web/src/components/download/downloading/index.tsx b/web/apps/web/src/components/download/downloading/index.tsx deleted file mode 100644 index 26eebb7e..00000000 --- a/web/apps/web/src/components/download/downloading/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { ArrowPathIcon, XMarkIcon } from '@heroicons/react/24/outline'; -import { Button, Heading, IconButton, Spinner, Text } from '@shellagent/ui'; -import { useInjection } from 'inversify-react'; -import { observer } from 'mobx-react-lite'; -import React, { useEffect, useState } from 'react'; - -import { DownloadModel } from '@/components/download/download.model'; -import { cn } from '@/utils/cn'; - -export const Downloading = observer(() => { - const [itemType, setItemType] = useState('models'); - const model = useInjection(DownloadModel); - useEffect(() => { - if (itemType === 'models' && model.drawer.isOpen) { - model.getDownloadingModels(); - model.getProgress(); - } - }, [itemType, model.drawer.isOpen]); - - return ( -
-
- {/* */} - -
-
- {model.downloadingModels.map((item, idx) => { - const progress = model.progressMap.get(item.model_id) || 0; - return ( -
-
- {item.error_status === 'failed' ? ( - - ) : ( - - )} -
-
-
- {item.filename} - {item.error_status !== 'failed' ? ( - {`${progress}%`} - ) : null} -
-
- {item.error_status === 'failed' ? ( - model.forceInstall(item.model_id)} - /> - ) : null} - model.cancelDownload(item.cache_path)} - /> -
-
-
- ); - })} -
-
- ); -}); diff --git a/web/apps/web/src/components/download/index.tsx b/web/apps/web/src/components/download/index.tsx deleted file mode 100644 index 2733a087..00000000 --- a/web/apps/web/src/components/download/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { ArrowDownTrayIcon, XMarkIcon } from '@heroicons/react/24/outline'; -import { - Drawer, - IconButton, - Tabs, - TabsList, - TabsTrigger, -} from '@shellagent/ui'; -import { useInjection } from 'inversify-react'; -import { observer } from 'mobx-react-lite'; -import React from 'react'; - -import { DownloadModel } from '@/components/download/download.model'; - -import { Completed } from './completed'; -import { Downloading } from './downloading'; - -export const Download = observer(() => { - const model = useInjection(DownloadModel); - return ( -
- - - model.setTab(val as any)}> - - - Downloading - - - Completed - - - - -
- {model.tab === 'downloading' ? : } -
-
-
- ); -}); diff --git a/web/apps/web/src/components/manager/index.tsx b/web/apps/web/src/components/manager/index.tsx deleted file mode 100644 index dd27163f..00000000 --- a/web/apps/web/src/components/manager/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client'; - -import { ButtonProps, AModal } from '@shellagent/ui'; -import { Flex } from 'react-system'; - -import { useGlobalStore } from '@/stores/global/global-provider'; - -import { ManagerContent } from './manager-content'; -import { ManagerSideBar } from './manger-sider'; - -interface ManagerDialogProps { - buttonSize?: ButtonProps['size']; -} - -export const ManagerDialog: React.FC = () => { - const { managerDialogOpen, setManagerDialogOpen } = useGlobalStore(state => ({ - managerDialogOpen: state.managerDialogOpen, - setManagerDialogOpen: state.setManagerDialogOpen, - })); - - return ( - setManagerDialogOpen(false)} - footer={null}> - - - - - - ); -}; diff --git a/web/apps/web/src/components/manager/manager-content/index.tsx b/web/apps/web/src/components/manager/manager-content/index.tsx deleted file mode 100644 index 0bf6d818..00000000 --- a/web/apps/web/src/components/manager/manager-content/index.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/** @jsxImportSource @emotion/react */ - -import { css } from '@emotion/react'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@shellagent/ui'; - -import { - BaseTypeEnum, - ManagerTypeEnum, - useGlobalStore, -} from '@/stores/global/global-provider'; - -import { ModelsInstalled } from './models/models-installed'; -import { ModelsMarketplace } from './models/models-marketplace'; -import { WidgetsInstalled } from './widgets/widgets-installed'; -import { WidgetsMarketplace } from './widgets/widgets-marketplace'; - -export const ManagerContent = () => { - const { setBaseType, managerType } = useGlobalStore(state => ({ - setBaseType: state.setBaseType, - managerType: state.managerType, - })); - - return ( - setBaseType(value as BaseTypeEnum)}> - - - Marketplace - - - Installed - - - - {managerType === ManagerTypeEnum.widget ? ( - - ) : ( - - )} - - - {managerType === ManagerTypeEnum.widget ? ( - - ) : ( - - )} - - - ); -}; diff --git a/web/apps/web/src/components/manager/manager-content/manager-confg.ts b/web/apps/web/src/components/manager/manager-content/manager-confg.ts deleted file mode 100644 index 3dcc91a1..00000000 --- a/web/apps/web/src/components/manager/manager-content/manager-confg.ts +++ /dev/null @@ -1 +0,0 @@ -export const IS_MANAGER_PROD = true; diff --git a/web/apps/web/src/components/manager/manager-content/models/models-common.model.ts b/web/apps/web/src/components/manager/manager-content/models/models-common.model.ts deleted file mode 100644 index 0f9313d2..00000000 --- a/web/apps/web/src/components/manager/manager-content/models/models-common.model.ts +++ /dev/null @@ -1,164 +0,0 @@ -import axios from 'axios'; -import { FormikProps } from 'formik'; -import { inject, injectable } from 'inversify'; -import { debounce, DebouncedFunc } from 'lodash-es'; -import { action, makeObservable, observable, runInAction } from 'mobx'; - -import { url_models_marketplace_install } from '@/components/manager/manager-definitions'; -import { ModalModel } from '@/utils/modal.model'; - -import { ToastModel } from '../../../../utils/toast.model'; - -@injectable() -export class ModelsCommonModel { - formikProps: FormikProps | undefined; - @observable types = []; - @observable bases = []; - @observable save_paths = []; - @observable installLoadingMap = new Map(); - @observable uninstallLoadingMap = new Map(); - @observable checkedType = 'All'; - @observable checkedBase = 'All'; - @observable checkedSavePath = 'All'; - debouneFunc: DebouncedFunc; - - constructor( - @inject(ToastModel) private emitter: ToastModel, - @inject(ModalModel) public installFromModal: ModalModel, - ) { - makeObservable(this); - this.debouneFunc = debounce( - () => { - this.emitter.emitter.emit( - 'message.success', - 'Add to installed queue. Please see the progress in download center', - ); - }, - 5000, - { - leading: true, - trailing: false, - }, - ); - } - - @action.bound - setCheckedType(val: string) { - this.checkedType = val; - } - - @action.bound - setCheckedSavePath(val: string) { - this.checkedSavePath = val; - } - - @action.bound - setCheckedBase(val: string) { - this.checkedBase = val; - } - - async loadTypeBaseList() { - try { - const res = await axios.post( - '/api/models/marketplace/types_bases_lists', - {}, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - runInAction(() => { - this.types = res.data.types; - this.bases = res.data.bases; - this.save_paths = res.data.save_paths; - }); - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - } - - async installFromLink(values: any) { - try { - await axios.post('/api/models/marketplace/install_from_link', values, { - headers: { - 'Content-Type': 'application/json', - }, - }); - this.emitter.emitter.emit( - 'message.success', - 'Add to installed queue. Please see the progress in download center', - ); - this.installFromModal.close(); - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - } - - async batchInstall(ids: string[], loadData: () => Promise) { - ids.forEach(id => this.install(id, loadData)); - } - - async install(id: string, loadData: () => Promise) { - try { - this.installLoadingMap.set(id, true); - try { - await axios.post( - url_models_marketplace_install, - { id }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - loadData(); - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } finally { - this.installLoadingMap.delete(id); - } - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - } - - async unInstall(idList: string[], loadData: () => Promise) { - try { - idList.forEach(id => { - runInAction(() => { - this.uninstallLoadingMap.set(id, true); - }); - }); - await axios.post( - '/api/models/marketplace/batch_uninstall', - { - id_list: idList, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - this.emitter.emitter.emit('message.success', 'Successfully uninstalled'); - loadData(); - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } finally { - idList.forEach(id => { - runInAction(() => { - this.uninstallLoadingMap.delete(id); - }); - }); - } - } - - async onSubmit() { - try { - await this.formikProps?.submitForm(); - } catch (error) { - // - } - } -} diff --git a/web/apps/web/src/components/manager/manager-content/models/models-installed.model.ts b/web/apps/web/src/components/manager/manager-content/models/models-installed.model.ts deleted file mode 100644 index a8fc886e..00000000 --- a/web/apps/web/src/components/manager/manager-content/models/models-installed.model.ts +++ /dev/null @@ -1,97 +0,0 @@ -import axios from 'axios'; -import { inject, injectable } from 'inversify'; -import { action, makeObservable, observable, runInAction } from 'mobx'; - -import { url_models_installed_list } from '@/components/manager/manager-definitions'; -import { SettingsModel } from '@/components/settings/settings.model'; -import { ToastModel } from '@/utils/toast.model'; - -import { ModelsCommonModel } from './models-common.model'; - -@injectable() -export class ModelsInstalledModel { - @observable data: any[] = []; - @observable keyword: string = ''; - @observable loadingData: boolean = false; - - constructor( - @inject(ModelsCommonModel) public common: ModelsCommonModel, - @inject(ToastModel) public emitter: ToastModel, - @inject(SettingsModel) private settings: SettingsModel, - ) { - makeObservable(this); - } - - @action.bound - setCheckedType(val: string) { - this.common.setCheckedType(val); - this.loadData(); - } - - @action.bound - setCheckedBase(val: string) { - this.common.setCheckedBase(val); - this.loadData(); - } - - async loadData() { - runInAction(() => { - this.loadingData = true; - }); - const settingsDisabled = process.env.NEXT_PUBLIC_DISABLE_SETTING === 'yes'; - let location; - if (settingsDisabled) { - location = 'models'; - } else { - const settingsEnv = await this.settings.loadSettingsEnv(); - location = settingsEnv?.model_location; - } - - if (location == null) { - this.emitter.emitter.emit( - 'message.error', - `model_location is null ${ - settingsDisabled ? '' : ' ,go to Settings to set a value' - }`, - ); - } - try { - const res = await axios.post( - url_models_installed_list, - { - base: - this.common.checkedBase === 'All' ? '' : this.common.checkedBase, - save_path: - this.common.checkedSavePath === 'All' - ? '' - : this.common.checkedSavePath, - query: this.keyword, - location, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - if (res?.data?.success) { - runInAction(() => { - this.data = res.data.data; - }); - } else { - this.emitter.emitter.emit('message.error', `load data error`); - } - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } finally { - runInAction(() => { - this.loadingData = false; - }); - } - } - - @action.bound - changeKeyword(keyword: string) { - this.keyword = keyword; - } -} diff --git a/web/apps/web/src/components/manager/manager-content/models/models-installed.tsx b/web/apps/web/src/components/manager/manager-content/models/models-installed.tsx deleted file mode 100644 index 82714ce6..00000000 --- a/web/apps/web/src/components/manager/manager-content/models/models-installed.tsx +++ /dev/null @@ -1,209 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { SearchOutlined } from '@ant-design/icons'; -import { css } from '@emotion/react'; -import { AInput, AModal, ATable } from '@shellagent/ui'; -import { Tag, Typography } from 'antd'; -import { ColumnsType } from 'antd/es/table'; -import { useInjection } from 'inversify-react'; -import { observer } from 'mobx-react-lite'; -import { useEffect } from 'react'; -import { Box, Flex } from 'react-system'; - -import { useGlobalStore } from '@/stores/global/global-provider'; - -import { ModelsCommonModel } from './models-common.model'; -import { ModelsInstalledModel } from './models-installed.model'; -import { - InstallFromLinkModalContent, - ModelsActionButton, - TopInstallButton, -} from './models-marketplace'; - -const ModelsSavePathBase = observer<{ - loadData: () => Promise; -}>(props => { - const model = useInjection(ModelsCommonModel); - return ( -
- { - return expanded ? 'Collapse' : 'More'; - }, - }}> - - Save Path: - - { - model.setCheckedSavePath('All'); - props.loadData(); - }}> - All - - {model.save_paths.map(type => ( - { - model.setCheckedSavePath(type); - props.loadData(); - }}> - {type} - - ))} - - - { - return expanded ? 'Collapse' : 'More'; - }, - }}> - - Base: - - { - model.setCheckedBase('All'); - props.loadData(); - }}> - All - - {model.bases.map(type => ( - { - model.setCheckedBase(type); - props.loadData(); - }}> - {type} - - ))} - -
- ); -}); - -export const ModelsInstalled = observer<{}>(() => { - const model = useInjection(ModelsInstalledModel); - const { managerDialogOpen } = useGlobalStore(state => ({ - managerDialogOpen: state.managerDialogOpen, - })); - useEffect(() => { - if (managerDialogOpen) { - model.loadData(); - model.common.loadTypeBaseList(); - } - }, [managerDialogOpen]); - const columns: ColumnsType = [ - { - title: 'Filename', - dataIndex: 'filename', - }, - { - title: 'Save Path', - dataIndex: 'save_path', - render: text => { - return {text}; - }, - }, - { - title: 'Local Path', - dataIndex: 'local_path', - }, - { - title: 'Action', - align: 'center', - fixed: 'right', - dataIndex: 'action', - render: (text, record) => { - return ( - model.loadData()} - /> - ); - }, - }, - ]; - return ( - <> - {/* - Location: {model.location} - */} - - - } - placeholder="Search MyShell..." - value={model.keyword} - onChange={e => model.changeKeyword(e.target.value)} - onPressEnter={() => model.loadData()} - /> - -
- model.loadData()} /> -
-
- model.loadData()} /> - - model.common.onSubmit()}> - - - - ); -}); diff --git a/web/apps/web/src/components/manager/manager-content/models/models-marketplace.model.ts b/web/apps/web/src/components/manager/manager-content/models/models-marketplace.model.ts deleted file mode 100644 index c28f3919..00000000 --- a/web/apps/web/src/components/manager/manager-content/models/models-marketplace.model.ts +++ /dev/null @@ -1,85 +0,0 @@ -import axios from 'axios'; -import { inject, injectable } from 'inversify'; -import { action, computed, makeObservable, observable } from 'mobx'; -import { z } from 'zod'; - -import { - ModelsMarketPlaceItem, - ModelsMarketPlaceRes, - url_models_marketplace_list, -} from '@/components/manager/manager-definitions'; -import { RequestModel } from '@/utils/request.model'; -import { ToastModel } from '@/utils/toast.model'; - -import { ModelsCommonModel } from './models-common.model'; - -@injectable() -export class ModelsMarketplaceModel { - @observable data: z.infer[] = []; - @observable keyword: string = ''; - @observable selectedIds: string[] = []; - - constructor( - @inject(ToastModel) private emitter: ToastModel, - @inject(RequestModel) public loadData: RequestModel, - @inject(ModelsCommonModel) public common: ModelsCommonModel, - ) { - makeObservable(this); - this.loadData.handlers = { - requestFunc: (requestId, abortController) => { - return axios.post(url_models_marketplace_list, this.requestBody, { - signal: abortController.signal, - headers: { - 'Content-Type': 'application/json', - }, - }); - }, - onsuccess: data => { - const result = ModelsMarketPlaceRes.parse(data); - if (result.success) { - this.data = result.data; - } else { - this.emitter.emitter.emit('message.error', result.message); - } - }, - onerror: e => { - this.emitter.emitter.emit('message.error', e.message); - }, - }; - } - - @computed get requestBody() { - return { - base: this.common.checkedBase === 'All' ? '' : this.common.checkedBase, - type: this.common.checkedType === 'All' ? '' : this.common.checkedType, - query: this.keyword, - }; - } - - @action.bound - setCheckedType(val: string) { - this.common.setCheckedType(val); - this.loadData.request(); - } - - @action.bound - setCheckedBase(val: string) { - this.common.setCheckedBase(val); - this.loadData.request(); - } - - @action.bound - changeKeyword(keyword: string) { - this.keyword = keyword; - } - - @action.bound - isRowCheckboxDisabled(record: any) { - return record.install_status !== 'not_installed'; - } - - @action.bound - setSelectedIds(ids: string[]) { - this.selectedIds = ids; - } -} diff --git a/web/apps/web/src/components/manager/manager-content/models/models-marketplace.tsx b/web/apps/web/src/components/manager/manager-content/models/models-marketplace.tsx deleted file mode 100644 index cd191bc2..00000000 --- a/web/apps/web/src/components/manager/manager-content/models/models-marketplace.tsx +++ /dev/null @@ -1,405 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { DownOutlined, SearchOutlined } from '@ant-design/icons'; -import { css } from '@emotion/react'; -import { - AButton, - ADropdownButton, - AInput, - AModal, - ATable, -} from '@shellagent/ui'; -import { Form, Input, Popconfirm, Select, Tag, Typography } from 'antd'; -import { ColumnsType } from 'antd/es/table'; -import { Field, FieldProps, Formik } from 'formik'; -import { useInjection } from 'inversify-react'; -import { observer } from 'mobx-react-lite'; -import { useEffect } from 'react'; -import { Box, Flex } from 'react-system'; -import { z } from 'zod'; - -import { ModelsMarketPlaceItem } from '@/components/manager/manager-definitions'; -import { useGlobalStore } from '@/stores/global/global-provider'; - -import { ModelsCommonModel } from './models-common.model'; -import { ModelsMarketplaceModel } from './models-marketplace.model'; - -export const ModelsTypeBase = observer<{ - loadData: () => Promise; -}>(props => { - const model = useInjection(ModelsCommonModel); - return ( -
- { - return expanded ? 'Collapse' : 'More'; - }, - }}> - - Type: - - { - model.setCheckedType('All'); - props.loadData(); - }}> - All - - {model.types.map(type => ( - { - model.setCheckedType(type); - props.loadData(); - }}> - {type} - - ))} - - - { - return expanded ? 'Collapse' : 'More'; - }, - }}> - - Base: - - { - model.setCheckedBase('All'); - props.loadData(); - }}> - All - - {model.bases.map(type => ( - { - model.setCheckedBase(type); - props.loadData(); - }}> - {type} - - ))} - -
- ); -}); - -export const ModelsActionButton = observer<{ - record: z.infer; - loadData: () => Promise; -}>(props => { - const model = useInjection(ModelsCommonModel); - const installLoading = model.installLoadingMap.get(props.record.id); - const uninstallLoading = model.uninstallLoadingMap.get(props.record.id); - return ( -
- {props.record.install_status === 'not_installed' ? ( - - model.install(props.record.id, () => props.loadData()) - }> - Install - - ) : null} - {props.record.install_status === 'installing' ? ( - - Installing - - ) : null} - {props.record.install_status === 'installed' ? ( - - { - model.unInstall([props.record.id], () => props.loadData()); - }}> - - Uninstall - - - - ) : null} -
- ); -}); - -export const TopInstallButton = observer<{ - selectedIds?: string[]; - loadData: () => Promise; -}>(props => { - const model = useInjection(ModelsCommonModel); - if (props.selectedIds == null) { - return ( - Install from Link - ); - } - return ( - { - model.batchInstall(props.selectedIds!, props.loadData); - }} - css={css` - .ant-btn { - color: var(--ant-color-primary); - } - `} - icon={} - menu={{ - onClick: e => { - if (e.key === 'install-from-link') { - model.installFromModal.open(); - } - }, - items: [ - { - key: 'install-from-link', - label: 'Install from Link', - }, - ], - }}> - Install - {props.selectedIds.length > 0 ? ` (${props.selectedIds.length})` : ''} - - ); -}); - -export const InstallFromLinkModalContent = observer(() => { - const model = useInjection(ModelsCommonModel); - return ( - { - model.installFromLink(values); - }}> - {formikProps => { - model.formikProps = formikProps; - return ( -
- - {({ field }: FieldProps) => ( - - - - )} - - - {({ field, form }: FieldProps) => { - return ( - - - - )} - -
- ); - }} -
- ); -}); - -export const ModelsMarketplace = observer<{}>(() => { - const model = useInjection(ModelsMarketplaceModel); - const { managerDialogOpen } = useGlobalStore(state => ({ - managerDialogOpen: state.managerDialogOpen, - })); - useEffect(() => { - if (managerDialogOpen) { - model.loadData.request(); - model.common.loadTypeBaseList(); - } - }, [managerDialogOpen]); - - const columns: ColumnsType> = [ - { - title: 'Name', - dataIndex: 'name', - render: (text, record) => { - return ( - - {text} - - ); - }, - }, - { - title: 'Description', - dataIndex: 'description', - render: text => { - return ( - - {text} - - ); - }, - }, - { - title: 'Type', - dataIndex: 'type', - render: text => { - return {text}; - }, - }, - { - title: 'Base', - dataIndex: 'base', - render: text => { - return {text}; - }, - }, - { - title: 'Action', - align: 'center', - fixed: 'right', - dataIndex: 'action', - render: (text, record) => { - return ( - model.loadData.request()} - /> - ); - }, - }, - ]; - return ( - <> - - - } - placeholder="Search MyShell..." - value={model.keyword} - onChange={e => model.changeKeyword(e.target.value)} - onPressEnter={() => model.loadData.request()} - /> - -
- model.loadData.request()} - /> -
-
- model.loadData.request()} /> - { - model.setSelectedIds(selectedRows.map(item => item.id)); - }, - onSelectAll: (selected, selectedRows) => { - model.setSelectedIds(selectedRows.map(item => item.id)); - }, - }} - loading={model.loadData.loading} - scroll={{ y: 320 }} - rowKey="id" - pagination={{ - position: ['bottomLeft'], - simple: true, - defaultCurrent: 1, - total: model.data.length, - showSizeChanger: false, - }} - dataSource={model.data} - columns={columns as any} - /> - model.common.onSubmit()}> - - - - ); -}); diff --git a/web/apps/web/src/components/manager/manager-content/widgets/widgets-common.model.ts b/web/apps/web/src/components/manager/manager-content/widgets/widgets-common.model.ts deleted file mode 100644 index ea32d48b..00000000 --- a/web/apps/web/src/components/manager/manager-content/widgets/widgets-common.model.ts +++ /dev/null @@ -1,246 +0,0 @@ -import axios from 'axios'; -import type { FormikProps } from 'formik'; -import { inject, injectable } from 'inversify'; -import { - action, - computed, - makeObservable, - observable, - runInAction, -} from 'mobx'; - -import { ToastModel } from '../../../../utils/toast.model'; - -export type WidgetRecord = { - local_commits_hash?: string[]; - remote_commit_hash: string; - current_commit: string; -}; - -export function getInstallStatus( - record: WidgetRecord, -): 'Install' | 'Update' | null { - if (!record) return null; - if (record.local_commits_hash?.indexOf(record.remote_commit_hash) === -1) { - return 'Update'; - } - return null; -} - -@injectable() -export class WidgetsCommonModel { - formikProps: FormikProps | undefined; - @observable installFromModalVisible = false; - @observable insatallFromGitLoading = false; - @observable installUpdateLoadingMap = new Map(); - @observable uninstallLoadingMap = new Map(); - @observable remoteVersionData: { - tags: Record; - commits: Record< - string, - { - author: string; - date: string; - message: string; - } - >; - } = { - tags: {}, - commits: {}, - }; - @observable remoteVersionQuery: string = ''; - - constructor(@inject(ToastModel) private emitter: ToastModel) { - makeObservable(this); - } - - @computed get remoteList() { - const tags = Object.keys(this.remoteVersionData.tags); - const commits = Object.keys(this.remoteVersionData.commits); - return [...tags, ...commits]; - } - - @action.bound - onChangeRemoteVersionQuery(val: string) { - this.remoteVersionQuery = val; - } - - onRemoteVersionOpenChange(open: boolean, git: string) { - if (open) { - this.loadRemoteVersions(git, this.remoteVersionQuery); - } else { - this.remoteVersionQuery = ''; - } - } - - async onInstallFromGitSubmit() { - try { - await this.formikProps?.submitForm(); - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - } - - @action.bound - openInstallFromGitModal() { - this.installFromModalVisible = true; - } - - @action.bound - closeInstallFromGitModal() { - this.installFromModalVisible = false; - } - - async installFromGit(values: any) { - runInAction(() => { - this.insatallFromGitLoading = true; - }); - try { - await axios.post('/api/widgets/marketplace/install_from_git', values, { - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - runInAction(() => { - this.insatallFromGitLoading = false; - }); - } - - async loadRemoteVersions(git: string, query: string = '') { - this.remoteVersionData = { - tags: {}, - commits: {}, - }; - try { - const res = await axios.post( - '/api/widgets/get_remote_versions', - { - git, - query, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - if (res?.data?.success) { - runInAction(() => { - this.remoteVersionData = res.data; - }); - } else { - this.emitter.emitter.emit('message.error', res.data.message); - } - } catch (e: any) { - this.emitter.emitter.emit('message.error', e.message); - } - } - - // eslint-disable-next-line class-methods-use-this - showUninstall(record: any): boolean { - return record?.local_commits_hash?.length > 0; - } - - getInstallStatus(record: any) { - return getInstallStatus(record); - } - - async installVersion( - id: string, - commit: string, - loadData: () => Promise, - ) { - if (this.remoteVersionData.tags[commit]) { - commit = this.remoteVersionData.tags[commit]; - } - runInAction(() => { - this.installUpdateLoadingMap.set(id, true); - }); - try { - await axios.post( - '/api/widgets/marketplace/install', - { - id, - commit, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - runInAction(() => { - this.installUpdateLoadingMap.delete(id); - }); - this.emitter.emitter.emit('message.success', 'Successfully installed'); - loadData(); - } catch (e: any) { - runInAction(() => { - this.installUpdateLoadingMap.delete(id); - }); - this.emitter.emitter.emit('message.error', e.message); - } - } - - async unInstallVersion( - id: string, - commit: string | undefined, - loadData: () => Promise, - ) { - runInAction(() => { - this.uninstallLoadingMap.set(id, true); - }); - try { - await axios.post( - '/api/widgets/marketplace/uninstall', - { - id, - commit, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - runInAction(() => { - this.uninstallLoadingMap.delete(id); - }); - this.emitter.emitter.emit('message.success', 'Successfully uninstalled'); - loadData(); - } catch (e: any) { - runInAction(() => { - this.uninstallLoadingMap.delete(id); - }); - this.emitter.emitter.emit('message.error', e.message); - } - } - - canUpdate( - commit: string, - record: WidgetRecord, - ): 'can_update' | 'installed' | 'current' { - commit = this.tagToCommit(commit); - if (commit === record.current_commit) return 'current'; - if ( - Array.isArray(record.local_commits_hash) && - record.local_commits_hash.indexOf(commit) > -1 - ) - return 'installed'; - return 'can_update'; - } - - /** - * 根据获取的 remoteVersionData 将 tag 转成 commit hash - * @param commit 可能是 tag 形式 v1 - */ - tagToCommit(commit: string) { - if (this.remoteVersionData.tags[commit]) { - commit = this.remoteVersionData.tags[commit]; - } - return commit; - } -} diff --git a/web/apps/web/src/components/manager/manager-content/widgets/widgets-installed.model.ts b/web/apps/web/src/components/manager/manager-content/widgets/widgets-installed.model.ts deleted file mode 100644 index 515b49b4..00000000 --- a/web/apps/web/src/components/manager/manager-content/widgets/widgets-installed.model.ts +++ /dev/null @@ -1,154 +0,0 @@ -import axios from 'axios'; -import { inject, injectable } from 'inversify'; -import { action, makeObservable, observable, runInAction } from 'mobx'; - -import { WidgetsCommonModel } from './widgets-common.model'; -import { ToastModel } from '../../../../utils/toast.model'; - -export type ActionKey = 'Install' | 'Update' | 'Uninstall'; - -@injectable() -export class WidgetsInstalledModel { - @observable data: any[] = []; - @observable keyword: string = ''; - @observable enableLoadingMap = new Map(); - - constructor( - @inject(WidgetsCommonModel) public common: WidgetsCommonModel, - @inject(ToastModel) private emitter: ToastModel, - ) { - makeObservable(this); - } - - async loadData() { - const res = await axios.post( - '/api/widgets/installed/list', - { - query: this.keyword.trim(), - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - if (res?.data?.success) { - runInAction(() => { - this.data = res.data.data; - }); - } - } - - isDisabled(id: string, type: 'install' | 'uninstall' | 'enable') { - if (type === 'install') { - return ( - this.common.uninstallLoadingMap.has(id) || this.enableLoadingMap.has(id) - ); - } - if (type === 'uninstall') { - return ( - this.common.installUpdateLoadingMap.has(id) || - this.enableLoadingMap.has(id) - ); - } - if (type === 'enable') { - return ( - this.common.uninstallLoadingMap.has(id) || - this.common.installUpdateLoadingMap.has(id) - ); - } - return false; - } - - async setEnableStatus(id: string, enabled: boolean) { - runInAction(() => { - this.enableLoadingMap.set(id, true); - }); - try { - await axios.post( - '/api/widgets/installed/set_widget_status', - { - id, - enabled, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - this.emitter.emitter.emit( - 'message.success', - enabled ? 'Successfully enabled' : 'Successfully disabled', - ); - runInAction(() => { - this.enableLoadingMap.delete(id); - }); - this.loadData(); - } catch (e: any) { - runInAction(() => { - this.enableLoadingMap.delete(id); - }); - this.emitter.emitter.emit('message.error', e.message); - } - } - - async setCurrent(id: string, current_commit: string) { - runInAction(() => { - this.common.installUpdateLoadingMap.set(id, true); - }); - try { - await axios.post( - '/api/widgets/installed/set_widget_status', - { - id, - current_commit, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - runInAction(() => { - this.common.installUpdateLoadingMap.delete(id); - }); - this.emitter.emitter.emit( - 'message.success', - 'Successfully set as current version', - ); - this.loadData(); - } catch (e: any) { - runInAction(() => { - this.common.installUpdateLoadingMap.delete(id); - }); - this.emitter.emitter.emit('message.error', e.message); - } - } - - @action.bound - changeKeyword(keyword: string) { - this.keyword = keyword; - } - - canSetCurrent(record: any, item: string) { - return this.isInstalled(record, item) && !this.isCurrent(record, item); - } - - isInstalled(record: any, item: string) { - let commit = item; - if (this.common.remoteVersionData.tags[item]) { - commit = this.common.remoteVersionData.tags[item]; - } - const installed = record?.local_commits_hash?.includes(commit); - return installed; - } - - isCurrent(record: any, item: string) { - let commit = item; - if (this.common.remoteVersionData.tags[item]) { - commit = this.common.remoteVersionData.tags[item]; - } - return commit === record.current_commit; - } -} diff --git a/web/apps/web/src/components/manager/manager-content/widgets/widgets-installed.tsx b/web/apps/web/src/components/manager/manager-content/widgets/widgets-installed.tsx deleted file mode 100644 index 110ae156..00000000 --- a/web/apps/web/src/components/manager/manager-content/widgets/widgets-installed.tsx +++ /dev/null @@ -1,128 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { SearchOutlined } from '@ant-design/icons'; -import { AInput, ATable } from '@shellagent/ui'; -import { Switch, Typography } from 'antd'; -import { ColumnsType } from 'antd/es/table'; -import { useInjection } from 'inversify-react'; -import { trace } from 'mobx'; -import { observer } from 'mobx-react-lite'; -import { useEffect } from 'react'; -import { Box, Flex } from 'react-system'; - -import { useGlobalStore } from '@/stores/global/global-provider'; - -import { WidgetsInstalledModel } from './widgets-installed.model'; -import { - InstallFromGitModal, - TopInstallButton, - WidgetsActionButton, -} from './widgets-marketplace'; - -export const WidgetsStatus = observer<{ - record: any; -}>(props => { - trace(); - const model = useInjection(WidgetsInstalledModel); - return ( - { - model.setEnableStatus(props.record.id, e); - }} - /> - ); -}); - -export const WidgetsInstalled = observer(() => { - trace(); - const model = useInjection(WidgetsInstalledModel); - - const { managerDialogOpen } = useGlobalStore(state => ({ - managerDialogOpen: state.managerDialogOpen, - })); - useEffect(() => { - if (managerDialogOpen) { - model.loadData(); - } - }, [managerDialogOpen]); - - const columns: ColumnsType = [ - { - title: 'Name', - dataIndex: 'name', - render: (text, record) => { - return ( - - {text} - - ); - }, - }, - { - title: 'Description', - dataIndex: 'description', - render: text => { - return ( - - {text} - - ); - }, - }, - { - title: 'Enable/Disable', - dataIndex: 'status', - render: (text, record) => { - return ; - }, - }, - { - title: 'Action', - align: 'center', - width: 1, - dataIndex: 'action', - render: (text, record) => { - return ( - model.loadData()} - /> - ); - }, - }, - ]; - return ( - <> - - - } - placeholder="Search MyShell..." - value={model.keyword} - onChange={e => model.changeKeyword(e.target.value)} - onPressEnter={() => model.loadData()} - /> - -
- -
-
- - - - - - ); -}); diff --git a/web/apps/web/src/components/manager/manager-content/widgets/widgets-marketplace.model.ts b/web/apps/web/src/components/manager/manager-content/widgets/widgets-marketplace.model.ts deleted file mode 100644 index 9cf8b927..00000000 --- a/web/apps/web/src/components/manager/manager-content/widgets/widgets-marketplace.model.ts +++ /dev/null @@ -1,42 +0,0 @@ -import axios from 'axios'; -import { inject, injectable } from 'inversify'; -import { action, makeObservable, observable, runInAction } from 'mobx'; - -import { WidgetsCommonModel } from './widgets-common.model'; - -export type ActionKey = 'Install' | 'Update' | 'Uninstall'; - -@injectable() -export class WidgetsMarketplaceModel { - constructor(@inject(WidgetsCommonModel) public common: WidgetsCommonModel) { - makeObservable(this); - } - - @observable data: any[] = []; - - @observable keyword: string = ''; - - async loadData() { - const res = await axios.post( - '/api/widgets/marketplace/list', - { - query: this.keyword.trim(), - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - if (res?.data?.success) { - runInAction(() => { - this.data = res.data.data; - }); - } - } - - @action.bound - changeKeyword(keyword: string) { - this.keyword = keyword; - } -} diff --git a/web/apps/web/src/components/manager/manager-content/widgets/widgets-marketplace.tsx b/web/apps/web/src/components/manager/manager-content/widgets/widgets-marketplace.tsx deleted file mode 100644 index eab3600a..00000000 --- a/web/apps/web/src/components/manager/manager-content/widgets/widgets-marketplace.tsx +++ /dev/null @@ -1,396 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { - CheckOutlined, - CloseOutlined, - DownOutlined, - SearchOutlined, -} from '@ant-design/icons'; -import { css } from '@emotion/react'; -import { - AButton, - ADropdownButton, - AInput, - AModal, - ATable, -} from '@shellagent/ui'; -import { - Dropdown, - Form, - Input, - Popconfirm, - theme, - Tooltip, - Typography, -} from 'antd'; -import { ColumnsType } from 'antd/es/table'; -import { Field, FieldProps, Formik } from 'formik'; -import { useInjection } from 'inversify-react'; -import { trace } from 'mobx'; -import { observer } from 'mobx-react-lite'; -import { useEffect } from 'react'; -import { Box, Flex } from 'react-system'; - -import { useGlobalStore } from '@/stores/global/global-provider'; - -import { WidgetsCommonModel } from './widgets-common.model'; -import { WidgetsMarketplaceModel } from './widgets-marketplace.model'; - -export const InstallFromGitModalContent = observer<{ - model: WidgetsCommonModel; -}>(props => { - return ( - { - await props.model.installFromGit(values); - }}> - {formikProps => { - props.model.formikProps = formikProps; - return ( -
- - {({ field }: FieldProps) => ( - - - - )} - - - {({ field }: FieldProps) => ( - - - - )} - -
- ); - }} -
- ); -}); - -export const InstallFromGitModal = observer<{ - model: WidgetsCommonModel; -}>(props => { - trace(); - return ( - props.model.onInstallFromGitSubmit()}> - - - ); -}); - -export const VersionCommitSearchInput = observer<{ - model: WidgetsCommonModel; - record: any; -}>(props => { - return ( - { - e.preventDefault(); - e.stopPropagation(); - props.model.onChangeRemoteVersionQuery(e.target.value); - }} - onPressEnter={() => { - props.model.loadRemoteVersions( - props.record.git, - props.model.remoteVersionQuery, - ); - }} - prefix={} - placeholder="Search versions/commits ..." - /> - ); -}); - -export const WidgetsActionButton = observer<{ - record: any; - model: WidgetsCommonModel; - loadData: () => Promise; -}>(props => { - const installLoading = props.model.installUpdateLoadingMap.get( - props.record.id, - ); - const uninstallLoading = props.model.uninstallLoadingMap.get(props.record.id); - const { token } = theme.useToken(); - - const contentStyle: React.CSSProperties = { - backgroundColor: token.colorBgElevated, - borderRadius: token.borderRadiusLG, - boxShadow: token.boxShadowSecondary, - }; - - const installItems = props.model.remoteList.map((item: string) => { - const canUpdate = props.model.canUpdate(item, props.record); - const label = ( - - - {item}{' '} - {canUpdate === 'can_update' ? ( - - (click to update){' '} - - ) : null} - {canUpdate === 'current' ? ( - (current) - ) : null} - - {canUpdate === 'current' ? ( - - ) : null} - {canUpdate === 'installed' ? ( - - ) : null} - - ); - return { - key: item, - disabled: canUpdate === 'current', - label: - canUpdate === 'installed' ? ( - - {label} - - ) : ( - label - ), - }; - }); - - return ( -
- { - props.model.onRemoteVersionOpenChange(open, props.record.git); - }} - menu={{ - selectedKeys: [], - items: installItems, - onClick: e => { - props.model.installVersion(props.record.id, e.key, props.loadData); - }, - }} - dropdownRender={menu => { - return ( -
- - - Install Another Version - - - - - - {menu} -
- ); - }}> - - - - {props.model.getInstallStatus(props.record) || 'Install'} - - {!installLoading && } - - -
- {props.model.showUninstall(props.record) && ( - - { - props.model.unInstallVersion( - props.record.id, - undefined, - props.loadData, - ); - }}> - } - loading={uninstallLoading} - disabled={installLoading} - menu={{ - items: props.record?.local_commits_hash?.map((item: string) => { - return { - key: item, - label: ( - - { - props.model.unInstallVersion( - props.record.id, - item, - props.loadData, - ); - }} - title="Uninstall selected version" - description={`Are you sure to uninstall version ${item}?`}> - - {item} - - - - - ), - }; - }), - }}> - Uninstall - - - - )} -
- ); -}); - -export const TopInstallButton = observer<{ - model: WidgetsCommonModel; -}>(props => { - return ( - - Install from Git - - ); -}); - -export const WidgetsMarketplace = observer(() => { - const model = useInjection(WidgetsMarketplaceModel); - const { managerDialogOpen } = useGlobalStore(state => ({ - managerDialogOpen: state.managerDialogOpen, - })); - useEffect(() => { - if (managerDialogOpen) { - model.loadData(); - } - }, [managerDialogOpen]); - const columns: ColumnsType = [ - { - title: 'Name', - dataIndex: 'name', - render: (text, record) => { - return ( - - {text} - - ); - }, - }, - { - title: 'Description', - dataIndex: 'description', - render: text => { - return ( - - {text} - - ); - }, - }, - { - title: 'Action', - align: 'center', - width: 1, - dataIndex: 'action', - render: (text, record) => { - return ( - model.loadData()} - /> - ); - }, - }, - ]; - return ( - <> - - - } - placeholder="Search MyShell..." - value={model.keyword} - onChange={e => model.changeKeyword(e.target.value)} - onPressEnter={() => model.loadData()} - /> - -
- -
-
- - - - - - ); -}); diff --git a/web/apps/web/src/components/manager/manager-definitions.ts b/web/apps/web/src/components/manager/manager-definitions.ts deleted file mode 100644 index 3b99aa30..00000000 --- a/web/apps/web/src/components/manager/manager-definitions.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { z } from 'zod'; - -// start-url_types_bases_lists -export const url_types_bases_lists = - '/api/models/marketplace/types_bases_lists'; -// stop-url_types_bases_lists - -// start-ModelsMarketPlace -export const url_models_marketplace_list = '/api/models/marketplace/list'; - -export const ModelsMarketPlaceItem = z.object({ - id: z.string(), - name: z.string(), - type: z.string(), - base: z.string(), - link: z.array( - z.object({ - url: z.string().describe('作为链接, 后端返回了其他数据先不做校验'), - }), - ), - filename: z.string(), - description: z.string(), - install_status: z.union([ - z.literal('not_installed'), - z.literal('installed'), - z.literal('installing'), - ]), -}); -export const ModelsMarketPlaceSuccessRes = z.object({ - success: z.literal(true), - data: z.array(ModelsMarketPlaceItem), -}); - -export const ModelsMarketPlaceFailureRes = z.object({ - success: z.literal(false), - message: z.string(), -}); - -export const ModelsMarketPlaceRes = z.union([ - ModelsMarketPlaceSuccessRes, - ModelsMarketPlaceFailureRes, -]); -// stop-ModelsMarketPlace - -// start-ModelsMarketPlaceInstall -export const url_models_marketplace_install = '/api/models/marketplace/install'; -export const ModelsInstallRequest = z.object({ - id: z.string(), - force: z.literal('True').optional(), -}); - -export const downloading_event = z.object({ - output: z.object({ - progress: z.number(), - }), -}); - -// stop-ModelsMarketPlaceInstall - -// start-Manager-Models-Installed-List -export const url_models_installed_list = '/api/models/installed/list'; -export const ModelInstalledResItem = z.object({ - id: z.string(), - filename: z.string(), - save_path: z.string(), - local_path: z.string(), - create_time: z.coerce.date(), - update_time: z.coerce.date(), -}); -// stop-Manager-Models-Installed-List diff --git a/web/apps/web/src/components/manager/manger-sider/index.tsx b/web/apps/web/src/components/manager/manger-sider/index.tsx deleted file mode 100644 index 970c39a7..00000000 --- a/web/apps/web/src/components/manager/manger-sider/index.tsx +++ /dev/null @@ -1,42 +0,0 @@ -'use client'; - -import { Heading, Tabs, TabsList, TabsTrigger } from '@shellagent/ui'; - -import { - useGlobalStore, - ManagerTypeEnum, -} from '@/stores/global/global-provider'; - -export const ManagerSideBar = () => { - const { managerType, setManagerType } = useGlobalStore(state => ({ - managerType: state.managerType, - setManagerType: state.setManagerType, - })); - - return ( -
- - Manager - - setManagerType(value as ManagerTypeEnum)} - className="w-full h-full p-0"> - - - Widgets - - - Models - - - -
- ); -}; diff --git a/web/apps/web/src/components/settings/settings.model.ts b/web/apps/web/src/components/settings/settings.model.ts index ae984573..a2d66ea4 100644 --- a/web/apps/web/src/components/settings/settings.model.ts +++ b/web/apps/web/src/components/settings/settings.model.ts @@ -1,4 +1,3 @@ -import axios from 'axios'; import { inject, injectable, postConstruct } from 'inversify'; import { action, makeObservable, observable } from 'mobx'; @@ -9,10 +8,21 @@ import { ToastModel } from '@/utils/toast.model'; import { DefaultEnvs, DefaultEnvsMap, - loadSettingEnvFormUrl, - saveSettingEnvFormUrl, SettingEnvFormValue, } from './settings-definitions'; +import { + autoUpdateSvc, + checkRepoStatusSvc, + getAutoUpdateSvc, + getCheckRepoStatusSvc, + getLastChecktimeSvc, + getUpdateChannelSvc, + loadEnvSvc, + restartSvc, + saveEnvSvc, + updateChannelSvc, + updateSvc, +} from '@/services/app'; export type SidebarValue = 'Environment' | 'SoftwareUpdate'; @@ -104,12 +114,8 @@ export class SettingsModel { @action.bound async getLastChecktime() { try { - const res = await axios.get(`/api/last_check_time`, { - headers: { - 'Content-Type': 'application/json', - }, - }); - this.lastChecktime = res.data.last_check_time; + const res = await getLastChecktimeSvc({}); + this.lastChecktime = res.last_check_time; } catch (e: any) { this.toast.error(e.message); } @@ -121,12 +127,8 @@ export class SettingsModel { return false; } try { - const res = await axios.get(`/api/auto_update`, { - headers: { - 'Content-Type': 'application/json', - }, - }); - this.isAutoCheck = res.data.auto_update; + const res = await getAutoUpdateSvc({}); + this.isAutoCheck = res.auto_update; return this.isAutoCheck; } catch (e: any) { this.toast.emitter.emit('message.error', e.message); @@ -140,12 +142,8 @@ export class SettingsModel { return false; } try { - const res = await axios.get(`/api/update_channel`, { - headers: { - 'Content-Type': 'application/json', - }, - }); - this.isBetaCheck = res.data.update_channel !== 'stable'; + const res = await getUpdateChannelSvc({}); + this.isBetaCheck = res.update_channel !== 'stable'; return this.isBetaCheck; } catch (e: any) { this.toast.emitter.emit('message.error', e.message); @@ -157,17 +155,9 @@ export class SettingsModel { async setBetaCheck(isBetaCheck: boolean) { try { this.isBetaCheckLoading = true; - await axios.post( - `/api/update_channel`, - { - update_channel: isBetaCheck ? 'next' : 'stable', - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); + await updateChannelSvc({ + update_channel: isBetaCheck ? 'next' : 'stable', + }); this.isBetaCheck = isBetaCheck; this.checkedStatus = null; } catch (e: any) { @@ -181,17 +171,9 @@ export class SettingsModel { async setAutoCheck(isAutoCheck: boolean) { try { this.isAutoCheckLoading = true; - await axios.post( - `/api/auto_update`, - { - auto_update: isAutoCheck, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); + await autoUpdateSvc({ + auto_update: isAutoCheck, + }); this.isAutoCheck = isAutoCheck; } catch (e: any) { this.toast.emitter.emit('message.error', e.message); @@ -204,12 +186,8 @@ export class SettingsModel { async checkNow() { try { this.isChecking = true; - const res = await axios.get(`/api/check_repo_status`, { - headers: { - 'Content-Type': 'application/json', - }, - }); - this.checkRet = res.data; + const res = await checkRepoStatusSvc({}); + this.checkRet = res; if (this.checkRet.has_new_stable) { this.checkedStatus = 'newUpdate'; @@ -227,12 +205,8 @@ export class SettingsModel { @action.bound async getCurrentVersion() { try { - const res = await axios.get(`/api/check_repo_status`, { - headers: { - 'Content-Type': 'application/json', - }, - }); - this.checkRet.current_version = res.data.current_version; + const res = await getCheckRepoStatusSvc({}); + this.checkRet.current_version = res.current_version; } catch (e: any) { this.toast.emitter.emit('message.error', e.message); } @@ -241,11 +215,7 @@ export class SettingsModel { @action.bound async restart() { this.isRestarting = true; - await axios.get(`/api/restart`, { - headers: { - 'Content-Type': 'application/json', - }, - }); + await restartSvc({}); const pollInterval = 3000; const poll = async () => { try { @@ -269,11 +239,7 @@ export class SettingsModel { async updateNow() { try { this.isUpdating = true; - await axios.get(`/api/update`, { - headers: { - 'Content-Type': 'application/json', - }, - }); + await updateSvc({}); this.isToRestart = true; } catch (e: any) { this.toast.emitter.emit('message.error', e.message); @@ -287,20 +253,15 @@ export class SettingsModel { if (settingsDisabled) return null; this.isLoadLoading = true; try { - const res = await axios.get(loadSettingEnvFormUrl, { - headers: { - 'Content-Type': 'application/json', - }, - }); - const ret: any = res.data; + const res = await loadEnvSvc({}); this.envs = new Map(); - const envs = formatEnvs2Form(ret?.envs); + const envs = formatEnvs2Form(res?.envs); envs.forEach((item: any) => { this.envs.set(item.key, item.value); }); this.isLoadLoading = false; return { - ...res.data, + ...res, envs, }; } catch (e: any) { @@ -316,18 +277,10 @@ export class SettingsModel { value?.envs.forEach((item: any) => { this.envs.set(item.key, item.value); }); - await axios.post( - saveSettingEnvFormUrl, - { - ...value, - envs: formatEnvs2Api(value.envs), - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); + await saveEnvSvc({ + ...value, + envs: formatEnvs2Api(value.envs), + }); } catch (e: any) { this.toast.emitter.emit('message.error', e.message); } diff --git a/web/apps/web/src/services/app/index.tsx b/web/apps/web/src/services/app/index.tsx index 06747c60..6a1793b5 100644 --- a/web/apps/web/src/services/app/index.tsx +++ b/web/apps/web/src/services/app/index.tsx @@ -26,6 +26,11 @@ import { GetAppVersionListResponse, } from './type'; import { APIFetch } from '../base'; +import { + loadSettingEnvFormUrl, + saveSettingEnvFormUrl, + SettingEnvFormValue, +} from '@/components/settings/settings-definitions'; // 保存 export const saveApp = (params: SaveAppRequest) => { @@ -127,3 +132,92 @@ export const importApp = (params: ExportBotResponse['data']) => { body: params, }); }; + +export const getLastChecktimeSvc: Fetcher< + { + last_check_time: string; + }, + {} +> = () => { + return APIFetch.get('/api/last_check_time'); +}; + +export const getAutoUpdateSvc: Fetcher< + { + auto_update: boolean; + }, + {} +> = () => { + return APIFetch.get('/api/auto_update'); +}; + +export const getUpdateChannelSvc: Fetcher< + { + update_channel: string; + }, + {} +> = () => { + return APIFetch.get('/api/update_channel'); +}; + +export const updateChannelSvc: Fetcher< + {}, + { + update_channel: string; + } +> = params => { + return APIFetch.post('/api/update_channel', { + body: params, + }); +}; + +export const autoUpdateSvc: Fetcher< + {}, + { + auto_update: boolean; + } +> = params => { + return APIFetch.post('/api/auto_update', { + body: params, + }); +}; + +export const checkRepoStatusSvc: Fetcher< + Partial<{ + has_new_stable: true; + target_release_date: string; + current_version: string; + latest_tag_name: string; + changelog: string; + }>, + {} +> = params => { + return APIFetch.get('/api/check_repo_status', { + body: params, + }); +}; + +export const getCheckRepoStatusSvc: Fetcher< + { + current_version: string; + }, + {} +> = () => { + return APIFetch.get('/api/update_channel'); +}; + +export const restartSvc: Fetcher<{}, {}> = () => { + return APIFetch.get('/api/restart'); +}; + +export const updateSvc: Fetcher<{}, {}> = () => { + return APIFetch.get(`/api/update`); +}; + +export const loadEnvSvc: Fetcher = () => { + return APIFetch.get(loadSettingEnvFormUrl); +}; + +export const saveEnvSvc: Fetcher = () => { + return APIFetch.get(saveSettingEnvFormUrl); +}; diff --git a/web/apps/web/src/services/app/message-type.ts b/web/apps/web/src/services/app/message-type.ts index d9190a98..ca173612 100644 --- a/web/apps/web/src/services/app/message-type.ts +++ b/web/apps/web/src/services/app/message-type.ts @@ -252,6 +252,7 @@ export interface ChatServerMessage { * 对齐主站 */ inputSetting?: MessageInputSetting; + message?: string; } /** diff --git a/web/apps/web/src/stores/global/global-provider.tsx b/web/apps/web/src/stores/global/global-provider.tsx index 8649ca67..93c603b6 100644 --- a/web/apps/web/src/stores/global/global-provider.tsx +++ b/web/apps/web/src/stores/global/global-provider.tsx @@ -2,7 +2,6 @@ import { type ReactNode, createContext, useRef, useContext } from 'react'; import { useStore } from 'zustand'; import { createStore } from 'zustand/vanilla'; -import { ManagerDialog } from '@/components/manager'; import { ChangelogDialog, SettingsDialog, @@ -66,7 +65,6 @@ export const GlobalStoreProvider = ({ children }: GlobalStoreProviderProps) => { return ( {children} - From d45c7a01dd2f3eaec22c1760b9e73a0f4a6a34bc Mon Sep 17 00:00:00 2001 From: kun Date: Thu, 9 Jan 2025 11:26:42 +0800 Subject: [PATCH 03/42] feat: hide in web --- web/apps/web/src/components/app/constants.tsx | 2 ++ .../src/components/app/task-list/index.tsx | 1 + .../common/list-footer-extra/index.tsx | 24 ++++++++++--------- .../src/components/layouts/side-content.tsx | 3 +++ .../src/components/layouts/side-footer.tsx | 4 ++++ web/apps/web/src/services/base.ts | 6 +++++ .../src/components/material-list/index.tsx | 1 + .../flow-engine/src/types/material-list.ts | 1 + 8 files changed, 31 insertions(+), 11 deletions(-) diff --git a/web/apps/web/src/components/app/constants.tsx b/web/apps/web/src/components/app/constants.tsx index 1be52954..770c83c5 100644 --- a/web/apps/web/src/components/app/constants.tsx +++ b/web/apps/web/src/components/app/constants.tsx @@ -137,6 +137,7 @@ export const materialList: MaterialListType = [ name: 'Workflow Runner', type: NodeTypeEnum.workflow, undraggable: true, + hidden: process.env.NEXT_PUBLIC_HIDE_IN_WEB === 'true', }, ], }, @@ -204,6 +205,7 @@ export const materialList: MaterialListType = [ type: NodeTypeEnum.widget, custom: true, undraggable: true, + hidden: process.env.NEXT_PUBLIC_HIDE_IN_WEB === 'true', }, ], }, diff --git a/web/apps/web/src/components/app/task-list/index.tsx b/web/apps/web/src/components/app/task-list/index.tsx index 3574f5c7..62accdd5 100644 --- a/web/apps/web/src/components/app/task-list/index.tsx +++ b/web/apps/web/src/components/app/task-list/index.tsx @@ -25,6 +25,7 @@ const TreeNode: React.FC<{ const hover = useHover(nodeRef); + if (data.hidden) return null; return (
{ feedbackModel.drawer.open(); }} /> - { - if (settingsDisabled) return; - settingsModel.modal.open(); - }} - /> + {process.env.NEXT_PUBLIC_HIDE_IN_WEB !== 'true' && ( + { + if (settingsDisabled) return; + settingsModel.modal.open(); + }} + /> + )}
); }; diff --git a/web/apps/web/src/components/layouts/side-content.tsx b/web/apps/web/src/components/layouts/side-content.tsx index 998ed7c4..a029ec43 100644 --- a/web/apps/web/src/components/layouts/side-content.tsx +++ b/web/apps/web/src/components/layouts/side-content.tsx @@ -13,6 +13,7 @@ interface ItemType { href: string; title: string; icon: React.ReactNode; + hidden?: boolean; } const ItemContent = ({ @@ -22,6 +23,7 @@ const ItemContent = ({ item: ItemType; isActive: (path: string) => boolean; }) => { + if (item.hidden) return null; return ( , }, ]; diff --git a/web/apps/web/src/components/layouts/side-footer.tsx b/web/apps/web/src/components/layouts/side-footer.tsx index befc9311..69e70f1a 100644 --- a/web/apps/web/src/components/layouts/side-footer.tsx +++ b/web/apps/web/src/components/layouts/side-footer.tsx @@ -24,6 +24,7 @@ interface ItemType { title: string; icon: React.ReactNode; passHref?: boolean; + hidden?: boolean; onClick?: () => void; disabled?: boolean; } @@ -44,7 +45,9 @@ const ItemFooter = ({ passHref, onClick, disabled, + hidden, }: ItemType) => { + if (hidden) return null; return ( { if (settingsDisabled) return; diff --git a/web/apps/web/src/services/base.ts b/web/apps/web/src/services/base.ts index aea1e0c8..e880e298 100644 --- a/web/apps/web/src/services/base.ts +++ b/web/apps/web/src/services/base.ts @@ -112,6 +112,12 @@ export const customFetch = async ( result = response; } + if (response.status === 401 && process.env.NEXT_PUBLIC_LOGIN_URL) { + const errorMessage = `Authentication failed! status: ${response.status}`; + reject(new Error(errorMessage)); + window.location.href = `${process.env.NEXT_PUBLIC_LOGIN_URL}?login=true&redirect=${window.location.href}`; + } + if (response.status > 400) { const errorMessage = `HTTP error! status: ${response.status}`; reject(new Error(errorMessage)); diff --git a/web/packages/flow-engine/src/components/material-list/index.tsx b/web/packages/flow-engine/src/components/material-list/index.tsx index 81c413d6..1db749ee 100644 --- a/web/packages/flow-engine/src/components/material-list/index.tsx +++ b/web/packages/flow-engine/src/components/material-list/index.tsx @@ -66,6 +66,7 @@ const DraggableTreeNode: React.FC<{ data: WidgetItem }> = ({ data }) => { } }; + if (data.hidden) return null; return (
JSX.Element; widget_name?: string; }; From 97b53f02cb30546e775ec66f69e9bb42b9567487 Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 9 Jan 2025 17:34:13 +0800 Subject: [PATCH 04/42] feat: home support app_type --- web/apps/web/src/app/app/page.tsx | 68 +++++----------- web/apps/web/src/app/container.ts | 3 + .../src/components/home/card-list/index.tsx | 45 +++++++++++ .../components/home/create-dialog/index.tsx | 1 + .../src/components/home/detail-form/index.tsx | 18 ++++- .../home/detail-form/type-select.tsx | 57 ++++++++++++++ .../src/components/home/filter-tabs/index.tsx | 57 ++++++++++++++ web/apps/web/src/services/home/type.ts | 1 + .../web/src/stores/home/models/home-model.ts | 77 +++++++++++++++++++ 9 files changed, 277 insertions(+), 50 deletions(-) create mode 100644 web/apps/web/src/components/home/card-list/index.tsx create mode 100644 web/apps/web/src/components/home/detail-form/type-select.tsx create mode 100644 web/apps/web/src/components/home/filter-tabs/index.tsx create mode 100644 web/apps/web/src/stores/home/models/home-model.ts diff --git a/web/apps/web/src/app/app/page.tsx b/web/apps/web/src/app/app/page.tsx index 897a4a25..be145b6b 100644 --- a/web/apps/web/src/app/app/page.tsx +++ b/web/apps/web/src/app/app/page.tsx @@ -1,84 +1,54 @@ 'use client'; import '../reflect-metadata-client-side'; -import { Heading, Spinner, Text } from '@shellagent/ui'; +import { Heading } from '@shellagent/ui'; import { useScroll } from 'ahooks'; import { useInjection } from 'inversify-react'; import { useEffect, useRef } from 'react'; -import useSWR from 'swr'; import { CreateDialog } from '@/components/home/create-dialog'; -import { FlowCard } from '@/components/home/flow-card'; import { ImportDialog } from '@/components/home/import-dialog'; -import { SettingsModel } from '@/components/settings/settings.model'; -import { fetchList } from '@/services/home'; +import { CardList } from '@/components/home/card-list'; +import { FilterTabs } from '@/components/home/filter-tabs'; import { cn } from '@/utils/cn'; +import { HomeModel } from '@/stores/home/models/home-model'; export default function AppPage() { const contentRef = useRef(null); const position = useScroll(contentRef); + const homeModel = useInjection(HomeModel); - const settingsModel = useInjection(SettingsModel); useEffect(() => { - (async function () { - await settingsModel.getIsBetaCheck(); - const isAutoCheck = await settingsModel.getAutoCheck(); - if (isAutoCheck) { - settingsModel.autoCheck(); - } - })(); + homeModel.autoCheck(); + homeModel.fetchList({ type: 'app' }); }, []); - const { data, isLoading, mutate } = useSWR('/api/list?type=app', () => - fetchList({ type: 'app' }), - ); - const onRefresh = () => { - mutate(); + homeModel.refreshList({ type: 'app' }); }; return (
0 ? 'border-default' : 'border-transparent', )}> - App -
- - +
+ App +
+ + +
+
+
+
- {isLoading && ( -
- -
- )} - {!isLoading && data?.data.length ? ( -
- {data?.data.map(item => ( - - ))} -
- ) : null} - {!isLoading && !data?.data.length && ( -
- Create your first App - - Click button to create a app - - -
- )} +
); diff --git a/web/apps/web/src/app/container.ts b/web/apps/web/src/app/container.ts index 9fc0f5bf..af2deea1 100644 --- a/web/apps/web/src/app/container.ts +++ b/web/apps/web/src/app/container.ts @@ -8,6 +8,7 @@ import { Container, ContainerModule, interfaces } from 'inversify'; import * as Mobx from 'mobx'; import { toast } from 'react-toastify'; +import { HomeModel } from '@/stores/home/models/home-model'; import { ComfyUIModel } from '@/components/app/plugins/comfyui/comfyui.model'; import { AssistantModel } from '@/components/assistant/model'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; @@ -103,6 +104,8 @@ export const webModule = new ContainerModule(bind => { bind('ComfyUIModel').to(ComfyUIModel).inSingletonScope(); + bind(HomeModel).toSelf().inSingletonScope(); + // refactor app builder container .bind('AppBuilderModel') diff --git a/web/apps/web/src/components/home/card-list/index.tsx b/web/apps/web/src/components/home/card-list/index.tsx new file mode 100644 index 00000000..51d210ca --- /dev/null +++ b/web/apps/web/src/components/home/card-list/index.tsx @@ -0,0 +1,45 @@ +'use client'; + +import { Heading, Spinner, Text } from '@shellagent/ui'; +import { CreateDialog } from '@/components/home/create-dialog'; +import { FlowCard } from '@/components/home/flow-card'; +import { useInjection } from 'inversify-react'; +import { observer } from 'mobx-react-lite'; +import { HomeModel } from '@/stores/home/models/home-model'; + +interface CardListProps { + type: 'app' | 'workflow'; + onRefresh: () => void; +} + +export const CardList = observer(({ type, onRefresh }: CardListProps) => { + const homeModel = useInjection(HomeModel); + + if (homeModel.listLoading) { + return ( +
+ +
+ ); + } + + if (!homeModel.filteredData.length) { + return ( +
+ Create your first {type} + + Click button to create a {type} + + +
+ ); + } + + return ( +
+ {homeModel.filteredData.map(item => ( + + ))} +
+ ); +}); diff --git a/web/apps/web/src/components/home/create-dialog/index.tsx b/web/apps/web/src/components/home/create-dialog/index.tsx index 5d6efbcc..bd6c32bf 100644 --- a/web/apps/web/src/components/home/create-dialog/index.tsx +++ b/web/apps/web/src/components/home/create-dialog/index.tsx @@ -107,6 +107,7 @@ export const CreateDialog: React.FC = ({ }); const onConfirm = async () => { + console.log('data>>>', data); run({ ...data, type, template_id: templateId }); }; diff --git a/web/apps/web/src/components/home/detail-form/index.tsx b/web/apps/web/src/components/home/detail-form/index.tsx index 88eb326a..7058e0b4 100644 --- a/web/apps/web/src/components/home/detail-form/index.tsx +++ b/web/apps/web/src/components/home/detail-form/index.tsx @@ -1,5 +1,6 @@ import { ISchema, MemoizedFormEngine, TValues } from '@shellagent/form-engine'; -import { Input, Textarea } from '@shellagent/ui'; +import { Input, Textarea, Select } from '@shellagent/ui'; +import TypeSelect from './type-select'; import React from 'react'; interface DetailFormProps { @@ -33,6 +34,20 @@ export default function DetailForm({ }, ], }, + app_type: { + type: 'string', + title: 'Type', + 'x-type': 'Control', + 'x-layout': 'Vertical', + 'x-component': 'TypeSelect', + default: 'chat_bot', + 'x-component-props': { + options: [ + { label: 'Chat Bot', value: 'chat_bot' }, + { label: 'X Bot', value: 'x_bot' }, + ], + }, + }, description: { type: 'string', title: 'Description', @@ -54,6 +69,7 @@ export default function DetailForm({ components={{ Input, Textarea, + TypeSelect, }} /> ); diff --git a/web/apps/web/src/components/home/detail-form/type-select.tsx b/web/apps/web/src/components/home/detail-form/type-select.tsx new file mode 100644 index 00000000..cef7d86d --- /dev/null +++ b/web/apps/web/src/components/home/detail-form/type-select.tsx @@ -0,0 +1,57 @@ +import { Checkbox } from '@shellagent/ui'; +import React from 'react'; +import { cn } from '@/utils/cn'; + +interface Option { + label: string; + value: string; +} + +interface TypeSelectProps { + value?: string; + defaultValue?: string; + onChange?: (value: string) => void; + options: Option[]; +} + +const TypeSelect: React.FC = ({ + value, + onChange, + options, + defaultValue, +}) => { + const [selectedValue, setSelectedValue] = React.useState( + value || defaultValue, + ); + + const handleSelect = (newValue: string) => { + console.log('newValue>>>', newValue); + setSelectedValue(newValue); + onChange?.(newValue); + }; + + return ( +
+ {options.map(option => ( +
handleSelect(option.value)} + data-value={option.value} + key={option.value} + className={cn( + 'flex h-10 pl-3.5 py-2.5 pr-3 items-center rounded-lg border cursor-pointer', + selectedValue === option.value && 'border-[#3E5CFA]', + )}> + + +
+ ))} +
+ ); +}; + +export default React.memo(TypeSelect); diff --git a/web/apps/web/src/components/home/filter-tabs/index.tsx b/web/apps/web/src/components/home/filter-tabs/index.tsx new file mode 100644 index 00000000..3afad932 --- /dev/null +++ b/web/apps/web/src/components/home/filter-tabs/index.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { Tag } from 'antd'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useInjection } from 'inversify-react'; +import { observer } from 'mobx-react-lite'; +import { useEffect } from 'react'; +import { HomeModel, type AppType } from '@/stores/home/models/home-model'; +import { cn } from '@/utils/cn'; + +interface FilterTabsProps { + className?: string; +} + +export const FilterTabs = observer(({ className }: FilterTabsProps) => { + const router = useRouter(); + const searchParams = useSearchParams(); + const homeModel = useInjection(HomeModel); + + useEffect(() => { + const appType = (searchParams.get('app_type') as AppType) || 'all'; + homeModel.setAppType(appType); + }, [searchParams]); + + const setFilter = (newFilter: AppType) => { + const params = new URLSearchParams(searchParams); + if (newFilter === 'all') { + params.delete('app_type'); + } else { + params.set('app_type', newFilter); + } + router.push(`?${params.toString()}`); + }; + + return ( +
+ setFilter('all')} + className="text-base rounded-lg"> + All + + setFilter('chat_bot')} + className="text-base rounded-lg"> + Chat Bot + + setFilter('x_bot')} + className="text-base rounded-lg"> + X Bot + +
+ ); +}); diff --git a/web/apps/web/src/services/home/type.ts b/web/apps/web/src/services/home/type.ts index 697e9466..f8c7a522 100644 --- a/web/apps/web/src/services/home/type.ts +++ b/web/apps/web/src/services/home/type.ts @@ -15,6 +15,7 @@ export interface GetListResponse { data: Array<{ id: string; metadata: Metadata & { + app_type?: string; create_time: string; update_time: string; }; diff --git a/web/apps/web/src/stores/home/models/home-model.ts b/web/apps/web/src/stores/home/models/home-model.ts new file mode 100644 index 00000000..39a4ceef --- /dev/null +++ b/web/apps/web/src/stores/home/models/home-model.ts @@ -0,0 +1,77 @@ +import { injectable, inject } from 'inversify'; +import { action, makeObservable, observable, runInAction } from 'mobx'; +import { fetchList } from '@/services/home'; +import type { GetListRequest, GetListResponse } from '@/services/home/type'; +import { SettingsModel } from '@/components/settings/settings.model'; +import { ToastModel } from '@/utils/toast.model'; + +export type AppType = 'all' | 'chat_bot' | 'x_bot'; + +@injectable() +export class HomeModel { + @observable listLoading = true; + @observable listData: GetListResponse['data'] = []; + @observable appType: AppType; + + get filteredData() { + if (this.appType === 'all') return this.listData; + return this.listData.filter( + item => item.metadata.app_type === this.appType, + ); + } + + constructor( + @inject(ToastModel) private emitter: ToastModel, + @inject(SettingsModel) public settingsModel: SettingsModel, + ) { + const params = new URLSearchParams(window?.location?.search); + this.appType = (params.get('app_type') as AppType) || 'all'; + + makeObservable(this); + } + + @action.bound + setAppType(type: AppType) { + this.appType = type; + } + + @action.bound + async fetchList(params: GetListRequest) { + try { + runInAction(() => { + this.listLoading = true; + }); + const res = await fetchList(params); + runInAction(() => { + this.listData = res.data; + }); + } catch (error: any) { + this.emitter.emitter.emit('message.error', error.message); + } finally { + runInAction(() => { + this.listLoading = false; + }); + } + } + + @action.bound + async refreshList(params: GetListRequest) { + try { + const res = await fetchList(params); + runInAction(() => { + this.listData = res.data; + }); + } catch (error: any) { + this.emitter.emitter.emit('message.error', error.message); + } + } + + @action.bound + async autoCheck() { + await this.settingsModel.getIsBetaCheck(); + const isAutoCheck = await this.settingsModel.getAutoCheck(); + if (isAutoCheck) { + this.settingsModel.autoCheck(); + } + } +} From f386703fb70154bad003e428bef1079dfec1e1a8 Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 9 Jan 2025 17:34:44 +0800 Subject: [PATCH 05/42] feat: support new proconfig protocol --- web/packages/pro-config-type/src/block.ts | 2 +- web/packages/pro-config-type/src/common.ts | 2 +- .../pro-config-type/src/transition.ts | 9 ++++--- .../shared/src/protocol/pro-config/block.ts | 24 +++++++++++++++++-- .../src/protocol/pro-config/transition.ts | 8 ++++++- 5 files changed, 37 insertions(+), 8 deletions(-) diff --git a/web/packages/pro-config-type/src/block.ts b/web/packages/pro-config-type/src/block.ts index 19335882..07ecb49a 100644 --- a/web/packages/pro-config-type/src/block.ts +++ b/web/packages/pro-config-type/src/block.ts @@ -159,4 +159,4 @@ export interface Dispatcher extends Block { }; blocks?: BlockChildren; transitions: { ALWAYS: TransitionCase[] }; -} \ No newline at end of file +} diff --git a/web/packages/pro-config-type/src/common.ts b/web/packages/pro-config-type/src/common.ts index fca8e804..d9e56091 100644 --- a/web/packages/pro-config-type/src/common.ts +++ b/web/packages/pro-config-type/src/common.ts @@ -1,4 +1,4 @@ -import {EventPrefix} from './transition'; +import { EventPrefix } from './transition'; /** * @example * "variable_name" diff --git a/web/packages/pro-config-type/src/transition.ts b/web/packages/pro-config-type/src/transition.ts index bd5694e5..269faa7d 100644 --- a/web/packages/pro-config-type/src/transition.ts +++ b/web/packages/pro-config-type/src/transition.ts @@ -35,11 +35,14 @@ export type AutomataSpecialEvents = 'DONE'; * - 'ALWAYS' means the state will transist as soon as its condition is satisfied after executing tasks. * - 'X.' means X(Twitter)-related events, including being mentioned and pinned tweet being replied. */ -export type StateSpecialEvents = 'CHAT' | 'ALWAYS' | 'X.MENTIONED' | 'X.PINNED.REPLIED'; +export type StateSpecialEvents = + | 'CHAT' + | 'ALWAYS' + | 'X.MENTIONED' + | 'X.PINNED.REPLIED'; export type ActionEvents = 'COPY' | 'OPEN' | 'DOWNLOAD'; - // TODO: Different payload for different typs type PayloadMap = { 'X.MENTIONED': {}; @@ -49,4 +52,4 @@ type PayloadMap = { export type SpecialEvents = | AutomataSpecialEvents | StateSpecialEvents - | ActionEvents + | ActionEvents; diff --git a/web/packages/shared/src/protocol/pro-config/block.ts b/web/packages/shared/src/protocol/pro-config/block.ts index 2abca96d..0e2145b5 100644 --- a/web/packages/shared/src/protocol/pro-config/block.ts +++ b/web/packages/shared/src/protocol/pro-config/block.ts @@ -2,10 +2,16 @@ import { z } from 'zod'; import { customKeySchema } from './common'; import { variableSchema, valueSchema, inputSchema } from './variables'; import { renderConfigSchema } from './render'; -import { transitionSchema } from './transition'; +import { transitionSchema, transitionCaseSchema } from './transition'; // 基础类型定义 -const blockTypeSchema = z.enum(['automata', 'state', 'task', 'workflow']); +const blockTypeSchema = z.enum([ + 'automata', + 'state', + 'task', + 'workflow', + 'dispatcher', +]); // Block 基础 Schema const blockSchema = z.object({ @@ -128,6 +134,19 @@ const workflowSchema = containerBlockSchema.extend({ blocks: blockChildrenSchema, }); +const dispatcherSchema = blockSchema.extend({ + type: z.literal('dispatcher'), + properties: z + .object({ + cache: z.boolean().optional(), + }) + .optional(), + blocks: blockChildrenSchema.optional(), + transitions: z.object({ + ALWAYS: z.array(transitionCaseSchema), + }), +}); + export { blockSchema, stateSchema, @@ -136,4 +155,5 @@ export { workflowSchema, blockChildrenSchema, containerBlockSchema, + dispatcherSchema, }; diff --git a/web/packages/shared/src/protocol/pro-config/transition.ts b/web/packages/shared/src/protocol/pro-config/transition.ts index d2364fb4..1b1f2138 100644 --- a/web/packages/shared/src/protocol/pro-config/transition.ts +++ b/web/packages/shared/src/protocol/pro-config/transition.ts @@ -4,6 +4,7 @@ import { valueSchema, boolExpressionSchema } from './variables'; // TransitionCase schema export const transitionCaseSchema = z.object({ + name: z.string().optional(), target: targetPathSchema.optional(), target_inputs: z.record(customKeySchema, valueSchema).optional(), condition: z.union([z.boolean(), boolExpressionSchema]).optional(), @@ -12,7 +13,12 @@ export const transitionCaseSchema = z.object({ // Special events schemas export const automataSpecialEventsSchema = z.enum(['DONE']); -export const stateSpecialEventsSchema = z.enum(['CHAT', 'ALWAYS']); +export const stateSpecialEventsSchema = z.enum([ + 'CHAT', + 'ALWAYS', + 'X.MENTIONED', + 'X.PINNED.REPLIED', +]); export const actionEventsSchema = z.enum(['COPY', 'OPEN', 'DOWNLOAD']); export const specialEventsSchema = z.union([ From f920479a4136339396b8e6ce3cd42ff6584e251e Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 9 Jan 2025 21:03:56 +0800 Subject: [PATCH 06/42] =?UTF-8?q?feat:=20=E9=A6=96=E9=A1=B5done?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/home/detail-form/index.tsx | 4 ++-- .../home/detail-form/type-select.tsx | 23 ++++++++----------- .../web/src/stores/home/models/home-model.ts | 5 +--- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/web/apps/web/src/components/home/detail-form/index.tsx b/web/apps/web/src/components/home/detail-form/index.tsx index 7058e0b4..a339a292 100644 --- a/web/apps/web/src/components/home/detail-form/index.tsx +++ b/web/apps/web/src/components/home/detail-form/index.tsx @@ -1,6 +1,6 @@ import { ISchema, MemoizedFormEngine, TValues } from '@shellagent/form-engine'; -import { Input, Textarea, Select } from '@shellagent/ui'; -import TypeSelect from './type-select'; +import { Input, Textarea } from '@shellagent/ui'; +import { TypeSelect } from './type-select'; import React from 'react'; interface DetailFormProps { diff --git a/web/apps/web/src/components/home/detail-form/type-select.tsx b/web/apps/web/src/components/home/detail-form/type-select.tsx index cef7d86d..04ece4ee 100644 --- a/web/apps/web/src/components/home/detail-form/type-select.tsx +++ b/web/apps/web/src/components/home/detail-form/type-select.tsx @@ -14,38 +14,37 @@ interface TypeSelectProps { options: Option[]; } -const TypeSelect: React.FC = ({ +export const TypeSelect: React.FC = ({ value, onChange, options, defaultValue, }) => { - const [selectedValue, setSelectedValue] = React.useState( - value || defaultValue, + const handleSelect = React.useCallback( + (newValue: string) => { + onChange?.(newValue); + }, + [onChange], ); - const handleSelect = (newValue: string) => { - console.log('newValue>>>', newValue); - setSelectedValue(newValue); - onChange?.(newValue); - }; + const actualValue = value ?? defaultValue; return (
{options.map(option => (
handleSelect(option.value)} data-value={option.value} key={option.value} className={cn( 'flex h-10 pl-3.5 py-2.5 pr-3 items-center rounded-lg border cursor-pointer', - selectedValue === option.value && 'border-[#3E5CFA]', + actualValue === option.value && 'border-[#3E5CFA]', )}> handleSelect(option.value)} + checked={actualValue === option.value} className="ml-auto rounded-full" />
@@ -53,5 +52,3 @@ const TypeSelect: React.FC = ({
); }; - -export default React.memo(TypeSelect); diff --git a/web/apps/web/src/stores/home/models/home-model.ts b/web/apps/web/src/stores/home/models/home-model.ts index 39a4ceef..4d22db7f 100644 --- a/web/apps/web/src/stores/home/models/home-model.ts +++ b/web/apps/web/src/stores/home/models/home-model.ts @@ -11,7 +11,7 @@ export type AppType = 'all' | 'chat_bot' | 'x_bot'; export class HomeModel { @observable listLoading = true; @observable listData: GetListResponse['data'] = []; - @observable appType: AppType; + @observable appType: AppType = 'all'; get filteredData() { if (this.appType === 'all') return this.listData; @@ -24,9 +24,6 @@ export class HomeModel { @inject(ToastModel) private emitter: ToastModel, @inject(SettingsModel) public settingsModel: SettingsModel, ) { - const params = new URLSearchParams(window?.location?.search); - this.appType = (params.get('app_type') as AppType) || 'all'; - makeObservable(this); } From 00de35e557a7fc50b99bfaf0df169a00ab46973e Mon Sep 17 00:00:00 2001 From: tiancheng Date: Fri, 10 Jan 2025 09:48:18 +0800 Subject: [PATCH 07/42] type: timer --- web/packages/pro-config-type/src/block.ts | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/web/packages/pro-config-type/src/block.ts b/web/packages/pro-config-type/src/block.ts index 07ecb49a..3bfa7500 100644 --- a/web/packages/pro-config-type/src/block.ts +++ b/web/packages/pro-config-type/src/block.ts @@ -69,6 +69,7 @@ export interface State extends Block { properties?: { is_final?: boolean; cache?: boolean; + timers?: Record; }; // tasks?: BlockChildren; blocks?: BlockChildren; // user_input字段无效 @@ -160,3 +161,45 @@ export interface Dispatcher extends Block { blocks?: BlockChildren; transitions: { ALWAYS: TransitionCase[] }; } + +// Timer +type Timezone = string; +type Date = { + minute?: number; // 0-60 + hour?: number; // 0-24 + date?: number; // 1-31 + month?: number; // 1-12 + year?: number; + timezone?: Timezone | 'client'; +}; + +// 可以用秒数。只是对用户更方便 +type Interval = { + // second?: number; + minute?: number; + hour?: number; + day?: number; +}; + +type Timer = { + // start time + start?: Required | 'last interaction'; + interval?: Interval; + // TODO: 结束时间,主站应该没有支持 + end?: { + date?: Date; + count?: number; + }; + // TODO: random,主站应该未支持 + delay?: + | Interval + | { + random_mode: 'even'; + min: number; + max: number; + }; + // cron style + // recurrence?: Date & { + // day_of_week?: 0-6; + // }; +}; From 0bbfd806a4e0ddede0410f51213e665accd29c46 Mon Sep 17 00:00:00 2001 From: tiancheng Date: Fri, 10 Jan 2025 13:01:52 +0800 Subject: [PATCH 08/42] type: twitter payload --- .../pro-config-type/src/transition.ts | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/web/packages/pro-config-type/src/transition.ts b/web/packages/pro-config-type/src/transition.ts index 269faa7d..6ae7bf34 100644 --- a/web/packages/pro-config-type/src/transition.ts +++ b/web/packages/pro-config-type/src/transition.ts @@ -43,10 +43,32 @@ export type StateSpecialEvents = export type ActionEvents = 'COPY' | 'OPEN' | 'DOWNLOAD'; -// TODO: Different payload for different typs -type PayloadMap = { - 'X.MENTIONED': {}; - 'X.PINNED.REPLIED': {}; +type TwitterUser = { + creation_date: string; + user_id: string; + username: string; + name: string; + is_private: boolean; + is_verified: boolean; + is_blue_verified: boolean; + is_bot: boolean; // 主站用的字段是 bot + verified_type: 'blue' | 'business' | 'government'; +}; +type TweetDetail = { + text: string; + media_urls: URLString[]; // 主站用的字段是 media_url + user: TwitterUser; + tweet_id: string; + creation_date: string; +}; + +export type PayloadMap = { + // 'CHAT': { + // text?: string; + // files?: URLString; + // }; + 'X.MENTIONED': TweetDetail; + 'X.PINNED.REPLIED': TweetDetail; }; export type SpecialEvents = From a56aafd194c7bc65a87b71b9e8ec77bf6a13d6c3 Mon Sep 17 00:00:00 2001 From: tiancheng Date: Fri, 10 Jan 2025 13:37:56 +0800 Subject: [PATCH 09/42] type: speficy image type for tweet detail --- web/packages/pro-config-type/src/transition.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/pro-config-type/src/transition.ts b/web/packages/pro-config-type/src/transition.ts index 6ae7bf34..5a97f633 100644 --- a/web/packages/pro-config-type/src/transition.ts +++ b/web/packages/pro-config-type/src/transition.ts @@ -56,7 +56,7 @@ type TwitterUser = { }; type TweetDetail = { text: string; - media_urls: URLString[]; // 主站用的字段是 media_url + images: URLString[]; // 主站用的字段是 media_url user: TwitterUser; tweet_id: string; creation_date: string; From 74697ed1d39ab0ac648ada128921075b2d50eb2b Mon Sep 17 00:00:00 2001 From: kun Date: Fri, 10 Jan 2025 14:40:29 +0800 Subject: [PATCH 10/42] feat: api --- web/apps/web/next.config.js | 3 +- web/apps/web/src/app/app/page.tsx | 16 ++-- web/apps/web/src/app/container.ts | 3 +- .../components/chat/app-builder-chat.model.ts | 10 +- .../common/performance-monitor/index.tsx | 91 ------------------- web/apps/web/src/services/app/index.tsx | 3 +- web/apps/web/src/services/base.ts | 9 +- web/apps/web/src/services/common/index.ts | 25 +++-- 8 files changed, 41 insertions(+), 119 deletions(-) delete mode 100644 web/apps/web/src/components/common/performance-monitor/index.tsx diff --git a/web/apps/web/next.config.js b/web/apps/web/next.config.js index 34dfd9d6..764fa90d 100644 --- a/web/apps/web/next.config.js +++ b/web/apps/web/next.config.js @@ -13,7 +13,7 @@ module.exports = { '@shellagent/form-engine', ], images: { - unoptimized: false, + unoptimized: true, }, webpack: ( config, @@ -50,6 +50,7 @@ module.exports = { return config; }, rewrites: async () => { + // console.log("process.env.NEXT_PUBLIC_SHELL_API_URL", process.env.NEXT_PUBLIC_SHELL_API_URL) return [ { source: '/api/:path*', diff --git a/web/apps/web/src/app/app/page.tsx b/web/apps/web/src/app/app/page.tsx index 897a4a25..4603382f 100644 --- a/web/apps/web/src/app/app/page.tsx +++ b/web/apps/web/src/app/app/page.tsx @@ -20,13 +20,15 @@ export default function AppPage() { const settingsModel = useInjection(SettingsModel); useEffect(() => { - (async function () { - await settingsModel.getIsBetaCheck(); - const isAutoCheck = await settingsModel.getAutoCheck(); - if (isAutoCheck) { - settingsModel.autoCheck(); - } - })(); + if (process.env.NEXT_PUBLIC_HIDE_IN_WEB !== 'true') { + (async function () { + await settingsModel.getIsBetaCheck(); + const isAutoCheck = await settingsModel.getAutoCheck(); + if (isAutoCheck) { + settingsModel.autoCheck(); + } + })(); + } }, []); const { data, isLoading, mutate } = useSWR('/api/list?type=app', () => diff --git a/web/apps/web/src/app/container.ts b/web/apps/web/src/app/container.ts index f2b97eef..14a89812 100644 --- a/web/apps/web/src/app/container.ts +++ b/web/apps/web/src/app/container.ts @@ -7,10 +7,11 @@ import { ImageCanvasModel } from 'image-canvas/model'; import { Container, interfaces } from 'inversify'; import * as Mobx from 'mobx'; import { toast } from 'react-toastify'; + import { ComfyUIModel } from '@/components/app/plugins/comfyui/comfyui.model'; import { AssistantModel } from '@/components/assistant/model'; -import { FeedbackModel } from '@/components/feedback/model'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; +import { FeedbackModel } from '@/components/feedback/model'; import { OpenImageCanvasModel } from '@/components/image-canvas/open-image-canvas.model'; import { SettingsModel } from '@/components/settings/settings.model'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; diff --git a/web/apps/web/src/components/chat/app-builder-chat.model.ts b/web/apps/web/src/components/chat/app-builder-chat.model.ts index 8c5b9c20..a1939f60 100644 --- a/web/apps/web/src/components/chat/app-builder-chat.model.ts +++ b/web/apps/web/src/components/chat/app-builder-chat.model.ts @@ -21,6 +21,7 @@ import type { ServerMessage } from '../../services/app/message-type'; import { EventStatusEnum, RunAppRequest } from '../../services/app/type'; import { ToastModel } from '../../utils/toast.model'; import { initBot } from '@/services/app'; +import { baseHeaders } from '@/services/base'; @injectable() export class AppBuilderChatModel { @@ -137,14 +138,14 @@ export class AppBuilderChatModel { this.isInitBotLoading = true; }); try { - const reuslt = await initBot({ + const result = await initBot({ automata, }); - if (reuslt.data.session_id == null && reuslt.data.message) { - this.emitter.emitter.emit('message.error', reuslt.data.message); + if (result.session_id == null && result.message) { + this.emitter.emitter.emit('message.error', result.message); return; } - this.session_id = reuslt.data.session_id; + this.session_id = result.session_id; this.openRunDrawer(); await this.chatNew.isReadyPromise; this.chatNew.clearMemory(); @@ -187,6 +188,7 @@ export class AppBuilderChatModel { headers: { method: 'POST', 'Content-Type': 'application/json', + ...baseHeaders, }, body: JSON.stringify({ session_id: this.session_id, diff --git a/web/apps/web/src/components/common/performance-monitor/index.tsx b/web/apps/web/src/components/common/performance-monitor/index.tsx deleted file mode 100644 index 02fc58ec..00000000 --- a/web/apps/web/src/components/common/performance-monitor/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { useRequest } from 'ahooks'; -import React from 'react'; - -interface MonitorData { - cpu_memory_used: number; - cpu_memory_total: number; - gpu_memory_used: number; - gpu_memory_total: number; -} - -async function fetchMonitorData(): Promise { - const response = await fetch('/api/tools/monitor_memory', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({}), - }); - if (!response.ok) throw new Error('Failed to fetch monitor data'); - return response.json(); -} - -const MemoryBar = ({ percentage }: { percentage: string }) => { - const getColor = (percentage: number) => { - if (percentage < 50) return 'bg-green-500 text-green-500'; - if (percentage < 80) return 'bg-orange-500 text-orange-500'; - return 'bg-red-500 text-red-500'; - }; - - const color = getColor(parseFloat(percentage)).split(' ')[0]; - return ( -
-
-
- ); -}; - -export default function PerformanceMonitor() { - const isDev = process.env.NEXT_PUBLIC_DEVELOPMENT === 'yes'; - const { data } = useRequest(fetchMonitorData, { - pollingInterval: isDev ? 120 * 1000 : 3000, - pollingWhenHidden: false, - }); - - if (!data) return null; - - const formatUsage = (used: number, total: number) => { - const percentage = ((used / total) * 100).toFixed(1); - const usedGB = used.toFixed(1); - const totalGB = total.toFixed(1); - return { percentage, usedGB, totalGB }; - }; - - const getColor = (percentage: number) => { - if (percentage < 50) return 'bg-green-500 text-green-500'; - if (percentage < 80) return 'bg-orange-500 text-orange-500'; - return 'bg-red-500 text-red-500'; - }; - - const cpuUsage = formatUsage(data.cpu_memory_used, data.cpu_memory_total); - const gpuUsage = formatUsage(data.gpu_memory_used, data.gpu_memory_total); - - const cpuColor = getColor(parseFloat(cpuUsage.percentage)).split(' ')[1]; - const gpuColor = getColor(parseFloat(gpuUsage.percentage)).split(' ')[1]; - - return ( -
-
-
- CPU - {cpuUsage.percentage}% -
- -
- {cpuUsage.usedGB} / {cpuUsage.totalGB}GB -
-
-
-
- GPU - {gpuUsage.percentage}% -
- -
- {gpuUsage.usedGB} / {gpuUsage.totalGB}GB -
-
-
- ); -} diff --git a/web/apps/web/src/services/app/index.tsx b/web/apps/web/src/services/app/index.tsx index 6a1793b5..dd627db0 100644 --- a/web/apps/web/src/services/app/index.tsx +++ b/web/apps/web/src/services/app/index.tsx @@ -25,7 +25,7 @@ import { GetAppVersionListRequest, GetAppVersionListResponse, } from './type'; -import { APIFetch } from '../base'; +import { APIFetch, baseHeaders } from '../base'; import { loadSettingEnvFormUrl, saveSettingEnvFormUrl, @@ -86,6 +86,7 @@ export const runApp = ( headers: { method: 'POST', 'Content-Type': 'application/json', + ...baseHeaders, }, body: JSON.stringify(params), signal: abortController.signal, diff --git a/web/apps/web/src/services/base.ts b/web/apps/web/src/services/base.ts index e880e298..1e6100fd 100644 --- a/web/apps/web/src/services/base.ts +++ b/web/apps/web/src/services/base.ts @@ -32,6 +32,10 @@ const baseOptions = { redirect: 'follow', }; +export const baseHeaders = { + 'myshell-service-name': 'organics-api', +}; + // 通用fetch,后续再次基础上拓展 export const customFetch = async ( url: string, @@ -91,7 +95,10 @@ export const customFetch = async ( globalThis .fetch(url, { ...options, - headers, + headers: { + ...baseHeaders, + ...headers, + }, } as RequestInit) .then(response => { const contentType = response.headers.get('content-type'); diff --git a/web/apps/web/src/services/common/index.ts b/web/apps/web/src/services/common/index.ts index 7c0e397e..cfc96f00 100644 --- a/web/apps/web/src/services/common/index.ts +++ b/web/apps/web/src/services/common/index.ts @@ -1,4 +1,5 @@ import { toast } from 'react-toastify'; +import { APIFetch } from '../base'; export interface UploadResponse { url: string; @@ -10,21 +11,19 @@ export async function upload( ): Promise { const formData = new FormData(); formData.append('file', file, name || file.name); - const response: { file_path: string } = await fetch('/api/upload', { - method: 'post', + const response = await APIFetch.post<{ file_path: string }>('/api/upload', { body: formData, - }) - .then(res => res.json()) - .catch(() => { - toast.error('upload error', { - position: 'top-center', - autoClose: 2000, - hideProgressBar: true, - pauseOnHover: true, - closeButton: false, - }); + }).catch(() => { + toast.error('upload error', { + position: 'top-center', + autoClose: 2000, + hideProgressBar: true, + pauseOnHover: true, + closeButton: false, }); + }); + console.log('response: ', response); return { - url: response?.file_path, + url: response?.file_path || '', }; } From 92dd7a1a101c5c326ba72a5155047fef384e02a8 Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 10 Jan 2025 16:27:36 +0800 Subject: [PATCH 11/42] WIP: x appbuilder --- web/apps/web/src/app/app/detail/page.tsx | 9 +- web/apps/web/src/components/app/constants.tsx | 36 +- .../web/src/components/app/nodes/index.tsx | 1 + .../x-state-node/hook/use-duplicate-state.tsx | 25 + .../x-state-node/hook/use-paste-state.tsx | 62 + .../app/nodes/x-state-node/index.tsx | 269 ++++ web/apps/web/src/services/app/type.ts | 1 + web/apps/web/src/services/home/type.ts | 2 +- .../stores/app/models/app-builder.model.ts | 9 +- .../stores/app/models/x-app-builder.model.ts | 14 + .../stores/app/schema/get-x-state-schema.ts | 25 + .../src/stores/app/schema/x-state-schema.ts | 1088 +++++++++++++++++ 12 files changed, 1536 insertions(+), 5 deletions(-) create mode 100644 web/apps/web/src/components/app/nodes/x-state-node/hook/use-duplicate-state.tsx create mode 100644 web/apps/web/src/components/app/nodes/x-state-node/hook/use-paste-state.tsx create mode 100644 web/apps/web/src/components/app/nodes/x-state-node/index.tsx create mode 100644 web/apps/web/src/stores/app/models/x-app-builder.model.ts create mode 100644 web/apps/web/src/stores/app/schema/get-x-state-schema.ts create mode 100644 web/apps/web/src/stores/app/schema/x-state-schema.ts diff --git a/web/apps/web/src/app/app/detail/page.tsx b/web/apps/web/src/app/app/detail/page.tsx index 04340710..d92b4f70 100644 --- a/web/apps/web/src/app/app/detail/page.tsx +++ b/web/apps/web/src/app/app/detail/page.tsx @@ -11,7 +11,12 @@ import { useEffect, useRef } from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; -import { edgeTypes, materialList, nodeTypes } from '@/components/app/constants'; +import { + edgeTypes, + materialList, + nodeTypes, + xNodeTypes, +} from '@/components/app/constants'; import FlowHeader from '@/components/app/flow-header'; import { Header } from '@/components/app/header'; import { ComfyUIEditorModal } from '@/components/app/plugins/comfyui/widgets/comfyui-editor'; @@ -101,7 +106,7 @@ const FlowEngineWrapper = observer( appBuilder.initAppBuilderLoading || appBuilder.fetchFlowListLoading } ref={flowRef} - nodeTypes={nodeTypes} + nodeTypes={appBuilder.appType === 'x_bot' ? xNodeTypes : nodeTypes} edgeTypes={edgeTypes} materialList={materialList} footerExtra={} diff --git a/web/apps/web/src/components/app/constants.tsx b/web/apps/web/src/components/app/constants.tsx index c650cb86..0f270ee9 100644 --- a/web/apps/web/src/components/app/constants.tsx +++ b/web/apps/web/src/components/app/constants.tsx @@ -13,6 +13,7 @@ import { StateNode, IntroNode, NoteNode, + XStateNode, } from '@/components/app/nodes'; export const nodeTypes = { @@ -22,12 +23,17 @@ export const nodeTypes = { [NodeTypeEnum.note]: NoteNode, }; +export const xNodeTypes = { + [NodeTypeEnum.state]: XStateNode, + [NodeTypeEnum.note]: NoteNode, +}; + export const edgeTypes = { [EdgeTypeEnum.default]: DefaultEdge, [EdgeTypeEnum.custom]: CustomEdge, }; -export const defaultFlow: IFlow = { +export const chatDefaultFlow: IFlow = { nodes: [ { width: 377, @@ -108,6 +114,16 @@ export const defaultFlow: IFlow = { }, }; +export const xDefaultFlow: IFlow = { + nodes: [], + edges: [], + viewport: { + x: 100, + y: 100, + zoom: 0.5, + }, +}; + export const defaultProConfig: Workflow = { type: 'workflow', blocks: [], @@ -210,6 +226,24 @@ export const materialList: MaterialListType = [ }, ]; +export const xMaterialList: MaterialListType = [ + { + plain: true, + items: [ + { + name: 'State', + display_name: 'State', + type: NodeTypeEnum.state, + }, + { + name: 'Note', + display_name: 'Note', + type: NodeTypeEnum.note, + }, + ], + }, +]; + export const inputSourceHandle = 'custom_message-input-source-handle'; export const buttonSourceHandle = 'custom_button-source-handle'; diff --git a/web/apps/web/src/components/app/nodes/index.tsx b/web/apps/web/src/components/app/nodes/index.tsx index 7e290b1f..6d7d167a 100644 --- a/web/apps/web/src/components/app/nodes/index.tsx +++ b/web/apps/web/src/components/app/nodes/index.tsx @@ -2,3 +2,4 @@ export { default as StartNode } from './start-node'; export { default as StateNode } from './state-node'; export { default as IntroNode } from './intro-node'; export { default as NoteNode } from './note-node'; +export { default as XStateNode } from './x-state-node'; diff --git a/web/apps/web/src/components/app/nodes/x-state-node/hook/use-duplicate-state.tsx b/web/apps/web/src/components/app/nodes/x-state-node/hook/use-duplicate-state.tsx new file mode 100644 index 00000000..c3cdb93c --- /dev/null +++ b/web/apps/web/src/components/app/nodes/x-state-node/hook/use-duplicate-state.tsx @@ -0,0 +1,25 @@ +import { useInjection } from 'inversify-react'; +import { useCallback } from 'react'; + +import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; + +import { usePasteState } from './use-paste-state'; + +export const useDuplicateState = () => { + const { pasteState } = usePasteState({ enabeKeyboard: false }); + const appBuilder = useInjection('AppBuilderModel'); + + const duplicateState = useCallback( + (id: string, displayName: string) => { + const data = { + ...(appBuilder.nodeData[id] || {}), + display_name: displayName, + name: 'State', + }; + pasteState(data); + }, + [pasteState], + ); + + return { duplicateState }; +}; diff --git a/web/apps/web/src/components/app/nodes/x-state-node/hook/use-paste-state.tsx b/web/apps/web/src/components/app/nodes/x-state-node/hook/use-paste-state.tsx new file mode 100644 index 00000000..d1c8c138 --- /dev/null +++ b/web/apps/web/src/components/app/nodes/x-state-node/hook/use-paste-state.tsx @@ -0,0 +1,62 @@ +import { useReactFlowStore, getCanvasCenter } from '@shellagent/flow-engine'; +import type { FieldValues } from '@shellagent/ui'; +import { useKeyPress } from 'ahooks'; +import { useInjection } from 'inversify-react'; +import { useCallback } from 'react'; + +import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { + getKeyboardKeyCodeBySystem, + isEventTargetInputArea, +} from '@/utils/common-helper'; + +export const usePasteState = ({ + enabeKeyboard = false, +}: { + enabeKeyboard: boolean; +}) => { + const appBuilder = useInjection('AppBuilderModel'); + + const { reactFlowWrapper, viewport, onAddNode } = useReactFlowStore( + state => ({ + reactFlowWrapper: state.reactFlowWrapper, + viewport: state.viewport, + onAddNode: state.onAddNode, + }), + ); + + const pasteState = useCallback( + async (data: FieldValues) => { + const { newId, displayName } = await appBuilder.duplicateState(data); + const position = getCanvasCenter(reactFlowWrapper, viewport); + onAddNode({ + type: data?.type || 'state', + position, + data: { + id: newId, + name: data?.name || '', + display_name: displayName, + }, + isCopy: true, + }); + }, + [ + appBuilder.setNodeData, + reactFlowWrapper, + viewport, + onAddNode, + appBuilder.nodeData, + ], + ); + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.v`, e => { + if (isEventTargetInputArea(e.target as HTMLElement)) { + return; + } + if (appBuilder.copyNodeData && enabeKeyboard) { + pasteState(appBuilder.copyNodeData); + } + }); + + return { pasteState }; +}; diff --git a/web/apps/web/src/components/app/nodes/x-state-node/index.tsx b/web/apps/web/src/components/app/nodes/x-state-node/index.tsx new file mode 100644 index 00000000..24e0db7d --- /dev/null +++ b/web/apps/web/src/components/app/nodes/x-state-node/index.tsx @@ -0,0 +1,269 @@ +import { + Connection, + DRAGGABLE_NODE_ID, + DraggableNodeType, + Node, + NodeId, + NodeProps, + NodeTypeEnum, + SourceHandle, + StateNode as StateNodeType, + TargetHandle, + useDrop, + useReactFlowStore, +} from '@shellagent/flow-engine'; +import { TValues } from '@shellagent/form-engine'; +import { RefSceneEnum } from '@shellagent/shared/protocol/app-scope'; +import { Task, TaskSchema } from '@shellagent/shared/protocol/task'; +import { customSnakeCase, getTaskDisplayName } from '@shellagent/shared/utils'; +import { FormRef } from '@shellagent/ui'; +import { useKeyPress } from 'ahooks'; +import { useInjection } from 'inversify-react'; +import React, { useCallback, useRef, useEffect, useState } from 'react'; + +import { EdgeDataTypeEnum, EdgeTypeEnum } from '@/components/app/edges'; +import NodeCard from '@/components/app/node-card'; +import NodeForm from '@/components/app/node-form'; +import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import emitter, { + EventType, + useEventEmitter, +} from '@/stores/app/models/emitter'; +import { useAppState } from '@/stores/app/use-app-state'; +import { + getKeyboardKeyCodeBySystem, + isEventTargetInputArea, +} from '@/utils/common-helper'; +import { getXStateSchema } from '@/stores/app/schema/get-x-state-schema'; + +import { useDuplicateState } from './hook/use-duplicate-state'; + +const XStateNode: React.FC> = ({ selected, data }) => { + const stateFormRef = useRef(null); + const appBuilder = useInjection('AppBuilderModel'); + const { onDelNode, selectedNodes, onConnect } = useReactFlowStore(state => ({ + onDelNode: state.onDelNode, + selectedNodes: state.selectedNodes, + onConnect: state.onConnect, + })); + + const { setStateConfigSheetOpen, currentStateId, setSelectedNode } = + useAppState(state => ({ + setStateConfigSheetOpen: state.setStateConfigSheetOpen, + currentStateId: state.currentStateId, + setSelectedNode: state.setSelectedNode, + })); + + const { duplicateState } = useDuplicateState(); + + const schema = getXStateSchema(data.display_name as string); + + const selectedNodeRef = useRef(null); + + const [formKey, setFormKey] = useState(currentStateId); + + useEffect(() => { + // 只能选一个,长度为1 + const selectedNode = selectedNodes[0]; + setSelectedNode(selectedNode); + + appBuilder.selectedStateId = selectedNode?.id; + + if (selectedNode && selectedNode.type === NodeTypeEnum.state) { + setStateConfigSheetOpen(selectedNode.id, true); + selectedNodeRef.current = selectedNode; + } else { + setStateConfigSheetOpen(selectedNodeRef.current?.id || '', false); + } + }, [selectedNodes]); + + const nodeRef = useRef(null); + + useEffect(() => { + nodeRef.current = document.querySelector( + `[data-id=${data.id}]`, + ) as HTMLElement; + }, [data.id]); + + useKeyPress( + ['delete', 'backspace'], + e => { + if (selected && e.target === nodeRef.current) { + appBuilder.deleteNodeData(data.id); + onDelNode({ id: data.id }); + appBuilder.clearValidateIssues(data.id as string); + appBuilder.handleRefScene({ + scene: RefSceneEnum.Enum.remove_state, + params: { + stateName: data.id as Lowercase, + }, + }); + if (currentStateId === data.id) { + setStateConfigSheetOpen(currentStateId, false); + } + } + }, + { + target: nodeRef, + }, + ); + + useKeyPress( + `${getKeyboardKeyCodeBySystem('ctrl')}.c`, + e => { + if (isEventTargetInputArea(e.target as HTMLElement)) { + return; + } + if (selected) { + appBuilder.setCopyData(data.id, data.display_name as string); + } + }, + { + target: nodeRef, + }, + ); + + useKeyPress( + `${getKeyboardKeyCodeBySystem('ctrl')}.d`, + e => { + e.preventDefault(); + if (isEventTargetInputArea(e.target as HTMLElement)) { + return; + } + if (selected) { + duplicateState(data.id, data.display_name as string); + } + }, + { + target: nodeRef, + }, + ); + + const onChange = useCallback( + (values: TValues) => { + const newValues = { + ...values, + id: data.id, + name: data.display_name, + type: data.type, + }; + const newData = { id: data.id as NodeId, data: newValues }; + + appBuilder.setNodeData(newData); + emitter.emit(EventType.FORM_CHANGE, { + id: data.id as NodeId, + data: `${new Date().valueOf()}`, + type: 'StateCard', + }); + }, + [data.id], + ); + + useEventEmitter(EventType.FORM_CHANGE, eventData => { + if (eventData.id === data.id && eventData.type === 'StateConfigSheet') { + setFormKey(eventData.data); + } + }); + + useEventEmitter(EventType.RESET_FORM, eventData => { + setFormKey(eventData.data); + }); + + const handleConnect = (connection: Connection) => { + if (connection.source && connection.target) { + onConnect({ + connect: connection, + edge: { + type: EdgeTypeEnum.custom, + data: { + id: data.id, + custom: true, + type: EdgeDataTypeEnum.ALWAYS, + source: connection.source, + target: connection.target, + conditions: [], + }, + style: { + stroke: '#B6BABF', + strokeWidth: selected ? 4 : 2, + }, + }, + }); + } + }; + + const [, drop] = useDrop( + () => ({ + accept: DRAGGABLE_NODE_ID, + drop: item => { + if ( + item.nodeType === NodeTypeEnum.widget || + item.nodeType === NodeTypeEnum.workflow + ) { + try { + const displayName = getTaskDisplayName( + item, + appBuilder.nodeData[data.id]?.blocks as Task[], + ); + const newTask = TaskSchema.parse({ + type: 'task', + display_name: displayName, + name: customSnakeCase(displayName), + mode: item.type, + ...(item.type === NodeTypeEnum.widget && { + widget_name: item.widget_name, + widget_class_name: item.name, + }), + inputs: {}, + outputs: {}, + custom: item.custom, + }); + + appBuilder.setNodeData({ + id: data.id, + data: { + ...appBuilder.nodeData[data.id], + blocks: [ + ...((appBuilder.nodeData[data.id]?.blocks as Task[]) || []), + newTask, + ], + }, + }); + const key = `${new Date().valueOf()}`; + setFormKey(key); + emitter.emit(EventType.FORM_CHANGE, { + id: data.id as NodeId, + data: key, + type: 'StateCard', + }); + } catch (error) { + console.error('Task parse error:', error); + } + } + }, + }), + [appBuilder.setNodeData, appBuilder.nodeData, data.id], + ); + + const dropRef = useRef(null); + drop(dropRef); + + return ( +
+ + + + + +
+ ); +}; + +export default XStateNode; diff --git a/web/apps/web/src/services/app/type.ts b/web/apps/web/src/services/app/type.ts index 7c352866..c8a9cde7 100644 --- a/web/apps/web/src/services/app/type.ts +++ b/web/apps/web/src/services/app/type.ts @@ -9,6 +9,7 @@ export interface AppMetadata { name: string; description: string; avatar?: string; // 默认logo,save自动截图 + app_type?: string; } // 获取automata diff --git a/web/apps/web/src/services/home/type.ts b/web/apps/web/src/services/home/type.ts index f8c7a522..84aa34a6 100644 --- a/web/apps/web/src/services/home/type.ts +++ b/web/apps/web/src/services/home/type.ts @@ -3,6 +3,7 @@ export interface Metadata { description: string; avatar?: string; // 默认logo,save自动截图 categories?: string[]; + app_type?: string; } export type Type = 'app' | 'workflow'; @@ -15,7 +16,6 @@ export interface GetListResponse { data: Array<{ id: string; metadata: Metadata & { - app_type?: string; create_time: string; update_time: string; }; diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 0fbc3275..79b6db9f 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -17,7 +17,7 @@ import { injectable, inject } from 'inversify'; import { isEmpty } from 'lodash-es'; import { action, makeObservable, observable, runInAction } from 'mobx'; -import { defaultFlow } from '@/components/app/constants'; +import { chatDefaultFlow, xDefaultFlow } from '@/components/app/constants'; import { ComfyUIModel } from '@/components/app/plugins/comfyui/comfyui.model'; import { COMFYUI_API, @@ -80,6 +80,7 @@ export class AppBuilderModel { @observable metadata: Metadata = { name: '', description: '', + app_type: 'chat_bot', }; flowInstance: ReactFlowInstance | null = null; @@ -138,6 +139,10 @@ export class AppBuilderModel { return this.config.validateIssues || {}; } + get appType() { + return this.metadata.app_type || 'chat_bot'; + } + get runDisabled() { return Object.values(this.validateIssues).some(issues => issues.some(issue => issue.type === IssueTypeEnum.critical), @@ -262,6 +267,8 @@ export class AppBuilderModel { metadata, } = await fetchFlow(params); if (instance) { + const defaultFlow = + metadata.app_type === 'x_bot' ? xDefaultFlow : chatDefaultFlow; const flow = formatReactFlow2Flow({ edges: reactflow?.edges.length ? reactflow.edges : defaultFlow.edges, nodes: reactflow?.nodes.length ? reactflow.nodes : defaultFlow.nodes, diff --git a/web/apps/web/src/stores/app/models/x-app-builder.model.ts b/web/apps/web/src/stores/app/models/x-app-builder.model.ts new file mode 100644 index 00000000..52428941 --- /dev/null +++ b/web/apps/web/src/stores/app/models/x-app-builder.model.ts @@ -0,0 +1,14 @@ +import { makeAutoObservable } from 'mobx'; +import { injectable } from 'inversify'; +import { IFlow } from '@shellagent/flow-engine'; +import { xDefaultFlow } from '@/components/app/constants'; + +@injectable() +export class XAppBuilderModel { + flow: IFlow = xDefaultFlow; + selectedStateId: string | null = null; + + constructor() { + makeAutoObservable(this); + } +} diff --git a/web/apps/web/src/stores/app/schema/get-x-state-schema.ts b/web/apps/web/src/stores/app/schema/get-x-state-schema.ts new file mode 100644 index 00000000..d9f86337 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/get-x-state-schema.ts @@ -0,0 +1,25 @@ +import { ISchema } from '@shellagent/form-engine'; + +export const getXStateSchema = (name: string): ISchema => { + return { + title: name, + type: 'object', + 'x-type': 'Section', + 'x-title-size': 'h4', + 'x-title-copiable': false, + properties: { + blocks: { + default: [], + type: 'array', + title: 'Task', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-component': 'TasksConfig', + 'x-component-props': { + draggable: false, + }, + }, + }, + }; +}; diff --git a/web/apps/web/src/stores/app/schema/x-state-schema.ts b/web/apps/web/src/stores/app/schema/x-state-schema.ts new file mode 100644 index 00000000..8dc30075 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/x-state-schema.ts @@ -0,0 +1,1088 @@ +import { ISchema } from '@shellagent/form-engine'; +import { FieldModeEnum } from '@shellagent/shared/protocol/extend-config'; + +import { validateCustomKey } from '@/stores/app/utils/validator'; +import { ENABLE_MIME } from '@/utils/file-types'; + +export const stateConfigSchema: ISchema = { + type: 'object', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-class': 'space-y-3', + properties: { + inputs: { + type: 'object', + title: 'Input', + additionalProperties: { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + // 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'UnfocusInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-full', + 'x-component-props': { + size: '2xs', + autoFocus: false, + maxLength: 30, + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + ], + }, + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + triggerClassName: 'h-7 w-32', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-32', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + }, + user_input: { + type: 'boolean', + default: true, + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + }, + }, + 'x-key': '{{name}} Inputs_{{counter}}', + 'x-type': 'Inline', + 'x-validator-render': 'ValidateRender', + 'x-validator': [ + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value.name); + }, + }, + ], + 'x-draggable': true, + 'x-deletable': true, + 'x-edit-dialog': { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 30, + size: 'sm', + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + type: { + type: 'string', + default: 'text', + title: 'Type', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "text"', + fullfill: { + schema: { + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices 12', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: { + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "image"', + fullfill: { + schema: { + default: [{ value: 'a', label: 'A' }], + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + properties: { + label: { + title: 'Label', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'Input', + 'x-component-props': { + maxLength: 30, + size: 'sm', + }, + }, + value: { + title: 'Value', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: { + 'image/apng': ['.apng'], + 'image/png': ['.png'], + 'image/jpeg': ['.jpeg'], + 'image/jpg': ['.jpg'], + 'image/webp': ['.webp'], + 'image/gif': ['.gif'], + 'image/bmp': ['.bmp'], + 'image/tiff': ['.tiff'], + }, + }, + }, + }, + required: ['value'], + title: 'Choice Item', + 'x-type': 'Card', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-role': 'core', + 'x-deletable': true, + }, + }, + }, + }, + ], + }, + user_input: { + type: 'boolean', + default: true, + title: 'User Input', + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-reactions': [ + { + target: 'source', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'source', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + source: { + title: 'Source', + type: 'string', + default: 'IM', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'IM', value: 'IM' }, + { label: 'form', value: 'form' }, + ], + }, + 'x-reactions': [ + { + target: 'choices', + when: '$this.value === "IM"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + default_value: { + type: 'string', + title: 'Default Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + value: { + type: 'string', + title: 'Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + description: { + type: 'string', + title: 'Description', + 'x-raw': true, + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3 pb-3', + 'x-component-props': { + maxLength: 500, + placeholder: + 'Please describe how to use this input variable. This description will be displayed when the user inputs.', + }, + 'x-validator': [ + { + exclusiveMaximum: 500, + message: 'Cannot exceed 500 characters', + }, + ], + 'x-type': 'Control', + }, + choices: { + 'x-hidden': true, + type: 'array', + 'x-title-size': 'h5', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: {}, + }, + }, + validations: { + type: 'array', + title: 'Validations', + 'x-title-size': 'h5', + additionalItems: { + type: 'object', + properties: { + void_core: { + type: 'void', + 'x-role': 'core', + 'x-type': 'Grid', + 'x-class': 'grid-cols-3', + properties: { + rule_type: { + type: 'string', + enum: [ + 'max_length', + 'min_number', + 'max_number', + 'max_file_size', + ], + 'x-component': 'Select', + 'x-component-props': { + placeholder: 'Rule type', + options: [ + { label: 'Max Length', value: 'max_length' }, + { label: 'Min Number', value: 'min_number' }, + { label: 'Max Number', value: 'max_number' }, + { label: 'Max File Size', value: 'max_file_size' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'max_length', + when: '$this.value === "max_length"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'min_number', + when: '$this.value === "min_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_number', + when: '$this.value === "max_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_file_size', + when: '$this.value === "max_file_size"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + max_length: { + type: 'number', + default: 500, + 'x-component': 'NumberInput', + 'x-type': 'Control', + // 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 15000, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 15000 }, + ], + }, + min_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_file_size: { + type: 'number', + default: 10 * 1024 * 1024, + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 100 * 1024 * 1024, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 100 * 1024 * 1024 }, + ], + }, + error_message: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + placeholder: 'Error Message', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + }, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, + blocks: { + title: 'Task', + type: 'array', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-component': 'TasksConfig', + 'x-component-props': { + draggable: true, + }, + }, + outputs: { + type: 'object', + title: 'Output', + additionalProperties: { + type: 'object', + properties: { + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-hidden': true, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ref, + 'x-parent-deletable': true, + 'x-title-editable': true, + 'x-title-component-props': { + showDialog: true, + defaultKey: 'display_name', + dialogConfig: { + title: 'Edit Output', + schema: { + type: 'object', + properties: { + name_mode: { + type: 'string', + default: FieldModeEnum.Enum.ui, + title: 'Mode', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'type', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'description', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'VariableNameInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-component-props': { + maxLength: 30, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + required: true, + message: 'Please input the Variable Name', + }, + { + maxLength: 50, + message: 'Cannot exceed 50 characters', + }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + display_name: { + type: 'string', + 'x-hidden': true, + }, + type: { + type: 'string', + default: 'text', + enum: [ + 'text', + 'image', + 'audio', + 'video', + 'text_file', + 'file', + ], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-layout': 'Vertical', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + description: { + type: 'string', + title: 'Description', + default: '', + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 300, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + exclusiveMaximum: 300, + message: 'Cannot exceed 300 characters', + }, + ], + 'x-type': 'Control', + }, + }, + }, + }, + }, + 'x-component-props': { + size: '2xs', + }, + 'x-layout': 'Horizontal', + 'x-validator': [ + { required: true, message: 'The value is required' }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-hidden': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + 'x-key': '{{name}} Outputs_{{counter}}', + 'x-type': 'Inline', + 'x-collapsible': true, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, + render: { + type: 'object', + title: 'Message', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + properties: { + text: { + type: 'string', + title: 'Text', + 'x-component': 'ExpressionInput', + 'x-type': 'Control', + 'x-switchable': true, + 'x-switchable-default': true, + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ui, + 'x-validator': [ + { + warningOnly: true, + critical: true, + validator(rule, value) { + return new Promise((resolve, reject) => { + if (/]*>/.test(value)) { + reject( + new Error( + 'The tag is deprecated. Please use Message.Image to render images instead.', + ), + ); + } else { + resolve(true); + } + }); + }, + }, + ], + 'x-validator-render': 'ValidateRender', + }, + image: { + type: 'string', + title: 'Image', + 'x-component': 'FileUpload', + 'x-type': 'Control', + 'x-switchable': true, + 'x-switchable-default': false, + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ui, + }, + }, + }, + id: { + type: 'string', + default: '', + 'x-hidden': true, + }, + name: { + type: 'string', + default: '', + 'x-hidden': true, + }, + type: { + type: 'string', + default: 'state', + 'x-hidden': true, + }, + }, +}; From 91849b1abe1e6b5fc025f280eb28b9921718e517 Mon Sep 17 00:00:00 2001 From: kun Date: Fri, 10 Jan 2025 20:36:13 +0800 Subject: [PATCH 12/42] feat: pull beta --- web/apps/web/src/services/app/type.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/apps/web/src/services/app/type.ts b/web/apps/web/src/services/app/type.ts index 7c352866..fac82b3a 100644 --- a/web/apps/web/src/services/app/type.ts +++ b/web/apps/web/src/services/app/type.ts @@ -81,9 +81,7 @@ export interface InitBotRequest { automata: Automata; } -export interface InitBotResponse { - data: ServerMessage; -} +export interface InitBotResponse extends ServerMessage {} /** * 聊天信息, 一般是传给后端处理 From 03bc0fdb3c3f7be6dc58bdc2cbf9eb422a2281ed Mon Sep 17 00:00:00 2001 From: kun Date: Fri, 10 Jan 2025 21:42:11 +0800 Subject: [PATCH 13/42] feat: content type --- web/apps/web/src/services/common/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/apps/web/src/services/common/index.ts b/web/apps/web/src/services/common/index.ts index cfc96f00..23164bcb 100644 --- a/web/apps/web/src/services/common/index.ts +++ b/web/apps/web/src/services/common/index.ts @@ -13,6 +13,9 @@ export async function upload( formData.append('file', file, name || file.name); const response = await APIFetch.post<{ file_path: string }>('/api/upload', { body: formData, + headers: { + 'Content-Type': 'multipart/form-data', + }, }).catch(() => { toast.error('upload error', { position: 'top-center', From 692adf722da58879048783c8370f226f69218cf4 Mon Sep 17 00:00:00 2001 From: shanexi Date: Mon, 13 Jan 2025 11:11:00 +0800 Subject: [PATCH 14/42] feat: get wigets from backend --- web/apps/web/src/app/app/detail/page.tsx | 4 +- web/apps/web/src/services/app/index.tsx | 61 +++++++++ .../stores/app/models/app-builder.model.ts | 118 +++++++++++++++++- 3 files changed, 180 insertions(+), 3 deletions(-) diff --git a/web/apps/web/src/app/app/detail/page.tsx b/web/apps/web/src/app/app/detail/page.tsx index 04340710..22b42648 100644 --- a/web/apps/web/src/app/app/detail/page.tsx +++ b/web/apps/web/src/app/app/detail/page.tsx @@ -63,6 +63,8 @@ const FlowEngineWrapper = observer( // 退出页面初始化状态 useEffect(() => { appBuilder.getFlowList({ type: 'workflow' }); + appBuilder.getLocalWidgetList(); + appBuilder.getMyShellWidgetList(); return () => { resetState(); }; @@ -103,7 +105,7 @@ const FlowEngineWrapper = observer( ref={flowRef} nodeTypes={nodeTypes} edgeTypes={edgeTypes} - materialList={materialList} + materialList={appBuilder.materialList} footerExtra={} header={
e.stopPropagation()}> diff --git a/web/apps/web/src/services/app/index.tsx b/web/apps/web/src/services/app/index.tsx index 5b946873..db9d13c3 100644 --- a/web/apps/web/src/services/app/index.tsx +++ b/web/apps/web/src/services/app/index.tsx @@ -30,6 +30,8 @@ import { GetIntroDisplayResponse, } from './type'; import { APIFetch } from '../base'; +import { MaterialListType } from '@shellagent/flow-engine'; +import { z } from 'zod'; // 保存 export const saveApp = (params: SaveAppRequest) => { @@ -145,3 +147,62 @@ export const checkIp = (params: CheckIpRequest) => { body: params, }); }; + +export const get_local_widget_list_res = z.object({ + widget_list: z.array( + z.object({ + title: z.string(), + items: z.array( + z.object({ + name: z.string(), + children: z.array( + z.object({ + name: z.string(), + display_name: z.string(), + }), + ), + }), + ), + }), + ), +}); + +export type type_get_local_widget_list_res = z.infer< + typeof get_local_widget_list_res +>; + +export const getLocalWidgetList: Fetcher< + type_get_local_widget_list_res, + {} +> = params => { + return APIFetch.get('/api/widget/get_local_widget_list', { + params, + }); +}; + +export const get_myshell_widget_list_res = z.array( + z.object({ + title: z.string(), + items: z.array( + z.object({ + name: z.string(), + display_name: z.string(), + type: z.string(), + widget_name: z.string(), + }), + ), + }), +); + +export type type_get_myshell_widget_list = z.infer< + typeof get_myshell_widget_list_res +>; + +export const getMyShellWidgetList: Fetcher< + type_get_myshell_widget_list, + {} +> = params => { + return APIFetch.get('/api/widget/get_myshell_widget_list', { + params, + }); +}; diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 0fbc3275..426836b0 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -1,4 +1,10 @@ -import { type IFlow, type ReactFlowInstance } from '@shellagent/flow-engine'; +import { + type MaterialListType, + type IFlow, + type ReactFlowInstance, + NodeTypeEnum, + WidgetItem, +} from '@shellagent/flow-engine'; import { CustomKey, CustomEventName } from '@shellagent/pro-config'; import { customSnakeCase, @@ -15,7 +21,13 @@ import type { FieldValues } from '@shellagent/ui'; import { message } from 'antd'; import { injectable, inject } from 'inversify'; import { isEmpty } from 'lodash-es'; -import { action, makeObservable, observable, runInAction } from 'mobx'; +import { + action, + computed, + makeObservable, + observable, + runInAction, +} from 'mobx'; import { defaultFlow } from '@/components/app/constants'; import { ComfyUIModel } from '@/components/app/plugins/comfyui/comfyui.model'; @@ -34,6 +46,10 @@ import { checkIp, saveApp, getIntroDisplay, + getLocalWidgetList, + type type_get_local_widget_list_res, + type type_get_myshell_widget_list, + getMyShellWidgetList, } from '@/services/app'; import type { GetAppFlowRequest, @@ -70,9 +86,27 @@ import { handleRenameRefOpt, handleReorderTask, } from './node-data-utils'; +import { z } from 'zod'; const settingsDisabled = process.env.NEXT_PUBLIC_DISABLE_SETTING === 'yes'; +const LocalWigets: MaterialListType = [ + { + plain: true, + items: [ + { + name: 'State', + display_name: 'State', + type: NodeTypeEnum.state, + }, + { + name: 'Note', + display_name: 'Note', + type: NodeTypeEnum.note, + }, + ], + }, +]; @injectable() export class AppBuilderModel { nodeData: NodeDataType = {}; @@ -112,6 +146,56 @@ export class AppBuilderModel { @observable openExportDialog = false; @observable openValidationDialog = false; @observable validateAlert: Issue[] = []; + @observable rawLocalWidgetList: type_get_local_widget_list_res = { + widget_list: [], + }; + + @observable rawMyShellWidgetList: type_get_myshell_widget_list = []; + + @computed get materialList() { + const backendLocalWidgets: MaterialListType = + this.rawLocalWidgetList.widget_list.map(m => { + return { + ...m, + plain: true, + items: m.items.reduce((acc, i) => { + acc = acc.concat( + i.children.map(c => { + if (c.name === 'ComfyUIWidget') { + return { + ...c, + type: NodeTypeEnum.widget, + undraggable: true, + custom: true, + }; + } + return { + ...c, + type: NodeTypeEnum.widget, + undraggable: true, + }; + }), + ); + return acc; + }, []), + }; + }); + const myshellWidgets: MaterialListType = this.rawMyShellWidgetList.map( + m => { + return { + ...m, + items: m.items.map(i => { + return { + ...i, + undraggable: true, + } as WidgetItem; + }), + plain: true, + }; + }, + ); + return LocalWigets.concat(backendLocalWidgets).concat(myshellWidgets); + } copyNodeData: FieldValues = {}; @@ -687,4 +771,34 @@ export class AppBuilderModel { this.appEditing = editing; }); } + + @action.bound + async getLocalWidgetList() { + try { + const res = await getLocalWidgetList({}); + runInAction(() => { + this.rawLocalWidgetList = res; + }); + } catch (e) { + if (e instanceof Error) { + this.emitter.error(e.message); + } + } finally { + } + } + + @action.bound + async getMyShellWidgetList() { + try { + const res = await getMyShellWidgetList({}); + runInAction(() => { + this.rawMyShellWidgetList = res; + }); + } catch (e) { + if (e instanceof Error) { + this.emitter.error(e.message); + } + } finally { + } + } } From 4b3da1343d94ca5a024be320d4b91abe0c7517e7 Mon Sep 17 00:00:00 2001 From: shanexi Date: Mon, 13 Jan 2025 14:04:39 +0800 Subject: [PATCH 15/42] fix: material list search filter --- .../app/node-form/widgets/tasks-config.tsx | 5 +- web/packages/flow-engine/jest.config.ts | 20 + web/packages/flow-engine/package.json | 5 +- .../src/components/material-list/index.tsx | 33 +- .../material-list/material-list.spec.ts | 984 ++++++++++++++++++ .../material-list/material-list.utils.ts | 31 + web/packages/flow-engine/tsconfig.spec.json | 14 + web/packages/flow-engine/wallaby.js | 5 + web/pnpm-lock.yaml | 252 ++--- 9 files changed, 1158 insertions(+), 191 deletions(-) create mode 100644 web/packages/flow-engine/jest.config.ts create mode 100644 web/packages/flow-engine/src/components/material-list/material-list.spec.ts create mode 100644 web/packages/flow-engine/src/components/material-list/material-list.utils.ts create mode 100644 web/packages/flow-engine/tsconfig.spec.json create mode 100644 web/packages/flow-engine/wallaby.js diff --git a/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx b/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx index 8288bbf1..185fd287 100644 --- a/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx @@ -12,6 +12,8 @@ import { useDrag, useDrop } from 'react-dnd'; import { materialList } from '@/components/app/constants'; import { TaskList } from '@/components/app/task-list'; import { useAppState } from '@/stores/app/use-app-state'; +import { useInjection } from 'inversify-react'; +import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; const TaskItem = ({ name, @@ -122,6 +124,7 @@ const TasksConfig = ({ onChange: (value: Task[]) => void; draggable?: boolean; }) => { + const appBuilder = useInjection('AppBuilderModel'); const btnRef = useRef(null); const [open, setOpen] = useState(false); const { getValues } = useFormContext(); @@ -225,7 +228,7 @@ const TasksConfig = ({ onClick={e => e.stopPropagation()}> diff --git a/web/packages/flow-engine/jest.config.ts b/web/packages/flow-engine/jest.config.ts new file mode 100644 index 00000000..b684018c --- /dev/null +++ b/web/packages/flow-engine/jest.config.ts @@ -0,0 +1,20 @@ +/* eslint-disable */ +export default { + displayName: 'flow-engine', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js'], + coverageDirectory: './coverage/', + moduleNameMapper: { + '^lodash-es$': 'lodash', + }, + coverageThreshold: { + global: { + branches: 90, + functions: 90, + lines: 90, + statements: 90, + }, + }, +}; diff --git a/web/packages/flow-engine/package.json b/web/packages/flow-engine/package.json index af54421c..556183c3 100644 --- a/web/packages/flow-engine/package.json +++ b/web/packages/flow-engine/package.json @@ -42,6 +42,9 @@ "autoprefixer": "^10.4.20", "postcss": "^8.4.41", "tailwindcss": "^3.4.10", - "typescript": "~5.3.3" + "typescript": "~5.3.3", + "@types/jest": "^29.4.0", + "jest": "^29.4.1", + "ts-jest": "^29.0.5" } } diff --git a/web/packages/flow-engine/src/components/material-list/index.tsx b/web/packages/flow-engine/src/components/material-list/index.tsx index 81c413d6..d8eded7b 100644 --- a/web/packages/flow-engine/src/components/material-list/index.tsx +++ b/web/packages/flow-engine/src/components/material-list/index.tsx @@ -27,6 +27,7 @@ import { NodeTypeEnum, } from '../../types'; import { getCanvasCenter } from '../../utils'; +import { fuzzyFilter } from './material-list.utils'; const DraggableTreeNode: React.FC<{ data: WidgetItem }> = ({ data }) => { const nodeRef = useRef(null); @@ -171,37 +172,7 @@ const MaterialList: React.FC<{ const filteredList = useMemo(() => { if (!searchValue) return data; - - const lowerCaseSearchValue = searchValue.toLowerCase(); - - return data.reduce((acc, item) => { - const filteredItems = item.items.reduce( - (widgetAcc, widgetItem) => { - const filteredChildren = widgetItem.children?.filter( - child => - child.name.toLowerCase().includes(lowerCaseSearchValue) || - child.display_name?.toLowerCase()?.includes(lowerCaseSearchValue), - ); - - if (filteredChildren && filteredChildren.length > 0) { - widgetAcc.push({ ...widgetItem, children: filteredChildren }); - } else if ( - widgetItem.name.toLowerCase().includes(lowerCaseSearchValue) - ) { - widgetAcc.push(widgetItem); - } - - return widgetAcc; - }, - [], - ); - - if (filteredItems.length > 0) { - acc.push({ ...item, items: filteredItems }); - } - - return acc; - }, []); + return fuzzyFilter(data, searchValue); }, [data, searchValue]); if (siderOpen === CollapseEnum.close) { diff --git a/web/packages/flow-engine/src/components/material-list/material-list.spec.ts b/web/packages/flow-engine/src/components/material-list/material-list.spec.ts new file mode 100644 index 00000000..5f0d7b52 --- /dev/null +++ b/web/packages/flow-engine/src/components/material-list/material-list.spec.ts @@ -0,0 +1,984 @@ +import { fuzzyFilter } from './material-list.utils'; + +describe('material list', () => { + it('filter', () => { + const input = [ + { + plain: true, + items: [ + { + name: 'State', + display_name: 'State', + type: 'state', + }, + { + name: 'Note', + display_name: 'Note', + type: 'note', + }, + ], + }, + { + title: 'Visual Generation', + items: [ + { + name: 'ComfyUIWidget', + display_name: 'ComfyUI Widget', + type: 'widget', + undraggable: true, + custom: true, + }, + ], + plain: true, + }, + { + title: 'Large Language Model', + items: [ + { + name: 'GPTWidget', + display_name: 'GPT', + type: 'widget', + undraggable: true, + }, + { + name: 'ClaudeWidget', + display_name: 'Claude 3.5 Sonnet', + type: 'widget', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Tools', + items: [ + { + name: 'CodeRunnerWidget', + display_name: 'Code Runner', + type: 'widget', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Myshell Widgets', + items: [ + { + name: 'ImageTextFuserWidget', + display_name: 'Image Text Fuser', + type: 'widget', + undraggable: true, + }, + { + name: 'ImageCanvasWidget', + display_name: 'Image Canvas', + type: 'widget', + undraggable: true, + }, + { + name: 'XWidget', + display_name: 'Twitter Search', + type: 'widget', + undraggable: true, + }, + { + name: 'Html2ImgWidget', + display_name: 'HTML to Image', + type: 'widget', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Music Generation', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Music Generation', + type: 'widget', + widget_name: '@myshell/1743838636299784192', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'SongCoverGen', + type: 'widget', + widget_name: '@myshell/1743838667480240128', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Suno Music Generator', + type: 'widget', + widget_name: '@myshell/1782688568476921856', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Suno Lyrics Generator', + type: 'widget', + widget_name: '@myshell/1782688665554087936', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Image Comprehension', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'ChatIMG', + type: 'widget', + widget_name: '@myshell/1743838640687026176', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'UDOP: Document Question Answering', + type: 'widget', + widget_name: '@myshell/1781988928627507200', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Image Generation', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'QR Code', + type: 'widget', + widget_name: '@myshell/1743838644772278272', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Fast IMG Generation', + type: 'widget', + widget_name: '@myshell/1743838681287888896', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Stable Diffusion', + type: 'widget', + widget_name: '@myshell/1747600574662053888', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Barbie Style', + type: 'widget', + widget_name: '@myshell/1753247312467128321', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Hiroshinagai Style', + type: 'widget', + widget_name: '@myshell/1753247312467128322', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Baby Mystic', + type: 'widget', + widget_name: '@myshell/1753386494521733121', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Photomaker', + type: 'widget', + widget_name: '@myshell/1753386494521733122', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'InstantID', + type: 'widget', + widget_name: '@myshell/1753386494521733123', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'ProfilePicGen', + type: 'widget', + widget_name: '@myshell/1753386494521733124', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'EmojiGen', + type: 'widget', + widget_name: '@myshell/1753387102772289537', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'LogoAI', + type: 'widget', + widget_name: '@myshell/1765963483306184706', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Product Poster', + type: 'widget', + widget_name: '@myshell/1765975854924537857', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Latent Consistency Model', + type: 'widget', + widget_name: '@myshell/1768270905997037569', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'SDXL', + type: 'widget', + widget_name: '@myshell/1773395894008381440', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Stable Diffusion with Civitai', + type: 'widget', + widget_name: '@myshell/1779862419876704256', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Stable Diffusion with 6 fixed category', + type: 'widget', + widget_name: '@myshell/1779862424125534208', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Stable Diffusion Recommend', + type: 'widget', + widget_name: '@myshell/1782389093624786944', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Stable Diffusion Transform', + type: 'widget', + widget_name: '@myshell/1782389162088411136', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Stable Diffusion Inpaint', + type: 'widget', + widget_name: '@myshell/1782399147535691776', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Image Gif Generation', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'GIF Generation', + type: 'widget', + widget_name: '@myshell/1743838648844947456', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Age Transformation', + type: 'widget', + widget_name: '@myshell/1743838658173079552', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Stable Video Diffusion', + type: 'widget', + widget_name: '@myshell/1743838672895086592', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Micromotion GIF', + type: 'widget', + widget_name: '@myshell/1743838694869045248', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Video Processing', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Lip-sync Video', + type: 'widget', + widget_name: '@myshell/1743838653634842624', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Video Generation', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'MagicAnimate', + type: 'widget', + widget_name: '@myshell/1743838663432736768', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'i2vgen', + type: 'widget', + widget_name: '@myshell/1753247312467128323', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'PIA', + type: 'widget', + widget_name: '@myshell/1772547768296177664', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Animate Diff', + type: 'widget', + widget_name: '@myshell/1773530812944785408', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Champ', + type: 'widget', + widget_name: '@myshell/1781992023651704832', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Stabel-Video-Diffusion', + type: 'widget', + widget_name: '@myshell/1782009031462813696', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Video Comprehension', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'ChatVideo', + type: 'widget', + widget_name: '@myshell/1743838676971950080', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Image Processing', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Deoldify Image', + type: 'widget', + widget_name: '@myshell/1743838685868068864', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Background Remove', + type: 'widget', + widget_name: '@myshell/1743838690490191872', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Image Edit', + type: 'widget', + widget_name: '@myshell/1743838699130458112', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Face To Many', + type: 'widget', + widget_name: '@myshell/1772527631624257536', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'GroundedSAM', + type: 'widget', + widget_name: '@myshell/1781992144279887872', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'RMBG', + type: 'widget', + widget_name: '@myshell/1781992261456158720', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Tts', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'MyShell MeloTTS', + type: 'widget', + widget_name: '@myshell/1745097608856756779', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Bark TTS', + type: 'widget', + widget_name: '@myshell/1781991719128457216', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Maker', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'MyShell Voice Clone', + type: 'widget', + widget_name: '@myshell/1745097608864468992', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Llm', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'llama-2-13b-chat-gguf', + type: 'widget', + widget_name: '@myshell/1773535843372556288', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'GPT 3.5', + type: 'widget', + widget_name: '@myshell/1744214024104448000', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'GPT 4', + type: 'widget', + widget_name: '@myshell/1744214047475109888', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'PaLM 2 Chat', + type: 'widget', + widget_name: '@myshell/1744214070917074944', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Mistral 7B Instruct', + type: 'widget', + widget_name: '@myshell/1744214093956386816', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Zephyr', + type: 'widget', + widget_name: '@myshell/1744214118128160768', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'PaLM 2 Code Chat 32k', + type: 'widget', + widget_name: '@myshell/1744214141922447360', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'PaLM 2 Chat 32k', + type: 'widget', + widget_name: '@myshell/1744214166769504256', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'PaLM 2 Code Chat', + type: 'widget', + widget_name: '@myshell/1744214190362464256', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Hermes Llama2 13B', + type: 'widget', + widget_name: '@myshell/1744214214349688832', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Llama v2 70B Chat', + type: 'widget', + widget_name: '@myshell/1744214240580866048', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Llama v2 13B Chat', + type: 'widget', + widget_name: '@myshell/1744214270301704192', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Hermes Llama2 70B', + type: 'widget', + widget_name: '@myshell/1744214294834188288', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Meta CodeLlama 34B', + type: 'widget', + widget_name: '@myshell/1744214319488307200', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Phind CodeLlama 34B', + type: 'widget', + widget_name: '@myshell/1744214345505574912', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Airoboros L2 70B', + type: 'widget', + widget_name: '@myshell/1744214372646916096', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'OpenOrca 7B', + type: 'widget', + widget_name: '@myshell/1744214396697055232', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Mythalion 13B', + type: 'widget', + widget_name: '@myshell/1744214421267288064', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'SLERP L2 13B', + type: 'widget', + widget_name: '@myshell/1744214446286311424', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'MythoMax L2 13B', + type: 'widget', + widget_name: '@myshell/1744217958906859520', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'MythoMax L2 13B 8k', + type: 'widget', + widget_name: '@myshell/1744217984328536064', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Claude2', + type: 'widget', + widget_name: '@myshell/1744218009586634752', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Claude', + type: 'widget', + widget_name: '@myshell/1744218034517577728', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Mixtral8x7b-instruct', + type: 'widget', + widget_name: '@myshell/1744218061138825216', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Gemini pro', + type: 'widget', + widget_name: '@myshell/1744218088699596800', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Claude3 Haiku', + type: 'widget', + widget_name: '@myshell/1744218088699596809', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Claude3 Sonnet', + type: 'widget', + widget_name: '@myshell/1744218088699596810', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Claude3 Opus', + type: 'widget', + widget_name: '@myshell/1744218088699596811', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Perplexity 70B Online', + type: 'widget', + widget_name: '@myshell/1744218113529876480', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'ShelLLM V0', + type: 'widget', + widget_name: '@myshell/1778308607748444160', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Tools', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'OCR', + type: 'widget', + widget_name: '@myshell/1781991441343897600', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Weather forecasting', + type: 'widget', + widget_name: '@myshell/1781991520180035584', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Pdf to Markdown', + type: 'widget', + widget_name: '@myshell/1781991889463336960', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Crawler', + type: 'widget', + widget_name: '@myshell/1781991963803181056', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Notion Database', + type: 'widget', + widget_name: '@myshell/1782389035948912640', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'MS Word to Markdown', + type: 'widget', + widget_name: '@myshell/1782389444583174144', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'JSON to Table', + type: 'widget', + widget_name: '@myshell/1782389478800306176', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Markdown to MS Word', + type: 'widget', + widget_name: '@myshell/1782389534211256320', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Markdown to PDF', + type: 'widget', + widget_name: '@myshell/1782389600359624704', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Data Visualizer', + type: 'widget', + widget_name: '@myshell/1782389648132747264', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'YouTube Downloader', + type: 'widget', + widget_name: '@myshell/1782389744312332288', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Wolfram Alpha Search', + type: 'widget', + widget_name: '@myshell/1782421790748663808', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Information Extractor', + type: 'widget', + widget_name: '@myshell/1782446934017605632', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Information Extractor - OpenAI Schema Generator', + type: 'widget', + widget_name: '@myshell/1782696474146430976', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Table to Markdown', + type: 'widget', + widget_name: '@myshell/1783317709148299264', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'CoinGecko', + type: 'widget', + widget_name: '@myshell/1783390687492411392', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'TripAdvisor', + type: 'widget', + widget_name: '@myshell/1784190913073004544', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Wikipedia', + type: 'widget', + widget_name: '@myshell/1784203525992894464', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Instagram Search', + type: 'widget', + widget_name: '@myshell/1784203553675300864', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Mindmap Generator', + type: 'widget', + widget_name: '@myshell/1784203578987925504', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Twitter Search', + type: 'widget', + widget_name: '@myshell/1784206090390036480', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Crypto News', + type: 'widget', + widget_name: '@myshell/1784593708744855552', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Google Map Search', + type: 'widget', + widget_name: '@myshell/1784596505469460480', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Video Asr', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Whisper large-v3', + type: 'widget', + widget_name: '@myshell/1781991624332992512', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'YouTube Transcriber', + type: 'widget', + widget_name: '@myshell/1782434338602958848', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Online Search', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Google Search', + type: 'widget', + widget_name: '@myshell/1781993973191249920', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Google Image Search', + type: 'widget', + widget_name: '@myshell/1781994045928869888', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Google Hotel Search', + type: 'widget', + widget_name: '@myshell/1781994096558313472', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Google Flight Search', + type: 'widget', + widget_name: '@myshell/1781994185011990528', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Google News Search', + type: 'widget', + widget_name: '@myshell/1781994285478154240', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Google Scholar Search', + type: 'widget', + widget_name: '@myshell/1781994348505960448', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Youtube Search', + type: 'widget', + widget_name: '@myshell/1781994395360530432', + undraggable: true, + }, + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Yelp Search', + type: 'widget', + widget_name: '@myshell/1781994428159987712', + undraggable: true, + }, + ], + plain: true, + }, + { + title: 'Image Upscale', + items: [ + { + name: 'MyShellAnyWidgetCallerWidget', + display_name: 'Stable Diffusion Upscale', + type: 'widget', + widget_name: '@myshell/1782696079298846720', + undraggable: true, + }, + ], + plain: true, + }, + ]; + + const res = fuzzyFilter(input, 'suno'); + + expect(res).toMatchInlineSnapshot(` + [ + { + "items": [ + { + "display_name": "Suno Music Generator", + "name": "MyShellAnyWidgetCallerWidget", + "type": "widget", + "undraggable": true, + "widget_name": "@myshell/1782688568476921856", + }, + { + "display_name": "Suno Lyrics Generator", + "name": "MyShellAnyWidgetCallerWidget", + "type": "widget", + "undraggable": true, + "widget_name": "@myshell/1782688665554087936", + }, + ], + "plain": true, + "title": "Music Generation", + }, + ] + `); + expect(true).toBe(true); + }); +}); diff --git a/web/packages/flow-engine/src/components/material-list/material-list.utils.ts b/web/packages/flow-engine/src/components/material-list/material-list.utils.ts new file mode 100644 index 00000000..4c7265bc --- /dev/null +++ b/web/packages/flow-engine/src/components/material-list/material-list.utils.ts @@ -0,0 +1,31 @@ +export function fuzzyFilter(input: any[], searchValue: string): any[] { + if (!searchValue) return input; + + const searchLower = searchValue.toLowerCase(); + + return input + .map(category => { + // 如果是普通项目(没有子项目) + if (!category.items) { + const matchesSearch = + category.name?.toLowerCase().includes(searchLower) || + category.display_name?.toLowerCase().includes(searchLower) || + category.widget_name?.toLowerCase().includes(searchLower); + return matchesSearch ? category : null; + } + + // 过滤子项目 + const filteredItems = category.items.filter( + (item: any) => + item.name?.toLowerCase().includes(searchLower) || + item.display_name?.toLowerCase().includes(searchLower) || + item.widget_name?.toLowerCase().includes(searchLower), + ); + + // 如果有匹配的子项目,返回包含匹配项的新类别 + return filteredItems.length > 0 + ? { ...category, items: filteredItems } + : null; + }) + .filter(Boolean); // 移除空值 +} diff --git a/web/packages/flow-engine/tsconfig.spec.json b/web/packages/flow-engine/tsconfig.spec.json new file mode 100644 index 00000000..bee6ca95 --- /dev/null +++ b/web/packages/flow-engine/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["jest", "node"], + "outDir": "./dist/out-tsc", + "module": "commonjs", + }, + "include": [ + "jest.config.ts", + ".eslintrc.js", + "**/*.ts", + "**/*.tsx" + ] +} diff --git a/web/packages/flow-engine/wallaby.js b/web/packages/flow-engine/wallaby.js new file mode 100644 index 00000000..685c3f40 --- /dev/null +++ b/web/packages/flow-engine/wallaby.js @@ -0,0 +1,5 @@ +module.exports = function (wallaby) { + return { + autoDetect: ['jest'], + }; +}; diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 0d477023..7ef78f70 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -407,19 +407,19 @@ importers: version: 8.4.4(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@storybook/addon-webpack5-compiler-swc': specifier: ^1.0.5 - version: 1.0.5(@swc/helpers@0.5.5)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + version: 1.0.5(@swc/helpers@0.5.5)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) '@storybook/blocks': specifier: ^8.3.1 version: 8.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@storybook/nextjs': specifier: ^8.3.1 - version: 8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(babel-plugin-macros@3.1.0)(next@14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(type-fest@2.19.0)(typescript@5.3.3)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + version: 8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(babel-plugin-macros@3.1.0)(esbuild@0.23.1)(next@14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(type-fest@2.19.0)(typescript@5.3.3)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) '@storybook/react': specifier: ^8.3.1 version: 8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@storybook/react-webpack5': specifier: ^8.4.7 - version: 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) + version: 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@storybook/test': specifier: ^8.3.1 version: 8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) @@ -437,7 +437,7 @@ importers: version: 18.3.0 css-loader: specifier: ^7.1.2 - version: 7.1.2(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + version: 7.1.2(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) eslint: specifier: ^8 version: 8.37.0 @@ -452,7 +452,7 @@ importers: version: 4.2.0 less-loader: specifier: ^12.2.0 - version: 12.2.0(less@4.2.0)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + version: 12.2.0(less@4.2.0)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) next-intl: specifier: ^2.22.1 version: 2.22.1(next@14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -467,7 +467,7 @@ importers: version: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) style-loader: specifier: ^4.0.0 - version: 4.0.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + version: 4.0.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) tailwindcss: specifier: ^3.4.1 version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) @@ -732,7 +732,7 @@ importers: version: 10.4.20(postcss@8.4.41) copy-webpack-plugin: specifier: ^12.0.2 - version: 12.0.2(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)) + version: 12.0.2(webpack@5.93.0(@swc/core@1.7.12)) jest: specifier: ^29.4.1 version: 29.7.0(@types/node@20.16.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) @@ -741,7 +741,7 @@ importers: version: 8.4.41 raw-loader: specifier: ^4.0.2 - version: 4.0.2(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)) + version: 4.0.2(webpack@5.93.0(@swc/core@1.7.12)) storybook: specifier: ^8.3.1 version: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -750,7 +750,7 @@ importers: version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) ts-jest: specifier: ^29.0.5 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -854,6 +854,9 @@ importers: '@shellagent/typescript-config': specifier: workspace:* version: link:../typescript-config + '@types/jest': + specifier: ^29.4.0 + version: 29.5.14 '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 @@ -866,12 +869,18 @@ importers: autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.41) + jest: + specifier: ^29.4.1 + version: 29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)) postcss: specifier: ^8.4.41 version: 8.4.41 tailwindcss: specifier: ^3.4.10 version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)) + ts-jest: + specifier: ^29.0.5 + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)))(typescript@5.3.3) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -1279,7 +1288,7 @@ importers: version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) ts-jest: specifier: ^29.0.5 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -2972,79 +2981,67 @@ packages: resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} @@ -3263,56 +3260,48 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@next/swc-linux-arm64-gnu@14.2.5': resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@next/swc-linux-arm64-musl@14.2.12': resolution: {integrity: sha512-EfD9L7o9biaQxjwP1uWXnk3vYZi64NVcKUN83hpVkKocB7ogJfyH2r7o1pPnMtir6gHZiGCeHKagJ0yrNSLNHw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@next/swc-linux-arm64-musl@14.2.5': resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@next/swc-linux-x64-gnu@14.2.12': resolution: {integrity: sha512-iQ+n2pxklJew9IpE47hE/VgjmljlHqtcD5UhZVeHICTPbLyrgPehaKf2wLRNjYH75udroBNCgrSSVSVpAbNoYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@next/swc-linux-x64-gnu@14.2.5': resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@next/swc-linux-x64-musl@14.2.12': resolution: {integrity: sha512-rFkUkNwcQ0ODn7cxvcVdpHlcOpYxMeyMfkJuzaT74xjAa5v4fxP4xDk5OoYmPi8QNLDs3UgZPMSBmpBuv9zKWA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@next/swc-linux-x64-musl@14.2.5': resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@next/swc-win32-arm64-msvc@14.2.12': resolution: {integrity: sha512-PQFYUvwtHs/u0K85SG4sAdDXYIPXpETf9mcEjWc0R4JmjgMKSDwIU/qfZdavtP6MPNiMjuKGXHCtyhR/M5zo8g==} @@ -4608,28 +4597,24 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [glibc] '@swc/core-linux-arm64-musl@1.7.12': resolution: {integrity: sha512-WKtanqasnJ9cBD1tMsmOzZzxJ0Hg2sfJC7UNs2Z4meNPBK4xwOrhpSq8Q9GE4xgoLeSEhU3MmQnbfJKRq3mAZQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [musl] '@swc/core-linux-x64-gnu@1.7.12': resolution: {integrity: sha512-NQ0bb9eCIp2z2WdRyELzfWc1LDJJ99OYdxT+CIwW9ixPVgAerOv0Oc+BkdijLw5VeYMGlK6JEI4HdLvQE34f1g==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [glibc] '@swc/core-linux-x64-musl@1.7.12': resolution: {integrity: sha512-D8Tegag3/045wvGiq3NFNbKVDnkocNcl5hdKQtEvZ3b1u3nHGu+xqmPteUh4Ps+GB/gbpB3o/eYNO5JPm0R66Q==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [musl] '@swc/core-win32-arm64-msvc@1.7.12': resolution: {integrity: sha512-x8DWG4fCkwI6CmC8U1YMxVTab9Fe4DmCCX6dLrTqqpFPXlVwgdKA9PNBSXsUUtHjvqAB/9cGgmpmNHuNJRa1dA==} @@ -14984,7 +14969,7 @@ snapshots: '@pkgr/core@0.1.1': {} - '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1))': dependencies: ansi-html: 0.0.9 core-js-pure: 3.38.0 @@ -14994,7 +14979,7 @@ snapshots: react-refresh: 0.14.2 schema-utils: 4.2.0 source-map: 0.7.4 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) optionalDependencies: type-fest: 2.19.0 webpack-hot-middleware: 2.26.1 @@ -16053,10 +16038,10 @@ snapshots: memoizerific: 1.11.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@storybook/addon-webpack5-compiler-swc@1.0.5(@swc/helpers@0.5.5)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)))': + '@storybook/addon-webpack5-compiler-swc@1.0.5(@swc/helpers@0.5.5)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1))': dependencies: '@swc/core': 1.7.12(@swc/helpers@0.5.5) - swc-loader: 0.2.6(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + swc-loader: 0.2.6(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) transitivePeerDependencies: - '@swc/helpers' - webpack @@ -16082,7 +16067,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-webpack5@8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': + '@storybook/builder-webpack5@8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': dependencies: '@storybook/core-webpack': 8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@types/node': 22.4.1 @@ -16091,25 +16076,25 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.1 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) es-module-lexer: 1.5.4 express: 4.21.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) fs-extra: 11.2.0 - html-webpack-plugin: 5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + html-webpack-plugin: 5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) magic-string: 0.30.11 path-browserify: 1.0.1 process: 0.11.10 semver: 7.6.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) - terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) + terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) - webpack-dev-middleware: 6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) + webpack-dev-middleware: 6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -16122,7 +16107,7 @@ snapshots: - uglify-js - webpack-cli - '@storybook/builder-webpack5@8.4.7(@swc/core@1.7.12(@swc/helpers@0.5.5))(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': + '@storybook/builder-webpack5@8.4.7(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': dependencies: '@storybook/core-webpack': 8.4.7(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@types/node': 22.4.1 @@ -16131,23 +16116,23 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.1 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) es-module-lexer: 1.5.4 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) - html-webpack-plugin: 5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) + html-webpack-plugin: 5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) magic-string: 0.30.11 path-browserify: 1.0.1 process: 0.11.10 semver: 7.6.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) - terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) + terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) - webpack-dev-middleware: 6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) + webpack-dev-middleware: 6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -16244,7 +16229,7 @@ snapshots: dependencies: storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@storybook/nextjs@8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(babel-plugin-macros@3.1.0)(next@14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(type-fest@2.19.0)(typescript@5.3.3)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)))': + '@storybook/nextjs@8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(babel-plugin-macros@3.1.0)(esbuild@0.23.1)(next@14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(type-fest@2.19.0)(typescript@5.3.3)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.25.2) @@ -16259,32 +16244,32 @@ snapshots: '@babel/preset-react': 7.24.7(@babel/core@7.25.2) '@babel/preset-typescript': 7.24.7(@babel/core@7.25.2) '@babel/runtime': 7.25.0 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) - '@storybook/builder-webpack5': 8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) - '@storybook/preset-react-webpack': 8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) + '@storybook/builder-webpack5': 8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) + '@storybook/preset-react-webpack': 8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@storybook/react': 8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@storybook/test': 8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@types/node': 22.4.1 '@types/semver': 7.5.8 - babel-loader: 9.2.1(@babel/core@7.25.2)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) - css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + babel-loader: 9.2.1(@babel/core@7.25.2)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) + css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) find-up: 5.0.0 fs-extra: 11.2.0 image-size: 1.1.1 loader-utils: 3.3.1 next: 14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - node-polyfill-webpack-plugin: 2.0.1(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + node-polyfill-webpack-plugin: 2.0.1(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) pnp-webpack-plugin: 1.7.0(typescript@5.3.3) postcss: 8.4.41 - postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 13.3.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + sass-loader: 13.3.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) semver: 7.6.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) styled-jsx: 5.1.6(@babel/core@7.25.2)(babel-plugin-macros@3.1.0)(react@18.3.1) ts-dedent: 2.2.0 tsconfig-paths: 4.2.0 @@ -16292,7 +16277,7 @@ snapshots: optionalDependencies: sharp: 0.33.5 typescript: 5.3.3 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -16312,11 +16297,11 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/preset-react-webpack@8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': + '@storybook/preset-react-webpack@8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': dependencies: '@storybook/core-webpack': 8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@storybook/react': 8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) '@types/node': 22.4.1 '@types/semver': 7.5.8 find-up: 5.0.0 @@ -16329,7 +16314,7 @@ snapshots: semver: 7.6.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) tsconfig-paths: 4.2.0 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: @@ -16340,11 +16325,11 @@ snapshots: - uglify-js - webpack-cli - '@storybook/preset-react-webpack@8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': + '@storybook/preset-react-webpack@8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': dependencies: '@storybook/core-webpack': 8.4.7(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@storybook/react': 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) '@types/node': 22.4.1 '@types/semver': 7.5.8 find-up: 5.0.0 @@ -16356,7 +16341,7 @@ snapshots: semver: 7.6.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) tsconfig-paths: 4.2.0 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: @@ -16375,7 +16360,7 @@ snapshots: dependencies: storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1))': dependencies: debug: 4.3.6 endent: 2.1.0 @@ -16385,7 +16370,7 @@ snapshots: react-docgen-typescript: 2.2.2(typescript@5.3.3) tslib: 2.6.3 typescript: 5.3.3 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) transitivePeerDependencies: - supports-color @@ -16401,10 +16386,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@storybook/react-webpack5@8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': + '@storybook/react-webpack5@8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': dependencies: - '@storybook/builder-webpack5': 8.4.7(@swc/core@1.7.12(@swc/helpers@0.5.5))(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) - '@storybook/preset-react-webpack': 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) + '@storybook/builder-webpack5': 8.4.7(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) + '@storybook/preset-react-webpack': 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@storybook/react': 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@types/node': 22.4.1 react: 18.3.1 @@ -17842,12 +17827,12 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@9.2.1(@babel/core@7.25.2)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + babel-loader@9.2.1(@babel/core@7.25.2)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: '@babel/core': 7.25.2 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) babel-plugin-istanbul@6.1.1: dependencies: @@ -18466,7 +18451,7 @@ snapshots: dependencies: toggle-selection: 1.0.6 - copy-webpack-plugin@12.0.2(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)): + copy-webpack-plugin@12.0.2(webpack@5.93.0(@swc/core@1.7.12)): dependencies: fast-glob: 3.3.2 glob-parent: 6.0.2 @@ -18474,7 +18459,7 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.2.0 serialize-javascript: 6.0.2 - webpack: 5.93.0(@swc/core@1.7.12)(esbuild@0.23.1) + webpack: 5.93.0(@swc/core@1.7.12) core-js-compat@3.38.1: dependencies: @@ -18638,7 +18623,7 @@ snapshots: dependencies: hyphenate-style-name: 1.1.0 - css-loader@6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + css-loader@6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: icss-utils: 5.1.0(postcss@8.4.41) postcss: 8.4.41 @@ -18649,9 +18634,9 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) - css-loader@7.1.2(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + css-loader@7.1.2(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: icss-utils: 5.1.0(postcss@8.4.41) postcss: 8.4.41 @@ -18662,7 +18647,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) css-select@4.3.0: dependencies: @@ -20106,7 +20091,7 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: '@babel/code-frame': 7.24.7 chalk: 4.1.2 @@ -20121,7 +20106,7 @@ snapshots: semver: 7.6.3 tapable: 2.2.1 typescript: 5.3.3 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) form-data@4.0.0: dependencies: @@ -20664,7 +20649,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + html-webpack-plugin@5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -20672,7 +20657,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) htmlparser2@3.10.1: dependencies: @@ -21949,11 +21934,11 @@ snapshots: layout-base@2.0.1: {} - less-loader@12.2.0(less@4.2.0)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + less-loader@12.2.0(less@4.2.0)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: less: 4.2.0 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) less@4.2.0: dependencies: @@ -22888,7 +22873,7 @@ snapshots: mkdirp: 0.5.6 resolve: 1.22.8 - node-polyfill-webpack-plugin@2.0.1(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + node-polyfill-webpack-plugin@2.0.1(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: assert: 2.1.0 browserify-zlib: 0.2.0 @@ -22915,7 +22900,7 @@ snapshots: url: 0.11.4 util: 0.12.5 vm-browserify: 1.1.2 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) node-preload@0.2.1: dependencies: @@ -23393,14 +23378,14 @@ snapshots: postcss: 8.4.41 ts-node: 10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3) - postcss-loader@8.1.1(postcss@8.4.41)(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + postcss-loader@8.1.1(postcss@8.4.41)(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: cosmiconfig: 9.0.0(typescript@5.3.3) jiti: 1.21.6 postcss: 8.4.41 semver: 7.6.3 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) transitivePeerDependencies: - typescript @@ -23607,12 +23592,6 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - raw-loader@4.0.2(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)): - dependencies: - loader-utils: 2.0.4 - schema-utils: 3.3.0 - webpack: 5.93.0(@swc/core@1.7.12)(esbuild@0.23.1) - raw-loader@4.0.2(webpack@5.93.0(@swc/core@1.7.12)): dependencies: loader-utils: 2.0.4 @@ -24764,10 +24743,10 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@13.3.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + sass-loader@13.3.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: neo-async: 2.6.2 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) sax@1.4.1: optional: true @@ -25239,13 +25218,13 @@ snapshots: strip-json-comments@3.1.1: {} - style-loader@3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + style-loader@3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) - style-loader@4.0.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + style-loader@4.0.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) style-to-object@1.0.6: dependencies: @@ -25311,11 +25290,11 @@ snapshots: lower-case: 1.1.4 upper-case: 1.1.3 - swc-loader@0.2.6(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + swc-loader@0.2.6(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: '@swc/core': 1.7.12(@swc/helpers@0.5.5) '@swc/counter': 0.1.3 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) swiper@11.1.12: {} @@ -25407,25 +25386,14 @@ snapshots: dependencies: memoizerific: 1.11.3 - terser-webpack-plugin@5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + terser-webpack-plugin@5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.31.6 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) - optionalDependencies: - '@swc/core': 1.7.12(@swc/helpers@0.5.5) - - terser-webpack-plugin@5.3.10(@swc/core@1.7.12)(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - jest-worker: 27.5.1 - schema-utils: 3.3.0 - serialize-javascript: 6.0.2 - terser: 5.31.6 - webpack: 5.93.0(@swc/core@1.7.12)(esbuild@0.23.1) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) optionalDependencies: '@swc/core': 1.7.12(@swc/helpers@0.5.5) esbuild: 0.23.1 @@ -25558,7 +25526,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3): + ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -25576,7 +25544,6 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.25.2) - esbuild: 0.23.1 ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.5.1)(typescript@5.3.3)))(typescript@5.3.3): dependencies: @@ -26206,7 +26173,7 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-dev-middleware@6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + webpack-dev-middleware@6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -26214,7 +26181,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.2.0 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) webpack-hot-middleware@2.26.1: dependencies: @@ -26226,7 +26193,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)): + webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 @@ -26249,7 +26216,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -26288,37 +26255,6 @@ snapshots: - esbuild - uglify-js - webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1): - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/wasm-edit': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.12.1 - acorn-import-attributes: 1.9.5(acorn@8.12.1) - browserslist: 4.23.3 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.17.1 - es-module-lexer: 1.5.4 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.7.12)(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)) - watchpack: 2.4.2 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - webworkify@1.5.0: {} whatwg-encoding@2.0.0: From 7f4578952373434ffab5972d948cdb37450e13d2 Mon Sep 17 00:00:00 2001 From: shanexi Date: Mon, 13 Jan 2025 14:06:50 +0800 Subject: [PATCH 16/42] fix: lint --- web/apps/web/src/stores/app/models/app-builder.model.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 426836b0..3bd779d7 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -783,7 +783,6 @@ export class AppBuilderModel { if (e instanceof Error) { this.emitter.error(e.message); } - } finally { } } @@ -798,7 +797,6 @@ export class AppBuilderModel { if (e instanceof Error) { this.emitter.error(e.message); } - } finally { } } } From 8f5a1978570432c783d46f8ea977847db418a190 Mon Sep 17 00:00:00 2001 From: shanexi Date: Mon, 13 Jan 2025 14:57:26 +0800 Subject: [PATCH 17/42] fix: apply to backend api change --- web/apps/web/src/services/app/index.tsx | 28 ++++++++++--------- .../stores/app/models/app-builder.model.ts | 11 ++++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/web/apps/web/src/services/app/index.tsx b/web/apps/web/src/services/app/index.tsx index db9d13c3..b43778d0 100644 --- a/web/apps/web/src/services/app/index.tsx +++ b/web/apps/web/src/services/app/index.tsx @@ -180,19 +180,21 @@ export const getLocalWidgetList: Fetcher< }); }; -export const get_myshell_widget_list_res = z.array( - z.object({ - title: z.string(), - items: z.array( - z.object({ - name: z.string(), - display_name: z.string(), - type: z.string(), - widget_name: z.string(), - }), - ), - }), -); +export const get_myshell_widget_list_res = z.object({ + widget_list: z.array( + z.object({ + title: z.string(), + items: z.array( + z.object({ + name: z.string(), + display_name: z.string(), + type: z.string(), + widget_name: z.string(), + }), + ), + }), + ), +}); export type type_get_myshell_widget_list = z.infer< typeof get_myshell_widget_list_res diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 3bd779d7..50630481 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -150,7 +150,9 @@ export class AppBuilderModel { widget_list: [], }; - @observable rawMyShellWidgetList: type_get_myshell_widget_list = []; + @observable rawMyShellWidgetList: type_get_myshell_widget_list = { + widget_list: [], + }; @computed get materialList() { const backendLocalWidgets: MaterialListType = @@ -180,8 +182,8 @@ export class AppBuilderModel { }, []), }; }); - const myshellWidgets: MaterialListType = this.rawMyShellWidgetList.map( - m => { + const myshellWidgets: MaterialListType = + this.rawMyShellWidgetList.widget_list.map(m => { return { ...m, items: m.items.map(i => { @@ -192,8 +194,7 @@ export class AppBuilderModel { }), plain: true, }; - }, - ); + }); return LocalWigets.concat(backendLocalWidgets).concat(myshellWidgets); } From a1a8e86f2cddb34052f75d312c6513fab22233c1 Mon Sep 17 00:00:00 2001 From: kun Date: Mon, 13 Jan 2025 15:29:44 +0800 Subject: [PATCH 18/42] feat: api files --- .../app/header/extra-action/import-modal.tsx | 3 ++- .../components/common/file-render/index.tsx | 26 +++++++------------ .../common/text-file-editor/index.tsx | 4 +-- .../common/uploader/display/index.tsx | 3 ++- .../node-form/widgets/render/display.tsx | 8 +++--- web/apps/web/src/services/base.ts | 10 ++++--- web/apps/web/src/services/common/index.ts | 3 --- .../web/src/stores/app/utils/validator.ts | 6 ++--- web/apps/web/src/utils/common-helper.ts | 5 ++++ 9 files changed, 31 insertions(+), 37 deletions(-) diff --git a/web/apps/web/src/components/app/header/extra-action/import-modal.tsx b/web/apps/web/src/components/app/header/extra-action/import-modal.tsx index d5272f67..da6bfebc 100644 --- a/web/apps/web/src/components/app/header/extra-action/import-modal.tsx +++ b/web/apps/web/src/components/app/header/extra-action/import-modal.tsx @@ -15,6 +15,7 @@ import { toast } from 'react-toastify'; import FileUploader from '@/components/common/uploader'; import { ExportBotResponse } from '@/services/app/type'; import { APIFetch } from '@/services/base'; +import { getFileUrl } from '@/utils/common-helper'; const ImportModal: React.FC<{ open: boolean; @@ -31,7 +32,7 @@ const ImportModal: React.FC<{ useEffect(() => { if (fileUrl) { setLoading(true); - APIFetch.get(`/api/files/${fileUrl}`) + APIFetch.get(getFileUrl(fileUrl)) .then(({ data, success }) => { if (!success) { throw new Error(''); diff --git a/web/apps/web/src/components/common/file-render/index.tsx b/web/apps/web/src/components/common/file-render/index.tsx index 4be071ca..c600b166 100644 --- a/web/apps/web/src/components/common/file-render/index.tsx +++ b/web/apps/web/src/components/common/file-render/index.tsx @@ -5,15 +5,19 @@ import { ImageRex, VideoRex, AudioRex } from '@/utils/file-types'; import Audio from '../uploader/display/audio'; +const getFileUrl = (url: string) => { + if (url?.startsWith('http')) return url; + if (url?.startsWith('data/upload')) return `/api/files/${url}`; + return `/api/files/input/${url}`; +}; + const FileRender = ({ url }: { url: string }) => { if (ImageRex.test(url)) { return ( { if (VideoRex.test(url)) { return ( { if (AudioRex.test(url)) { return (
-
); } diff --git a/web/apps/web/src/components/common/text-file-editor/index.tsx b/web/apps/web/src/components/common/text-file-editor/index.tsx index 8e67c50b..10d9b9c8 100644 --- a/web/apps/web/src/components/common/text-file-editor/index.tsx +++ b/web/apps/web/src/components/common/text-file-editor/index.tsx @@ -5,7 +5,7 @@ import TextareaAutosize from 'react-textarea-autosize'; import { toast } from 'react-toastify'; import { upload } from '@/services/common'; -import { generateUUID } from '@/utils/common-helper'; +import { generateUUID, getFileUrl } from '@/utils/common-helper'; import { FileStatus } from '../uploader'; @@ -90,7 +90,7 @@ const TextFileEditor = (props: { onClick={() => handleDeleteImage(idx)} className="w-4 h-4 z-10 absolute top-[-4px] right-[-4px] hidden group-hover/menu:flex" /> - +
))}
diff --git a/web/apps/web/src/components/common/uploader/display/index.tsx b/web/apps/web/src/components/common/uploader/display/index.tsx index 48471eb9..325998a6 100644 --- a/web/apps/web/src/components/common/uploader/display/index.tsx +++ b/web/apps/web/src/components/common/uploader/display/index.tsx @@ -8,6 +8,7 @@ import Image from '@/components/common/uploader/display/image'; import Other from '@/components/common/uploader/display/other'; import Video from '@/components/common/uploader/display/video'; import { ENABLE_PREVIEW_FILE, getLocalTypeBySuffix } from '@/utils/file-types'; +import { getFileUrl } from '@/utils/common-helper'; type DisplayType = 'audio' | 'video' | 'image' | 'other'; @@ -46,7 +47,7 @@ export default function Display({ if (typeof file === 'string') { const [filename, suffix] = last(file.split('/'))?.split('.') || []; const filetype = getLocalTypeBySuffix(suffix); - const fileurl = file.startsWith('http') ? file : `/api/files/${file}`; + const fileurl = getFileUrl(file); return [fileurl, filename, filetype]; } const { name, size, type } = file; diff --git a/web/apps/web/src/components/workflow/node-form/widgets/render/display.tsx b/web/apps/web/src/components/workflow/node-form/widgets/render/display.tsx index 7c3ba8f1..f38808b8 100644 --- a/web/apps/web/src/components/workflow/node-form/widgets/render/display.tsx +++ b/web/apps/web/src/components/workflow/node-form/widgets/render/display.tsx @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import ReactPlayer from 'react-player'; import { cn } from '@/utils/cn'; +import { getFileUrl } from '@/utils/common-helper'; const ImageRex = /.(jpg|jpeg|png|apng|gif|bmp|tiff|webp)$/; const VideoRex = /.(mp4|avi|mov|wmv|flv)$/; @@ -56,7 +57,7 @@ interface ImageLayoutProps { const ImageLayout = ({ field, images }: ImageLayoutProps) => { const generateImageConfig = (src: string) => ({ - src: `/api/files/${src}`, + src: getFileUrl(src), wrapperClassName: 'rounded-lg overflow-hidden', }); return ( @@ -96,10 +97,7 @@ const VideoLayout = ({ field, videos }: VideoLayoutProps) => { } content={ - `/api/files/${url}`)} - width="100%" - /> + getFileUrl(url))} width="100%" /> } /> ); diff --git a/web/apps/web/src/services/base.ts b/web/apps/web/src/services/base.ts index 1e6100fd..812fdd0f 100644 --- a/web/apps/web/src/services/base.ts +++ b/web/apps/web/src/services/base.ts @@ -26,9 +26,9 @@ const baseOptions = { method: 'GET', mode: 'cors', credentials: 'include', // always send cookies、HTTP Basic authentication. - headers: { - 'Content-Type': ContentType.json, - }, + // headers: { + // 'Content-Type': ContentType.json, + // }, redirect: 'follow', }; @@ -122,7 +122,9 @@ export const customFetch = async ( if (response.status === 401 && process.env.NEXT_PUBLIC_LOGIN_URL) { const errorMessage = `Authentication failed! status: ${response.status}`; reject(new Error(errorMessage)); - window.location.href = `${process.env.NEXT_PUBLIC_LOGIN_URL}?login=true&redirect=${window.location.href}`; + window.location.href = `${ + process.env.NEXT_PUBLIC_LOGIN_URL + }?login=true&redirect=${decodeURIComponent(window.location.href)}`; } if (response.status > 400) { diff --git a/web/apps/web/src/services/common/index.ts b/web/apps/web/src/services/common/index.ts index 23164bcb..cfc96f00 100644 --- a/web/apps/web/src/services/common/index.ts +++ b/web/apps/web/src/services/common/index.ts @@ -13,9 +13,6 @@ export async function upload( formData.append('file', file, name || file.name); const response = await APIFetch.post<{ file_path: string }>('/api/upload', { body: formData, - headers: { - 'Content-Type': 'multipart/form-data', - }, }).catch(() => { toast.error('upload error', { position: 'top-center', diff --git a/web/apps/web/src/stores/app/utils/validator.ts b/web/apps/web/src/stores/app/utils/validator.ts index 69dd281d..d9bb0974 100644 --- a/web/apps/web/src/stores/app/utils/validator.ts +++ b/web/apps/web/src/stores/app/utils/validator.ts @@ -1,3 +1,4 @@ +import { getFileUrl } from '@/utils/common-helper'; import { IFlow } from '@shellagent/flow-engine'; import { Automata } from '@shellagent/pro-config'; import { @@ -61,10 +62,7 @@ export async function validateImage( if (!url || url === '/') { return Promise.resolve(true); } - const apiUrl = - url?.startsWith('/api/files') || url?.startsWith('http') - ? url - : `/api/files/${url}`; + const apiUrl = getFileUrl(url); try { const response = await fetch(apiUrl, { method: 'GET' }); const contentLength = response.headers.get('content-length'); diff --git a/web/apps/web/src/utils/common-helper.ts b/web/apps/web/src/utils/common-helper.ts index 0e1da293..6798ba0a 100644 --- a/web/apps/web/src/utils/common-helper.ts +++ b/web/apps/web/src/utils/common-helper.ts @@ -155,3 +155,8 @@ export const filterVariable = (data: Record) => { {} as any, ); }; + +export const getFileUrl = (url: string, prefix?: string) => { + if (url?.startsWith('http') || url?.startsWith('/api/files')) return url; + return `/api/files/${url}`; +}; From 7a0607c6ffc5100dd8caca0d710bb2895a65e5a5 Mon Sep 17 00:00:00 2001 From: shanexi Date: Mon, 13 Jan 2025 15:47:41 +0800 Subject: [PATCH 19/42] get_widget_schema add params myshell_widget_name --- .../src/components/app/config-form/widget-config/index.tsx | 5 ++++- web/apps/web/src/services/workflow/type.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/web/apps/web/src/components/app/config-form/widget-config/index.tsx b/web/apps/web/src/components/app/config-form/widget-config/index.tsx index 7d1eb1ae..43ffc3d5 100644 --- a/web/apps/web/src/components/app/config-form/widget-config/index.tsx +++ b/web/apps/web/src/components/app/config-form/widget-config/index.tsx @@ -48,7 +48,10 @@ const StandardWidgetConfig: React.FC = ({ useEffect(() => { if (values?.widget_class_name && !widgetSchema[values.widget_class_name]) { - getWidgetSchema({ widget_name: values.widget_class_name }); + getWidgetSchema({ + widget_name: values.widget_class_name, + myshell_widget_name: values.widget_name, + }); } }, [values?.widget_class_name, getWidgetSchema, widgetSchema]); diff --git a/web/apps/web/src/services/workflow/type.ts b/web/apps/web/src/services/workflow/type.ts index 30dd5b2a..964d09fd 100644 --- a/web/apps/web/src/services/workflow/type.ts +++ b/web/apps/web/src/services/workflow/type.ts @@ -19,6 +19,7 @@ export type GetWidgetListResponse = { // /api/workflow/get_widget_schema export type GetWidgetSchemaRequest = { widget_name: string; // 后续需要考虑版本 + myshell_widget_name: string; }; export type GetWidgetSchemaResponse = { From e36fe6816794710cbfad5aa4d80ea13277271228 Mon Sep 17 00:00:00 2001 From: shanexi Date: Mon, 13 Jan 2025 15:54:07 +0800 Subject: [PATCH 20/42] fix: type --- web/apps/web/src/stores/app/schema-provider.tsx | 2 +- web/apps/web/src/stores/workflow/schema-provider.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/apps/web/src/stores/app/schema-provider.tsx b/web/apps/web/src/stores/app/schema-provider.tsx index 6c9c8e82..ba604c04 100644 --- a/web/apps/web/src/stores/app/schema-provider.tsx +++ b/web/apps/web/src/stores/app/schema-provider.tsx @@ -87,7 +87,7 @@ export const SchemaProvider: React.FC = ({ useEffect(() => { if (name && !widgetSchema[name] && type === NodeTypeEnum.widget) { - getWidgetSchema({ widget_name: name }); + getWidgetSchema({ widget_name: name, myshell_widget_name: '' }); } }, [name]); diff --git a/web/apps/web/src/stores/workflow/schema-provider.tsx b/web/apps/web/src/stores/workflow/schema-provider.tsx index 930b76cb..f71afe59 100644 --- a/web/apps/web/src/stores/workflow/schema-provider.tsx +++ b/web/apps/web/src/stores/workflow/schema-provider.tsx @@ -102,7 +102,7 @@ export const SchemaProvider: React.FC = ({ id !== NodeIdEnum.start && id !== NodeIdEnum.end ) { - getWidgetSchema({ widget_name: name }); + getWidgetSchema({ widget_name: name, myshell_widget_name: '' }); } }, [name]); From d634c1a3da0b00a4d364950671819015d68935a2 Mon Sep 17 00:00:00 2001 From: tiancheng Date: Mon, 13 Jan 2025 16:51:52 +0800 Subject: [PATCH 21/42] type: x bot hello world example --- .../example/twitter_bot_hello_world.ts | 117 ++++++++++++++++++ web/packages/pro-config-type/src/block.ts | 2 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 web/packages/pro-config-type/example/twitter_bot_hello_world.ts diff --git a/web/packages/pro-config-type/example/twitter_bot_hello_world.ts b/web/packages/pro-config-type/example/twitter_bot_hello_world.ts new file mode 100644 index 00000000..e8b4572b --- /dev/null +++ b/web/packages/pro-config-type/example/twitter_bot_hello_world.ts @@ -0,0 +1,117 @@ +import { Automata } from '../src/block'; +import { PayloadMap } from '../src/transition'; + +/** + * “@new” 表示和线上pro config不同的改动细节。 + * FIXME 需要根据情况手动改 + */ + +const bot = { + type: 'automata', + name: 'Twitter bot', + initial: 'idle', + context: { + global_var: '', + }, + blocks: { + idle: { + type: 'state', + properties: { + timers: { + daily: { + timer: { + start: { + year: 2025, + month: 1, + date: 1, + hour: 0, + minute: 0, + timezone: 'Asia/Beijing', + }, + interval: { + day: 1, + }, + }, + event: 'daily_tweet', + }, + }, + }, + transitions: { + 'X.MENTIONED': { + target: 'llm_condition', + target_inputs: { + /** + * @type {PayloadMap["X.MENTIONED"]} + */ + content: '{{payload.text}}', + }, + }, + 'X.PINNED.REPLIED': { + target: 'llm_condition', + target_inputs: { + /** + * @type {PayloadMap["X.PINNED.REPLIED"]} + */ + content: '{{payload.text}}', + }, + }, + daily_tweet: { + target: 'write_tweet', + }, + }, + }, + llm_condition: { + type: 'dispatcher', + inputs: { + content: { + type: 'text', + user_input: false, + }, + }, + blocks: [], + outputs: { + reply: 'Replied {{content}}', + }, + transitions: { + ALWAYS: [ + { + name: 'Transition case 1', + condition: '{{len(content) > 5}}', + target: 'reply', + target_inputs: { + reply: '{{reply}}', + }, + }, + { + name: 'Transition case 2', + target: 'reply', + target_inputs: { + reply: '{{reply}}', + }, + }, + ], + }, + }, + reply: { + type: 'state', + inputs: { + reply: { + type: 'text', + user_input: false, + }, + }, + blocks: [], + render: { + text: '{{reply}}', + }, + }, + write_tweet: { + type: 'state', // 改为required @new + inputs: {}, + outputs: {}, + render: { + text: 'write some random stuff', + }, + }, + }, +} satisfies Automata; diff --git a/web/packages/pro-config-type/src/block.ts b/web/packages/pro-config-type/src/block.ts index 3bfa7500..6124ad13 100644 --- a/web/packages/pro-config-type/src/block.ts +++ b/web/packages/pro-config-type/src/block.ts @@ -86,7 +86,7 @@ export interface Automata extends ContainerBlock { cache?: boolean; }; initial: CustomKey; - blocks: BlockChildren; // 后续支持Automata | Task 等等 + blocks: BlockChildren; // 后续支持Automata | Task 等等 transitions?: { [key in CustomEventName | 'DONE']?: Transition }; } From 162c4000e696d88816b48c6dd16c2b9cae40a466 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 14 Jan 2025 12:38:27 +0800 Subject: [PATCH 22/42] WIP --- web/apps/web/src/app/app/detail/page.tsx | 16 +- web/apps/web/src/app/app/page.tsx | 6 +- web/apps/web/src/app/container.ts | 3 + web/apps/web/src/components/app/constants.tsx | 97 +- .../web/src/components/app/edges/index.tsx | 1 + .../components/app/edges/x-custom-edge.tsx | 117 ++ .../app/new-transition-sheet/index.tsx | 111 ++ .../src/components/app/node-form/index.tsx | 4 + .../app/node-form/widgets/index.tsx | 1 + .../app/node-form/widgets/state-selector.tsx | 39 + .../widgets/transition-condition-editor.tsx | 48 +- .../hook/use-duplicate-state.tsx | 25 + .../hook/use-paste-state.tsx | 62 + .../app/nodes/condition-state-node/index.tsx | 272 +++++ .../web/src/components/app/nodes/index.tsx | 1 + .../app/nodes/x-state-node/index.tsx | 2 +- .../app/state-config-sheet/index.tsx | 21 +- .../src/components/home/filter-tabs/index.tsx | 52 +- .../stores/app/models/app-builder.model.ts | 20 +- .../web/src/stores/app/models/flow.model.ts | 146 +++ .../stores/app/models/x-app-builder.model.ts | 14 - .../web/src/stores/app/schema-provider.tsx | 10 + .../app/schema/condition-state-schema.ts | 81 ++ .../app/schema/get-condition-state-schema.ts | 27 + .../app/schema/get-transition-schema.ts | 50 + .../stores/app/schema/get-x-state-schema.ts | 1 - .../src/stores/app/schema/x-state-config.ts | 1088 +++++++++++++++++ .../web/src/stores/home/models/home-model.ts | 12 +- .../flow-engine/src/store/flow/provider.tsx | 2 +- web/packages/flow-engine/src/types/node.ts | 31 +- .../shared/src/protocol/node/index.ts | 34 + .../utils/__test__/get_display_name.spec.ts | 7 +- .../shared/src/utils/get_display_name.ts | 26 +- 33 files changed, 2276 insertions(+), 151 deletions(-) create mode 100644 web/apps/web/src/components/app/edges/x-custom-edge.tsx create mode 100644 web/apps/web/src/components/app/new-transition-sheet/index.tsx create mode 100644 web/apps/web/src/components/app/node-form/widgets/state-selector.tsx create mode 100644 web/apps/web/src/components/app/nodes/condition-state-node/hook/use-duplicate-state.tsx create mode 100644 web/apps/web/src/components/app/nodes/condition-state-node/hook/use-paste-state.tsx create mode 100644 web/apps/web/src/components/app/nodes/condition-state-node/index.tsx create mode 100644 web/apps/web/src/stores/app/models/flow.model.ts delete mode 100644 web/apps/web/src/stores/app/models/x-app-builder.model.ts create mode 100644 web/apps/web/src/stores/app/schema/condition-state-schema.ts create mode 100644 web/apps/web/src/stores/app/schema/get-condition-state-schema.ts create mode 100644 web/apps/web/src/stores/app/schema/get-transition-schema.ts create mode 100644 web/apps/web/src/stores/app/schema/x-state-config.ts diff --git a/web/apps/web/src/app/app/detail/page.tsx b/web/apps/web/src/app/app/detail/page.tsx index d92b4f70..d25c82b4 100644 --- a/web/apps/web/src/app/app/detail/page.tsx +++ b/web/apps/web/src/app/app/detail/page.tsx @@ -14,8 +14,10 @@ import { HTML5Backend } from 'react-dnd-html5-backend'; import { edgeTypes, materialList, + xMaterialList, nodeTypes, xNodeTypes, + xEdgeTypes, } from '@/components/app/constants'; import FlowHeader from '@/components/app/flow-header'; import { Header } from '@/components/app/header'; @@ -43,6 +45,13 @@ const TransitionSheet = dynamic( }, ); +const TransitionSheetNew = dynamic( + () => import('@/components/app/new-transition-sheet'), + { + ssr: false, + }, +); + const ChatSheet = dynamic(() => import('@/components/app/chat-sheet'), { ssr: false, }); @@ -107,14 +116,17 @@ const FlowEngineWrapper = observer( } ref={flowRef} nodeTypes={appBuilder.appType === 'x_bot' ? xNodeTypes : nodeTypes} - edgeTypes={edgeTypes} - materialList={materialList} + edgeTypes={appBuilder.appType === 'x_bot' ? xEdgeTypes : edgeTypes} + materialList={ + appBuilder.appType === 'x_bot' ? xMaterialList : materialList + } footerExtra={} header={
e.stopPropagation()}> +
} diff --git a/web/apps/web/src/app/app/page.tsx b/web/apps/web/src/app/app/page.tsx index be145b6b..cd316e01 100644 --- a/web/apps/web/src/app/app/page.tsx +++ b/web/apps/web/src/app/app/page.tsx @@ -31,19 +31,19 @@ export default function AppPage() {
0 ? 'border-default' : 'border-transparent', )}> -
+
App
-
+
diff --git a/web/apps/web/src/app/container.ts b/web/apps/web/src/app/container.ts index af2deea1..7cabcc8f 100644 --- a/web/apps/web/src/app/container.ts +++ b/web/apps/web/src/app/container.ts @@ -23,6 +23,7 @@ import { WidgetsInstalledModel } from '@/components/manager/manager-content/widg import { WidgetsMarketplaceModel } from '@/components/manager/manager-content/widgets/widgets-marketplace.model'; import { SettingsModel } from '@/components/settings/settings.model'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { FlowModel } from '@/stores/app/models/flow.model'; import { FormEngineModel } from '@/utils/form-engine.model'; import { FormikModel } from '@/utils/formik.model'; import { ModalModel } from '@/utils/modal.model'; @@ -139,6 +140,8 @@ export const webModule = new ContainerModule(bind => { return () => context.container.get('AppBuilderModel'); }); + bind(FlowModel).toSelf().inSingletonScope(); + // assistant bind(AssistantModel).toSelf().inSingletonScope(); // feedback diff --git a/web/apps/web/src/components/app/constants.tsx b/web/apps/web/src/components/app/constants.tsx index 0f270ee9..61f7d1bd 100644 --- a/web/apps/web/src/components/app/constants.tsx +++ b/web/apps/web/src/components/app/constants.tsx @@ -7,13 +7,19 @@ import { } from '@shellagent/flow-engine'; import { Workflow } from '@shellagent/pro-config'; -import { DefaultEdge, CustomEdge, EdgeTypeEnum } from '@/components/app/edges'; +import { + DefaultEdge, + CustomEdge, + EdgeTypeEnum, + XCustomEdge, +} from '@/components/app/edges'; import { StartNode, StateNode, IntroNode, NoteNode, XStateNode, + ConditionStateNode, } from '@/components/app/nodes'; export const nodeTypes = { @@ -25,6 +31,7 @@ export const nodeTypes = { export const xNodeTypes = { [NodeTypeEnum.state]: XStateNode, + [NodeTypeEnum.condition_state]: ConditionStateNode, [NodeTypeEnum.note]: NoteNode, }; @@ -33,6 +40,10 @@ export const edgeTypes = { [EdgeTypeEnum.custom]: CustomEdge, }; +export const xEdgeTypes = { + [EdgeTypeEnum.custom]: XCustomEdge, +}; + export const chatDefaultFlow: IFlow = { nodes: [ { @@ -235,6 +246,11 @@ export const xMaterialList: MaterialListType = [ display_name: 'State', type: NodeTypeEnum.state, }, + { + name: 'Condition State', + display_name: 'Condition State', + type: NodeTypeEnum.condition_state, + }, { name: 'Note', display_name: 'Note', @@ -242,6 +258,85 @@ export const xMaterialList: MaterialListType = [ }, ], }, + { + plain: true, + no_border: true, + items: [ + { + display_name: 'Workflow Runner', + name: 'Workflow Runner', + type: NodeTypeEnum.workflow, + undraggable: true, + }, + ], + }, + { + title: 'Large Language Model', + plain: true, + no_border: true, + items: [ + { + name: 'GPTWidget', + display_name: 'GPT', + type: NodeTypeEnum.widget, + undraggable: true, + }, + { + name: 'ClaudeWidget', + display_name: 'Claude', + type: NodeTypeEnum.widget, + undraggable: true, + widget_name: '@myshell_llm/1744218088699596812', + }, + ], + }, + { + title: 'Tools', + plain: true, + no_border: true, + items: [ + { + name: 'ImageCanvasWidget', + display_name: 'Image Canvas', + type: NodeTypeEnum.widget, + undraggable: true, + }, + { + name: 'XWidget', + display_name: 'Twitter', + type: NodeTypeEnum.widget, + undraggable: true, + widget_name: '@myshell/1784206090390036480', + }, + { + name: 'Html2ImgWidget', + display_name: 'HTML to Image', + type: NodeTypeEnum.widget, + undraggable: true, + widget_name: '@myshell/1850127954369142784', + }, + { + name: 'ImageTextFuserWidget', + display_name: 'Image Text Fuser', + type: NodeTypeEnum.widget, + undraggable: true, + }, + ], + }, + { + title: 'Plugins', + plain: true, + no_border: true, + items: [ + { + name: 'ComfyUIWidget', + display_name: 'ComfyUI', + type: NodeTypeEnum.widget, + custom: true, + undraggable: true, + }, + ], + }, ]; export const inputSourceHandle = 'custom_message-input-source-handle'; diff --git a/web/apps/web/src/components/app/edges/index.tsx b/web/apps/web/src/components/app/edges/index.tsx index 3c694775..bd528265 100644 --- a/web/apps/web/src/components/app/edges/index.tsx +++ b/web/apps/web/src/components/app/edges/index.tsx @@ -1,4 +1,5 @@ export { EdgeTypeEnum, EdgeDataTypeEnum } from './type'; export { CustomEdge } from './custom-edge'; export { DefaultEdge } from './default-edge'; +export { XCustomEdge } from './x-custom-edge'; export { type ICondition, type CustomEdgeData, type ICustomEdge } from './type'; diff --git a/web/apps/web/src/components/app/edges/x-custom-edge.tsx b/web/apps/web/src/components/app/edges/x-custom-edge.tsx new file mode 100644 index 00000000..2d67b3d3 --- /dev/null +++ b/web/apps/web/src/components/app/edges/x-custom-edge.tsx @@ -0,0 +1,117 @@ +import { + BaseEdge, + EdgeProps, + getBezierPath, + NodeIdEnum, + useReactFlowStore, + EdgeText, +} from '@shellagent/flow-engine'; +import { RefSceneEnum } from '@shellagent/shared/protocol/app-scope'; +import { useKeyPress } from 'ahooks'; +import { useInjection } from 'inversify-react'; +import React, { useEffect } from 'react'; +import { useAppState } from '@/stores/app/use-app-state'; + +import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; + +export const XCustomEdge = ({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + markerEnd, + selected, + target, + source, + sourceHandleId, + data, +}: EdgeProps<{ + onDelete: (id: string) => void; + label?: string; +}>) => { + const appBuilder = useInjection('AppBuilderModel'); + const [edgePath] = getBezierPath({ + sourceX: sourceX - 8, + sourceY, + sourcePosition, + targetX: targetX + 2, + targetY, + targetPosition, + }); + + const setTransitionSheetOpen = useAppState( + state => state.setTransitionSheetOpen, + ); + + const onDelEdge = useReactFlowStore(state => state.onDelEdge); + const selectedEdges = useReactFlowStore(state => state.selectedEdges); + const edges = useReactFlowStore(state => state.edges); + + useKeyPress(['delete', 'backspace'], () => { + if ( + selected && + source !== NodeIdEnum.start && + target !== NodeIdEnum.intro + ) { + onDelEdge({ id }); + appBuilder.handleRefScene({ + scene: RefSceneEnum.Enum.remove_edge, + params: { + edges: edges as any, + removeEdge: { + target: target as Lowercase, + source: source as Lowercase, + }, + }, + }); + } + }); + + useEffect(() => { + if (selectedEdges.length > 0) { + setTransitionSheetOpen({ open: false, source: '', sourceHandle: '' }); + setTimeout(() => { + setTransitionSheetOpen({ + open: true, + source, + sourceHandle: sourceHandleId || '', + data: { + ...(data || {}), + id, + target, + source, + }, + }); + }); + } + }, [selectedEdges]); + + return ( + <> + + + + ); +}; + +XCustomEdge.displayName = 'XCustomEdge'; diff --git a/web/apps/web/src/components/app/new-transition-sheet/index.tsx b/web/apps/web/src/components/app/new-transition-sheet/index.tsx new file mode 100644 index 00000000..8372f5c4 --- /dev/null +++ b/web/apps/web/src/components/app/new-transition-sheet/index.tsx @@ -0,0 +1,111 @@ +'use client'; + +import { + useReactFlowStore, + getColor, + NodeTypeEnum, +} from '@shellagent/flow-engine'; +import { TValues } from '@shellagent/form-engine'; +import { Drawer } from '@shellagent/ui'; +import { useInjection } from 'inversify-react'; +import { useMemo, useCallback, memo } from 'react'; + +import NodeForm from '@/components/app/node-form'; +import { SchemaProvider } from '@/stores/app/schema-provider'; +import { getXTransitionSchema } from '@/stores/app/schema/get-transition-schema'; +import { observer } from 'mobx-react-lite'; +import { EditableTitle } from '@/components/app/editable-title'; +import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { useAppState } from '@/stores/app/use-app-state'; + +const TransitionSheetNew: React.FC<{}> = observer(() => { + const appBuilder = useInjection('AppBuilderModel'); + + const { + sourceHandle, + source, + currentEdegData, + transitionSheetOpen, + setTransitionSheetOpen, + runDrawerWidth, + } = useAppState(state => ({ + sourceHandle: state.currentTransitionSourceHandle, + source: state.currentTransitionSource, + transitionSheetOpen: state.transitionSheetOpen, + setTransitionSheetOpen: state.setTransitionSheetOpen, + currentEdegData: state.currentEdegData, + runDrawerWidth: state.runDrawerWidth, + })); + + console.log('transitionSheetOpen>>', transitionSheetOpen); + + const handleClose = useCallback(() => { + setTransitionSheetOpen({ open: false, source: '', sourceHandle: '' }); + }, []); + + const title = useMemo(() => { + return ( + { + // TODO: 修改名称 + // const { key: newKey, name: newName } = getNewKey({ + // name: value, + // nameKey: 'name', + // values: appBuilder.nodeData[currentStateId]?.blocks, + // prefix: 'Blocks', + // }); + // const values = nodeFormRef.current?.getValues(path); + // nodeFormRef.current?.setValue(path, { + // ...values, + // display_name: newName, + // name: newKey, + // }); + }} + /> + ); + }, [currentEdegData]); + + const handleChange = useCallback((values: TValues) => { + console.log('values>>>>', values); + }, []); + + const schema = useMemo(() => { + return getXTransitionSchema( + appBuilder.nodeData[currentEdegData?.target || '']?.inputs ?? {}, + ); + }, [appBuilder.nodeData, currentEdegData]); + + return ( + + + + + + ); +}); + +export default memo(TransitionSheetNew, () => true); diff --git a/web/apps/web/src/components/app/node-form/index.tsx b/web/apps/web/src/components/app/node-form/index.tsx index a70afbcc..c7683cf6 100644 --- a/web/apps/web/src/components/app/node-form/index.tsx +++ b/web/apps/web/src/components/app/node-form/index.tsx @@ -18,6 +18,7 @@ import { JSONView, FormRef, } from '@shellagent/ui'; +import { DatePicker } from 'antd'; import { isEmpty } from 'lodash-es'; import React, { useEffect, @@ -45,6 +46,7 @@ import { ValidateRender, ButtonValidateRender, DialogValidateRender, + StateSelector, } from './widgets'; import { OpenImageCanvas } from '../../image-canvas/open-image-canvas'; @@ -120,6 +122,8 @@ const NodeForm = forwardRef( ValidateRender, ButtonValidateRender, DialogValidateRender, + DatePicker, + StateSelector, ...components, }} /> diff --git a/web/apps/web/src/components/app/node-form/widgets/index.tsx b/web/apps/web/src/components/app/node-form/widgets/index.tsx index aa9edcd6..5ed7b15b 100644 --- a/web/apps/web/src/components/app/node-form/widgets/index.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/index.tsx @@ -10,3 +10,4 @@ export * from './variable-name-input'; export * from './validate-render'; export * from './validate-render/button'; export * from './validate-render/dialog'; +export * from './state-selector'; diff --git a/web/apps/web/src/components/app/node-form/widgets/state-selector.tsx b/web/apps/web/src/components/app/node-form/widgets/state-selector.tsx new file mode 100644 index 00000000..40d788bf --- /dev/null +++ b/web/apps/web/src/components/app/node-form/widgets/state-selector.tsx @@ -0,0 +1,39 @@ +import { Select } from '@shellagent/ui'; +import { useReactFlowStore, NodeTypeEnum } from '@shellagent/flow-engine'; + +interface StateSelectorProps { + value: string; + onChange: (value: string) => void; + className?: string; + excludeTargets?: string[]; +} + +export const StateSelector = ({ + value, + onChange, + className, + excludeTargets = [], +}: StateSelectorProps) => { + const { nodes } = useReactFlowStore(state => ({ + nodes: state.nodes, + })); + + const options = nodes + .filter( + node => + node.data.type !== NodeTypeEnum.start && + !excludeTargets.includes(node.id), + ) + .map(node => ({ + label: node.data.display_name || 'Untitled State', + value: node.id, + })); + + return ( +
+ -
-); - const ConditionItem = ({ value, onChange, onDelete, onOpen, - options, + index, + conditions, }: { value: ICondition; onChange: (value: ICondition) => void; onDelete: (value: ICondition) => void; onOpen: (open: boolean, value: ICondition) => void; - options: { label: string; value: string }[]; + index: number; + conditions: ICondition[]; }) => { const inputConRef = useRef(null); const handleChange = (field: 'condition' | 'target', newValue: string) => { @@ -60,6 +47,10 @@ const ConditionItem = ({ ); }; + const excludeTargets = conditions + .slice(0, index) + .map(condition => condition.target); + return (
@@ -76,10 +67,10 @@ const ConditionItem = ({
handleChange('target', v)} className="w-[100px]" + excludeTargets={excludeTargets} /> { - const includedTargets = value - .slice(0, index) - .map(condition => condition.target); - return nodes - .filter( - node => - node.data.type !== NodeTypeEnum.start && - !includedTargets.includes(node.id), - ) - .map(node => ({ - label: node.data.display_name || 'Untitled State', - value: node.id, - })); - }; - return (
{value?.map?.((condition, index) => ( @@ -179,7 +154,8 @@ const TransitionConditionEditor = ({ onChange={v => handleChange(index, v)} onDelete={handleDelete} onOpen={() => handleOpen(true, index)} - options={getOptions(index)} + index={index} + conditions={value} /> ))} + ))}
); }); diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 79b6db9f..3a12a9a8 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -1,4 +1,10 @@ -import { type IFlow, type ReactFlowInstance } from '@shellagent/flow-engine'; +import { + type IFlow, + type ReactFlowInstance, + FlowAction, + type XYPosition, + NodeTypeEnum, +} from '@shellagent/flow-engine'; import { CustomKey, CustomEventName } from '@shellagent/pro-config'; import { customSnakeCase, @@ -694,4 +700,16 @@ export class AppBuilderModel { this.appEditing = editing; }); } + + @action.bound + onAddStateNode(onAddNode: FlowAction['onAddNode'], position: XYPosition) { + onAddNode({ + type: NodeTypeEnum.state, + position: this.flowInstance?.project(position) || position, + data: { + name: 'State', + display_name: 'State', + }, + }); + } } diff --git a/web/apps/web/src/stores/app/models/flow.model.ts b/web/apps/web/src/stores/app/models/flow.model.ts new file mode 100644 index 00000000..533f145f --- /dev/null +++ b/web/apps/web/src/stores/app/models/flow.model.ts @@ -0,0 +1,146 @@ +import { makeAutoObservable } from 'mobx'; +import { injectable } from 'inversify'; +import { Edge, Node } from '@shellagent/flow-engine'; +import { EdgeDataTypeEnum, CustomEdgeData } from '@/components/app/edges'; + +type SheetMode = 'button' | 'workflow' | 'widget' | ''; + +interface BaseState { + currentStateId: string; + stateConfigSheetOpen: boolean; + insideSheetOpen: boolean; + insideSheetMode: SheetMode; + transitionSheetOpen: boolean; + currentButtonId: string; + currentTaskIndex?: number; + currentTransitionSource: string; + currentTransitionSourceHandle: string; + currentEdgeData?: CustomEdgeData; + targetInputsSheetOpen: boolean; +} + +@injectable() +export class FlowModel { + currentStateId: string = ''; + stateConfigSheetOpen: boolean = false; + insideSheetOpen: boolean = false; + insideSheetMode: 'button' | 'workflow' | 'widget' | '' = ''; + transitionSheetOpen: boolean = false; + runDrawerWidth: number = 715; + selectedNode: Node | undefined = undefined; + currentTaskIndex?: number; + currentButtonId: string = ''; + currentTransitionSource: string = ''; + currentTransitionSourceHandle: string = ''; + currentEdgeData: CustomEdgeData | undefined = undefined; + targetInputsSheetOpen: boolean = false; + selectedEdge: Edge | null = null; + + constructor() { + makeAutoObservable(this); + } + + private resetSheetState() { + const baseState: BaseState = { + currentStateId: '', + stateConfigSheetOpen: false, + insideSheetOpen: false, + insideSheetMode: '', + transitionSheetOpen: false, + currentButtonId: '', + currentTaskIndex: undefined, + currentTransitionSource: '', + currentTransitionSourceHandle: '', + currentEdgeData: undefined, + targetInputsSheetOpen: false, + }; + Object.assign(this, baseState); + } + + setSelectedNode = (node: Node) => { + this.selectedNode = node; + }; + + setStateConfigSheetOpen = (stateId: string, open: boolean) => { + if (!open) { + this.resetSheetState(); + return; + } + + if (this.currentStateId === stateId) return; + + this.resetSheetState(); + this.stateConfigSheetOpen = true; + this.currentStateId = stateId; + }; + + setInsideSheetOpen = (params: { + stateId: string; + open: boolean; + mode?: SheetMode; + buttonId?: string; + currentTaskIndex?: number; + }) => { + const { stateId, open, mode, buttonId, currentTaskIndex } = params; + + if (!open) { + this.resetSheetState(); + return; + } + + this.resetSheetState(); + this.stateConfigSheetOpen = true; + this.currentStateId = stateId; + this.insideSheetOpen = true; + this.insideSheetMode = mode || ''; + this.currentButtonId = mode === 'button' ? buttonId || '' : ''; + this.currentTaskIndex = currentTaskIndex; + }; + + setTransitionSheetOpen = (params: { + open: boolean; + source: string; + sourceHandle: string; + data?: CustomEdgeData; + }) => { + const { open, source, sourceHandle, data } = params; + + if (!open) { + this.resetSheetState(); + return; + } + + this.resetSheetState(); + this.transitionSheetOpen = true; + this.currentTransitionSource = source; + this.currentTransitionSourceHandle = sourceHandle; + this.currentEdgeData = data; + }; + + setTargetInputsSheetOpen = (open: boolean) => { + this.targetInputsSheetOpen = open; + }; + + setRunDrawerWidth = (width: number) => { + this.runDrawerWidth = width; + }; + + setSelectedEdge = (edge: Edge) => { + this.selectedEdge = edge; + }; + + resetState = () => { + this.resetSheetState(); + this.runDrawerWidth = 715; + this.selectedNode = undefined; + this.currentEdgeData = { + id: '', + custom: true, + event_key: '', + type: EdgeDataTypeEnum.ALWAYS, + target: '', + conditions: [], + }; + this.selectedEdge = null; + }; +} diff --git a/web/apps/web/src/stores/app/models/x-app-builder.model.ts b/web/apps/web/src/stores/app/models/x-app-builder.model.ts deleted file mode 100644 index 52428941..00000000 --- a/web/apps/web/src/stores/app/models/x-app-builder.model.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { makeAutoObservable } from 'mobx'; -import { injectable } from 'inversify'; -import { IFlow } from '@shellagent/flow-engine'; -import { xDefaultFlow } from '@/components/app/constants'; - -@injectable() -export class XAppBuilderModel { - flow: IFlow = xDefaultFlow; - selectedStateId: string | null = null; - - constructor() { - makeAutoObservable(this); - } -} diff --git a/web/apps/web/src/stores/app/schema-provider.tsx b/web/apps/web/src/stores/app/schema-provider.tsx index 6c9c8e82..33caa16b 100644 --- a/web/apps/web/src/stores/app/schema-provider.tsx +++ b/web/apps/web/src/stores/app/schema-provider.tsx @@ -14,6 +14,8 @@ import { generateUUID } from '@/utils/common-helper'; import { introConfigSchema } from './schema/intro-config'; import { startSchema } from './schema/start-node'; import { stateConfigSchema } from './schema/state-config'; +import { xStateConfigSchema } from './schema/x-state-config'; +import { getConditionStateSchema } from './schema/condition-state-schema'; type SchemaState = { id: string; @@ -110,6 +112,14 @@ export const SchemaProvider: React.FC = ({ return stateConfigSchema; } + if (type === 'x-state-config') { + return xStateConfigSchema; + } + + if (type === 'condition-state-config') { + return getConditionStateSchema; + } + if (type === NodeTypeEnum.state) { return getStateSchema(display_name); } diff --git a/web/apps/web/src/stores/app/schema/condition-state-schema.ts b/web/apps/web/src/stores/app/schema/condition-state-schema.ts new file mode 100644 index 00000000..c23289a7 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/condition-state-schema.ts @@ -0,0 +1,81 @@ +import { ISchema } from '@shellagent/form-engine'; + +export const getConditionStateSchema: ISchema = { + type: 'object', + 'x-type': 'Block', + 'x-title-size': 'h5', + 'x-class': 'space-y-3', + properties: { + blocks: { + title: 'Task', + type: 'array', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': false, + 'x-component': 'TasksConfig', + 'x-component-props': { + draggable: true, + }, + }, + transitions: { + type: 'object', + title: 'Transition', + additionalProperties: { + type: 'object', + 'x-type': 'Card', + 'x-title-editable': true, + 'x-deletable': true, + properties: { + condition: { + type: 'string', + 'x-type': 'Control', + 'x-component': 'ExpressionInput', + 'x-component-props': { + placeholder: 'Condition', + singleLine: true, + }, + }, + target: { + title: 'Target State', + type: 'string', + 'x-component': 'StateSelector', + 'x-type': 'Control', + 'x-inline': true, + }, + target_inputs: { + type: 'object', + properties: { + name: { + type: 'string', + 'x-component': 'Input', + }, + }, + }, + }, + 'x-edit-dialog': { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 30, + size: 'sm', + placeholder: 'Please name the event', + }, + }, + }, + }, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': false, + 'x-addable': true, + }, + }, +}; diff --git a/web/apps/web/src/stores/app/schema/get-condition-state-schema.ts b/web/apps/web/src/stores/app/schema/get-condition-state-schema.ts new file mode 100644 index 00000000..f06cc521 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/get-condition-state-schema.ts @@ -0,0 +1,27 @@ +import { ISchema } from '@shellagent/form-engine'; + +export const getConditionStateSchema = (name: string): ISchema => { + return { + title: name, + type: 'object', + 'x-type': 'Section', + 'x-title-size': 'h4', + 'x-title-component-props': { + className: 'bg-red-500', + }, + 'x-title-copiable': false, + properties: { + blocks: { + default: [], + type: 'array', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-component': 'TasksConfig', + 'x-component-props': { + draggable: false, + }, + }, + }, + }; +}; diff --git a/web/apps/web/src/stores/app/schema/get-transition-schema.ts b/web/apps/web/src/stores/app/schema/get-transition-schema.ts new file mode 100644 index 00000000..25479e26 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/get-transition-schema.ts @@ -0,0 +1,50 @@ +import { ISchema, TValues } from '@shellagent/form-engine'; +import { getSchemaByInputs } from '@/stores/app/schema/get-workflow-schema'; + +export const getXTransitionSchema = (inputs: TValues): ISchema => { + return { + type: 'object', + 'x-type': 'Block', + properties: { + type: { + type: 'string', + 'x-type': 'Control', + 'x-component': 'Select', + 'x-component-props': { + placeholder: 'Select your transition type', + options: [ + { label: 'Always', value: 'ALWAYS' }, + { label: 'MENTIONED', value: 'X.MENTIONED' }, + { label: 'REPLIED', value: 'X.PINNED.REPLIED' }, + ], + }, + }, + start: { + title: 'Start Time', + type: 'string', + 'x-type': 'Control', + 'x-component': 'DatePicker', + }, + interval: { + title: 'Repeat Frequency', + type: 'string', + 'x-type': 'Control', + 'x-component': 'DatePicker', + }, + end: { + title: 'End Time', + type: 'string', + 'x-type': 'Control', + 'x-component': 'DatePicker', + }, + target: { + title: 'Target State', + 'x-inline': true, + type: 'string', + 'x-type': 'Control', + 'x-component': 'StateSelector', + }, + target_inputs: getSchemaByInputs(inputs), + }, + }; +}; diff --git a/web/apps/web/src/stores/app/schema/get-x-state-schema.ts b/web/apps/web/src/stores/app/schema/get-x-state-schema.ts index d9f86337..d12ed6e8 100644 --- a/web/apps/web/src/stores/app/schema/get-x-state-schema.ts +++ b/web/apps/web/src/stores/app/schema/get-x-state-schema.ts @@ -11,7 +11,6 @@ export const getXStateSchema = (name: string): ISchema => { blocks: { default: [], type: 'array', - title: 'Task', 'x-type': 'Block', 'x-title-size': 'h4', 'x-collapsible': true, diff --git a/web/apps/web/src/stores/app/schema/x-state-config.ts b/web/apps/web/src/stores/app/schema/x-state-config.ts new file mode 100644 index 00000000..866f4333 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/x-state-config.ts @@ -0,0 +1,1088 @@ +import { ISchema } from '@shellagent/form-engine'; +import { FieldModeEnum } from '@shellagent/shared/protocol/extend-config'; + +import { validateCustomKey } from '@/stores/app/utils/validator'; +import { ENABLE_MIME } from '@/utils/file-types'; + +export const xStateConfigSchema: ISchema = { + type: 'object', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-class': 'space-y-3', + properties: { + inputs: { + type: 'object', + title: 'Input', + additionalProperties: { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + // 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'UnfocusInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-full', + 'x-component-props': { + size: '2xs', + autoFocus: false, + maxLength: 30, + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + ], + }, + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + triggerClassName: 'h-7 w-32', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-32', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + }, + user_input: { + type: 'boolean', + default: true, + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + }, + }, + 'x-key': '{{name}} Inputs_{{counter}}', + 'x-type': 'Inline', + 'x-validator-render': 'ValidateRender', + 'x-validator': [ + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value.name); + }, + }, + ], + 'x-draggable': true, + 'x-deletable': true, + 'x-edit-dialog': { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 30, + size: 'sm', + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + type: { + type: 'string', + default: 'text', + title: 'Type', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "text"', + fullfill: { + schema: { + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices 12', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: { + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "image"', + fullfill: { + schema: { + default: [{ value: 'a', label: 'A' }], + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + properties: { + label: { + title: 'Label', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'Input', + 'x-component-props': { + maxLength: 30, + size: 'sm', + }, + }, + value: { + title: 'Value', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: { + 'image/apng': ['.apng'], + 'image/png': ['.png'], + 'image/jpeg': ['.jpeg'], + 'image/jpg': ['.jpg'], + 'image/webp': ['.webp'], + 'image/gif': ['.gif'], + 'image/bmp': ['.bmp'], + 'image/tiff': ['.tiff'], + }, + }, + }, + }, + required: ['value'], + title: 'Choice Item', + 'x-type': 'Card', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-role': 'core', + 'x-deletable': true, + }, + }, + }, + }, + ], + }, + user_input: { + type: 'boolean', + default: true, + title: 'User Input', + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-reactions': [ + { + target: 'source', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'source', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + source: { + title: 'Source', + type: 'string', + default: 'IM', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'IM', value: 'IM' }, + { label: 'form', value: 'form' }, + ], + }, + 'x-reactions': [ + { + target: 'choices', + when: '$this.value === "IM"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + default_value: { + type: 'string', + title: 'Default Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + value: { + type: 'string', + title: 'Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + description: { + type: 'string', + title: 'Description', + 'x-raw': true, + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3 pb-3', + 'x-component-props': { + maxLength: 500, + placeholder: + 'Please describe how to use this input variable. This description will be displayed when the user inputs.', + }, + 'x-validator': [ + { + exclusiveMaximum: 500, + message: 'Cannot exceed 500 characters', + }, + ], + 'x-type': 'Control', + }, + choices: { + 'x-hidden': true, + type: 'array', + 'x-title-size': 'h5', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: {}, + }, + }, + validations: { + type: 'array', + title: 'Validations', + 'x-title-size': 'h5', + additionalItems: { + type: 'object', + properties: { + void_core: { + type: 'void', + 'x-role': 'core', + 'x-type': 'Grid', + 'x-class': 'grid-cols-3', + properties: { + rule_type: { + type: 'string', + enum: [ + 'max_length', + 'min_number', + 'max_number', + 'max_file_size', + ], + 'x-component': 'Select', + 'x-component-props': { + placeholder: 'Rule type', + options: [ + { label: 'Max Length', value: 'max_length' }, + { label: 'Min Number', value: 'min_number' }, + { label: 'Max Number', value: 'max_number' }, + { label: 'Max File Size', value: 'max_file_size' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'max_length', + when: '$this.value === "max_length"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'min_number', + when: '$this.value === "min_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_number', + when: '$this.value === "max_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_file_size', + when: '$this.value === "max_file_size"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + max_length: { + type: 'number', + default: 500, + 'x-component': 'NumberInput', + 'x-type': 'Control', + // 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 15000, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 15000 }, + ], + }, + min_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_file_size: { + type: 'number', + default: 10 * 1024 * 1024, + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 100 * 1024 * 1024, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 100 * 1024 * 1024 }, + ], + }, + error_message: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + placeholder: 'Error Message', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + }, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, + blocks: { + title: 'Task', + type: 'array', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-component': 'TasksConfig', + 'x-component-props': { + draggable: true, + }, + }, + outputs: { + type: 'object', + title: 'Output', + additionalProperties: { + type: 'object', + properties: { + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-hidden': true, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ref, + 'x-parent-deletable': true, + 'x-title-editable': true, + 'x-title-component-props': { + showDialog: true, + defaultKey: 'display_name', + dialogConfig: { + title: 'Edit Output', + schema: { + type: 'object', + properties: { + name_mode: { + type: 'string', + default: FieldModeEnum.Enum.ui, + title: 'Mode', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'type', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'description', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'VariableNameInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-component-props': { + maxLength: 30, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + required: true, + message: 'Please input the Variable Name', + }, + { + maxLength: 50, + message: 'Cannot exceed 50 characters', + }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + display_name: { + type: 'string', + 'x-hidden': true, + }, + type: { + type: 'string', + default: 'text', + enum: [ + 'text', + 'image', + 'audio', + 'video', + 'text_file', + 'file', + ], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-layout': 'Vertical', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + description: { + type: 'string', + title: 'Description', + default: '', + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 300, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + exclusiveMaximum: 300, + message: 'Cannot exceed 300 characters', + }, + ], + 'x-type': 'Control', + }, + }, + }, + }, + }, + 'x-component-props': { + size: '2xs', + }, + 'x-layout': 'Horizontal', + 'x-validator': [ + { required: true, message: 'The value is required' }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-hidden': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + 'x-key': '{{name}} Outputs_{{counter}}', + 'x-type': 'Inline', + 'x-collapsible': true, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, + render: { + type: 'object', + title: 'Message', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + properties: { + text: { + type: 'string', + title: 'Text', + 'x-component': 'ExpressionInput', + 'x-type': 'Control', + 'x-switchable': true, + 'x-switchable-default': true, + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ui, + 'x-validator': [ + { + warningOnly: true, + critical: true, + validator(rule, value) { + return new Promise((resolve, reject) => { + if (/]*>/.test(value)) { + reject( + new Error( + 'The tag is deprecated. Please use Message.Image to render images instead.', + ), + ); + } else { + resolve(true); + } + }); + }, + }, + ], + 'x-validator-render': 'ValidateRender', + }, + image: { + type: 'string', + title: 'Image', + 'x-component': 'FileUpload', + 'x-type': 'Control', + 'x-switchable': true, + 'x-switchable-default': false, + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ui, + }, + }, + }, + id: { + type: 'string', + default: '', + 'x-hidden': true, + }, + name: { + type: 'string', + default: '', + 'x-hidden': true, + }, + type: { + type: 'string', + default: 'state', + 'x-hidden': true, + }, + }, +}; diff --git a/web/apps/web/src/stores/home/models/home-model.ts b/web/apps/web/src/stores/home/models/home-model.ts index 4d22db7f..709bf8ce 100644 --- a/web/apps/web/src/stores/home/models/home-model.ts +++ b/web/apps/web/src/stores/home/models/home-model.ts @@ -15,9 +15,15 @@ export class HomeModel { get filteredData() { if (this.appType === 'all') return this.listData; - return this.listData.filter( - item => item.metadata.app_type === this.appType, - ); + if (this.appType === 'chat_bot') { + return this.listData.filter(item => item.metadata.app_type !== 'x_bot'); + } + if (this.appType === 'x_bot') { + return this.listData.filter( + item => item.metadata.app_type === this.appType, + ); + } + return this.listData; } constructor( diff --git a/web/packages/flow-engine/src/store/flow/provider.tsx b/web/packages/flow-engine/src/store/flow/provider.tsx index 27a72930..d909490d 100644 --- a/web/packages/flow-engine/src/store/flow/provider.tsx +++ b/web/packages/flow-engine/src/store/flow/provider.tsx @@ -179,7 +179,7 @@ export const FlowStoreProvider = ({ children }: FlowStoreProviderProps) => { node.data.name?.startsWith(name), ); - const reg = new RegExp(`${name.toLowerCase()}(\\d+)$`); + const reg = new RegExp(`${customSnakeCase(name)}(\\d+)$`); // 获取最大编号 let maxIndex = 0; diff --git a/web/packages/flow-engine/src/types/node.ts b/web/packages/flow-engine/src/types/node.ts index 99ed32c7..cbc7d7d4 100644 --- a/web/packages/flow-engine/src/types/node.ts +++ b/web/packages/flow-engine/src/types/node.ts @@ -1,29 +1,10 @@ -export enum DataTypeEnum { - string = 'string', - map = 'map', - all = 'all', -} - -// 全量node类型 -export enum NodeTypeEnum { - start = 'start', - end = 'end', - widget = 'widget', - state = 'state', - note = 'note', - intro = 'intro', - workflow = 'workflow', -} +import { + NodeTypeEnum, + type NodeType, + NodeIdEnum, +} from '@shellagent/shared/protocol/node'; -export type NodeType = keyof typeof NodeTypeEnum; - -export type DataType = keyof typeof DataTypeEnum; - -export enum NodeIdEnum { - start = '@@@start', - end = '@@@end', - intro = 'intro', -} +export { NodeTypeEnum, NodeType, NodeIdEnum }; // 节点id通过uuid生成 export type WidgetNodeId = `key_${string}`; diff --git a/web/packages/shared/src/protocol/node/index.ts b/web/packages/shared/src/protocol/node/index.ts index 511bbd9d..749d6987 100644 --- a/web/packages/shared/src/protocol/node/index.ts +++ b/web/packages/shared/src/protocol/node/index.ts @@ -6,3 +6,37 @@ export const reservedStateName = { } as const; export const reservedStateNameSchema = z.nativeEnum(reservedStateName); + +export enum NodeTypeEnum { + start = 'start', + end = 'end', + widget = 'widget', + state = 'state', + condition_state = 'condition_state', + workflow = 'workflow', + intro = 'intro', + note = 'note', +} + +export type NodeType = keyof typeof NodeTypeEnum; + +export const nodeTypeSchema = z.nativeEnum(NodeTypeEnum); + +export enum NodeIdEnum { + start = '@@@start', + end = '@@@end', + intro = 'intro', +} + +export type WidgetItem = { + display_name: string; // widget显示名称 + name: string; // widget名称(唯一标识) + icon?: string; // icon + desc?: string; // widget描述 + children?: WidgetItem[]; // 子widget + type?: NodeType; + custom?: boolean; // 是否为自定义widget + undraggable?: boolean; // 是否可以拖拽至画布中 + customRender?: () => JSX.Element; + widget_name?: string; +}; diff --git a/web/packages/shared/src/utils/__test__/get_display_name.spec.ts b/web/packages/shared/src/utils/__test__/get_display_name.spec.ts index 639ee7f2..4e00ba37 100644 --- a/web/packages/shared/src/utils/__test__/get_display_name.spec.ts +++ b/web/packages/shared/src/utils/__test__/get_display_name.spec.ts @@ -1,8 +1,5 @@ -import { - getTaskDisplayName, - getButtonDisplayName, - NodeTypeEnum, -} from '../get_display_name'; +import { getTaskDisplayName, getButtonDisplayName } from '../get_display_name'; +import { NodeTypeEnum } from '../../type/app-builder/flow'; import { Task } from '../../protocol/task'; import { Button } from '../../protocol/render-button'; diff --git a/web/packages/shared/src/utils/get_display_name.ts b/web/packages/shared/src/utils/get_display_name.ts index 0f92ee01..f20de3f2 100644 --- a/web/packages/shared/src/utils/get_display_name.ts +++ b/web/packages/shared/src/utils/get_display_name.ts @@ -1,30 +1,6 @@ import { Task } from '../protocol/task'; import { Button } from '../protocol/render-button'; - -export enum NodeTypeEnum { - start = 'start', - end = 'end', - widget = 'widget', - state = 'state', - workflow = 'workflow', - intro = 'intro', - note = 'note', -} - -export type NodeType = keyof typeof NodeTypeEnum; - -export type WidgetItem = { - display_name: string; // widget显示名称 - name: string; // widget名称(唯一标识) - icon?: string; // icon - desc?: string; // widget描述 - children?: WidgetItem[]; // 子widget - type?: NodeType; - custom?: boolean; // 是否为自定义widget - undraggable?: boolean; // 是否可以拖拽至画布中 - customRender?: () => JSX.Element; - widget_name?: string; -}; +import { WidgetItem, NodeTypeEnum } from '../protocol/node'; export const getTaskDisplayName = (value: WidgetItem, tasks: Task[]) => { let indexMap: Record = {}; From 6a51a7c5cec5e5e8de4c9d91d7976f213407fcf0 Mon Sep 17 00:00:00 2001 From: shanexi Date: Tue, 14 Jan 2025 16:53:20 +0800 Subject: [PATCH 23/42] feat: convert to x bot --- .../components/chat/app-builder-chat.model.ts | 22 ++++++++++++++++--- web/apps/web/src/services/app/index.tsx | 9 ++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/web/apps/web/src/components/chat/app-builder-chat.model.ts b/web/apps/web/src/components/chat/app-builder-chat.model.ts index 428594d6..5d62bba0 100644 --- a/web/apps/web/src/components/chat/app-builder-chat.model.ts +++ b/web/apps/web/src/components/chat/app-builder-chat.model.ts @@ -12,15 +12,17 @@ import { JsonSchema7 } from 'node_modules/@shellagent/form-engine/src/types/json import { upload } from '@/services/common'; +import { convertXBotSvc } from '@/services/app'; +import { type AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import type { ServerMessage } from '../../services/app/message-type'; +import { EventStatusEnum, RunAppRequest } from '../../services/app/type'; +import { ToastModel } from '../../utils/toast.model'; import { patchImageUrl, patchMessageActionPopupForm, serverMessageToMessage, transformEmbedObjs, } from './app-builder-chat-utils'; -import type { ServerMessage } from '../../services/app/message-type'; -import { EventStatusEnum, RunAppRequest } from '../../services/app/type'; -import { ToastModel } from '../../utils/toast.model'; @injectable() export class AppBuilderChatModel { @@ -48,6 +50,8 @@ export class AppBuilderChatModel { constructor( @inject(ToastModel) private emitter: ToastModel, @inject(ChatNewModel) public chatNew: ChatNewModel, + @inject('Factory') + public appBuilderModelFactory: () => AppBuilderModel, ) { this.chatNew.handlers.sendTextMessagePost = async ( text: string, @@ -136,6 +140,18 @@ export class AppBuilderChatModel { runInAction(() => { this.isInitBotLoading = true; }); + if (this.appBuilderModelFactory().metadata.app_type) { + try { + const res = await convertXBotSvc({ automata }); + automata = res.automata; + } catch (e) { + if (e instanceof Error) { + this.emitter.error(e.message); + } else { + this.emitter.error(`convert to x automata error`); + } + } + } try { const res = await axios.post( `/api/app/init_bot`, diff --git a/web/apps/web/src/services/app/index.tsx b/web/apps/web/src/services/app/index.tsx index 5b946873..28fcafa5 100644 --- a/web/apps/web/src/services/app/index.tsx +++ b/web/apps/web/src/services/app/index.tsx @@ -68,6 +68,15 @@ export const initBot: Fetcher = params => { }); }; +export const convertXBotSvc: Fetcher< + InitBotRequest, + InitBotRequest +> = params => { + return APIFetch.post('/api/app/convert_x_bot', { + body: params, + }); +}; + export const runApp = ( params: RunAppRequest, cb: { From 90a8d0b968462120587c05171c80c83b5f2f0faa Mon Sep 17 00:00:00 2001 From: shanexi Date: Tue, 14 Jan 2025 16:54:00 +0800 Subject: [PATCH 24/42] fix: fix x_bot --- web/apps/web/src/components/chat/app-builder-chat.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/web/src/components/chat/app-builder-chat.model.ts b/web/apps/web/src/components/chat/app-builder-chat.model.ts index 5d62bba0..2932b9e9 100644 --- a/web/apps/web/src/components/chat/app-builder-chat.model.ts +++ b/web/apps/web/src/components/chat/app-builder-chat.model.ts @@ -140,7 +140,7 @@ export class AppBuilderChatModel { runInAction(() => { this.isInitBotLoading = true; }); - if (this.appBuilderModelFactory().metadata.app_type) { + if (this.appBuilderModelFactory().metadata.app_type === 'x_bot') { try { const res = await convertXBotSvc({ automata }); automata = res.automata; From bff8f5281a1a5f92376aaad90f3af438bb38d4af Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 14 Jan 2025 21:45:51 +0800 Subject: [PATCH 25/42] [WIP] ui done --- web/apps/web/src/components/app/constants.tsx | 79 +- .../src/components/app/edges/custom-edge.tsx | 9 +- web/apps/web/src/components/app/edges/type.ts | 1 + .../components/app/edges/x-custom-edge.tsx | 35 +- .../app/new-transition-sheet/index.tsx | 64 +- .../src/components/app/node-form/index.tsx | 2 + .../app/node-form/widgets/index.tsx | 1 + .../app/node-form/widgets/timer-selector.tsx | 300 +++++ .../widgets/transition-condition-editor.tsx | 2 +- .../web/src/components/app/nodes/index.tsx | 1 + .../app/nodes/x-intro-node/index.tsx | 135 +++ web/apps/web/src/constants/timezones.ts | 0 .../app/schema/condition-state-schema.ts | 1002 ++++++++++++++++- .../app/schema/get-transition-schema.ts | 85 +- web/apps/web/src/stores/app/use-app-state.tsx | 52 +- web/apps/web/src/types/index.d.ts | 1 - web/packages/shared/package.json | 3 +- web/packages/shared/src/constants/index.ts | 1 + .../shared/src/constants/timezones.ts | 190 ++++ .../shared/src/protocol/pro-config/block.ts | 52 + web/pnpm-lock.yaml | 36 +- 21 files changed, 1944 insertions(+), 107 deletions(-) create mode 100644 web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx create mode 100644 web/apps/web/src/components/app/nodes/x-intro-node/index.tsx create mode 100644 web/apps/web/src/constants/timezones.ts create mode 100644 web/packages/shared/src/constants/index.ts create mode 100644 web/packages/shared/src/constants/timezones.ts diff --git a/web/apps/web/src/components/app/constants.tsx b/web/apps/web/src/components/app/constants.tsx index 61f7d1bd..420ed61d 100644 --- a/web/apps/web/src/components/app/constants.tsx +++ b/web/apps/web/src/components/app/constants.tsx @@ -20,6 +20,7 @@ import { NoteNode, XStateNode, ConditionStateNode, + XIntroNode, } from '@/components/app/nodes'; export const nodeTypes = { @@ -30,6 +31,8 @@ export const nodeTypes = { }; export const xNodeTypes = { + [NodeTypeEnum.start]: StartNode, + [NodeTypeEnum.intro]: XIntroNode, [NodeTypeEnum.state]: XStateNode, [NodeTypeEnum.condition_state]: ConditionStateNode, [NodeTypeEnum.note]: NoteNode, @@ -41,6 +44,7 @@ export const edgeTypes = { }; export const xEdgeTypes = { + [EdgeTypeEnum.default]: DefaultEdge, [EdgeTypeEnum.custom]: XCustomEdge, }; @@ -126,8 +130,79 @@ export const chatDefaultFlow: IFlow = { }; export const xDefaultFlow: IFlow = { - nodes: [], - edges: [], + nodes: [ + { + width: 377, + height: 185, + id: NodeIdEnum.start, + position: { + x: 18.00341796875, + y: 290.0034713745117, + }, + type: NodeTypeEnum.start, + selectable: true, + focusable: true, + draggable: true, + data: { + type: NodeTypeEnum.start, + id: NodeIdEnum.start, + display_name: 'Start', + }, + selected: false, + positionAbsolute: { + x: 18.00341796875, + y: 290.0034713745117, + }, + dragging: false, + }, + { + id: 'intro', + position: { + x: 592.1845769352449, + y: 290.0034713745117, + }, + type: 'intro', + selectable: true, + focusable: true, + draggable: true, + data: { + type: 'intro', + id: NodeIdEnum.intro, + name: 'Intro', + display_name: 'Ready', + }, + width: 500, + height: 218, + selected: false, + positionAbsolute: { + x: 592.1845769352449, + y: 290.0034713745117, + }, + dragging: false, + }, + ], + edges: [ + { + type: 'default_edge', + style: { + strokeWidth: 2, + stroke: '#d1d5db', + }, + markerEnd: { + color: '#5A646Es', + height: 25, + strokeWidth: 2, + type: MarkerType.ArrowClosed, + width: 10, + }, + source: NodeIdEnum.start, + sourceHandle: NodeIdEnum.start, + target: 'intro', + targetHandle: 'intro', + animated: false, + id: 'reactflow__edge-@@@start@@@start-introintro', + }, + ], viewport: { x: 100, y: 100, diff --git a/web/apps/web/src/components/app/edges/custom-edge.tsx b/web/apps/web/src/components/app/edges/custom-edge.tsx index acbdc994..19e133e6 100644 --- a/web/apps/web/src/components/app/edges/custom-edge.tsx +++ b/web/apps/web/src/components/app/edges/custom-edge.tsx @@ -36,8 +36,11 @@ export const CustomEdge = ({ const [buttonWidth, setButtonWidth] = useState(0); const appBuilder = useInjection('AppBuilderModel'); - const setTransitionSheetOpen = useAppState( - state => state.setTransitionSheetOpen, + const { setTransitionSheetOpen, transitionSheetOpen } = useAppState( + state => ({ + setTransitionSheetOpen: state.setTransitionSheetOpen, + transitionSheetOpen: state.transitionSheetOpen, + }), ); useEffect(() => { @@ -80,7 +83,7 @@ export const CustomEdge = ({ // }, [edges, id, source]); useKeyPress(['delete', 'backspace'], () => { - if (selected) { + if (selected && !transitionSheetOpen) { onDelEdge({ id }); appBuilder.handleRefScene({ scene: RefSceneEnum.Enum.remove_edge, diff --git a/web/apps/web/src/components/app/edges/type.ts b/web/apps/web/src/components/app/edges/type.ts index b4f89c6b..5cd29a5a 100644 --- a/web/apps/web/src/components/app/edges/type.ts +++ b/web/apps/web/src/components/app/edges/type.ts @@ -29,6 +29,7 @@ export type CustomEdgeData = { condition: string; target_inputs: Record; }[]; + display_name?: string; }; export type ICustomEdge = Edge; diff --git a/web/apps/web/src/components/app/edges/x-custom-edge.tsx b/web/apps/web/src/components/app/edges/x-custom-edge.tsx index 2d67b3d3..de2bc454 100644 --- a/web/apps/web/src/components/app/edges/x-custom-edge.tsx +++ b/web/apps/web/src/components/app/edges/x-custom-edge.tsx @@ -13,6 +13,8 @@ import React, { useEffect } from 'react'; import { useAppState } from '@/stores/app/use-app-state'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { TransitionTypeEnum } from '@/stores/app/schema/get-transition-schema'; +import { CustomEdgeData } from './type'; export const XCustomEdge = ({ id, @@ -28,10 +30,7 @@ export const XCustomEdge = ({ source, sourceHandleId, data, -}: EdgeProps<{ - onDelete: (id: string) => void; - label?: string; -}>) => { +}: EdgeProps) => { const appBuilder = useInjection('AppBuilderModel'); const [edgePath] = getBezierPath({ sourceX: sourceX - 8, @@ -42,20 +41,20 @@ export const XCustomEdge = ({ targetPosition, }); - const setTransitionSheetOpen = useAppState( - state => state.setTransitionSheetOpen, + const { setNewTransitionSheetOpen, newTransitionSheetOpen } = useAppState( + state => ({ + setNewTransitionSheetOpen: state.setNewTransitionSheetOpen, + newTransitionSheetOpen: state.newTransitionSheetOpen, + }), ); const onDelEdge = useReactFlowStore(state => state.onDelEdge); const selectedEdges = useReactFlowStore(state => state.selectedEdges); const edges = useReactFlowStore(state => state.edges); + const nodes = useReactFlowStore(state => state.nodes); useKeyPress(['delete', 'backspace'], () => { - if ( - selected && - source !== NodeIdEnum.start && - target !== NodeIdEnum.intro - ) { + if (selected && !newTransitionSheetOpen) { onDelEdge({ id }); appBuilder.handleRefScene({ scene: RefSceneEnum.Enum.remove_edge, @@ -71,10 +70,10 @@ export const XCustomEdge = ({ }); useEffect(() => { - if (selectedEdges.length > 0) { - setTransitionSheetOpen({ open: false, source: '', sourceHandle: '' }); + if (selectedEdges.length > 0 && selectedEdges[0].id === id) { + setNewTransitionSheetOpen({ open: false, source: '', sourceHandle: '' }); setTimeout(() => { - setTransitionSheetOpen({ + setNewTransitionSheetOpen({ open: true, source, sourceHandle: sourceHandleId || '', @@ -84,6 +83,8 @@ export const XCustomEdge = ({ target, source, }, + transitionType: nodes.find(node => node.id === source) + ?.type as TransitionTypeEnum, }); }); } @@ -96,16 +97,16 @@ export const XCustomEdge = ({ path={edgePath} markerEnd={markerEnd} style={{ - stroke: selected ? '#3E5CFA' : '#E5E7EB', + stroke: selected ? '#3E5CFA' : '#B6BABF', strokeWidth: 2, }} /> = observer(() => { sourceHandle, source, currentEdegData, - transitionSheetOpen, - setTransitionSheetOpen, - runDrawerWidth, + newTransitionSheetOpen, + setNewTransitionSheetOpen, + transitionType, } = useAppState(state => ({ sourceHandle: state.currentTransitionSourceHandle, source: state.currentTransitionSource, - transitionSheetOpen: state.transitionSheetOpen, - setTransitionSheetOpen: state.setTransitionSheetOpen, + newTransitionSheetOpen: state.newTransitionSheetOpen, + setNewTransitionSheetOpen: state.setNewTransitionSheetOpen, currentEdegData: state.currentEdegData, - runDrawerWidth: state.runDrawerWidth, + transitionType: state.transitionType, })); - console.log('transitionSheetOpen>>', transitionSheetOpen); + const onChangeEdgeData = useReactFlowStore(state => state.onChangeEdgeData); const handleClose = useCallback(() => { - setTransitionSheetOpen({ open: false, source: '', sourceHandle: '' }); + setNewTransitionSheetOpen({ open: false, source: '', sourceHandle: '' }); }, []); - const title = useMemo(() => { - return ( - { - // TODO: 修改名称 - // const { key: newKey, name: newName } = getNewKey({ - // name: value, - // nameKey: 'name', - // values: appBuilder.nodeData[currentStateId]?.blocks, - // prefix: 'Blocks', - // }); - // const values = nodeFormRef.current?.getValues(path); - // nodeFormRef.current?.setValue(path, { - // ...values, - // display_name: newName, - // name: newKey, - // }); - }} - /> - ); - }, [currentEdegData]); - const handleChange = useCallback((values: TValues) => { console.log('values>>>>', values); }, []); @@ -73,8 +46,9 @@ const TransitionSheetNew: React.FC<{}> = observer(() => { const schema = useMemo(() => { return getXTransitionSchema( appBuilder.nodeData[currentEdegData?.target || '']?.inputs ?? {}, + transitionType, ); - }, [appBuilder.nodeData, currentEdegData]); + }, [appBuilder.nodeData, currentEdegData, transitionType]); return ( = observer(() => { placement="right" mask={false} getContainer={false} - title={title} - open={transitionSheetOpen} + title={ + { + if (currentEdegData?.id) { + onChangeEdgeData(currentEdegData.id, { + ...currentEdegData, + display_name: value, + }); + } + }} + /> + } + open={newTransitionSheetOpen} autoFocus={false} keyboard={false} onClose={handleClose} diff --git a/web/apps/web/src/components/app/node-form/index.tsx b/web/apps/web/src/components/app/node-form/index.tsx index c7683cf6..6beceb62 100644 --- a/web/apps/web/src/components/app/node-form/index.tsx +++ b/web/apps/web/src/components/app/node-form/index.tsx @@ -47,6 +47,7 @@ import { ButtonValidateRender, DialogValidateRender, StateSelector, + TimerSelector, } from './widgets'; import { OpenImageCanvas } from '../../image-canvas/open-image-canvas'; @@ -124,6 +125,7 @@ const NodeForm = forwardRef( DialogValidateRender, DatePicker, StateSelector, + TimerSelector, ...components, }} /> diff --git a/web/apps/web/src/components/app/node-form/widgets/index.tsx b/web/apps/web/src/components/app/node-form/widgets/index.tsx index 5ed7b15b..0c5d5c64 100644 --- a/web/apps/web/src/components/app/node-form/widgets/index.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/index.tsx @@ -11,3 +11,4 @@ export * from './validate-render'; export * from './validate-render/button'; export * from './validate-render/dialog'; export * from './state-selector'; +export * from './timer-selector'; diff --git a/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx b/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx new file mode 100644 index 00000000..3ef3297b --- /dev/null +++ b/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx @@ -0,0 +1,300 @@ +import React, { useState, useCallback } from 'react'; +import { + DatePicker, + TimePicker, + Modal, + Select, + InputNumber, + Radio, + Space, + Button, +} from 'antd'; +import type { RadioChangeEvent } from 'antd'; +import { z } from 'zod'; +import dayjs from 'dayjs'; +import { TIMEZONES } from '@shellagent/shared/constants'; +import { timerSchema } from '@shellagent/shared/protocol/pro-config'; + +type Timer = z.infer; + +enum IntervalUnit { + minute = 'minute', + hour = 'hour', + day = 'day', +} + +enum EndTimeType { + never = 'never', + endDate = 'endDate', + repeat = 'repeat', +} + +const INTERVAL_OPTIONS = [ + { value: IntervalUnit.minute, label: 'Minute' }, + { value: IntervalUnit.hour, label: 'Hour' }, + { value: IntervalUnit.day, label: 'Day' }, +]; + +const DEFAULT_START = { + year: dayjs().year(), + month: dayjs().month() + 1, + date: dayjs().date(), + hour: dayjs().hour(), + minute: dayjs().minute(), + timezone: '', +}; + +const DEFAULT_INTERVAL = { hour: 1 }; + +const getInitialEndTimeType = (end?: Timer['end']) => + end?.count + ? EndTimeType.repeat + : end?.date + ? EndTimeType.endDate + : EndTimeType.never; + +interface TimerSelectorProps { + value?: Timer; + onChange?: (value: Timer) => void; +} + +export const TimerSelector: React.FC = ({ + value, + onChange, +}) => { + const { + start = DEFAULT_START, + interval = DEFAULT_INTERVAL, + end, + } = value || {}; + + const [timeZoneModalVisible, setTimeZoneModalVisible] = useState(false); + const [intervalUnit, setIntervalUnit] = useState(IntervalUnit.hour); + const [endTimeType, setEndTimeType] = useState(getInitialEndTimeType(end)); + const [tempTimeZone, setTempTimeZone] = useState(start.timezone); + + const handleChange = useCallback( + (updates: Partial) => { + onChange?.({ start, interval, end, ...updates }); + }, + [start, interval, end, onChange], + ); + + const createDateHandler = useCallback( + (field: keyof Timer, dateField: 'start' | 'end') => { + return (date: dayjs.Dayjs | null) => { + if (!date) return; + + const dateValue = { + year: date.year(), + month: date.month() + 1, + date: date.date(), + ...(dateField === 'end' ? { hour: 23, minute: 59 } : {}), + }; + + handleChange({ + [field]: dateField === 'end' ? { date: dateValue } : dateValue, + }); + }; + }, + [handleChange], + ); + + const handleIntervalUnitChange = useCallback( + (unit: IntervalUnit) => { + setIntervalUnit(unit); + const currentValue = interval[unit as keyof typeof interval] || 1; + handleChange({ interval: { [unit]: currentValue } }); + }, + [handleChange, interval], + ); + + const handleEndTimeTypeChange = useCallback( + (e: RadioChangeEvent) => { + const newType = e.target.value as EndTimeType; + setEndTimeType(newType); + + const endValues = { + [EndTimeType.never]: undefined, + [EndTimeType.endDate]: { date: end?.date }, + [EndTimeType.repeat]: { count: end?.count || 1 }, + } as const; + + handleChange({ end: endValues[newType] }); + }, + [handleChange, end], + ); + + const handleTimeZoneConfirm = useCallback(() => { + setTimeZoneModalVisible(false); + handleChange({ start: { ...start, timezone: tempTimeZone } }); + }, [handleChange, start, tempTimeZone]); + + return ( + <> +
+
+
+
+

+ Start Time +

+ + + + + +
+ +
+

+ Repeat Frequency +

+ + Per + { + handleChange({ + interval: { [intervalUnit]: value ?? undefined }, + }); + }} + /> + setTempTimeZone(value)} + filterOption={(input, option) => + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + /> + +
+ + + ); +}; diff --git a/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx b/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx index 84b2f1e1..d1bb67e6 100644 --- a/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx @@ -146,7 +146,7 @@ const TransitionConditionEditor = ({ })); return ( -
+
{value?.map?.((condition, index) => ( > = ({ selected, data }) => { + const stateFormRef = useRef(null); + const appBuilder = useInjection('AppBuilderModel'); + const { selectedNodes, onConnect } = useReactFlowStore(state => ({ + selectedNodes: state.selectedNodes, + onConnect: state.onConnect, + })); + + const { setStateConfigSheetOpen, currentStateId, setSelectedNode } = + useAppState(state => ({ + setStateConfigSheetOpen: state.setStateConfigSheetOpen, + currentStateId: state.currentStateId, + setSelectedNode: state.setSelectedNode, + })); + + const selectedNodeRef = useRef(null); + + const [formKey, setFormKey] = useState(currentStateId); + + useEffect(() => { + // 只能选一个,长度为1 + const selectedNode = selectedNodes[0]; + setSelectedNode(selectedNode); + + appBuilder.selectedStateId = selectedNode?.id; + + if (selectedNode && selectedNode.type === NodeTypeEnum.intro) { + setStateConfigSheetOpen(selectedNode.id, true); + selectedNodeRef.current = selectedNode; + } else { + setStateConfigSheetOpen(selectedNodeRef.current?.id || '', false); + } + }, [selectedNodes]); + + const onChange = useCallback( + (values: TValues) => { + const newValues = { + ...values, + id: data.id, + name: data.display_name, + type: data.type, + }; + const newData = { id: data.id as NodeId, data: newValues }; + + appBuilder.setNodeData(newData); + emitter.emit(EventType.FORM_CHANGE, { + id: data.id as NodeId, + data: `${new Date().valueOf()}`, + type: 'StateCard', + }); + }, + [data.id], + ); + + useEventEmitter(EventType.FORM_CHANGE, eventData => { + if (eventData.id === data.id && eventData.type === 'StateConfigSheet') { + setFormKey(eventData.data); + } + }); + + useEventEmitter(EventType.RESET_FORM, eventData => { + setFormKey(eventData.data); + }); + + const handleConnect = (connection: Connection) => { + if (connection.source && connection.target) { + onConnect({ + connect: connection, + edge: { + type: EdgeTypeEnum.custom, + data: { + id: data.id, + custom: true, + type: EdgeDataTypeEnum.ALWAYS, + source: connection.source, + target: connection.target, + conditions: [], + }, + style: { + stroke: '#B6BABF', + strokeWidth: selected ? 4 : 2, + }, + }, + }); + } + }; + + const schema = getXStateSchema(data.display_name as string); + + return ( +
+ + + + + +
+ ); +}; + +export default IntroNode; diff --git a/web/apps/web/src/constants/timezones.ts b/web/apps/web/src/constants/timezones.ts new file mode 100644 index 00000000..e69de29b diff --git a/web/apps/web/src/stores/app/schema/condition-state-schema.ts b/web/apps/web/src/stores/app/schema/condition-state-schema.ts index c23289a7..15fd82d8 100644 --- a/web/apps/web/src/stores/app/schema/condition-state-schema.ts +++ b/web/apps/web/src/stores/app/schema/condition-state-schema.ts @@ -1,4 +1,8 @@ import { ISchema } from '@shellagent/form-engine'; +import { FieldModeEnum } from '@shellagent/shared/protocol/extend-config'; + +import { validateCustomKey } from '@/stores/app/utils/validator'; +import { ENABLE_MIME } from '@/utils/file-types'; export const getConditionStateSchema: ISchema = { type: 'object', @@ -6,17 +10,1013 @@ export const getConditionStateSchema: ISchema = { 'x-title-size': 'h5', 'x-class': 'space-y-3', properties: { + inputs: { + type: 'object', + title: 'Input', + additionalProperties: { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + // 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'UnfocusInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-full', + 'x-component-props': { + size: '2xs', + autoFocus: false, + maxLength: 30, + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + ], + }, + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + triggerClassName: 'h-7 w-32', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-32', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + }, + user_input: { + type: 'boolean', + default: true, + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + }, + }, + 'x-key': '{{name}} Inputs_{{counter}}', + 'x-type': 'Inline', + 'x-validator-render': 'ValidateRender', + 'x-validator': [ + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value.name); + }, + }, + ], + 'x-draggable': true, + 'x-deletable': true, + 'x-edit-dialog': { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 30, + size: 'sm', + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + type: { + type: 'string', + default: 'text', + title: 'Type', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "text"', + fullfill: { + schema: { + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices 12', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: { + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "image"', + fullfill: { + schema: { + default: [{ value: 'a', label: 'A' }], + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + properties: { + label: { + title: 'Label', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'Input', + 'x-component-props': { + maxLength: 30, + size: 'sm', + }, + }, + value: { + title: 'Value', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: { + 'image/apng': ['.apng'], + 'image/png': ['.png'], + 'image/jpeg': ['.jpeg'], + 'image/jpg': ['.jpg'], + 'image/webp': ['.webp'], + 'image/gif': ['.gif'], + 'image/bmp': ['.bmp'], + 'image/tiff': ['.tiff'], + }, + }, + }, + }, + required: ['value'], + title: 'Choice Item', + 'x-type': 'Card', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-role': 'core', + 'x-deletable': true, + }, + }, + }, + }, + ], + }, + user_input: { + type: 'boolean', + default: true, + title: 'User Input', + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-reactions': [ + { + target: 'source', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'source', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + source: { + title: 'Source', + type: 'string', + default: 'IM', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'IM', value: 'IM' }, + { label: 'form', value: 'form' }, + ], + }, + 'x-reactions': [ + { + target: 'choices', + when: '$this.value === "IM"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + default_value: { + type: 'string', + title: 'Default Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + value: { + type: 'string', + title: 'Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + description: { + type: 'string', + title: 'Description', + 'x-raw': true, + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3 pb-3', + 'x-component-props': { + maxLength: 500, + placeholder: + 'Please describe how to use this input variable. This description will be displayed when the user inputs.', + }, + 'x-validator': [ + { + exclusiveMaximum: 500, + message: 'Cannot exceed 500 characters', + }, + ], + 'x-type': 'Control', + }, + choices: { + 'x-hidden': true, + type: 'array', + 'x-title-size': 'h5', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: {}, + }, + }, + validations: { + type: 'array', + title: 'Validations', + 'x-title-size': 'h5', + additionalItems: { + type: 'object', + properties: { + void_core: { + type: 'void', + 'x-role': 'core', + 'x-type': 'Grid', + 'x-class': 'grid-cols-3', + properties: { + rule_type: { + type: 'string', + enum: [ + 'max_length', + 'min_number', + 'max_number', + 'max_file_size', + ], + 'x-component': 'Select', + 'x-component-props': { + placeholder: 'Rule type', + options: [ + { label: 'Max Length', value: 'max_length' }, + { label: 'Min Number', value: 'min_number' }, + { label: 'Max Number', value: 'max_number' }, + { label: 'Max File Size', value: 'max_file_size' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'max_length', + when: '$this.value === "max_length"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'min_number', + when: '$this.value === "min_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_number', + when: '$this.value === "max_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_file_size', + when: '$this.value === "max_file_size"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + max_length: { + type: 'number', + default: 500, + 'x-component': 'NumberInput', + 'x-type': 'Control', + // 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 15000, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 15000 }, + ], + }, + min_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_file_size: { + type: 'number', + default: 10 * 1024 * 1024, + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 100 * 1024 * 1024, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 100 * 1024 * 1024 }, + ], + }, + error_message: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + placeholder: 'Error Message', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + }, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, blocks: { title: 'Task', type: 'array', 'x-type': 'Block', 'x-title-size': 'h4', - 'x-collapsible': false, + 'x-collapsible': true, 'x-component': 'TasksConfig', 'x-component-props': { draggable: true, }, }, + outputs: { + type: 'object', + title: 'Output', + additionalProperties: { + type: 'object', + properties: { + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-hidden': true, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ref, + 'x-parent-deletable': true, + 'x-title-editable': true, + 'x-title-component-props': { + showDialog: true, + defaultKey: 'display_name', + dialogConfig: { + title: 'Edit Output', + schema: { + type: 'object', + properties: { + name_mode: { + type: 'string', + default: FieldModeEnum.Enum.ui, + title: 'Mode', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'type', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'description', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'VariableNameInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-component-props': { + maxLength: 30, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + required: true, + message: 'Please input the Variable Name', + }, + { + maxLength: 50, + message: 'Cannot exceed 50 characters', + }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + display_name: { + type: 'string', + 'x-hidden': true, + }, + type: { + type: 'string', + default: 'text', + enum: [ + 'text', + 'image', + 'audio', + 'video', + 'text_file', + 'file', + ], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-layout': 'Vertical', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + description: { + type: 'string', + title: 'Description', + default: '', + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 300, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + exclusiveMaximum: 300, + message: 'Cannot exceed 300 characters', + }, + ], + 'x-type': 'Control', + }, + }, + }, + }, + }, + 'x-component-props': { + size: '2xs', + }, + 'x-layout': 'Horizontal', + 'x-validator': [ + { required: true, message: 'The value is required' }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-hidden': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + 'x-key': '{{name}} Outputs_{{counter}}', + 'x-type': 'Inline', + 'x-collapsible': true, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, transitions: { type: 'object', title: 'Transition', diff --git a/web/apps/web/src/stores/app/schema/get-transition-schema.ts b/web/apps/web/src/stores/app/schema/get-transition-schema.ts index 25479e26..9df54947 100644 --- a/web/apps/web/src/stores/app/schema/get-transition-schema.ts +++ b/web/apps/web/src/stores/app/schema/get-transition-schema.ts @@ -1,41 +1,80 @@ import { ISchema, TValues } from '@shellagent/form-engine'; +import { NodeTypeEnum } from '@shellagent/flow-engine'; import { getSchemaByInputs } from '@/stores/app/schema/get-workflow-schema'; +import { generateUUID } from '@/utils/common-helper'; -export const getXTransitionSchema = (inputs: TValues): ISchema => { +export enum TransitionTypeEnum { + intro = NodeTypeEnum.intro, + state = NodeTypeEnum.state, + condition_state = NodeTypeEnum.condition_state, +} + +export const getXTransitionSchema = ( + inputs: TValues, + transitionType: TransitionTypeEnum, +): ISchema => { + let options: { label: string; value: string }[] = []; + if (transitionType === TransitionTypeEnum.intro) { + options = [ + { label: 'timer', value: 'timer' }, + { label: 'mentioned', value: 'X.MENTIONED' }, + { label: 'pinned.commented', value: 'X.PINNED.REPLIED' }, + ]; + } else if (transitionType === TransitionTypeEnum.state) { + options = [{ label: 'always', value: 'ALWAYS' }]; + } else if (transitionType === TransitionTypeEnum.condition_state) { + options = [{ label: 'always', value: 'ALWAYS' }]; + } return { type: 'object', 'x-type': 'Block', properties: { - type: { + transition_type: { type: 'string', 'x-type': 'Control', 'x-component': 'Select', 'x-component-props': { + disabled: transitionType !== TransitionTypeEnum.intro, + defaultValue: + transitionType !== TransitionTypeEnum.intro ? 'ALWAYS' : 'timer', placeholder: 'Select your transition type', - options: [ - { label: 'Always', value: 'ALWAYS' }, - { label: 'MENTIONED', value: 'X.MENTIONED' }, - { label: 'REPLIED', value: 'X.PINNED.REPLIED' }, - ], + options: options, }, + 'x-reactions': [ + { + target: 'timers', + when: '$this.value === "timer"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'timers', + when: '$this.value === "X.MENTIONED"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'timers', + when: '$this.value === "X.PINNED.REPLIED"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], }, - start: { - title: 'Start Time', - type: 'string', - 'x-type': 'Control', - 'x-component': 'DatePicker', - }, - interval: { - title: 'Repeat Frequency', - type: 'string', - 'x-type': 'Control', - 'x-component': 'DatePicker', - }, - end: { - title: 'End Time', - type: 'string', + timers: { + type: 'object', 'x-type': 'Control', - 'x-component': 'DatePicker', + 'x-component': 'TimerSelector', + 'x-hidden': false, }, target: { title: 'Target State', diff --git a/web/apps/web/src/stores/app/use-app-state.tsx b/web/apps/web/src/stores/app/use-app-state.tsx index a89e1504..aa1b143a 100644 --- a/web/apps/web/src/stores/app/use-app-state.tsx +++ b/web/apps/web/src/stores/app/use-app-state.tsx @@ -1,6 +1,6 @@ import { Node } from '@shellagent/flow-engine'; import { create } from 'zustand'; - +import { TransitionTypeEnum } from '@/stores/app/schema/get-transition-schema'; import { EdgeDataTypeEnum, CustomEdgeData } from '@/components/app/edges'; export type State = { @@ -9,6 +9,7 @@ export type State = { insideSheetOpen: boolean; insideSheetMode: 'button' | 'workflow' | 'widget' | ''; transitionSheetOpen: boolean; + newTransitionSheetOpen: boolean; runDrawerWidth: number; selectedNode: Node | undefined; currentTaskIndex?: number; @@ -17,6 +18,7 @@ export type State = { currentTransitionSourceHandle: string; currentEdegData: CustomEdgeData; targetInputsSheetOpen: boolean; + transitionType: TransitionTypeEnum; }; type Action = { @@ -34,6 +36,14 @@ type Action = { source: string; sourceHandle: string; data?: CustomEdgeData; + transitionType?: TransitionTypeEnum; + }) => void; + setNewTransitionSheetOpen: (params: { + open: boolean; + source: string; + sourceHandle: string; + data?: CustomEdgeData; + transitionType?: TransitionTypeEnum; }) => void; setTargetInputsSheetOpen: (open: State['targetInputsSheetOpen']) => void; setRunDrawerWidth: (width: number) => void; @@ -43,6 +53,7 @@ type Action = { const initialState: State = { stateConfigSheetOpen: false, targetInputsSheetOpen: false, + newTransitionSheetOpen: false, selectedNode: undefined, currentStateId: '', currentButtonId: '', @@ -60,6 +71,7 @@ const initialState: State = { target: '', conditions: [], }, + transitionType: TransitionTypeEnum.intro, }; export const useAppState = create(set => ({ @@ -76,6 +88,7 @@ export const useAppState = create(set => ({ insideSheetOpen: false, insideSheetMode: '', transitionSheetOpen: false, + newTransitionSheetOpen: false, currentTransitionSource: '', currentTransitionSourceHandle: '', currentEdegData: undefined, @@ -89,6 +102,7 @@ export const useAppState = create(set => ({ insideSheetOpen: false, insideSheetMode: '', transitionSheetOpen: false, + newTransitionSheetOpen: false, currentTransitionSource: '', currentTransitionSourceHandle: '', currentEdegData: undefined, @@ -118,6 +132,40 @@ export const useAppState = create(set => ({ currentTransitionSourceHandle: '', currentEdegData: undefined, targetInputsSheetOpen: false, + transitionType: TransitionTypeEnum.intro, + }; + }); + }, + setNewTransitionSheetOpen: ({ + open, + source, + sourceHandle, + data, + transitionType, + }) => { + set(() => { + if (open) { + return { + newTransitionSheetOpen: true, + currentTransitionSource: source, + currentTransitionSourceHandle: sourceHandle, + currentEdegData: data || undefined, + transitionType, + stateConfigSheetOpen: false, + currentStateId: '', + insideSheetOpen: false, + insideSheetMode: '', + currentButtonId: '', + currentTaskIndex: undefined, + }; + } + return { + newTransitionSheetOpen: false, + currentTransitionSource: '', + currentTransitionSourceHandle: '', + currentEdegData: undefined, + targetInputsSheetOpen: false, + transitionType: TransitionTypeEnum.intro, }; }); }, @@ -132,6 +180,7 @@ export const useAppState = create(set => ({ currentButtonId: mode === 'button' ? buttonId : '', currentTaskIndex, transitionSheetOpen: false, + newTransitionSheetOpen: false, currentTransitionSource: '', currentTransitionSourceHandle: '', currentEdegData: undefined, @@ -145,6 +194,7 @@ export const useAppState = create(set => ({ currentButtonId: '', currentTaskIndex: undefined, transitionSheetOpen: false, + newTransitionSheetOpen: false, currentTransitionSource: '', currentTransitionSourceHandle: '', currentEdegData: undefined, diff --git a/web/apps/web/src/types/index.d.ts b/web/apps/web/src/types/index.d.ts index 478c9aba..f6a5c479 100644 --- a/web/apps/web/src/types/index.d.ts +++ b/web/apps/web/src/types/index.d.ts @@ -1,4 +1,3 @@ declare module 'json-source-map'; declare module 'json5'; declare module 'react-toastify'; -declare module 'dayjs'; diff --git a/web/packages/shared/package.json b/web/packages/shared/package.json index 179407c8..99df79b3 100644 --- a/web/packages/shared/package.json +++ b/web/packages/shared/package.json @@ -18,7 +18,8 @@ "./protocol/pro-config": "./src/protocol/pro-config/index.ts", "./protocol/node": "./src/protocol/node/index.ts", "./utils": "./src/utils/index.ts", - "./type": "./src/type/index.ts" + "./type": "./src/type/index.ts", + "./constants": "./src/constants/index.ts" }, "devDependencies": { "@shellagent/eslint-config": "workspace:*", diff --git a/web/packages/shared/src/constants/index.ts b/web/packages/shared/src/constants/index.ts new file mode 100644 index 00000000..d0cbe8f4 --- /dev/null +++ b/web/packages/shared/src/constants/index.ts @@ -0,0 +1 @@ +export * from './timezones'; diff --git a/web/packages/shared/src/constants/timezones.ts b/web/packages/shared/src/constants/timezones.ts new file mode 100644 index 00000000..548009b5 --- /dev/null +++ b/web/packages/shared/src/constants/timezones.ts @@ -0,0 +1,190 @@ +export const TIMEZONES = [ + { + value: 'Pacific/Midway', + label: '(UTC-11:00) Midway Island, American Samoa', + }, + { value: 'America/Adak', label: '(UTC-10:00) Aleutian Islands' }, + { value: 'Pacific/Honolulu', label: '(UTC-10:00) Hawaii' }, + { value: 'Pacific/Marquesas', label: '(UTC-09:30) Marquesas Islands' }, + { value: 'America/Anchorage', label: '(UTC-09:00) Alaska' }, + { value: 'America/Tijuana', label: '(UTC-08:00) Baja California' }, + { + value: 'America/Los_Angeles', + label: '(UTC-08:00) Pacific Time (US and Canada)', + }, + { value: 'America/Phoenix', label: '(UTC-07:00) Arizona' }, + { + value: 'America/Denver', + label: '(UTC-07:00) Mountain Time (US and Canada), Navajo Nation', + }, + { value: 'America/Guatemala', label: '(UTC-06:00) Central America' }, + { + value: 'America/Chicago', + label: '(UTC-06:00) Central Time (US and Canada)', + }, + { + value: 'America/Chihuahua', + label: '(UTC-06:00) Chihuahua, La Paz, Mazatlan', + }, + { value: 'Pacific/Easter', label: '(UTC-06:00) Easter Island' }, + { + value: 'America/Mexico_City', + label: '(UTC-06:00) Guadalajara, Mexico City, Monterrey', + }, + { value: 'America/Regina', label: '(UTC-06:00) Saskatchewan' }, + { value: 'America/Bogota', label: '(UTC-05:00) Bogota, Lima, Quito' }, + { value: 'America/Cancun', label: '(UTC-05:00) Chetumal' }, + { + value: 'America/New_York', + label: '(UTC-05:00) Eastern Time (US and Canada)', + }, + { value: 'America/Port-au-Prince', label: '(UTC-05:00) Haiti' }, + { value: 'America/Havana', label: '(UTC-05:00) Havana' }, + { + value: 'America/Indiana/Indianapolis', + label: '(UTC-05:00) Indiana (East)', + }, + { value: 'America/Asuncion', label: '(UTC-04:00) Asuncion' }, + { value: 'America/Halifax', label: '(UTC-04:00) Atlantic Time (Canada)' }, + { value: 'America/Caracas', label: '(UTC-04:00) Caracas' }, + { value: 'America/Cuiaba', label: '(UTC-04:00) Cuiaba' }, + { + value: 'America/Guyana', + label: '(UTC-04:00) Georgetown, La Paz, Manaus, San Juan', + }, + { value: 'America/Santiago', label: '(UTC-04:00) Santiago' }, + { value: 'America/Grand_Turk', label: '(UTC-04:00) Turks and Caicos' }, + { value: 'America/St_Johns', label: '(UTC-03:30) Newfoundland' }, + { value: 'America/Araguaina', label: '(UTC-03:00) Araguaina' }, + { value: 'America/Sao_Paulo', label: '(UTC-03:00) Brasilia' }, + { value: 'America/Cayenne', label: '(UTC-03:00) Cayenne, Fortaleza' }, + { + value: 'America/Argentina/Buenos_Aires', + label: '(UTC-03:00) City of Buenos Aires', + }, + { value: 'America/Godthab', label: '(UTC-03:00) Greenland' }, + { value: 'America/Montevideo', label: '(UTC-03:00) Montevideo' }, + { value: 'America/Miquelon', label: '(UTC-03:00) Saint Pierre and Miquelon' }, + { value: 'America/Bahia', label: '(UTC-03:00) Salvador' }, + { value: 'America/Noronha', label: '(UTC-02:00) Fernando de Noronha' }, + { value: 'Atlantic/Azores', label: '(UTC-01:00) Azores' }, + { value: 'Atlantic/Cape_Verde', label: '(UTC-01:00) Cabo Verde Islands' }, + { value: 'Europe/London', label: '(UTC) Dublin, Edinburgh, Lisbon, London' }, + { value: 'Africa/Monrovia', label: '(UTC) Monrovia, Reykjavik' }, + { + value: 'Europe/Amsterdam', + label: '(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna', + }, + { + value: 'Europe/Belgrade', + label: '(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague', + }, + { + value: 'Europe/Brussels', + label: '(UTC+01:00) Brussels, Copenhagen, Madrid, Paris', + }, + { + value: 'Europe/Sarajevo', + label: '(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb', + }, + { value: 'Africa/West_Central', label: '(UTC+01:00) West Central Africa' }, + { value: 'Africa/Casablanca', label: '(UTC+01:00) Casablanca' }, + { value: 'Africa/Windhoek', label: '(UTC+01:00) Windhoek' }, + { value: 'Europe/Athens', label: '(UTC+02:00) Athens, Bucharest' }, + { value: 'Asia/Beirut', label: '(UTC+02:00) Beirut' }, + { value: 'Africa/Cairo', label: '(UTC+02:00) Cairo' }, + { value: 'Asia/Damascus', label: '(UTC+02:00) Damascus' }, + { value: 'Asia/Gaza', label: '(UTC+02:00) Gaza, Hebron' }, + { value: 'Africa/Harare', label: '(UTC+02:00) Harare, Pretoria' }, + { + value: 'Europe/Helsinki', + label: '(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius', + }, + { value: 'Asia/Jerusalem', label: '(UTC+02:00) Jerusalem' }, + { value: 'Europe/Kaliningrad', label: '(UTC+02:00) Kaliningrad' }, + { value: 'Africa/Tripoli', label: '(UTC+02:00) Tripoli' }, + { value: 'Asia/Amman', label: '(UTC+03:00) Amman' }, + { value: 'Asia/Baghdad', label: '(UTC+03:00) Baghdad' }, + { value: 'Europe/Istanbul', label: '(UTC+03:00) Istanbul' }, + { value: 'Asia/Kuwait', label: '(UTC+03:00) Kuwait, Riyadh' }, + { value: 'Europe/Minsk', label: '(UTC+03:00) Minsk' }, + { value: 'Europe/Moscow', label: '(UTC+03:00) Moscow, St. Petersburg' }, + { value: 'Africa/Nairobi', label: '(UTC+03:00) Nairobi' }, + { value: 'Asia/Tehran', label: '(UTC+03:30) Tehran' }, + { value: 'Asia/Muscat', label: '(UTC+04:00) Abu Dhabi, Muscat' }, + { + value: 'Europe/Astrakhan', + label: '(UTC+04:00) Astrakhan, Ulyanovsk, Volgograd', + }, + { value: 'Asia/Baku', label: '(UTC+04:00) Baku' }, + { value: 'Europe/Samara', label: '(UTC+04:00) Izhevsk, Samara' }, + { value: 'Indian/Mauritius', label: '(UTC+04:00) Port Louis' }, + { value: 'Europe/Tbilisi', label: '(UTC+04:00) Tbilisi' }, + { value: 'Asia/Yerevan', label: '(UTC+04:00) Yerevan' }, + { value: 'Asia/Kabul', label: '(UTC+04:30) Kabul' }, + { value: 'Asia/Tashkent', label: '(UTC+05:00) Tashkent, Ashgabat' }, + { value: 'Asia/Yekaterinburg', label: '(UTC+05:00) Ekaterinburg' }, + { value: 'Asia/Karachi', label: '(UTC+05:00) Islamabad, Karachi' }, + { value: 'Asia/Qyzylorda', label: '(UTC+05:00) Qyzylorda' }, + { + value: 'Asia/Kolkata', + label: '(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi', + }, + { value: 'Asia/Colombo', label: '(UTC+05:30) Sri Jayawardenepura' }, + { value: 'Asia/Kathmandu', label: '(UTC+05:45) Kathmandu' }, + { value: 'Asia/Dhaka', label: '(UTC+06:00) Dhaka' }, + { value: 'Asia/Yangon', label: '(UTC+06:30) Yangon (Rangoon)' }, + { value: 'Asia/Novosibirsk', label: '(UTC+07:00) Novosibirsk' }, + { value: 'Asia/Bangkok', label: '(UTC+07:00) Bangkok, Hanoi, Jakarta' }, + { value: 'Asia/Barnaul', label: '(UTC+07:00) Barnaul, Gorno-Altaysk' }, + { value: 'Asia/Hovd', label: '(UTC+07:00) Hovd' }, + { value: 'Asia/Krasnoyarsk', label: '(UTC+07:00) Krasnoyarsk' }, + { value: 'Asia/Tomsk', label: '(UTC+07:00) Tomsk' }, + { + value: 'Asia/Shanghai', + label: '(UTC+08:00) Beijing, Chongqing, Hong Kong SAR, Urumqi', + }, + { value: 'Asia/Irkutsk', label: '(UTC+08:00) Irkutsk' }, + { value: 'Asia/Singapore', label: '(UTC+08:00) Kuala Lumpur, Singapore' }, + { value: 'Australia/Perth', label: '(UTC+08:00) Perth' }, + { value: 'Asia/Taipei', label: '(UTC+08:00) Taipei' }, + { value: 'Asia/Ulaanbaatar', label: '(UTC+08:00) Ulaanbaatar' }, + { value: 'Asia/Pyongyang', label: '(UTC+08:30) Pyongyang' }, + { value: 'Australia/Eucla', label: '(UTC+08:45) Eucla' }, + { value: 'Asia/Chita', label: '(UTC+09:00) Chita' }, + { value: 'Asia/Tokyo', label: '(UTC+09:00) Osaka, Sapporo, Tokyo' }, + { value: 'Asia/Seoul', label: '(UTC+09:00) Seoul' }, + { value: 'Asia/Yakutsk', label: '(UTC+09:00) Yakutsk' }, + { value: 'Australia/Adelaide', label: '(UTC+09:30) Adelaide' }, + { value: 'Australia/Darwin', label: '(UTC+09:30) Darwin' }, + { value: 'Australia/Brisbane', label: '(UTC+10:00) Brisbane' }, + { + value: 'Australia/Sydney', + label: '(UTC+10:00) Canberra, Melbourne, Sydney', + }, + { value: 'Pacific/Guam', label: '(UTC+10:00) Guam, Port Moresby' }, + { value: 'Australia/Hobart', label: '(UTC+10:00) Hobart' }, + { value: 'Asia/Vladivostok', label: '(UTC+10:00) Vladivostok' }, + { value: 'Australia/Lord_Howe', label: '(UTC+10:30) Lord Howe Island' }, + { value: 'Pacific/Bougainville', label: '(UTC+11:00) Bougainville Island' }, + { value: 'Asia/Srednekolymsk', label: '(UTC+11:00) Chokurdakh' }, + { value: 'Asia/Magadan', label: '(UTC+11:00) Magadan' }, + { value: 'Pacific/Norfolk', label: '(UTC+11:00) Norfolk Island' }, + { value: 'Asia/Sakhalin', label: '(UTC+11:00) Sakhalin' }, + { + value: 'Pacific/Guadalcanal', + label: '(UTC+11:00) Solomon Islands, New Caledonia', + }, + { + value: 'Asia/Kamchatka', + label: '(UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky', + }, + { value: 'Pacific/Auckland', label: '(UTC+12:00) Auckland, Wellington' }, + { value: 'Pacific/Fiji', label: '(UTC+12:00) Fiji Islands' }, + { value: 'Pacific/Chatham', label: '(UTC+12:45) Chatham Islands' }, + { value: 'Pacific/Tongatapu', label: "(UTC+13:00) Nuku'alofa" }, + { value: 'Pacific/Apia', label: '(UTC+13:00) Samoa' }, + { value: 'Pacific/Kiritimati', label: '(UTC+14:00) Kiritimati Island' }, +]; + +export type TimezoneValue = (typeof TIMEZONES)[number]['value']; diff --git a/web/packages/shared/src/protocol/pro-config/block.ts b/web/packages/shared/src/protocol/pro-config/block.ts index 0e2145b5..2f04e2cd 100644 --- a/web/packages/shared/src/protocol/pro-config/block.ts +++ b/web/packages/shared/src/protocol/pro-config/block.ts @@ -4,6 +4,45 @@ import { variableSchema, valueSchema, inputSchema } from './variables'; import { renderConfigSchema } from './render'; import { transitionSchema, transitionCaseSchema } from './transition'; +// Timer related schemas +const timezoneSchema = z.string(); + +const dateSchema = z.object({ + minute: z.number().min(0).max(60).optional(), + hour: z.number().min(0).max(24).optional(), + date: z.number().min(1).max(31).optional(), + month: z.number().min(1).max(12).optional(), + year: z.number().optional(), + timezone: z.union([timezoneSchema, z.literal('client')]).optional(), +}); + +const intervalSchema = z.object({ + minute: z.number().optional(), + hour: z.number().optional(), + day: z.number().optional(), +}); + +const timerSchema = z.object({ + start: dateSchema.required(), + interval: intervalSchema.optional(), + end: z + .object({ + date: dateSchema.optional(), + count: z.number().optional(), + }) + .optional(), + delay: z + .union([ + intervalSchema, + z.object({ + random_mode: z.literal('even'), + min: z.number(), + max: z.number(), + }), + ]) + .optional(), +}); + // 基础类型定义 const blockTypeSchema = z.enum([ 'automata', @@ -61,6 +100,15 @@ const stateSchema = blockSchema.extend({ .object({ is_final: z.boolean().optional(), cache: z.boolean().optional(), + timers: z + .record( + customKeySchema, + z.object({ + timer: timerSchema, + event: z.string(), + }), + ) + .optional(), }) .optional(), blocks: blockChildrenSchema.optional(), @@ -156,4 +204,8 @@ export { blockChildrenSchema, containerBlockSchema, dispatcherSchema, + timerSchema, + dateSchema, + intervalSchema, + timezoneSchema, }; diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 0d477023..edd5a237 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -470,7 +470,7 @@ importers: version: 4.0.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) tailwindcss: specifier: ^3.4.1 - version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) + version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) tsconfig-paths-webpack-plugin: specifier: ^4.2.0 version: 4.2.0 @@ -747,7 +747,7 @@ importers: version: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) tailwindcss: specifier: ^3.4.10 - version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) + version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) ts-jest: specifier: ^29.0.5 version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) @@ -1066,7 +1066,7 @@ importers: version: 20.16.1 tailwindcss: specifier: ^3.4.10 - version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) + version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -1276,7 +1276,7 @@ importers: version: 4.0.2(webpack@5.93.0(@swc/core@1.7.12)) tailwindcss: specifier: ^3.4.10 - version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) + version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) ts-jest: specifier: ^29.0.5 version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) @@ -16595,7 +16595,7 @@ snapshots: lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) + tailwindcss: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) '@testing-library/dom@10.4.0': dependencies: @@ -16645,7 +16645,7 @@ snapshots: node-plop: 0.26.3 picocolors: 1.0.1 proxy-agent: 6.4.0 - ts-node: 10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3) + ts-node: 10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3) update-check: 1.5.4 validate-npm-package-name: 5.0.1 transitivePeerDependencies: @@ -21304,7 +21304,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.16.1 - ts-node: 10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3) + ts-node: 10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -23377,13 +23377,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.4.41 - postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)): + postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)): dependencies: lilconfig: 3.1.2 yaml: 2.5.0 optionalDependencies: postcss: 8.4.41 - ts-node: 10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3) + ts-node: 10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3) postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -25336,9 +25336,9 @@ snapshots: tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3))): dependencies: - tailwindcss: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) + tailwindcss: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) - tailwindcss@3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)): + tailwindcss@3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -25357,7 +25357,7 @@ snapshots: postcss: 8.4.41 postcss-import: 15.1.0(postcss@8.4.41) postcss-js: 4.0.1(postcss@8.4.41) - postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) + postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) postcss-nested: 6.2.0(postcss@8.4.41) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -25616,41 +25616,41 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.25.2) - ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3): + ts-node@10.9.2(@swc/core@1.7.12)(@types/node@16.18.105)(typescript@5.1.6): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.16.1 + '@types/node': 16.18.105 acorn: 8.12.1 acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.3.3 + typescript: 5.1.6 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: '@swc/core': 1.7.12(@swc/helpers@0.5.5) - ts-node@10.9.2(@swc/core@1.7.12)(@types/node@16.18.105)(typescript@5.1.6): + ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 16.18.105 + '@types/node': 20.16.1 acorn: 8.12.1 acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.1.6 + typescript: 5.3.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: From dc8822fc89f235fda7d36c77f690f237b41adefa Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 15 Jan 2025 01:20:41 +0800 Subject: [PATCH 26/42] [WIP] add twiter payload --- web/apps/web/src/app/container.ts | 4 + web/apps/web/src/components/app/edges/type.ts | 9 +- .../components/app/edges/x-custom-edge.tsx | 7 +- .../app/new-transition-sheet/index.tsx | 61 ++++++--- .../variable-select/use-select-options.tsx | 3 + .../stores/app/models/app-builder-utils.ts | 15 +++ .../stores/app/models/app-builder.model.ts | 20 ++- .../app/schema/get-transition-schema.ts | 93 ++++++------- web/apps/web/src/stores/app/use-app-state.tsx | 9 +- .../src/stores/app/utils/data-transformer.ts | 4 +- web/apps/web/src/types/app/types.ts | 10 +- web/packages/shared/package.json | 1 + .../app-scope/__test__/ref-util.spec.ts | 122 ++++++++++++++++++ .../shared/src/protocol/app-scope/protocol.ts | 48 +++++++ .../shared/src/protocol/app-scope/ref-util.ts | 40 +++++- .../shared/src/protocol/app-scope/scope.ts | 7 + .../src/protocol/pro-config/transition.ts | 32 +++++ .../shared/src/protocol/transition/index.ts | 40 ++++++ .../utils/__test__/get_display_name.spec.ts | 3 +- 19 files changed, 451 insertions(+), 77 deletions(-) create mode 100644 web/packages/shared/src/protocol/transition/index.ts diff --git a/web/apps/web/src/app/container.ts b/web/apps/web/src/app/container.ts index 7cabcc8f..5c1f3ab0 100644 --- a/web/apps/web/src/app/container.ts +++ b/web/apps/web/src/app/container.ts @@ -123,6 +123,10 @@ export const webModule = new ContainerModule(bind => { return model.nodeData; }; /* eslint-disable no-underscore-dangle, func-names */ + (window as any)._get_app_builder_transitions_data = function () { + return model.transitionsData; + }; + /* eslint-disable no-underscore-dangle, func-names */ (window as any)._get_app_builder_scopes = function () { return model.scopes; }; diff --git a/web/apps/web/src/components/app/edges/type.ts b/web/apps/web/src/components/app/edges/type.ts index 5cd29a5a..2aebd9aa 100644 --- a/web/apps/web/src/components/app/edges/type.ts +++ b/web/apps/web/src/components/app/edges/type.ts @@ -1,4 +1,5 @@ import { Edge } from '@shellagent/flow-engine'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; export enum EdgeTypeEnum { custom = 'custom_edge', @@ -22,7 +23,7 @@ export type CustomEdgeData = { id?: string; custom?: boolean; event_key?: string; - type?: EdgeDataTypeEnum; + type?: TransitionTargetEnum; source?: string; target?: string; conditions?: { @@ -32,4 +33,10 @@ export type CustomEdgeData = { display_name?: string; }; +export type XCustomEdgeData = { + id?: string; + type?: TransitionTargetEnum; + display_name?: string; +}; + export type ICustomEdge = Edge; diff --git a/web/apps/web/src/components/app/edges/x-custom-edge.tsx b/web/apps/web/src/components/app/edges/x-custom-edge.tsx index de2bc454..a7a15ee1 100644 --- a/web/apps/web/src/components/app/edges/x-custom-edge.tsx +++ b/web/apps/web/src/components/app/edges/x-custom-edge.tsx @@ -13,9 +13,8 @@ import React, { useEffect } from 'react'; import { useAppState } from '@/stores/app/use-app-state'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; -import { TransitionTypeEnum } from '@/stores/app/schema/get-transition-schema'; -import { CustomEdgeData } from './type'; - +import { XCustomEdgeData } from './type'; +import { TransitionTypeEnum } from '@shellagent/shared/protocol/transition'; export const XCustomEdge = ({ id, sourceX, @@ -30,7 +29,7 @@ export const XCustomEdge = ({ source, sourceHandleId, data, -}: EdgeProps) => { +}: EdgeProps) => { const appBuilder = useInjection('AppBuilderModel'); const [edgePath] = getBezierPath({ sourceX: sourceX - 8, diff --git a/web/apps/web/src/components/app/new-transition-sheet/index.tsx b/web/apps/web/src/components/app/new-transition-sheet/index.tsx index b34b4b12..57fc9f2d 100644 --- a/web/apps/web/src/components/app/new-transition-sheet/index.tsx +++ b/web/apps/web/src/components/app/new-transition-sheet/index.tsx @@ -13,6 +13,7 @@ import { observer } from 'mobx-react-lite'; import { EditableTitle } from '@/components/app/editable-title'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { useAppState } from '@/stores/app/use-app-state'; +import { type TransitionData } from '@shellagent/shared/protocol/transition'; const TransitionSheetNew: React.FC<{}> = observer(() => { const appBuilder = useInjection('AppBuilderModel'); @@ -33,15 +34,22 @@ const TransitionSheetNew: React.FC<{}> = observer(() => { transitionType: state.transitionType, })); - const onChangeEdgeData = useReactFlowStore(state => state.onChangeEdgeData); + const handleId = currentEdegData?.id as string; const handleClose = useCallback(() => { setNewTransitionSheetOpen({ open: false, source: '', sourceHandle: '' }); }, []); - const handleChange = useCallback((values: TValues) => { - console.log('values>>>>', values); - }, []); + const handleChange = useCallback( + (values: TransitionData) => { + appBuilder.setTransitionsData( + currentEdegData?.source || '', + handleId, + values, + ); + }, + [currentEdegData?.source, handleId], + ); const schema = useMemo(() => { return getXTransitionSchema( @@ -50,6 +58,32 @@ const TransitionSheetNew: React.FC<{}> = observer(() => { ); }, [appBuilder.nodeData, currentEdegData, transitionType]); + const title = useMemo(() => { + const name = + appBuilder.transitionsData[currentEdegData?.source || '']?.[handleId] + ?.name; + return ( + { + if (currentEdegData?.source) { + appBuilder.setTransitionsData(currentEdegData.source, handleId, { + ...appBuilder.transitionsData[currentEdegData.source][handleId], + name: value, + }); + } + }} + /> + ); + }, [appBuilder.transitionsData, currentEdegData?.source]); + + const values = useMemo(() => { + return ( + appBuilder.transitionsData[currentEdegData?.source || '']?.[handleId] || + {} + ); + }, [appBuilder.transitionsData, currentEdegData?.source, handleId]); + return ( = observer(() => { placement="right" mask={false} getContainer={false} - title={ - { - if (currentEdegData?.id) { - onChangeEdgeData(currentEdegData.id, { - ...currentEdegData, - display_name: value, - }); - } - }} - /> - } + title={title} open={newTransitionSheetOpen} autoFocus={false} keyboard={false} @@ -84,10 +106,11 @@ const TransitionSheetNew: React.FC<{}> = observer(() => { name="" display_name=""> handleChange(values as TransitionData)} /> diff --git a/web/apps/web/src/components/app/node-form/widgets/variable-select/use-select-options.tsx b/web/apps/web/src/components/app/node-form/widgets/variable-select/use-select-options.tsx index 79f74a6c..0396f4c4 100644 --- a/web/apps/web/src/components/app/node-form/widgets/variable-select/use-select-options.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/variable-select/use-select-options.tsx @@ -24,6 +24,9 @@ export const useSelectOptions = (name?: string) => { name?.startsWith(reservedKeySchema.Enum.condition) ) { refType = refTypeSchema.Enum.target_input; + if (parent?.startsWith(`${reservedKeySchema.Enum.condition}.twitter`)) { + refType = refTypeSchema.Enum.target_input_twitter; + } } else if (parent?.startsWith(`${reservedKeySchema.Enum.inputs}.`)) { refType = refTypeSchema.Enum.state_input; } else if (parent?.startsWith(`${reservedKeySchema.Enum.blocks}.`)) { diff --git a/web/apps/web/src/stores/app/models/app-builder-utils.ts b/web/apps/web/src/stores/app/models/app-builder-utils.ts index 9761a529..8d269eb1 100644 --- a/web/apps/web/src/stores/app/models/app-builder-utils.ts +++ b/web/apps/web/src/stores/app/models/app-builder-utils.ts @@ -170,6 +170,21 @@ export function convertRefOptsToCascaderOpts( }); } + if (!isEmpty(refOpts.local.twitter?.variables)) { + localOptions.push({ + label: 'Twitter', + value: 'Twitter', + children: Object.entries(refOpts.local.twitter.variables).map( + ([key, value]) => ({ + label: value.display_name, + value: `{{payload.${key}}}`, + field_type: value.type, + parent: 'twitter', + }), + ), + }); + } + if (!isEmpty(refOpts.local.inputs.variables)) { localOptions.push({ label: 'Input', diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 3a12a9a8..d79cf941 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -59,9 +59,14 @@ import { genAutomata, formatReactFlow2Flow, } from '@/stores/app/utils/data-transformer'; -import type { Config, Metadata, NodeDataType } from '@/types/app/types'; +import type { + Config, + Metadata, + NodeDataType, + TransitionDataType, +} from '@/types/app/types'; import { ToastModel } from '@/utils/toast.model'; - +import { type TransitionData } from '@shellagent/shared/protocol/transition'; import { CascaderOption, convertRefOptsToCascaderOpts, @@ -82,6 +87,7 @@ const settingsDisabled = process.env.NEXT_PUBLIC_DISABLE_SETTING === 'yes'; @injectable() export class AppBuilderModel { nodeData: NodeDataType = {}; + transitionsData: TransitionDataType = {}; @observable rerenderButtons: Record = {}; @observable metadata: Metadata = { name: '', @@ -172,6 +178,8 @@ export class AppBuilderModel { eventKey, ); + console.log('refOpts>>', refOpts); + const cascaderOpts = convertRefOptsToCascaderOpts(refOpts); return cascaderOpts; } @@ -187,6 +195,14 @@ export class AppBuilderModel { this.nodeData[id] = data; } + @action.bound + setTransitionsData(id: string, handleId: string, data: TransitionData) { + if (!this.transitionsData[id]) { + this.transitionsData[id] = {}; + } + this.transitionsData[id][handleId] = data; + } + @action.bound deleteNodeData(id: string) { delete this.nodeData[id]; diff --git a/web/apps/web/src/stores/app/schema/get-transition-schema.ts b/web/apps/web/src/stores/app/schema/get-transition-schema.ts index 9df54947..c8a7842e 100644 --- a/web/apps/web/src/stores/app/schema/get-transition-schema.ts +++ b/web/apps/web/src/stores/app/schema/get-transition-schema.ts @@ -1,13 +1,7 @@ import { ISchema, TValues } from '@shellagent/form-engine'; -import { NodeTypeEnum } from '@shellagent/flow-engine'; import { getSchemaByInputs } from '@/stores/app/schema/get-workflow-schema'; import { generateUUID } from '@/utils/common-helper'; - -export enum TransitionTypeEnum { - intro = NodeTypeEnum.intro, - state = NodeTypeEnum.state, - condition_state = NodeTypeEnum.condition_state, -} +import { TransitionTypeEnum } from '@shellagent/shared/protocol/transition'; export const getXTransitionSchema = ( inputs: TValues, @@ -16,59 +10,70 @@ export const getXTransitionSchema = ( let options: { label: string; value: string }[] = []; if (transitionType === TransitionTypeEnum.intro) { options = [ - { label: 'timer', value: 'timer' }, + { label: 'timer', value: 'TIMER' }, { label: 'mentioned', value: 'X.MENTIONED' }, { label: 'pinned.commented', value: 'X.PINNED.REPLIED' }, ]; - } else if (transitionType === TransitionTypeEnum.state) { - options = [{ label: 'always', value: 'ALWAYS' }]; - } else if (transitionType === TransitionTypeEnum.condition_state) { + } else { options = [{ label: 'always', value: 'ALWAYS' }]; } return { type: 'object', 'x-type': 'Block', properties: { - transition_type: { + name: { + type: 'string', + default: 'transition', + 'x-hidden': true, + }, + id: { + type: 'string', + default: generateUUID(), + 'x-hidden': true, + }, + type: { type: 'string', 'x-type': 'Control', 'x-component': 'Select', + enum: ['TIMER', 'X.MENTIONED', 'X.PINNED.REPLIED'], + default: + transitionType !== TransitionTypeEnum.intro ? 'ALWAYS' : 'TIMER', 'x-component-props': { disabled: transitionType !== TransitionTypeEnum.intro, - defaultValue: - transitionType !== TransitionTypeEnum.intro ? 'ALWAYS' : 'timer', placeholder: 'Select your transition type', - options: options, + options, }, - 'x-reactions': [ - { - target: 'timers', - when: '$this.value === "timer"', - fullfill: { - schema: { - 'x-hidden': false, - }, - }, - }, - { - target: 'timers', - when: '$this.value === "X.MENTIONED"', - fullfill: { - schema: { - 'x-hidden': true, - }, - }, - }, - { - target: 'timers', - when: '$this.value === "X.PINNED.REPLIED"', - fullfill: { - schema: { - 'x-hidden': true, - }, - }, - }, - ], + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + // 'x-reactions': [ + // { + // target: 'timers', + // when: '$this.value === "timer"', + // fullfill: { + // schema: { + // 'x-hidden': false, + // }, + // }, + // }, + // { + // target: 'timers', + // when: '$this.value === "X.MENTIONED"', + // fullfill: { + // schema: { + // 'x-hidden': true, + // }, + // }, + // }, + // { + // target: 'timers', + // when: '$this.value === "X.PINNED.REPLIED"', + // fullfill: { + // schema: { + // 'x-hidden': true, + // }, + // }, + // }, + // ], }, timers: { type: 'object', diff --git a/web/apps/web/src/stores/app/use-app-state.tsx b/web/apps/web/src/stores/app/use-app-state.tsx index aa1b143a..1134f9c7 100644 --- a/web/apps/web/src/stores/app/use-app-state.tsx +++ b/web/apps/web/src/stores/app/use-app-state.tsx @@ -1,7 +1,10 @@ import { Node } from '@shellagent/flow-engine'; import { create } from 'zustand'; -import { TransitionTypeEnum } from '@/stores/app/schema/get-transition-schema'; -import { EdgeDataTypeEnum, CustomEdgeData } from '@/components/app/edges'; +import { CustomEdgeData } from '@/components/app/edges'; +import { + TransitionTypeEnum, + TransitionTargetEnum, +} from '@shellagent/shared/protocol/transition'; export type State = { currentStateId: string; @@ -67,7 +70,7 @@ const initialState: State = { id: '', custom: true, event_key: '', - type: EdgeDataTypeEnum.ALWAYS, + type: TransitionTargetEnum.ALWAYS, target: '', conditions: [], }, diff --git a/web/apps/web/src/stores/app/utils/data-transformer.ts b/web/apps/web/src/stores/app/utils/data-transformer.ts index 3275fe20..a8ab4665 100644 --- a/web/apps/web/src/stores/app/utils/data-transformer.ts +++ b/web/apps/web/src/stores/app/utils/data-transformer.ts @@ -8,6 +8,8 @@ import { ICustomEdge, CustomEdgeData, } from '@/components/app/edges'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; + import { NodeDataType } from '@/types/app/types'; import { transformChoicesToValues, @@ -126,7 +128,7 @@ export const genAutomata: ( targetEdges.forEach(edge => { const data = edge.data as CustomEdgeData; const transitionKey = - data.type && data.type !== EdgeDataTypeEnum.STATE + data.type && data.type !== TransitionTargetEnum.STATE ? data.type : (data.event_key as string); diff --git a/web/apps/web/src/types/app/types.ts b/web/apps/web/src/types/app/types.ts index fcdd6592..ac5c27c9 100644 --- a/web/apps/web/src/types/app/types.ts +++ b/web/apps/web/src/types/app/types.ts @@ -3,8 +3,11 @@ import { TValues, TFieldMode } from '@shellagent/form-engine'; import { Automata } from '@shellagent/pro-config'; import { Refs } from '@shellagent/shared/protocol/app-scope'; import { Issue } from '@shellagent/shared/type'; - import { Metadata } from '@/services/home/type'; +import { + TransitionData, + TransitionTargetType, +} from '@shellagent/shared/protocol/transition'; export type Config = { // 弃用 @@ -23,3 +26,8 @@ export type ShellAgent = { export type { Metadata }; export type NodeDataType = Record; + +export type TransitionDataType = Record< + string, + Record +>; diff --git a/web/packages/shared/package.json b/web/packages/shared/package.json index 99df79b3..4216fda9 100644 --- a/web/packages/shared/package.json +++ b/web/packages/shared/package.json @@ -17,6 +17,7 @@ "./protocol/extend-config": "./src/protocol/extend-config/index.ts", "./protocol/pro-config": "./src/protocol/pro-config/index.ts", "./protocol/node": "./src/protocol/node/index.ts", + "./protocol/transition": "./src/protocol/transition/index.ts", "./utils": "./src/utils/index.ts", "./type": "./src/type/index.ts", "./constants": "./src/constants/index.ts" diff --git a/web/packages/shared/src/protocol/app-scope/__test__/ref-util.spec.ts b/web/packages/shared/src/protocol/app-scope/__test__/ref-util.spec.ts index 448fc84b..214b77a1 100644 --- a/web/packages/shared/src/protocol/app-scope/__test__/ref-util.spec.ts +++ b/web/packages/shared/src/protocol/app-scope/__test__/ref-util.spec.ts @@ -21,6 +21,7 @@ import { renameKeyPrefix, } from '../ref-util'; import { Edge, Edges, edgeSchema, edgesSchema, refsSchema } from '../scope'; +import { TwitterTransitionTargetEnum } from '../../transition'; describe('ref util', () => { describe('find descendants', () => { @@ -2181,3 +2182,124 @@ describe('task re order', () => { `); }); }); + +describe('target_input_twitter', () => { + it('should return twitter payload for target_input_twitter', () => { + const options = getRefOptions( + { + scopes: { + context: { variables: {} }, + edges: [], + states: { + state_1: { + name: 'state_1', + display_name: 'State 1', + children: { + inputs: { variables: {} }, + tasks: [], + outputs: { + variables: {}, + render: { buttons: {} }, + }, + }, + }, + }, + }, + }, + 'state_1', + 'target_input_twitter', + undefined, + undefined, + ); + + expect(options.local.twitter).toMatchInlineSnapshot(` + { + "variables": { + "creation_date": { + "display_name": "creation_date", + "type": "string", + }, + "images": { + "display_name": "images", + "type": "array", + }, + "text": { + "display_name": "text", + "type": "string", + }, + "tweet_id": { + "display_name": "tweet_id", + "type": "string", + }, + "user.creation_date": { + "display_name": "user.creation_date", + "type": "string", + }, + "user.is_blue_verified": { + "display_name": "user.is_blue_verified", + "type": "boolean", + }, + "user.is_bot": { + "display_name": "user.is_bot", + "type": "boolean", + }, + "user.is_private": { + "display_name": "user.is_private", + "type": "boolean", + }, + "user.is_verified": { + "display_name": "user.is_verified", + "type": "boolean", + }, + "user.name": { + "display_name": "user.name", + "type": "string", + }, + "user.user_id": { + "display_name": "user.user_id", + "type": "string", + }, + "user.username": { + "display_name": "user.username", + "type": "string", + }, + "user.verified_type": { + "display_name": "user.verified_type", + "type": "string", + }, + }, + } + `); + }); + + it('should return empty twitter payload for non-twitter refType', () => { + const options = getRefOptions( + { + scopes: { + context: { variables: {} }, + edges: [], + states: { + state_1: { + name: 'state_1', + display_name: 'State 1', + children: { + inputs: { variables: {} }, + tasks: [], + outputs: { + variables: {}, + render: { buttons: {} }, + }, + }, + }, + }, + }, + }, + 'state_1', + 'state_input', + undefined, + undefined, + ); + + expect(options.local.twitter).toBeUndefined(); + }); +}); diff --git a/web/packages/shared/src/protocol/app-scope/protocol.ts b/web/packages/shared/src/protocol/app-scope/protocol.ts index 1cfe5b84..f1e8069f 100644 --- a/web/packages/shared/src/protocol/app-scope/protocol.ts +++ b/web/packages/shared/src/protocol/app-scope/protocol.ts @@ -177,3 +177,51 @@ export const scopesSchema = z.object({ }); export type Scopes = z.infer; + +export const tweetDetailSchema = z + .object({ + text: z.object({ type: z.literal('string'), display_name: z.string() }), + images: z.object({ type: z.literal('array'), display_name: z.string() }), + tweet_id: z.object({ type: z.literal('string'), display_name: z.string() }), + creation_date: z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + 'user.creation_date': z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + 'user.user_id': z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + 'user.username': z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + 'user.name': z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + 'user.is_private': z.object({ + type: z.literal('boolean'), + display_name: z.string(), + }), + 'user.is_verified': z.object({ + type: z.literal('boolean'), + display_name: z.string(), + }), + 'user.is_blue_verified': z.object({ + type: z.literal('boolean'), + display_name: z.string(), + }), + 'user.is_bot': z.object({ + type: z.literal('boolean'), + display_name: z.string(), + }), + 'user.verified_type': z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + }) + .strict(); diff --git a/web/packages/shared/src/protocol/app-scope/ref-util.ts b/web/packages/shared/src/protocol/app-scope/ref-util.ts index 4557080e..bd820c9c 100644 --- a/web/packages/shared/src/protocol/app-scope/ref-util.ts +++ b/web/packages/shared/src/protocol/app-scope/ref-util.ts @@ -22,6 +22,7 @@ import { setNodedataKeyValParamSchema, } from './scope'; import { reservedStateNameSchema } from '../node'; +import { TwitterTransitionTargetEnum } from '../transition'; import { cloneDeep, find, @@ -87,6 +88,10 @@ export function getRefOptions( assignStateRender(); assignButtonsPayload(); break; + case 'target_input_twitter': + assignStateRender(); + assignTwitterPayload(); + break; case 'state_output_key': // noops default: { @@ -95,7 +100,8 @@ export function getRefOptions( } try { - return refOptionsOutputSchema.parse(ret); + const result = refOptionsOutputSchema.parse(ret); + return result; } catch (error) { console.warn('RefOptionsOutputSchema Zod Validate Error', error); return ret; @@ -184,6 +190,38 @@ export function getRefOptions( const tasks = state.children.tasks; ret.local.tasks = tasks; } + + function assignTwitterPayload() { + ret.local.twitter = { + variables: { + text: { type: 'string', display_name: 'text' }, + images: { type: 'array', display_name: 'images' }, + tweet_id: { type: 'string', display_name: 'tweet_id' }, + creation_date: { type: 'string', display_name: 'creation_date' }, + 'user.creation_date': { + type: 'string', + display_name: 'user.creation_date', + }, + 'user.user_id': { type: 'string', display_name: 'user.user_id' }, + 'user.username': { type: 'string', display_name: 'user.username' }, + 'user.name': { type: 'string', display_name: 'user.name' }, + 'user.is_private': { type: 'boolean', display_name: 'user.is_private' }, + 'user.is_verified': { + type: 'boolean', + display_name: 'user.is_verified', + }, + 'user.is_blue_verified': { + type: 'boolean', + display_name: 'user.is_blue_verified', + }, + 'user.is_bot': { type: 'boolean', display_name: 'user.is_bot' }, + 'user.verified_type': { + type: 'string', + display_name: 'user.verified_type', + }, + }, + }; + } } export function findDescendants( diff --git a/web/packages/shared/src/protocol/app-scope/scope.ts b/web/packages/shared/src/protocol/app-scope/scope.ts index 3b653636..fc6d03da 100644 --- a/web/packages/shared/src/protocol/app-scope/scope.ts +++ b/web/packages/shared/src/protocol/app-scope/scope.ts @@ -5,6 +5,7 @@ import { outputVariablesSchema, taskSchema, variablesSchema, + tweetDetailSchema, } from './protocol'; import { reservedStateNameSchema } from '../node'; @@ -25,6 +26,7 @@ export const refTypeSchema = z.enum([ 'state_render', 'target_input', 'state_output_key', + 'target_input_twitter', ]); export type RefType = z.infer; @@ -48,6 +50,11 @@ export const refOptionsOutputSchema = z.object({ variables: outputVariablesSchema, }), buttons: buttonsSchema, + twitter: z + .object({ + variables: tweetDetailSchema, + }) + .optional(), }), }); diff --git a/web/packages/shared/src/protocol/pro-config/transition.ts b/web/packages/shared/src/protocol/pro-config/transition.ts index 1b1f2138..f02c6648 100644 --- a/web/packages/shared/src/protocol/pro-config/transition.ts +++ b/web/packages/shared/src/protocol/pro-config/transition.ts @@ -33,3 +33,35 @@ export const transitionSchema = z.union([ transitionCaseSchema, z.array(transitionCaseSchema), ]); + +// Twitter related schemas +export const twitterUserSchema = z.object({ + creation_date: z.string(), + user_id: z.string(), + username: z.string(), + name: z.string(), + is_private: z.boolean(), + is_verified: z.boolean(), + is_blue_verified: z.boolean(), + is_bot: z.boolean(), + verified_type: z.enum(['blue', 'business', 'government']), +}); + +export const tweetDetailSchema = z.object({ + text: z.string(), + images: z.array(z.string().url()), + user: twitterUserSchema, + tweet_id: z.string(), + creation_date: z.string(), +}); + +// Payload map schema +export const payloadMapSchema = z.object({ + 'X.MENTIONED': tweetDetailSchema, + 'X.PINNED.REPLIED': tweetDetailSchema, +}); + +// Export types +export type TwitterUser = z.infer; +export type TweetDetail = z.infer; +export type PayloadMap = z.infer; diff --git a/web/packages/shared/src/protocol/transition/index.ts b/web/packages/shared/src/protocol/transition/index.ts new file mode 100644 index 00000000..b4244fec --- /dev/null +++ b/web/packages/shared/src/protocol/transition/index.ts @@ -0,0 +1,40 @@ +import { z } from 'zod'; +import { timerSchema } from '../pro-config'; +import { NodeTypeEnum } from '../node'; + +export type TransitionData = { + name: string; + type: TransitionTargetEnum; + timers: z.infer; + target: string; + target_inputs: { + [key: string]: any; + }; + id: string; +}; + +export enum TransitionTypeEnum { + intro = NodeTypeEnum.intro, + state = NodeTypeEnum.state, + condition_state = NodeTypeEnum.condition_state, +} + +export enum TransitionTargetEnum { + 'X.MENTIONED' = 'X.MENTIONED', + 'X.PINNED.REPLIED' = 'X.PINNED.REPLIED', + ALWAYS = 'ALWAYS', + CHAT = 'CHAT', + TIMER = 'TIMER', + STATE = 'STATE', +} + +export enum TwitterTransitionTargetEnum { + 'X.MENTIONED' = 'X.MENTIONED', + 'X.PINNED.REPLIED' = 'X.PINNED.REPLIED', +} + +export type TransitionTargetType = + | 'X.MENTIONED' + | 'X.PINNED.REPLIED' + | 'ALWAYS' + | 'CHAT'; diff --git a/web/packages/shared/src/utils/__test__/get_display_name.spec.ts b/web/packages/shared/src/utils/__test__/get_display_name.spec.ts index 4e00ba37..cb94b416 100644 --- a/web/packages/shared/src/utils/__test__/get_display_name.spec.ts +++ b/web/packages/shared/src/utils/__test__/get_display_name.spec.ts @@ -1,5 +1,6 @@ import { getTaskDisplayName, getButtonDisplayName } from '../get_display_name'; -import { NodeTypeEnum } from '../../type/app-builder/flow'; +import { NodeTypeEnum } from '../../protocol/node'; + import { Task } from '../../protocol/task'; import { Button } from '../../protocol/render-button'; From f3d0b8bbb5816fdf1076e26befc60aee893157b8 Mon Sep 17 00:00:00 2001 From: shanexi Date: Wed, 15 Jan 2025 10:55:55 +0800 Subject: [PATCH 27/42] feat: merge by title --- .../stores/app/models/app-builder.model.ts | 67 ++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 50630481..58b5255f 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -1,11 +1,11 @@ import { - type MaterialListType, type IFlow, + type MaterialListType, type ReactFlowInstance, NodeTypeEnum, WidgetItem, } from '@shellagent/flow-engine'; -import { CustomKey, CustomEventName } from '@shellagent/pro-config'; +import { CustomEventName, CustomKey } from '@shellagent/pro-config'; import { customSnakeCase, getRefOptions, @@ -19,7 +19,7 @@ import { import { Issue, IssueTypeEnum } from '@shellagent/shared/type'; import type { FieldValues } from '@shellagent/ui'; import { message } from 'antd'; -import { injectable, inject } from 'inversify'; +import { inject, injectable } from 'inversify'; import { isEmpty } from 'lodash-es'; import { action, @@ -38,36 +38,36 @@ import { import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; import { SettingsModel } from '@/components/settings/settings.model'; import { + type type_get_local_widget_list_res, + type type_get_myshell_widget_list, + checkIp, + exportApp, fetchAppVersionList, fetchAutomata, fetchFlow, - releaseApp, - exportApp, - checkIp, - saveApp, getIntroDisplay, getLocalWidgetList, - type type_get_local_widget_list_res, - type type_get_myshell_widget_list, getMyShellWidgetList, + releaseApp, + saveApp, } from '@/services/app'; import type { GetAppFlowRequest, GetAppVersionListResponse, GetAutomataRequest, } from '@/services/app/type'; -import { fetchList as fetchFlowList, editItem } from '@/services/home'; +import { editItem, fetchList as fetchFlowList } from '@/services/home'; import type { GetListRequest, GetListResponse } from '@/services/home/type'; import emitter, { EventType } from '@/stores/app/models/emitter'; import { - onDownload, checkDependency, LackDependency, + onDownload, } from '@/stores/app/utils/automata-export'; import { - genNodeData, - genAutomata, formatReactFlow2Flow, + genAutomata, + genNodeData, } from '@/stores/app/utils/data-transformer'; import type { Config, Metadata, NodeDataType } from '@/types/app/types'; import { ToastModel } from '@/utils/toast.model'; @@ -86,7 +86,6 @@ import { handleRenameRefOpt, handleReorderTask, } from './node-data-utils'; -import { z } from 'zod'; const settingsDisabled = process.env.NEXT_PUBLIC_DISABLE_SETTING === 'yes'; @@ -195,7 +194,45 @@ export class AppBuilderModel { plain: true, }; }); - return LocalWigets.concat(backendLocalWidgets).concat(myshellWidgets); + + const mergedWidgets = [...LocalWigets]; + + // 合并 backendLocalWidgets + backendLocalWidgets.forEach(backendWidget => { + const existingIndex = mergedWidgets.findIndex( + w => w.title === backendWidget.title, + ); + if (existingIndex !== -1) { + mergedWidgets[existingIndex] = { + ...mergedWidgets[existingIndex], + items: [ + ...mergedWidgets[existingIndex].items, + ...backendWidget.items, + ], + }; + } else { + mergedWidgets.push(backendWidget); + } + }); + + // 合并 myshellWidgets + myshellWidgets.forEach(myshellWidget => { + const existingIndex = mergedWidgets.findIndex( + w => w.title === myshellWidget.title, + ); + if (existingIndex !== -1) { + mergedWidgets[existingIndex] = { + ...mergedWidgets[existingIndex], + items: [ + ...mergedWidgets[existingIndex].items, + ...myshellWidget.items, + ], + }; + } else { + mergedWidgets.push(myshellWidget); + } + }); + return mergedWidgets; } copyNodeData: FieldValues = {}; From a450574e046a495b3312cc06fc3e3017d28b4f8e Mon Sep 17 00:00:00 2001 From: shanexi Date: Wed, 15 Jan 2025 11:06:33 +0800 Subject: [PATCH 28/42] fix: widget schema stale data --- .../app/config-form/widget-config/index.tsx | 17 ++++++++++++--- .../stores/app/models/app-builder.model.ts | 21 +------------------ .../src/stores/workflow/workflow-store.tsx | 4 +++- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/web/apps/web/src/components/app/config-form/widget-config/index.tsx b/web/apps/web/src/components/app/config-form/widget-config/index.tsx index 43ffc3d5..7981e083 100644 --- a/web/apps/web/src/components/app/config-form/widget-config/index.tsx +++ b/web/apps/web/src/components/app/config-form/widget-config/index.tsx @@ -47,17 +47,28 @@ const StandardWidgetConfig: React.FC = ({ ); useEffect(() => { - if (values?.widget_class_name && !widgetSchema[values.widget_class_name]) { + if ( + values?.widget_class_name && + values?.widget_name && + !widgetSchema[[values.widget_class_name, values.widget_name].join('_')] + ) { getWidgetSchema({ widget_name: values.widget_class_name, myshell_widget_name: values.widget_name, }); } - }, [values?.widget_class_name, getWidgetSchema, widgetSchema]); + }, [ + values?.widget_class_name, + values?.widget_name, + getWidgetSchema, + widgetSchema, + ]); const schema = useMemo(() => { const schema = getSchemaByWidget({ - ...widgetSchema[values?.widget_class_name], + ...widgetSchema[ + [values?.widget_class_name, values?.widget_name].join('_') + ], }); // Special process ImageCanvasWidget if (values?.widget_class_name === 'ImageCanvasWidget') { diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 58b5255f..b3c5ec68 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -195,27 +195,8 @@ export class AppBuilderModel { }; }); - const mergedWidgets = [...LocalWigets]; + const mergedWidgets = [...LocalWigets, ...backendLocalWidgets]; - // 合并 backendLocalWidgets - backendLocalWidgets.forEach(backendWidget => { - const existingIndex = mergedWidgets.findIndex( - w => w.title === backendWidget.title, - ); - if (existingIndex !== -1) { - mergedWidgets[existingIndex] = { - ...mergedWidgets[existingIndex], - items: [ - ...mergedWidgets[existingIndex].items, - ...backendWidget.items, - ], - }; - } else { - mergedWidgets.push(backendWidget); - } - }); - - // 合并 myshellWidgets myshellWidgets.forEach(myshellWidget => { const existingIndex = mergedWidgets.findIndex( w => w.title === myshellWidget.title, diff --git a/web/apps/web/src/stores/workflow/workflow-store.tsx b/web/apps/web/src/stores/workflow/workflow-store.tsx index 37fe5d02..71245a15 100644 --- a/web/apps/web/src/stores/workflow/workflow-store.tsx +++ b/web/apps/web/src/stores/workflow/workflow-store.tsx @@ -207,7 +207,9 @@ export const createWorkflowStore = () => { set( produce(state => { - state.widgetSchema[params.widget_name] = response; + state.widgetSchema[ + [params.widget_name, params.myshell_widget_name].join('_') + ] = response; state.loading.getWidgetSchema[params.widget_name] = false; }), ); From f7bc95b64287c121d928321d2bd5ed6eb55f3811 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 15 Jan 2025 13:29:06 +0800 Subject: [PATCH 29/42] [WIP] transition form --- web/apps/web/src/app/container.ts | 4 -- web/apps/web/src/components/app/edges/type.ts | 6 -- .../components/app/edges/x-custom-edge.tsx | 12 ++-- .../app/new-transition-sheet/index.tsx | 70 ++++++++++--------- .../app/new-transition-sheet/utils/index.ts | 0 .../app/nodes/condition-state-node/index.tsx | 10 +-- .../app/nodes/x-intro-node/index.tsx | 10 +-- .../app/nodes/x-state-node/index.tsx | 12 ++-- .../stores/app/models/app-builder.model.ts | 19 +---- .../app/schema/get-transition-schema.ts | 7 +- web/apps/web/src/stores/app/use-app-state.tsx | 11 ++- web/apps/web/src/types/app/types.ts | 9 --- .../shared/src/protocol/transition/index.ts | 12 +++- 13 files changed, 87 insertions(+), 95 deletions(-) create mode 100644 web/apps/web/src/components/app/new-transition-sheet/utils/index.ts diff --git a/web/apps/web/src/app/container.ts b/web/apps/web/src/app/container.ts index 5c1f3ab0..7cabcc8f 100644 --- a/web/apps/web/src/app/container.ts +++ b/web/apps/web/src/app/container.ts @@ -123,10 +123,6 @@ export const webModule = new ContainerModule(bind => { return model.nodeData; }; /* eslint-disable no-underscore-dangle, func-names */ - (window as any)._get_app_builder_transitions_data = function () { - return model.transitionsData; - }; - /* eslint-disable no-underscore-dangle, func-names */ (window as any)._get_app_builder_scopes = function () { return model.scopes; }; diff --git a/web/apps/web/src/components/app/edges/type.ts b/web/apps/web/src/components/app/edges/type.ts index 2aebd9aa..8a114f2d 100644 --- a/web/apps/web/src/components/app/edges/type.ts +++ b/web/apps/web/src/components/app/edges/type.ts @@ -33,10 +33,4 @@ export type CustomEdgeData = { display_name?: string; }; -export type XCustomEdgeData = { - id?: string; - type?: TransitionTargetEnum; - display_name?: string; -}; - export type ICustomEdge = Edge; diff --git a/web/apps/web/src/components/app/edges/x-custom-edge.tsx b/web/apps/web/src/components/app/edges/x-custom-edge.tsx index a7a15ee1..5e8fc500 100644 --- a/web/apps/web/src/components/app/edges/x-custom-edge.tsx +++ b/web/apps/web/src/components/app/edges/x-custom-edge.tsx @@ -13,8 +13,11 @@ import React, { useEffect } from 'react'; import { useAppState } from '@/stores/app/use-app-state'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; -import { XCustomEdgeData } from './type'; -import { TransitionTypeEnum } from '@shellagent/shared/protocol/transition'; + +import { + TransitionTypeEnum, + TransitionData, +} from '@shellagent/shared/protocol/transition'; export const XCustomEdge = ({ id, sourceX, @@ -29,7 +32,7 @@ export const XCustomEdge = ({ source, sourceHandleId, data, -}: EdgeProps) => { +}: EdgeProps) => { const appBuilder = useInjection('AppBuilderModel'); const [edgePath] = getBezierPath({ sourceX: sourceX - 8, @@ -76,6 +79,7 @@ export const XCustomEdge = ({ open: true, source, sourceHandle: sourceHandleId || '', + id, data: { ...(data || {}), id, @@ -103,7 +107,7 @@ export const XCustomEdge = ({ = observer(() => { const appBuilder = useInjection('AppBuilderModel'); + const { setEdges, onChangeEdgeData, edges } = useReactFlowStore(state => ({ + setEdges: state.setEdges, + onChangeEdgeData: state.onChangeEdgeData, + edges: state.edges, + })); + const { sourceHandle, source, - currentEdegData, + currentEdgeId, newTransitionSheetOpen, setNewTransitionSheetOpen, transitionType, @@ -30,59 +36,57 @@ const TransitionSheetNew: React.FC<{}> = observer(() => { source: state.currentTransitionSource, newTransitionSheetOpen: state.newTransitionSheetOpen, setNewTransitionSheetOpen: state.setNewTransitionSheetOpen, - currentEdegData: state.currentEdegData, + currentEdgeId: state.currentEdgeId, transitionType: state.transitionType, })); - const handleId = currentEdegData?.id as string; - const handleClose = useCallback(() => { setNewTransitionSheetOpen({ open: false, source: '', sourceHandle: '' }); }, []); + const edgeData = edges.find(edge => edge.id === currentEdgeId) + ?.data as TransitionData; + const handleChange = useCallback( (values: TransitionData) => { - appBuilder.setTransitionsData( - currentEdegData?.source || '', - handleId, - values, - ); + if (edgeData?.target !== values.target) { + const currentEdge = edges.find(edge => edge.id === currentEdgeId); + if (currentEdge) { + setEdges( + edges.map(edge => { + if (edge.id === currentEdge.id) { + return { + ...edge, + target: values.target, + data: values, + }; + } + return edge; + }), + ); + } + } else { + onChangeEdgeData(currentEdgeId, values); + } }, - [currentEdegData?.source, handleId], + [edgeData, edges, setEdges, onChangeEdgeData, currentEdgeId], ); const schema = useMemo(() => { return getXTransitionSchema( - appBuilder.nodeData[currentEdegData?.target || '']?.inputs ?? {}, + appBuilder.nodeData[edgeData?.target || '']?.inputs ?? {}, transitionType, ); - }, [appBuilder.nodeData, currentEdegData, transitionType]); + }, [appBuilder.nodeData, edgeData, transitionType]); const title = useMemo(() => { - const name = - appBuilder.transitionsData[currentEdegData?.source || '']?.[handleId] - ?.name; return ( { - if (currentEdegData?.source) { - appBuilder.setTransitionsData(currentEdegData.source, handleId, { - ...appBuilder.transitionsData[currentEdegData.source][handleId], - name: value, - }); - } - }} + value={edgeData?.name} + onChange={value => handleChange({ ...edgeData, name: value })} /> ); - }, [appBuilder.transitionsData, currentEdegData?.source]); - - const values = useMemo(() => { - return ( - appBuilder.transitionsData[currentEdegData?.source || '']?.[handleId] || - {} - ); - }, [appBuilder.transitionsData, currentEdegData?.source, handleId]); + }, [edgeData]); return ( = observer(() => { parent="condition.twitter" key={sourceHandle} schema={schema} - values={values} + values={edgeData} onChange={values => handleChange(values as TransitionData)} /> diff --git a/web/apps/web/src/components/app/new-transition-sheet/utils/index.ts b/web/apps/web/src/components/app/new-transition-sheet/utils/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx b/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx index 1c65d639..97adacfe 100644 --- a/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx +++ b/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx @@ -21,7 +21,8 @@ import { useKeyPress } from 'ahooks'; import { useInjection } from 'inversify-react'; import React, { useCallback, useRef, useEffect, useState } from 'react'; -import { EdgeDataTypeEnum, EdgeTypeEnum } from '@/components/app/edges'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; +import { EdgeTypeEnum } from '@/components/app/edges'; import NodeCard from '@/components/app/node-card'; import NodeForm from '@/components/app/node-form'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; @@ -180,11 +181,12 @@ const ConditionStateNode: React.FC> = ({ type: EdgeTypeEnum.custom, data: { id: data.id, - custom: true, - type: EdgeDataTypeEnum.ALWAYS, + name: 'transition', + timers: {}, + type: TransitionTargetEnum.ALWAYS, source: connection.source, target: connection.target, - conditions: [], + target_inputs: {}, }, style: { stroke: '#B6BABF', diff --git a/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx b/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx index b90cfd8c..cbc0d9bf 100644 --- a/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx +++ b/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx @@ -14,7 +14,7 @@ import { FormRef } from '@shellagent/ui'; import { useInjection } from 'inversify-react'; import React, { useCallback, useRef, useEffect, useState } from 'react'; -import { EdgeDataTypeEnum, EdgeTypeEnum } from '@/components/app/edges'; +import { EdgeTypeEnum } from '@/components/app/edges'; import NodeCard from '@/components/app/node-card'; import NodeForm from '@/components/app/node-form'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; @@ -24,6 +24,7 @@ import emitter, { } from '@/stores/app/models/emitter'; import { useAppState } from '@/stores/app/use-app-state'; import { getXStateSchema } from '@/stores/app/schema/get-x-state-schema'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; const IntroNode: React.FC> = ({ selected, data }) => { const stateFormRef = useRef(null); @@ -97,11 +98,12 @@ const IntroNode: React.FC> = ({ selected, data }) => { type: EdgeTypeEnum.custom, data: { id: data.id, - custom: true, - type: EdgeDataTypeEnum.ALWAYS, + name: 'transition', + timers: {}, + type: TransitionTargetEnum.TIMER, source: connection.source, target: connection.target, - conditions: [], + target_inputs: {}, }, style: { stroke: '#B6BABF', diff --git a/web/apps/web/src/components/app/nodes/x-state-node/index.tsx b/web/apps/web/src/components/app/nodes/x-state-node/index.tsx index 27d3a028..d1c274c7 100644 --- a/web/apps/web/src/components/app/nodes/x-state-node/index.tsx +++ b/web/apps/web/src/components/app/nodes/x-state-node/index.tsx @@ -19,9 +19,10 @@ import { customSnakeCase, getTaskDisplayName } from '@shellagent/shared/utils'; import { FormRef } from '@shellagent/ui'; import { useKeyPress } from 'ahooks'; import { useInjection } from 'inversify-react'; -import React, { useCallback, useRef, useEffect, useState } from 'react'; -import { EdgeDataTypeEnum, EdgeTypeEnum } from '@/components/app/edges'; +import React, { useCallback, useRef, useEffect, useState } from 'react'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; +import { EdgeTypeEnum } from '@/components/app/edges'; import NodeCard from '@/components/app/node-card'; import NodeForm from '@/components/app/node-form'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; @@ -177,11 +178,12 @@ const XStateNode: React.FC> = ({ selected, data }) => { type: EdgeTypeEnum.custom, data: { id: data.id, - custom: true, - type: EdgeDataTypeEnum.ALWAYS, + name: 'transition', + timers: {}, + type: TransitionTargetEnum.ALWAYS, source: connection.source, target: connection.target, - conditions: [], + target_inputs: {}, }, style: { stroke: '#B6BABF', diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index d79cf941..59cd19f9 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -59,14 +59,8 @@ import { genAutomata, formatReactFlow2Flow, } from '@/stores/app/utils/data-transformer'; -import type { - Config, - Metadata, - NodeDataType, - TransitionDataType, -} from '@/types/app/types'; +import type { Config, Metadata, NodeDataType } from '@/types/app/types'; import { ToastModel } from '@/utils/toast.model'; -import { type TransitionData } from '@shellagent/shared/protocol/transition'; import { CascaderOption, convertRefOptsToCascaderOpts, @@ -87,7 +81,6 @@ const settingsDisabled = process.env.NEXT_PUBLIC_DISABLE_SETTING === 'yes'; @injectable() export class AppBuilderModel { nodeData: NodeDataType = {}; - transitionsData: TransitionDataType = {}; @observable rerenderButtons: Record = {}; @observable metadata: Metadata = { name: '', @@ -178,8 +171,6 @@ export class AppBuilderModel { eventKey, ); - console.log('refOpts>>', refOpts); - const cascaderOpts = convertRefOptsToCascaderOpts(refOpts); return cascaderOpts; } @@ -195,14 +186,6 @@ export class AppBuilderModel { this.nodeData[id] = data; } - @action.bound - setTransitionsData(id: string, handleId: string, data: TransitionData) { - if (!this.transitionsData[id]) { - this.transitionsData[id] = {}; - } - this.transitionsData[id][handleId] = data; - } - @action.bound deleteNodeData(id: string) { delete this.nodeData[id]; diff --git a/web/apps/web/src/stores/app/schema/get-transition-schema.ts b/web/apps/web/src/stores/app/schema/get-transition-schema.ts index c8a7842e..b0003ce9 100644 --- a/web/apps/web/src/stores/app/schema/get-transition-schema.ts +++ b/web/apps/web/src/stores/app/schema/get-transition-schema.ts @@ -23,12 +23,7 @@ export const getXTransitionSchema = ( properties: { name: { type: 'string', - default: 'transition', - 'x-hidden': true, - }, - id: { - type: 'string', - default: generateUUID(), + 'x-type': 'Control', 'x-hidden': true, }, type: { diff --git a/web/apps/web/src/stores/app/use-app-state.tsx b/web/apps/web/src/stores/app/use-app-state.tsx index 1134f9c7..5ae5ec17 100644 --- a/web/apps/web/src/stores/app/use-app-state.tsx +++ b/web/apps/web/src/stores/app/use-app-state.tsx @@ -22,6 +22,7 @@ export type State = { currentEdegData: CustomEdgeData; targetInputsSheetOpen: boolean; transitionType: TransitionTypeEnum; + currentEdgeId: string; }; type Action = { @@ -40,6 +41,7 @@ type Action = { sourceHandle: string; data?: CustomEdgeData; transitionType?: TransitionTypeEnum; + id?: string; }) => void; setNewTransitionSheetOpen: (params: { open: boolean; @@ -47,6 +49,7 @@ type Action = { sourceHandle: string; data?: CustomEdgeData; transitionType?: TransitionTypeEnum; + id?: string; }) => void; setTargetInputsSheetOpen: (open: State['targetInputsSheetOpen']) => void; setRunDrawerWidth: (width: number) => void; @@ -59,6 +62,7 @@ const initialState: State = { newTransitionSheetOpen: false, selectedNode: undefined, currentStateId: '', + currentEdgeId: '', currentButtonId: '', insideSheetOpen: false, insideSheetMode: '', @@ -113,7 +117,7 @@ export const useAppState = create(set => ({ } return state; }), - setTransitionSheetOpen: ({ open, source, sourceHandle, data }) => { + setTransitionSheetOpen: ({ open, source, sourceHandle, data, id }) => { set(() => { if (open) { return { @@ -123,6 +127,7 @@ export const useAppState = create(set => ({ currentEdegData: data || undefined, stateConfigSheetOpen: false, currentStateId: '', + currentEdgeId: id, insideSheetOpen: false, insideSheetMode: '', currentButtonId: '', @@ -136,6 +141,7 @@ export const useAppState = create(set => ({ currentEdegData: undefined, targetInputsSheetOpen: false, transitionType: TransitionTypeEnum.intro, + currentEdgeId: '', }; }); }, @@ -145,6 +151,7 @@ export const useAppState = create(set => ({ sourceHandle, data, transitionType, + id, }) => { set(() => { if (open) { @@ -160,6 +167,7 @@ export const useAppState = create(set => ({ insideSheetMode: '', currentButtonId: '', currentTaskIndex: undefined, + currentEdgeId: id, }; } return { @@ -169,6 +177,7 @@ export const useAppState = create(set => ({ currentEdegData: undefined, targetInputsSheetOpen: false, transitionType: TransitionTypeEnum.intro, + currentEdgeId: '', }; }); }, diff --git a/web/apps/web/src/types/app/types.ts b/web/apps/web/src/types/app/types.ts index ac5c27c9..13d4da53 100644 --- a/web/apps/web/src/types/app/types.ts +++ b/web/apps/web/src/types/app/types.ts @@ -4,10 +4,6 @@ import { Automata } from '@shellagent/pro-config'; import { Refs } from '@shellagent/shared/protocol/app-scope'; import { Issue } from '@shellagent/shared/type'; import { Metadata } from '@/services/home/type'; -import { - TransitionData, - TransitionTargetType, -} from '@shellagent/shared/protocol/transition'; export type Config = { // 弃用 @@ -26,8 +22,3 @@ export type ShellAgent = { export type { Metadata }; export type NodeDataType = Record; - -export type TransitionDataType = Record< - string, - Record ->; diff --git a/web/packages/shared/src/protocol/transition/index.ts b/web/packages/shared/src/protocol/transition/index.ts index b4244fec..256536ce 100644 --- a/web/packages/shared/src/protocol/transition/index.ts +++ b/web/packages/shared/src/protocol/transition/index.ts @@ -3,14 +3,15 @@ import { timerSchema } from '../pro-config'; import { NodeTypeEnum } from '../node'; export type TransitionData = { + id: string; name: string; type: TransitionTargetEnum; timers: z.infer; + source: string; target: string; target_inputs: { [key: string]: any; }; - id: string; }; export enum TransitionTypeEnum { @@ -19,6 +20,7 @@ export enum TransitionTypeEnum { condition_state = NodeTypeEnum.condition_state, } +// 临时 export enum TransitionTargetEnum { 'X.MENTIONED' = 'X.MENTIONED', 'X.PINNED.REPLIED' = 'X.PINNED.REPLIED', @@ -38,3 +40,11 @@ export type TransitionTargetType = | 'X.PINNED.REPLIED' | 'ALWAYS' | 'CHAT'; + +export const transitionTargetTypeSchema = z.union([ + z.literal('X.MENTIONED'), + z.literal('X.PINNED.REPLIED'), + z.literal('ALWAYS'), + z.literal('CHAT'), + z.string().uuid(), +]); From 8977364d1885a987feb20d205fef3988ddbf92fd Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 15 Jan 2025 16:37:35 +0800 Subject: [PATCH 30/42] done --- .../components/app/edges/x-custom-edge.tsx | 3 +- .../src/components/app/flow-header/index.tsx | 8 +- .../app/new-transition-sheet/index.tsx | 26 +- .../app/node-form/widgets/state-selector.tsx | 9 +- .../app/node-form/widgets/timer-selector.tsx | 42 ++- .../widgets/transition-condition-editor.tsx | 4 +- .../widgets/validate-render/dialog.tsx | 6 +- .../widgets/validate-render/index.tsx | 6 +- .../app/nodes/condition-state-node/index.tsx | 25 +- .../app/nodes/x-intro-node/index.tsx | 14 +- .../app/nodes/x-state-node/index.tsx | 25 +- .../app/state-config-sheet/index.tsx | 4 +- web/apps/web/src/services/app/type.ts | 2 +- web/apps/web/src/services/home/type.ts | 2 +- .../stores/app/models/app-builder.model.ts | 40 ++- .../web/src/stores/app/schema-provider.tsx | 4 + .../app/schema/condition-state-schema.ts | 60 ---- .../app/schema/get-transition-schema.ts | 94 +++--- .../utils/__test__/data-transformer.spec.ts} | 0 .../src/stores/app/utils/data-transformer.ts | 261 ++++++++++++----- web/packages/shared/package.json | 1 + .../shared/src/protocol/transition/index.ts | 34 ++- web/pnpm-lock.yaml | 275 +++++++----------- 23 files changed, 528 insertions(+), 417 deletions(-) rename web/apps/web/src/{components/app/new-transition-sheet/utils/index.ts => stores/app/utils/__test__/data-transformer.spec.ts} (100%) diff --git a/web/apps/web/src/components/app/edges/x-custom-edge.tsx b/web/apps/web/src/components/app/edges/x-custom-edge.tsx index 5e8fc500..ed40c6fa 100644 --- a/web/apps/web/src/components/app/edges/x-custom-edge.tsx +++ b/web/apps/web/src/components/app/edges/x-custom-edge.tsx @@ -82,7 +82,6 @@ export const XCustomEdge = ({ id, data: { ...(data || {}), - id, target, source, }, @@ -107,7 +106,7 @@ export const XCustomEdge = ({ = observer(() => { const appBuilder = useInjection('AppBuilderModel'); @@ -79,14 +80,23 @@ const TransitionSheetNew: React.FC<{}> = observer(() => { ); }, [appBuilder.nodeData, edgeData, transitionType]); - const title = useMemo(() => { - return ( - handleChange({ ...edgeData, name: value })} - /> - ); - }, [edgeData]); + const handleTitleChange = useCallback( + (value: string) => { + const { key: newKey, name: newName } = getNewKey({ + name: value, + nameKey: 'name', + values: edges, + prefix: 'Transition', + }); + handleChange({ ...edgeData, name: newName, key: newKey }); + }, + [edgeData, edges, handleChange], + ); + + const title = useMemo( + () => , + [edgeData], + ); return ( void; className?: string; excludeTargets?: string[]; + disabled?: boolean; } export const StateSelector = ({ @@ -13,6 +14,7 @@ export const StateSelector = ({ onChange, className, excludeTargets = [], + disabled = false, }: StateSelectorProps) => { const { nodes } = useReactFlowStore(state => ({ nodes: state.nodes, @@ -31,7 +33,12 @@ export const StateSelector = ({ return (
-
); }; diff --git a/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx b/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx index 3ef3297b..89f60ac0 100644 --- a/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useEffect } from 'react'; import { DatePicker, TimePicker, @@ -10,12 +10,9 @@ import { Button, } from 'antd'; import type { RadioChangeEvent } from 'antd'; -import { z } from 'zod'; import dayjs from 'dayjs'; import { TIMEZONES } from '@shellagent/shared/constants'; -import { timerSchema } from '@shellagent/shared/protocol/pro-config'; - -type Timer = z.infer; +import { type Timer } from '@shellagent/shared/protocol/transition'; enum IntervalUnit { minute = 'minute', @@ -35,17 +32,6 @@ const INTERVAL_OPTIONS = [ { value: IntervalUnit.day, label: 'Day' }, ]; -const DEFAULT_START = { - year: dayjs().year(), - month: dayjs().month() + 1, - date: dayjs().date(), - hour: dayjs().hour(), - minute: dayjs().minute(), - timezone: '', -}; - -const DEFAULT_INTERVAL = { hour: 1 }; - const getInitialEndTimeType = (end?: Timer['end']) => end?.count ? EndTimeType.repeat @@ -54,18 +40,26 @@ const getInitialEndTimeType = (end?: Timer['end']) => : EndTimeType.never; interface TimerSelectorProps { - value?: Timer; - onChange?: (value: Timer) => void; + value: Timer; + onChange: (value: Timer) => void; + defaultValues: Timer; } export const TimerSelector: React.FC = ({ value, onChange, + defaultValues, }) => { + useEffect(() => { + if (defaultValues) { + onChange(defaultValues); + } + }, []); + const { - start = DEFAULT_START, - interval = DEFAULT_INTERVAL, - end, + start = defaultValues.start, + interval = defaultValues.interval, + end = defaultValues.end, } = value || {}; const [timeZoneModalVisible, setTimeZoneModalVisible] = useState(false); @@ -75,7 +69,7 @@ export const TimerSelector: React.FC = ({ const handleChange = useCallback( (updates: Partial) => { - onChange?.({ start, interval, end, ...updates }); + onChange({ start, interval, end, ...updates }); }, [start, interval, end, onChange], ); @@ -103,7 +97,7 @@ export const TimerSelector: React.FC = ({ const handleIntervalUnitChange = useCallback( (unit: IntervalUnit) => { setIntervalUnit(unit); - const currentValue = interval[unit as keyof typeof interval] || 1; + const currentValue = interval?.[unit as keyof typeof interval] || 1; handleChange({ interval: { [unit]: currentValue } }); }, [handleChange, interval], @@ -183,7 +177,7 @@ export const TimerSelector: React.FC = ({ className="flex h-10 items-center justify-end rounded-lg border border-default bg-surface-search-field text-default text-sm shadow-background-default p-3 ring-offset-surface-default placeholder:text-subtler focus:outline-none focus:ring-0 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-30" style={{ width: '80px', textAlign: 'right' }} min={1} - value={interval[intervalUnit as keyof typeof interval]} + value={interval?.[intervalUnit as keyof typeof interval]} onChange={value => { handleChange({ interval: { [intervalUnit]: value ?? undefined }, diff --git a/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx b/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx index d1bb67e6..c4423783 100644 --- a/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx @@ -4,8 +4,8 @@ import { Cog8ToothIcon, ArrowRightIcon, } from '@heroicons/react/24/outline'; -import { useReactFlowStore, NodeTypeEnum } from '@shellagent/flow-engine'; -import { Button, Input, Select, IconButton, Drawer } from '@shellagent/ui'; +import { useReactFlowStore } from '@shellagent/flow-engine'; +import { Button, Input, IconButton, Drawer } from '@shellagent/ui'; import { produce } from 'immer'; import { useInjection } from 'inversify-react'; import { useRef, useState } from 'react'; diff --git a/web/apps/web/src/components/app/node-form/widgets/validate-render/dialog.tsx b/web/apps/web/src/components/app/node-form/widgets/validate-render/dialog.tsx index a364ad71..f88b811d 100644 --- a/web/apps/web/src/components/app/node-form/widgets/validate-render/dialog.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/validate-render/dialog.tsx @@ -30,7 +30,11 @@ function DialogValidateRender({ rules, value, field }: ValidateRenderProps) { const getDebounceError = debounce(async () => { const reactflow = appBuilder.flowInstance?.toObject() as IFlow; - const automata = genAutomata(reactflow, appBuilder.nodeData); + const automata = genAutomata({ + flow: reactflow, + nodeData: appBuilder.nodeData, + appType: appBuilder.appType, + }); const content = replaceContextByAutomata(value, automata); getFirstError(rules, content) diff --git a/web/apps/web/src/components/app/node-form/widgets/validate-render/index.tsx b/web/apps/web/src/components/app/node-form/widgets/validate-render/index.tsx index f7a66dd2..483e2317 100644 --- a/web/apps/web/src/components/app/node-form/widgets/validate-render/index.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/validate-render/index.tsx @@ -38,7 +38,11 @@ function ValidateRender({ rules, value, field }: ValidateRenderProps) { const getDebounceError = debounce(async () => { const reactflow = appBuilder.flowInstance?.toObject() as IFlow; - const automata = genAutomata(reactflow, appBuilder.nodeData); + const automata = genAutomata({ + flow: reactflow, + nodeData: appBuilder.nodeData, + appType: appBuilder.appType, + }); const content = replaceContextByAutomata(value, automata); getFirstError(rules, content) diff --git a/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx b/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx index 97adacfe..458d0927 100644 --- a/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx +++ b/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx @@ -26,6 +26,7 @@ import { EdgeTypeEnum } from '@/components/app/edges'; import NodeCard from '@/components/app/node-card'; import NodeForm from '@/components/app/node-form'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { getNewKey } from '@shellagent/shared/utils'; import emitter, { EventType, useEventEmitter, @@ -36,7 +37,6 @@ import { isEventTargetInputArea, } from '@/utils/common-helper'; import { getConditionStateSchema } from '@/stores/app/schema/get-condition-state-schema'; - import { useDuplicateState } from './hook/use-duplicate-state'; const ConditionStateNode: React.FC> = ({ @@ -45,11 +45,14 @@ const ConditionStateNode: React.FC> = ({ }) => { const stateFormRef = useRef(null); const appBuilder = useInjection('AppBuilderModel'); - const { onDelNode, selectedNodes, onConnect } = useReactFlowStore(state => ({ - onDelNode: state.onDelNode, - selectedNodes: state.selectedNodes, - onConnect: state.onConnect, - })); + const { onDelNode, selectedNodes, onConnect, edges } = useReactFlowStore( + state => ({ + onDelNode: state.onDelNode, + selectedNodes: state.selectedNodes, + onConnect: state.onConnect, + edges: state.edges, + }), + ); const { setStateConfigSheetOpen, currentStateId, setSelectedNode } = useAppState(state => ({ @@ -175,13 +178,19 @@ const ConditionStateNode: React.FC> = ({ const handleConnect = (connection: Connection) => { if (connection.source && connection.target) { + const { key: newKey, name: newName } = getNewKey({ + name: 'Untitled', + nameKey: 'name', + values: edges, + prefix: 'Transition', + }); onConnect({ connect: connection, edge: { type: EdgeTypeEnum.custom, data: { - id: data.id, - name: 'transition', + name: newName, + key: newKey, timers: {}, type: TransitionTargetEnum.ALWAYS, source: connection.source, diff --git a/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx b/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx index cbc0d9bf..88730a6a 100644 --- a/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx +++ b/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx @@ -18,6 +18,7 @@ import { EdgeTypeEnum } from '@/components/app/edges'; import NodeCard from '@/components/app/node-card'; import NodeForm from '@/components/app/node-form'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { getNewKey } from '@shellagent/shared/utils'; import emitter, { EventType, useEventEmitter, @@ -29,9 +30,10 @@ import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; const IntroNode: React.FC> = ({ selected, data }) => { const stateFormRef = useRef(null); const appBuilder = useInjection('AppBuilderModel'); - const { selectedNodes, onConnect } = useReactFlowStore(state => ({ + const { selectedNodes, onConnect, edges } = useReactFlowStore(state => ({ selectedNodes: state.selectedNodes, onConnect: state.onConnect, + edges: state.edges, })); const { setStateConfigSheetOpen, currentStateId, setSelectedNode } = @@ -92,13 +94,19 @@ const IntroNode: React.FC> = ({ selected, data }) => { const handleConnect = (connection: Connection) => { if (connection.source && connection.target) { + const { key: newKey, name: newName } = getNewKey({ + name: 'Untitled', + nameKey: 'name', + values: edges, + prefix: 'Transition', + }); onConnect({ connect: connection, edge: { type: EdgeTypeEnum.custom, data: { - id: data.id, - name: 'transition', + name: newName, + key: newKey, timers: {}, type: TransitionTargetEnum.TIMER, source: connection.source, diff --git a/web/apps/web/src/components/app/nodes/x-state-node/index.tsx b/web/apps/web/src/components/app/nodes/x-state-node/index.tsx index d1c274c7..9a687d79 100644 --- a/web/apps/web/src/components/app/nodes/x-state-node/index.tsx +++ b/web/apps/web/src/components/app/nodes/x-state-node/index.tsx @@ -26,6 +26,7 @@ import { EdgeTypeEnum } from '@/components/app/edges'; import NodeCard from '@/components/app/node-card'; import NodeForm from '@/components/app/node-form'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { getNewKey } from '@shellagent/shared/utils'; import emitter, { EventType, useEventEmitter, @@ -36,17 +37,19 @@ import { isEventTargetInputArea, } from '@/utils/common-helper'; import { getXStateSchema } from '@/stores/app/schema/get-x-state-schema'; - import { useDuplicateState } from './hook/use-duplicate-state'; const XStateNode: React.FC> = ({ selected, data }) => { const stateFormRef = useRef(null); const appBuilder = useInjection('AppBuilderModel'); - const { onDelNode, selectedNodes, onConnect } = useReactFlowStore(state => ({ - onDelNode: state.onDelNode, - selectedNodes: state.selectedNodes, - onConnect: state.onConnect, - })); + const { onDelNode, selectedNodes, onConnect, edges } = useReactFlowStore( + state => ({ + onDelNode: state.onDelNode, + selectedNodes: state.selectedNodes, + onConnect: state.onConnect, + edges: state.edges, + }), + ); const { setStateConfigSheetOpen, currentStateId, setSelectedNode } = useAppState(state => ({ @@ -172,13 +175,19 @@ const XStateNode: React.FC> = ({ selected, data }) => { const handleConnect = (connection: Connection) => { if (connection.source && connection.target) { + const { key: newKey, name: newName } = getNewKey({ + name: 'Untitled', + nameKey: 'name', + values: edges, + prefix: 'Transition', + }); onConnect({ connect: connection, edge: { type: EdgeTypeEnum.custom, data: { - id: data.id, - name: 'transition', + name: newName, + key: newKey, timers: {}, type: TransitionTargetEnum.ALWAYS, source: connection.source, diff --git a/web/apps/web/src/components/app/state-config-sheet/index.tsx b/web/apps/web/src/components/app/state-config-sheet/index.tsx index 33eb7eac..6429882d 100644 --- a/web/apps/web/src/components/app/state-config-sheet/index.tsx +++ b/web/apps/web/src/components/app/state-config-sheet/index.tsx @@ -227,7 +227,9 @@ const StateConfigSheet: React.FC<{}> = () => { case NodeTypeEnum.condition_state: return 'condition-state-config'; case NodeTypeEnum.intro: - return 'intro-config'; + return appBuilder.appType === 'x_bot' + ? 'x-intro-config' + : 'intro-config'; default: return 'state-config'; } diff --git a/web/apps/web/src/services/app/type.ts b/web/apps/web/src/services/app/type.ts index c8a9cde7..266c6c80 100644 --- a/web/apps/web/src/services/app/type.ts +++ b/web/apps/web/src/services/app/type.ts @@ -9,7 +9,7 @@ export interface AppMetadata { name: string; description: string; avatar?: string; // 默认logo,save自动截图 - app_type?: string; + app_type?: 'chat_bot' | 'x_bot'; } // 获取automata diff --git a/web/apps/web/src/services/home/type.ts b/web/apps/web/src/services/home/type.ts index 84aa34a6..fd05e2bf 100644 --- a/web/apps/web/src/services/home/type.ts +++ b/web/apps/web/src/services/home/type.ts @@ -3,7 +3,7 @@ export interface Metadata { description: string; avatar?: string; // 默认logo,save自动截图 categories?: string[]; - app_type?: string; + app_type?: 'chat_bot' | 'x_bot'; } export type Type = 'app' | 'workflow'; diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 59cd19f9..0a714347 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -245,7 +245,11 @@ export class AppBuilderModel { try { this.getAutomataLoading = true; const { data } = await fetchAutomata(params); - this.nodeData = genNodeData(data, nodes); + this.nodeData = genNodeData({ + automata: data, + nodes, + appType: this.appType, + }); this.config.refs = this.config.refs || fieldsModeMap2Refs(data, this.config.fieldsModeMap); } finally { @@ -385,7 +389,11 @@ export class AppBuilderModel { const result = await releaseApp({ app_id, reactflow, - automata: genAutomata(reactflow, this.nodeData), + automata: genAutomata({ + flow: reactflow, + nodeData: this.nodeData, + appType: this.appType, + }), config: this.config, version_name: this.versionName, metadata: this.metadata, @@ -416,7 +424,11 @@ export class AppBuilderModel { const result = await saveApp({ reactflow, config: this.config, - automata: genAutomata(reactflow, this.nodeData), + automata: genAutomata({ + flow: reactflow, + nodeData: this.nodeData, + appType: this.appType, + }), app_id, }); if (result.success) { @@ -451,7 +463,11 @@ export class AppBuilderModel { const result = await saveApp({ app_id, reactflow, - automata: genAutomata(reactflow, this.nodeData), + automata: genAutomata({ + flow: reactflow, + nodeData: this.nodeData, + appType: this.appType, + }), config: this.config, }); if (result.success) { @@ -555,7 +571,12 @@ export class AppBuilderModel { const comfyui_api = settingsDisabled ? DEFAULT_COMFYUI_API : this.settingsModel.envs.get(COMFYUI_API) || ''; - const automata = genAutomata(reactflow, this.nodeData, comfyui_api); + const automata = genAutomata({ + flow: reactflow, + nodeData: this.nodeData, + comfyui_api, + appType: this.appType, + }); try { await this.chatModel.initBot(automata); } catch (error: any) { @@ -588,10 +609,11 @@ export class AppBuilderModel { @action.bound async checkIntroIp() { - const automata = genAutomata( - this.flowInstance?.toObject() as IFlow, - this.nodeData, - ); + const automata = genAutomata({ + flow: this.flowInstance?.toObject() as IFlow, + nodeData: this.nodeData, + appType: this.appType, + }); try { const { images = [], text = '' } = await getIntroDisplay({ automata, diff --git a/web/apps/web/src/stores/app/schema-provider.tsx b/web/apps/web/src/stores/app/schema-provider.tsx index 33caa16b..973ad3d6 100644 --- a/web/apps/web/src/stores/app/schema-provider.tsx +++ b/web/apps/web/src/stores/app/schema-provider.tsx @@ -108,6 +108,10 @@ export const SchemaProvider: React.FC = ({ return introConfigSchema; } + if (type === 'x-intro-config') { + return stateConfigSchema; + } + if (type === 'state-config') { return stateConfigSchema; } diff --git a/web/apps/web/src/stores/app/schema/condition-state-schema.ts b/web/apps/web/src/stores/app/schema/condition-state-schema.ts index 15fd82d8..87873765 100644 --- a/web/apps/web/src/stores/app/schema/condition-state-schema.ts +++ b/web/apps/web/src/stores/app/schema/condition-state-schema.ts @@ -1017,65 +1017,5 @@ export const getConditionStateSchema: ISchema = { 'x-collapsible': true, 'x-addable': true, }, - transitions: { - type: 'object', - title: 'Transition', - additionalProperties: { - type: 'object', - 'x-type': 'Card', - 'x-title-editable': true, - 'x-deletable': true, - properties: { - condition: { - type: 'string', - 'x-type': 'Control', - 'x-component': 'ExpressionInput', - 'x-component-props': { - placeholder: 'Condition', - singleLine: true, - }, - }, - target: { - title: 'Target State', - type: 'string', - 'x-component': 'StateSelector', - 'x-type': 'Control', - 'x-inline': true, - }, - target_inputs: { - type: 'object', - properties: { - name: { - type: 'string', - 'x-component': 'Input', - }, - }, - }, - }, - 'x-edit-dialog': { - type: 'object', - properties: { - name: { - type: 'string', - default: 'Untitled', - title: 'Variable Name', - 'x-role': 'title', - 'x-type': 'Control', - 'x-component': 'Input', - 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', - 'x-component-props': { - maxLength: 30, - size: 'sm', - placeholder: 'Please name the event', - }, - }, - }, - }, - }, - 'x-type': 'Block', - 'x-title-size': 'h4', - 'x-collapsible': false, - 'x-addable': true, - }, }, }; diff --git a/web/apps/web/src/stores/app/schema/get-transition-schema.ts b/web/apps/web/src/stores/app/schema/get-transition-schema.ts index b0003ce9..a1152adf 100644 --- a/web/apps/web/src/stores/app/schema/get-transition-schema.ts +++ b/web/apps/web/src/stores/app/schema/get-transition-schema.ts @@ -1,7 +1,7 @@ import { ISchema, TValues } from '@shellagent/form-engine'; import { getSchemaByInputs } from '@/stores/app/schema/get-workflow-schema'; -import { generateUUID } from '@/utils/common-helper'; import { TransitionTypeEnum } from '@shellagent/shared/protocol/transition'; +import dayjs from 'dayjs'; export const getXTransitionSchema = ( inputs: TValues, @@ -31,50 +31,76 @@ export const getXTransitionSchema = ( 'x-type': 'Control', 'x-component': 'Select', enum: ['TIMER', 'X.MENTIONED', 'X.PINNED.REPLIED'], - default: - transitionType !== TransitionTypeEnum.intro ? 'ALWAYS' : 'TIMER', 'x-component-props': { disabled: transitionType !== TransitionTypeEnum.intro, placeholder: 'Select your transition type', options, + defaultValue: + transitionType !== TransitionTypeEnum.intro ? 'ALWAYS' : 'TIMER', }, - 'x-value-prop-name': 'defaultValue', + 'x-hidden': transitionType !== TransitionTypeEnum.intro, 'x-onchange-prop-name': 'onValueChange', - // 'x-reactions': [ - // { - // target: 'timers', - // when: '$this.value === "timer"', - // fullfill: { - // schema: { - // 'x-hidden': false, - // }, - // }, - // }, - // { - // target: 'timers', - // when: '$this.value === "X.MENTIONED"', - // fullfill: { - // schema: { - // 'x-hidden': true, - // }, - // }, - // }, - // { - // target: 'timers', - // when: '$this.value === "X.PINNED.REPLIED"', - // fullfill: { - // schema: { - // 'x-hidden': true, - // }, - // }, - // }, - // ], + 'x-reactions': [ + { + target: 'timers', + when: '$this.value === "timer"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'timers', + when: '$this.value === "X.MENTIONED"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'timers', + when: '$this.value === "X.PINNED.REPLIED"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], }, timers: { type: 'object', 'x-type': 'Control', 'x-component': 'TimerSelector', - 'x-hidden': false, + 'x-hidden': transitionType !== TransitionTypeEnum.intro, + 'x-component-props': { + defaultValues: { + start: { + year: dayjs().year(), + month: dayjs().month() + 1, + date: dayjs().date(), + hour: dayjs().hour(), + minute: dayjs().minute(), + timezone: '', + }, + interval: { + hour: 1, + }, + }, + }, + }, + condition: { + title: 'Condition', + type: 'string', + 'x-type': 'Control', + 'x-component': 'ExpressionInput', + 'x-component-props': { + placeholder: 'Condition', + singleLine: true, + }, + 'x-hidden': transitionType !== TransitionTypeEnum.condition_state, }, target: { title: 'Target State', diff --git a/web/apps/web/src/components/app/new-transition-sheet/utils/index.ts b/web/apps/web/src/stores/app/utils/__test__/data-transformer.spec.ts similarity index 100% rename from web/apps/web/src/components/app/new-transition-sheet/utils/index.ts rename to web/apps/web/src/stores/app/utils/__test__/data-transformer.spec.ts diff --git a/web/apps/web/src/stores/app/utils/data-transformer.ts b/web/apps/web/src/stores/app/utils/data-transformer.ts index a8ab4665..a9ee7d5a 100644 --- a/web/apps/web/src/stores/app/utils/data-transformer.ts +++ b/web/apps/web/src/stores/app/utils/data-transformer.ts @@ -1,14 +1,15 @@ import { IFlow, NodeIdEnum, NodeTypeEnum } from '@shellagent/flow-engine'; import { TValue } from '@shellagent/form-engine'; import { Automata, State } from '@shellagent/pro-config'; -import { get, set } from 'lodash-es'; +import { get, isString } from 'lodash-es'; +import { ICustomEdge, CustomEdgeData } from '@/components/app/edges'; import { - EdgeDataTypeEnum, - ICustomEdge, - CustomEdgeData, -} from '@/components/app/edges'; -import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; + TransitionTargetEnum, + type TransitionData, + type Timer, + transitionTargetTypeSchema, +} from '@shellagent/shared/protocol/transition'; import { NodeDataType } from '@/types/app/types'; import { @@ -51,10 +52,15 @@ function formatTemplateString(data: any): any { } // 根据automata生成nodeData -export const genNodeData = ( - automata: Automata, - nodes: IFlow['nodes'], -): NodeDataType => { +export const genNodeData = ({ + automata, + nodes, + appType, +}: { + automata: Automata; + nodes: IFlow['nodes']; + appType: 'chat_bot' | 'x_bot'; +}): NodeDataType => { const nodeData: NodeDataType = { [NodeIdEnum.start]: { id: NodeIdEnum.start, @@ -76,13 +82,19 @@ export const genNodeData = ( outputs: {}, blocks: [], }; - } else if (block.type === 'state') { + } else if ( + block.type === 'state' || + (appType === 'x_bot' && block.type === 'dispatcher') + ) { const state = block as State; const display_name = nodes.find(node => node.id === key)?.data .display_name; nodeData[key as Lowercase] = { id: key, - type: NodeTypeEnum.state, + type: + block.type === 'dispatcher' + ? NodeTypeEnum.condition_state + : NodeTypeEnum.state, name: state.name, display_name, render: state.render, @@ -105,78 +117,171 @@ export const genNodeData = ( }; // 根据生成automata -export const genAutomata: ( - flow: IFlow, - nodeData: NodeDataType, - comfyui_api?: string, -) => Automata = (flow, nodeData, comfyui_api) => { - const initial = - (flow.edges.find(edge => edge.source === NodeIdEnum.start) - ?.target as Lowercase) || ''; - const blocks: Automata['blocks'] = {}; - - flow.nodes - .filter( - node => - node.type === NodeTypeEnum.state || node.type === NodeTypeEnum.intro, - ) - .forEach(node => { - const transitions: Record = {}; - const targetEdges = (flow.edges as ICustomEdge[]).filter( - edge => edge.source === node.id, - ); - targetEdges.forEach(edge => { - const data = edge.data as CustomEdgeData; - const transitionKey = - data.type && data.type !== TransitionTargetEnum.STATE - ? data.type - : (data.event_key as string); - - if (!transitions[transitionKey]) { - transitions[transitionKey] = []; - } +export const genAutomata: (props: { + flow: IFlow; + nodeData: NodeDataType; + comfyui_api?: string; + appType: 'chat_bot' | 'x_bot'; +}) => Automata = ({ flow, nodeData, comfyui_api, appType }) => { + if (appType === 'chat_bot') { + const initial = + (flow.edges.find(edge => edge.source === NodeIdEnum.start) + ?.target as Lowercase) || ''; + const blocks: Automata['blocks'] = {}; + + flow.nodes + .filter( + node => + node.type === NodeTypeEnum.state || node.type === NodeTypeEnum.intro, + ) + .forEach(node => { + const transitions: Record = {}; + const targetEdges = (flow.edges as ICustomEdge[]).filter( + edge => edge.source === node.id, + ); + targetEdges.forEach(edge => { + const data = edge.data as CustomEdgeData; + const transitionKey = + data.type && data.type !== TransitionTargetEnum.STATE + ? data.type + : (data.event_key as string); + + if (!transitions[transitionKey]) { + transitions[transitionKey] = []; + } - const transitionItems = data?.conditions?.length - ? data.conditions.map(item => ({ - target: edge.target, - // 空字符串不传 - condition: item?.condition || undefined, - target_inputs: item?.target_inputs, - })) - : [{ target: edge.target }]; + const transitionItems = data?.conditions?.length + ? data.conditions.map(item => ({ + target: edge.target, + // 空字符串不传 + condition: item?.condition || undefined, + target_inputs: item?.target_inputs, + })) + : [{ target: edge.target }]; + + transitions[transitionKey].push(...transitionItems); + }); - transitions[transitionKey].push(...transitionItems); + blocks[node.id as Lowercase] = { + type: + nodeData[node.id]?.type === 'intro' + ? 'state' + : nodeData[node.id]?.type || 'state', + name: nodeData[node.id]?.name, + render: nodeData[node.id]?.render, + blocks: nodeData[node.id]?.blocks?.map((item: any) => ({ + ...item, + ...(item.widget_class_name === 'ComfyUIWidget' + ? { api: comfyui_api || item.api } + : {}), + })), + inputs: transformChoicesToValues(nodeData[node.id]?.inputs || {}), + outputs: nodeData[node.id]?.outputs, + transitions, + } as State; }); - blocks[node.id as Lowercase] = { - type: - nodeData[node.id]?.type === 'intro' - ? 'state' - : nodeData[node.id]?.type || 'state', - name: nodeData[node.id]?.name, - render: nodeData[node.id]?.render, - blocks: nodeData[node.id]?.blocks?.map((item: any) => ({ - ...item, - ...(item.widget_class_name === 'ComfyUIWidget' - ? { api: comfyui_api || item.api } - : {}), - })), - inputs: transformChoicesToValues(nodeData[node.id]?.inputs || {}), - outputs: nodeData[node.id]?.outputs, - transitions, - } as State; + return replaceContext2Api({ + type: 'automata', + properties: { + cache: false, + }, + context: nodeData[NodeIdEnum.start]?.context || {}, + initial, + blocks, + transitions: {}, }); + } else if (appType === 'x_bot') { + const initial = + (flow.edges.find(edge => edge.source === NodeIdEnum.start) + ?.target as Lowercase) || ''; + const blocks: Automata['blocks'] = {}; - return replaceContext2Api({ - type: 'automata', - properties: { - cache: false, - }, - context: nodeData[NodeIdEnum.start]?.context || {}, - initial, - blocks, - transitions: {}, - }); + // 处理所有状态节点 + flow.nodes + .filter( + node => + node.type == NodeTypeEnum.state || + node.type === NodeTypeEnum.intro || + node.type === NodeTypeEnum.condition_state, + ) + .forEach(node => { + const transitions: Record = {}; + const timers: Record = {}; + + // 获取从该节点出发的所有边 + const targetEdges = (flow.edges as ICustomEdge[]).filter( + edge => edge.source === node.id, + ); + + // 处理每条边 + targetEdges.forEach(edge => { + const transitionData = edge.data as TransitionData; + let transitionKey: string = transitionData.type; + + // 修改 timer 处理逻辑 + if (transitionData.timers) { + transitionKey = transitionData.key; + timers[transitionKey] = { + timer: transitionData.timers, + event: transitionData.key, + }; + } + + if (isString(transitionData.condition)) { + if (!transitions[transitionKey]) { + transitions[transitionKey] = []; + } + transitions[transitionKey].push({ + target: transitionData.target, + condition: transitionData.condition, + target_inputs: transitionData.target_inputs, + }); + } else { + transitions[transitionKey] = { + target: transitionData.target, + target_inputs: transitionData.target_inputs, + }; + } + }); + + // 构建 block + blocks[node.id as Lowercase] = { + type: + nodeData[node.id]?.type === 'condition_state' + ? 'dispatcher' + : 'state', + name: nodeData[node.id]?.name, + properties: { + ...(Object.keys(timers).length > 0 ? { timers } : {}), + cache: false, + }, + render: nodeData[node.id]?.render, + blocks: nodeData[node.id]?.blocks?.map((item: any) => ({ + ...item, + ...(item.widget_class_name === 'ComfyUIWidget' + ? { api: comfyui_api || item.api } + : {}), + })), + inputs: transformChoicesToValues(nodeData[node.id]?.inputs || {}), + outputs: nodeData[node.id]?.outputs, + transitions, + } as State; + }); + + return replaceContext2Api({ + type: 'automata', + properties: { + cache: false, + }, + context: nodeData[NodeIdEnum.start]?.context || {}, + initial, + blocks, + transitions: {}, + }); + } else { + return {}; + } }; export const replaceContextByAutomata = (value: TValue, automata: Automata) => { diff --git a/web/packages/shared/package.json b/web/packages/shared/package.json index 4216fda9..22cbfca6 100644 --- a/web/packages/shared/package.json +++ b/web/packages/shared/package.json @@ -45,6 +45,7 @@ "slug": "^9.1.0", "unicode": "^14.0.0", "unidecode": "^1.1.0", + "uuid": "^11.0.5", "zod": "^3.23.8" } } diff --git a/web/packages/shared/src/protocol/transition/index.ts b/web/packages/shared/src/protocol/transition/index.ts index 256536ce..32dcc46b 100644 --- a/web/packages/shared/src/protocol/transition/index.ts +++ b/web/packages/shared/src/protocol/transition/index.ts @@ -1,17 +1,21 @@ import { z } from 'zod'; import { timerSchema } from '../pro-config'; import { NodeTypeEnum } from '../node'; +import { v4 as uuidv4 } from 'uuid'; + +export type Timer = z.infer; export type TransitionData = { - id: string; name: string; + key: string; type: TransitionTargetEnum; - timers: z.infer; + timers: Timer; source: string; target: string; target_inputs: { [key: string]: any; }; + condition: string; }; export enum TransitionTypeEnum { @@ -41,10 +45,22 @@ export type TransitionTargetType = | 'ALWAYS' | 'CHAT'; -export const transitionTargetTypeSchema = z.union([ - z.literal('X.MENTIONED'), - z.literal('X.PINNED.REPLIED'), - z.literal('ALWAYS'), - z.literal('CHAT'), - z.string().uuid(), -]); +export const transitionTargetTypeSchema = z + .union([ + z.literal('X.MENTIONED'), + z.literal('X.PINNED.REPLIED'), + z.literal('ALWAYS'), + z.literal('CHAT'), + ]) + .transform(val => { + const predefinedValues = [ + 'X.MENTIONED', + 'X.PINNED.REPLIED', + 'ALWAYS', + 'CHAT', + ]; + if (predefinedValues.indexOf(val) !== -1) { + return val; + } + return uuidv4(); + }); diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index edd5a237..755b30cd 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -407,19 +407,19 @@ importers: version: 8.4.4(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@storybook/addon-webpack5-compiler-swc': specifier: ^1.0.5 - version: 1.0.5(@swc/helpers@0.5.5)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + version: 1.0.5(@swc/helpers@0.5.5)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) '@storybook/blocks': specifier: ^8.3.1 version: 8.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@storybook/nextjs': specifier: ^8.3.1 - version: 8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(babel-plugin-macros@3.1.0)(next@14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(type-fest@2.19.0)(typescript@5.3.3)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + version: 8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(babel-plugin-macros@3.1.0)(esbuild@0.23.1)(next@14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(type-fest@2.19.0)(typescript@5.3.3)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) '@storybook/react': specifier: ^8.3.1 version: 8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@storybook/react-webpack5': specifier: ^8.4.7 - version: 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) + version: 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@storybook/test': specifier: ^8.3.1 version: 8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) @@ -437,7 +437,7 @@ importers: version: 18.3.0 css-loader: specifier: ^7.1.2 - version: 7.1.2(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + version: 7.1.2(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) eslint: specifier: ^8 version: 8.37.0 @@ -452,7 +452,7 @@ importers: version: 4.2.0 less-loader: specifier: ^12.2.0 - version: 12.2.0(less@4.2.0)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + version: 12.2.0(less@4.2.0)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) next-intl: specifier: ^2.22.1 version: 2.22.1(next@14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -467,10 +467,10 @@ importers: version: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) style-loader: specifier: ^4.0.0 - version: 4.0.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + version: 4.0.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) tailwindcss: specifier: ^3.4.1 - version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) + version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) tsconfig-paths-webpack-plugin: specifier: ^4.2.0 version: 4.2.0 @@ -732,7 +732,7 @@ importers: version: 10.4.20(postcss@8.4.41) copy-webpack-plugin: specifier: ^12.0.2 - version: 12.0.2(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)) + version: 12.0.2(webpack@5.93.0(@swc/core@1.7.12)) jest: specifier: ^29.4.1 version: 29.7.0(@types/node@20.16.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) @@ -741,16 +741,16 @@ importers: version: 8.4.41 raw-loader: specifier: ^4.0.2 - version: 4.0.2(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)) + version: 4.0.2(webpack@5.93.0(@swc/core@1.7.12)) storybook: specifier: ^8.3.1 version: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) tailwindcss: specifier: ^3.4.10 - version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) + version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) ts-jest: specifier: ^29.0.5 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -1005,6 +1005,9 @@ importers: unidecode: specifier: ^1.1.0 version: 1.1.0 + uuid: + specifier: ^11.0.5 + version: 11.0.5 zod: specifier: ^3.23.8 version: 3.23.8 @@ -1038,13 +1041,13 @@ importers: version: 0.1.3 jest: specifier: ^29.4.1 - version: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.5.1)(typescript@5.3.3)) + version: 29.7.0(@types/node@20.16.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) node-emoji: specifier: ^2.1.3 version: 2.1.3 ts-jest: specifier: ^29.0.5 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.5.1)(typescript@5.3.3)))(typescript@5.3.3) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -1066,7 +1069,7 @@ importers: version: 20.16.1 tailwindcss: specifier: ^3.4.10 - version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) + version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -1276,10 +1279,10 @@ importers: version: 4.0.2(webpack@5.93.0(@swc/core@1.7.12)) tailwindcss: specifier: ^3.4.10 - version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) + version: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) ts-jest: specifier: ^29.0.5 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -11917,6 +11920,10 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true + uuid@11.0.5: + resolution: {integrity: sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -14634,6 +14641,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3))': dependencies: @@ -14984,7 +14992,7 @@ snapshots: '@pkgr/core@0.1.1': {} - '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1))': dependencies: ansi-html: 0.0.9 core-js-pure: 3.38.0 @@ -14994,7 +15002,7 @@ snapshots: react-refresh: 0.14.2 schema-utils: 4.2.0 source-map: 0.7.4 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) optionalDependencies: type-fest: 2.19.0 webpack-hot-middleware: 2.26.1 @@ -16053,10 +16061,10 @@ snapshots: memoizerific: 1.11.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@storybook/addon-webpack5-compiler-swc@1.0.5(@swc/helpers@0.5.5)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)))': + '@storybook/addon-webpack5-compiler-swc@1.0.5(@swc/helpers@0.5.5)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1))': dependencies: '@swc/core': 1.7.12(@swc/helpers@0.5.5) - swc-loader: 0.2.6(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + swc-loader: 0.2.6(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) transitivePeerDependencies: - '@swc/helpers' - webpack @@ -16082,7 +16090,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-webpack5@8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': + '@storybook/builder-webpack5@8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': dependencies: '@storybook/core-webpack': 8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@types/node': 22.4.1 @@ -16091,25 +16099,25 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.1 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) es-module-lexer: 1.5.4 express: 4.21.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) fs-extra: 11.2.0 - html-webpack-plugin: 5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + html-webpack-plugin: 5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) magic-string: 0.30.11 path-browserify: 1.0.1 process: 0.11.10 semver: 7.6.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) - terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) + terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) - webpack-dev-middleware: 6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) + webpack-dev-middleware: 6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -16122,7 +16130,7 @@ snapshots: - uglify-js - webpack-cli - '@storybook/builder-webpack5@8.4.7(@swc/core@1.7.12(@swc/helpers@0.5.5))(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': + '@storybook/builder-webpack5@8.4.7(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': dependencies: '@storybook/core-webpack': 8.4.7(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@types/node': 22.4.1 @@ -16131,23 +16139,23 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.1 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) es-module-lexer: 1.5.4 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) - html-webpack-plugin: 5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) + html-webpack-plugin: 5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) magic-string: 0.30.11 path-browserify: 1.0.1 process: 0.11.10 semver: 7.6.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) - terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) + terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) - webpack-dev-middleware: 6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) + webpack-dev-middleware: 6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -16244,7 +16252,7 @@ snapshots: dependencies: storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@storybook/nextjs@8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(babel-plugin-macros@3.1.0)(next@14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(type-fest@2.19.0)(typescript@5.3.3)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)))': + '@storybook/nextjs@8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(babel-plugin-macros@3.1.0)(esbuild@0.23.1)(next@14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(type-fest@2.19.0)(typescript@5.3.3)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.25.2) @@ -16259,32 +16267,32 @@ snapshots: '@babel/preset-react': 7.24.7(@babel/core@7.25.2) '@babel/preset-typescript': 7.24.7(@babel/core@7.25.2) '@babel/runtime': 7.25.0 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) - '@storybook/builder-webpack5': 8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) - '@storybook/preset-react-webpack': 8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) + '@storybook/builder-webpack5': 8.3.1(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) + '@storybook/preset-react-webpack': 8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@storybook/react': 8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@storybook/test': 8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@types/node': 22.4.1 '@types/semver': 7.5.8 - babel-loader: 9.2.1(@babel/core@7.25.2)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) - css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + babel-loader: 9.2.1(@babel/core@7.25.2)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) + css-loader: 6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) find-up: 5.0.0 fs-extra: 11.2.0 image-size: 1.1.1 loader-utils: 3.3.1 next: 14.2.12(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - node-polyfill-webpack-plugin: 2.0.1(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + node-polyfill-webpack-plugin: 2.0.1(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) pnp-webpack-plugin: 1.7.0(typescript@5.3.3) postcss: 8.4.41 - postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 13.3.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + sass-loader: 13.3.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) semver: 7.6.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + style-loader: 3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) styled-jsx: 5.1.6(@babel/core@7.25.2)(babel-plugin-macros@3.1.0)(react@18.3.1) ts-dedent: 2.2.0 tsconfig-paths: 4.2.0 @@ -16292,7 +16300,7 @@ snapshots: optionalDependencies: sharp: 0.33.5 typescript: 5.3.3 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -16312,11 +16320,11 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/preset-react-webpack@8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': + '@storybook/preset-react-webpack@8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': dependencies: '@storybook/core-webpack': 8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@storybook/react': 8.3.1(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) '@types/node': 22.4.1 '@types/semver': 7.5.8 find-up: 5.0.0 @@ -16329,7 +16337,7 @@ snapshots: semver: 7.6.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) tsconfig-paths: 4.2.0 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: @@ -16340,11 +16348,11 @@ snapshots: - uglify-js - webpack-cli - '@storybook/preset-react-webpack@8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': + '@storybook/preset-react-webpack@8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': dependencies: '@storybook/core-webpack': 8.4.7(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@storybook/react': 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) '@types/node': 22.4.1 '@types/semver': 7.5.8 find-up: 5.0.0 @@ -16356,7 +16364,7 @@ snapshots: semver: 7.6.3 storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) tsconfig-paths: 4.2.0 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: @@ -16375,7 +16383,7 @@ snapshots: dependencies: storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1))': dependencies: debug: 4.3.6 endent: 2.1.0 @@ -16385,7 +16393,7 @@ snapshots: react-docgen-typescript: 2.2.2(typescript@5.3.3) tslib: 2.6.3 typescript: 5.3.3 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) transitivePeerDependencies: - supports-color @@ -16401,10 +16409,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@storybook/react-webpack5@8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': + '@storybook/react-webpack5@8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3)': dependencies: - '@storybook/builder-webpack5': 8.4.7(@swc/core@1.7.12(@swc/helpers@0.5.5))(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) - '@storybook/preset-react-webpack': 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) + '@storybook/builder-webpack5': 8.4.7(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) + '@storybook/preset-react-webpack': 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@storybook/react': 8.4.7(@storybook/test@8.3.1(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.3.3) '@types/node': 22.4.1 react: 18.3.1 @@ -16595,7 +16603,7 @@ snapshots: lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) + tailwindcss: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) '@testing-library/dom@10.4.0': dependencies: @@ -17842,12 +17850,12 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@9.2.1(@babel/core@7.25.2)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + babel-loader@9.2.1(@babel/core@7.25.2)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: '@babel/core': 7.25.2 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) babel-plugin-istanbul@6.1.1: dependencies: @@ -18466,7 +18474,7 @@ snapshots: dependencies: toggle-selection: 1.0.6 - copy-webpack-plugin@12.0.2(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)): + copy-webpack-plugin@12.0.2(webpack@5.93.0(@swc/core@1.7.12)): dependencies: fast-glob: 3.3.2 glob-parent: 6.0.2 @@ -18474,7 +18482,7 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.2.0 serialize-javascript: 6.0.2 - webpack: 5.93.0(@swc/core@1.7.12)(esbuild@0.23.1) + webpack: 5.93.0(@swc/core@1.7.12) core-js-compat@3.38.1: dependencies: @@ -18576,6 +18584,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true create-jest@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -18638,7 +18647,7 @@ snapshots: dependencies: hyphenate-style-name: 1.1.0 - css-loader@6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + css-loader@6.11.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: icss-utils: 5.1.0(postcss@8.4.41) postcss: 8.4.41 @@ -18649,9 +18658,9 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) - css-loader@7.1.2(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + css-loader@7.1.2(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: icss-utils: 5.1.0(postcss@8.4.41) postcss: 8.4.41 @@ -18662,7 +18671,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) css-select@4.3.0: dependencies: @@ -20106,7 +20115,7 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: '@babel/code-frame': 7.24.7 chalk: 4.1.2 @@ -20121,7 +20130,7 @@ snapshots: semver: 7.6.3 tapable: 2.2.1 typescript: 5.3.3 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) form-data@4.0.0: dependencies: @@ -20664,7 +20673,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + html-webpack-plugin@5.6.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -20672,7 +20681,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) htmlparser2@3.10.1: dependencies: @@ -21258,6 +21267,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true jest-cli@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -21339,6 +21349,7 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - supports-color + optional: true jest-config@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -21401,6 +21412,7 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - supports-color + optional: true jest-config@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -21743,6 +21755,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true jest@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -21949,11 +21962,11 @@ snapshots: layout-base@2.0.1: {} - less-loader@12.2.0(less@4.2.0)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + less-loader@12.2.0(less@4.2.0)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: less: 4.2.0 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) less@4.2.0: dependencies: @@ -22888,7 +22901,7 @@ snapshots: mkdirp: 0.5.6 resolve: 1.22.8 - node-polyfill-webpack-plugin@2.0.1(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + node-polyfill-webpack-plugin@2.0.1(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: assert: 2.1.0 browserify-zlib: 0.2.0 @@ -22915,7 +22928,7 @@ snapshots: url: 0.11.4 util: 0.12.5 vm-browserify: 1.1.2 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) node-preload@0.2.1: dependencies: @@ -23377,7 +23390,7 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.4.41 - postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)): + postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)): dependencies: lilconfig: 3.1.2 yaml: 2.5.0 @@ -23393,14 +23406,14 @@ snapshots: postcss: 8.4.41 ts-node: 10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3) - postcss-loader@8.1.1(postcss@8.4.41)(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + postcss-loader@8.1.1(postcss@8.4.41)(typescript@5.3.3)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: cosmiconfig: 9.0.0(typescript@5.3.3) jiti: 1.21.6 postcss: 8.4.41 semver: 7.6.3 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) transitivePeerDependencies: - typescript @@ -23607,12 +23620,6 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - raw-loader@4.0.2(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)): - dependencies: - loader-utils: 2.0.4 - schema-utils: 3.3.0 - webpack: 5.93.0(@swc/core@1.7.12)(esbuild@0.23.1) - raw-loader@4.0.2(webpack@5.93.0(@swc/core@1.7.12)): dependencies: loader-utils: 2.0.4 @@ -24764,10 +24771,10 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@13.3.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + sass-loader@13.3.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: neo-async: 2.6.2 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) sax@1.4.1: optional: true @@ -25239,13 +25246,13 @@ snapshots: strip-json-comments@3.1.1: {} - style-loader@3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + style-loader@3.3.4(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) - style-loader@4.0.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + style-loader@4.0.0(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) style-to-object@1.0.6: dependencies: @@ -25311,11 +25318,11 @@ snapshots: lower-case: 1.1.4 upper-case: 1.1.3 - swc-loader@0.2.6(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + swc-loader@0.2.6(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: '@swc/core': 1.7.12(@swc/helpers@0.5.5) '@swc/counter': 0.1.3 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) swiper@11.1.12: {} @@ -25336,9 +25343,9 @@ snapshots: tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3))): dependencies: - tailwindcss: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) + tailwindcss: 3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) - tailwindcss@3.4.10(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)): + tailwindcss@3.4.10(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -25357,7 +25364,7 @@ snapshots: postcss: 8.4.41 postcss-import: 15.1.0(postcss@8.4.41) postcss-js: 4.0.1(postcss@8.4.41) - postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)) + postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) postcss-nested: 6.2.0(postcss@8.4.41) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -25407,25 +25414,14 @@ snapshots: dependencies: memoizerific: 1.11.3 - terser-webpack-plugin@5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + terser-webpack-plugin@5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.31.6 - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) - optionalDependencies: - '@swc/core': 1.7.12(@swc/helpers@0.5.5) - - terser-webpack-plugin@5.3.10(@swc/core@1.7.12)(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - jest-worker: 27.5.1 - schema-utils: 3.3.0 - serialize-javascript: 6.0.2 - terser: 5.31.6 - webpack: 5.93.0(@swc/core@1.7.12)(esbuild@0.23.1) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) optionalDependencies: '@swc/core': 1.7.12(@swc/helpers@0.5.5) esbuild: 0.23.1 @@ -25558,7 +25554,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3): + ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -25576,26 +25572,6 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.25.2) - esbuild: 0.23.1 - - ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.5.1)(typescript@5.3.3)))(typescript@5.3.3): - dependencies: - bs-logger: 0.2.6 - ejs: 3.1.10 - fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.5.1)(typescript@5.3.3)) - jest-util: 29.7.0 - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.6.3 - typescript: 5.3.3 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.25.2 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.25.2) ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)))(typescript@5.3.3): dependencies: @@ -26056,6 +26032,8 @@ snapshots: uuid@10.0.0: {} + uuid@11.0.5: {} + uuid@8.3.2: {} uuid@9.0.1: {} @@ -26206,7 +26184,7 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-dev-middleware@6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))): + webpack-dev-middleware@6.1.3(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -26214,7 +26192,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.2.0 optionalDependencies: - webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)) + webpack: 5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1) webpack-hot-middleware@2.26.1: dependencies: @@ -26226,7 +26204,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5)): + webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 @@ -26249,7 +26227,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))) + terser-webpack-plugin: 5.3.10(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12(@swc/helpers@0.5.5))(esbuild@0.23.1)) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -26288,37 +26266,6 @@ snapshots: - esbuild - uglify-js - webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1): - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/wasm-edit': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.12.1 - acorn-import-attributes: 1.9.5(acorn@8.12.1) - browserslist: 4.23.3 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.17.1 - es-module-lexer: 1.5.4 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.7.12)(esbuild@0.23.1)(webpack@5.93.0(@swc/core@1.7.12)(esbuild@0.23.1)) - watchpack: 2.4.2 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - webworkify@1.5.0: {} whatwg-encoding@2.0.0: From 9a75ae05875d86dbf997635ba5439aacc44217cc Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 15 Jan 2025 16:41:24 +0800 Subject: [PATCH 31/42] fix --- web/packages/pro-config-type/src/common.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web/packages/pro-config-type/src/common.ts b/web/packages/pro-config-type/src/common.ts index d9e56091..9c8ef9ff 100644 --- a/web/packages/pro-config-type/src/common.ts +++ b/web/packages/pro-config-type/src/common.ts @@ -1,4 +1,3 @@ -import { EventPrefix } from './transition'; /** * @example * "variable_name" From 395d4af2e559c75708e97f80e3354e649abe315b Mon Sep 17 00:00:00 2001 From: shanexi Date: Wed, 15 Jan 2025 16:48:08 +0800 Subject: [PATCH 32/42] merge --- web/apps/web/src/app/app/detail/page.tsx | 21 +- web/apps/web/src/app/app/page.tsx | 68 +- web/apps/web/src/app/container.ts | 6 + web/apps/web/src/components/app/constants.tsx | 208 +++- .../src/components/app/edges/custom-edge.tsx | 9 +- .../web/src/components/app/edges/index.tsx | 1 + web/apps/web/src/components/app/edges/type.ts | 4 +- .../components/app/edges/x-custom-edge.tsx | 120 ++ .../src/components/app/flow-header/index.tsx | 8 +- .../app/new-transition-sheet/index.tsx | 134 ++ .../src/components/app/node-form/index.tsx | 6 + .../app/node-form/widgets/index.tsx | 2 + .../app/node-form/widgets/state-selector.tsx | 46 + .../app/node-form/widgets/timer-selector.tsx | 294 +++++ .../widgets/transition-condition-editor.tsx | 54 +- .../widgets/validate-render/dialog.tsx | 6 +- .../widgets/validate-render/index.tsx | 6 +- .../variable-select/use-select-options.tsx | 3 + .../hook/use-duplicate-state.tsx | 25 + .../hook/use-paste-state.tsx | 62 + .../app/nodes/condition-state-node/index.tsx | 283 +++++ .../web/src/components/app/nodes/index.tsx | 3 + .../app/nodes/x-intro-node/index.tsx | 145 +++ .../x-state-node/hook/use-duplicate-state.tsx | 25 + .../x-state-node/hook/use-paste-state.tsx | 62 + .../app/nodes/x-state-node/index.tsx | 280 +++++ .../app/state-config-sheet/index.tsx | 23 +- .../components/chat/app-builder-chat.model.ts | 22 +- .../src/components/home/card-list/index.tsx | 45 + .../components/home/create-dialog/index.tsx | 1 + .../src/components/home/detail-form/index.tsx | 16 + .../home/detail-form/type-select.tsx | 54 + .../src/components/home/filter-tabs/index.tsx | 53 + web/apps/web/src/constants/timezones.ts | 0 web/apps/web/src/services/app/index.tsx | 9 + web/apps/web/src/services/app/type.ts | 1 + web/apps/web/src/services/home/type.ts | 1 + .../stores/app/models/app-builder-utils.ts | 15 + .../stores/app/models/app-builder.model.ts | 63 +- .../web/src/stores/app/models/flow.model.ts | 146 +++ .../web/src/stores/app/schema-provider.tsx | 14 + .../app/schema/condition-state-schema.ts | 1021 ++++++++++++++++ .../app/schema/get-condition-state-schema.ts | 27 + .../app/schema/get-transition-schema.ts | 115 ++ .../stores/app/schema/get-x-state-schema.ts | 24 + .../src/stores/app/schema/x-state-config.ts | 1088 +++++++++++++++++ .../src/stores/app/schema/x-state-schema.ts | 1088 +++++++++++++++++ web/apps/web/src/stores/app/use-app-state.tsx | 70 +- .../utils/__test__/data-transformer.spec.ts | 0 .../src/stores/app/utils/data-transformer.ts | 261 ++-- .../web/src/stores/home/models/home-model.ts | 80 ++ web/apps/web/src/types/app/types.ts | 1 - web/apps/web/src/types/index.d.ts | 1 - .../flow-engine/src/store/flow/provider.tsx | 2 +- web/packages/flow-engine/src/types/node.ts | 31 +- .../example/twitter_bot_hello_world.ts | 117 ++ web/packages/pro-config-type/src/block.ts | 58 +- web/packages/pro-config-type/src/common.ts | 4 +- .../pro-config-type/src/transition.ts | 37 +- web/packages/shared/package.json | 5 +- web/packages/shared/src/constants/index.ts | 1 + .../shared/src/constants/timezones.ts | 190 +++ .../app-scope/__test__/ref-util.spec.ts | 122 ++ .../shared/src/protocol/app-scope/protocol.ts | 48 + .../shared/src/protocol/app-scope/ref-util.ts | 40 +- .../shared/src/protocol/app-scope/scope.ts | 7 + .../shared/src/protocol/node/index.ts | 34 + .../shared/src/protocol/pro-config/block.ts | 76 +- .../src/protocol/pro-config/transition.ts | 40 +- .../shared/src/protocol/transition/index.ts | 66 + .../utils/__test__/get_display_name.spec.ts | 8 +- .../shared/src/utils/get_display_name.ts | 26 +- web/pnpm-lock.yaml | 56 +- 73 files changed, 6786 insertions(+), 302 deletions(-) create mode 100644 web/apps/web/src/components/app/edges/x-custom-edge.tsx create mode 100644 web/apps/web/src/components/app/new-transition-sheet/index.tsx create mode 100644 web/apps/web/src/components/app/node-form/widgets/state-selector.tsx create mode 100644 web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx create mode 100644 web/apps/web/src/components/app/nodes/condition-state-node/hook/use-duplicate-state.tsx create mode 100644 web/apps/web/src/components/app/nodes/condition-state-node/hook/use-paste-state.tsx create mode 100644 web/apps/web/src/components/app/nodes/condition-state-node/index.tsx create mode 100644 web/apps/web/src/components/app/nodes/x-intro-node/index.tsx create mode 100644 web/apps/web/src/components/app/nodes/x-state-node/hook/use-duplicate-state.tsx create mode 100644 web/apps/web/src/components/app/nodes/x-state-node/hook/use-paste-state.tsx create mode 100644 web/apps/web/src/components/app/nodes/x-state-node/index.tsx create mode 100644 web/apps/web/src/components/home/card-list/index.tsx create mode 100644 web/apps/web/src/components/home/detail-form/type-select.tsx create mode 100644 web/apps/web/src/components/home/filter-tabs/index.tsx create mode 100644 web/apps/web/src/constants/timezones.ts create mode 100644 web/apps/web/src/stores/app/models/flow.model.ts create mode 100644 web/apps/web/src/stores/app/schema/condition-state-schema.ts create mode 100644 web/apps/web/src/stores/app/schema/get-condition-state-schema.ts create mode 100644 web/apps/web/src/stores/app/schema/get-transition-schema.ts create mode 100644 web/apps/web/src/stores/app/schema/get-x-state-schema.ts create mode 100644 web/apps/web/src/stores/app/schema/x-state-config.ts create mode 100644 web/apps/web/src/stores/app/schema/x-state-schema.ts create mode 100644 web/apps/web/src/stores/app/utils/__test__/data-transformer.spec.ts create mode 100644 web/apps/web/src/stores/home/models/home-model.ts create mode 100644 web/packages/pro-config-type/example/twitter_bot_hello_world.ts create mode 100644 web/packages/shared/src/constants/index.ts create mode 100644 web/packages/shared/src/constants/timezones.ts create mode 100644 web/packages/shared/src/protocol/transition/index.ts diff --git a/web/apps/web/src/app/app/detail/page.tsx b/web/apps/web/src/app/app/detail/page.tsx index 22b42648..63da99d5 100644 --- a/web/apps/web/src/app/app/detail/page.tsx +++ b/web/apps/web/src/app/app/detail/page.tsx @@ -11,7 +11,14 @@ import { useEffect, useRef } from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; -import { edgeTypes, materialList, nodeTypes } from '@/components/app/constants'; +import { + edgeTypes, + materialList, + xMaterialList, + nodeTypes, + xNodeTypes, + xEdgeTypes, +} from '@/components/app/constants'; import FlowHeader from '@/components/app/flow-header'; import { Header } from '@/components/app/header'; import { ComfyUIEditorModal } from '@/components/app/plugins/comfyui/widgets/comfyui-editor'; @@ -38,6 +45,13 @@ const TransitionSheet = dynamic( }, ); +const TransitionSheetNew = dynamic( + () => import('@/components/app/new-transition-sheet'), + { + ssr: false, + }, +); + const ChatSheet = dynamic(() => import('@/components/app/chat-sheet'), { ssr: false, }); @@ -103,8 +117,8 @@ const FlowEngineWrapper = observer( appBuilder.initAppBuilderLoading || appBuilder.fetchFlowListLoading } ref={flowRef} - nodeTypes={nodeTypes} - edgeTypes={edgeTypes} + nodeTypes={appBuilder.appType === 'x_bot' ? xNodeTypes : nodeTypes} + edgeTypes={appBuilder.appType === 'x_bot' ? xEdgeTypes : edgeTypes} materialList={appBuilder.materialList} footerExtra={} header={ @@ -112,6 +126,7 @@ const FlowEngineWrapper = observer( +
} diff --git a/web/apps/web/src/app/app/page.tsx b/web/apps/web/src/app/app/page.tsx index 897a4a25..cd316e01 100644 --- a/web/apps/web/src/app/app/page.tsx +++ b/web/apps/web/src/app/app/page.tsx @@ -1,84 +1,54 @@ 'use client'; import '../reflect-metadata-client-side'; -import { Heading, Spinner, Text } from '@shellagent/ui'; +import { Heading } from '@shellagent/ui'; import { useScroll } from 'ahooks'; import { useInjection } from 'inversify-react'; import { useEffect, useRef } from 'react'; -import useSWR from 'swr'; import { CreateDialog } from '@/components/home/create-dialog'; -import { FlowCard } from '@/components/home/flow-card'; import { ImportDialog } from '@/components/home/import-dialog'; -import { SettingsModel } from '@/components/settings/settings.model'; -import { fetchList } from '@/services/home'; +import { CardList } from '@/components/home/card-list'; +import { FilterTabs } from '@/components/home/filter-tabs'; import { cn } from '@/utils/cn'; +import { HomeModel } from '@/stores/home/models/home-model'; export default function AppPage() { const contentRef = useRef(null); const position = useScroll(contentRef); + const homeModel = useInjection(HomeModel); - const settingsModel = useInjection(SettingsModel); useEffect(() => { - (async function () { - await settingsModel.getIsBetaCheck(); - const isAutoCheck = await settingsModel.getAutoCheck(); - if (isAutoCheck) { - settingsModel.autoCheck(); - } - })(); + homeModel.autoCheck(); + homeModel.fetchList({ type: 'app' }); }, []); - const { data, isLoading, mutate } = useSWR('/api/list?type=app', () => - fetchList({ type: 'app' }), - ); - const onRefresh = () => { - mutate(); + homeModel.refreshList({ type: 'app' }); }; return (
0 ? 'border-default' : 'border-transparent', )}> - App -
- - +
+ App +
+ + +
+
+
+
- {isLoading && ( -
- -
- )} - {!isLoading && data?.data.length ? ( -
- {data?.data.map(item => ( - - ))} -
- ) : null} - {!isLoading && !data?.data.length && ( -
- Create your first App - - Click button to create a app - - -
- )} +
); diff --git a/web/apps/web/src/app/container.ts b/web/apps/web/src/app/container.ts index 9fc0f5bf..7cabcc8f 100644 --- a/web/apps/web/src/app/container.ts +++ b/web/apps/web/src/app/container.ts @@ -8,6 +8,7 @@ import { Container, ContainerModule, interfaces } from 'inversify'; import * as Mobx from 'mobx'; import { toast } from 'react-toastify'; +import { HomeModel } from '@/stores/home/models/home-model'; import { ComfyUIModel } from '@/components/app/plugins/comfyui/comfyui.model'; import { AssistantModel } from '@/components/assistant/model'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; @@ -22,6 +23,7 @@ import { WidgetsInstalledModel } from '@/components/manager/manager-content/widg import { WidgetsMarketplaceModel } from '@/components/manager/manager-content/widgets/widgets-marketplace.model'; import { SettingsModel } from '@/components/settings/settings.model'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { FlowModel } from '@/stores/app/models/flow.model'; import { FormEngineModel } from '@/utils/form-engine.model'; import { FormikModel } from '@/utils/formik.model'; import { ModalModel } from '@/utils/modal.model'; @@ -103,6 +105,8 @@ export const webModule = new ContainerModule(bind => { bind('ComfyUIModel').to(ComfyUIModel).inSingletonScope(); + bind(HomeModel).toSelf().inSingletonScope(); + // refactor app builder container .bind('AppBuilderModel') @@ -136,6 +140,8 @@ export const webModule = new ContainerModule(bind => { return () => context.container.get('AppBuilderModel'); }); + bind(FlowModel).toSelf().inSingletonScope(); + // assistant bind(AssistantModel).toSelf().inSingletonScope(); // feedback diff --git a/web/apps/web/src/components/app/constants.tsx b/web/apps/web/src/components/app/constants.tsx index c650cb86..420ed61d 100644 --- a/web/apps/web/src/components/app/constants.tsx +++ b/web/apps/web/src/components/app/constants.tsx @@ -7,12 +7,20 @@ import { } from '@shellagent/flow-engine'; import { Workflow } from '@shellagent/pro-config'; -import { DefaultEdge, CustomEdge, EdgeTypeEnum } from '@/components/app/edges'; +import { + DefaultEdge, + CustomEdge, + EdgeTypeEnum, + XCustomEdge, +} from '@/components/app/edges'; import { StartNode, StateNode, IntroNode, NoteNode, + XStateNode, + ConditionStateNode, + XIntroNode, } from '@/components/app/nodes'; export const nodeTypes = { @@ -22,12 +30,25 @@ export const nodeTypes = { [NodeTypeEnum.note]: NoteNode, }; +export const xNodeTypes = { + [NodeTypeEnum.start]: StartNode, + [NodeTypeEnum.intro]: XIntroNode, + [NodeTypeEnum.state]: XStateNode, + [NodeTypeEnum.condition_state]: ConditionStateNode, + [NodeTypeEnum.note]: NoteNode, +}; + export const edgeTypes = { [EdgeTypeEnum.default]: DefaultEdge, [EdgeTypeEnum.custom]: CustomEdge, }; -export const defaultFlow: IFlow = { +export const xEdgeTypes = { + [EdgeTypeEnum.default]: DefaultEdge, + [EdgeTypeEnum.custom]: XCustomEdge, +}; + +export const chatDefaultFlow: IFlow = { nodes: [ { width: 377, @@ -108,6 +129,87 @@ export const defaultFlow: IFlow = { }, }; +export const xDefaultFlow: IFlow = { + nodes: [ + { + width: 377, + height: 185, + id: NodeIdEnum.start, + position: { + x: 18.00341796875, + y: 290.0034713745117, + }, + type: NodeTypeEnum.start, + selectable: true, + focusable: true, + draggable: true, + data: { + type: NodeTypeEnum.start, + id: NodeIdEnum.start, + display_name: 'Start', + }, + selected: false, + positionAbsolute: { + x: 18.00341796875, + y: 290.0034713745117, + }, + dragging: false, + }, + { + id: 'intro', + position: { + x: 592.1845769352449, + y: 290.0034713745117, + }, + type: 'intro', + selectable: true, + focusable: true, + draggable: true, + data: { + type: 'intro', + id: NodeIdEnum.intro, + name: 'Intro', + display_name: 'Ready', + }, + width: 500, + height: 218, + selected: false, + positionAbsolute: { + x: 592.1845769352449, + y: 290.0034713745117, + }, + dragging: false, + }, + ], + edges: [ + { + type: 'default_edge', + style: { + strokeWidth: 2, + stroke: '#d1d5db', + }, + markerEnd: { + color: '#5A646Es', + height: 25, + strokeWidth: 2, + type: MarkerType.ArrowClosed, + width: 10, + }, + source: NodeIdEnum.start, + sourceHandle: NodeIdEnum.start, + target: 'intro', + targetHandle: 'intro', + animated: false, + id: 'reactflow__edge-@@@start@@@start-introintro', + }, + ], + viewport: { + x: 100, + y: 100, + zoom: 0.5, + }, +}; + export const defaultProConfig: Workflow = { type: 'workflow', blocks: [], @@ -210,6 +312,108 @@ export const materialList: MaterialListType = [ }, ]; +export const xMaterialList: MaterialListType = [ + { + plain: true, + items: [ + { + name: 'State', + display_name: 'State', + type: NodeTypeEnum.state, + }, + { + name: 'Condition State', + display_name: 'Condition State', + type: NodeTypeEnum.condition_state, + }, + { + name: 'Note', + display_name: 'Note', + type: NodeTypeEnum.note, + }, + ], + }, + { + plain: true, + no_border: true, + items: [ + { + display_name: 'Workflow Runner', + name: 'Workflow Runner', + type: NodeTypeEnum.workflow, + undraggable: true, + }, + ], + }, + { + title: 'Large Language Model', + plain: true, + no_border: true, + items: [ + { + name: 'GPTWidget', + display_name: 'GPT', + type: NodeTypeEnum.widget, + undraggable: true, + }, + { + name: 'ClaudeWidget', + display_name: 'Claude', + type: NodeTypeEnum.widget, + undraggable: true, + widget_name: '@myshell_llm/1744218088699596812', + }, + ], + }, + { + title: 'Tools', + plain: true, + no_border: true, + items: [ + { + name: 'ImageCanvasWidget', + display_name: 'Image Canvas', + type: NodeTypeEnum.widget, + undraggable: true, + }, + { + name: 'XWidget', + display_name: 'Twitter', + type: NodeTypeEnum.widget, + undraggable: true, + widget_name: '@myshell/1784206090390036480', + }, + { + name: 'Html2ImgWidget', + display_name: 'HTML to Image', + type: NodeTypeEnum.widget, + undraggable: true, + widget_name: '@myshell/1850127954369142784', + }, + { + name: 'ImageTextFuserWidget', + display_name: 'Image Text Fuser', + type: NodeTypeEnum.widget, + undraggable: true, + }, + ], + }, + { + title: 'Plugins', + plain: true, + no_border: true, + items: [ + { + name: 'ComfyUIWidget', + display_name: 'ComfyUI', + type: NodeTypeEnum.widget, + custom: true, + undraggable: true, + }, + ], + }, +]; + export const inputSourceHandle = 'custom_message-input-source-handle'; export const buttonSourceHandle = 'custom_button-source-handle'; diff --git a/web/apps/web/src/components/app/edges/custom-edge.tsx b/web/apps/web/src/components/app/edges/custom-edge.tsx index acbdc994..19e133e6 100644 --- a/web/apps/web/src/components/app/edges/custom-edge.tsx +++ b/web/apps/web/src/components/app/edges/custom-edge.tsx @@ -36,8 +36,11 @@ export const CustomEdge = ({ const [buttonWidth, setButtonWidth] = useState(0); const appBuilder = useInjection('AppBuilderModel'); - const setTransitionSheetOpen = useAppState( - state => state.setTransitionSheetOpen, + const { setTransitionSheetOpen, transitionSheetOpen } = useAppState( + state => ({ + setTransitionSheetOpen: state.setTransitionSheetOpen, + transitionSheetOpen: state.transitionSheetOpen, + }), ); useEffect(() => { @@ -80,7 +83,7 @@ export const CustomEdge = ({ // }, [edges, id, source]); useKeyPress(['delete', 'backspace'], () => { - if (selected) { + if (selected && !transitionSheetOpen) { onDelEdge({ id }); appBuilder.handleRefScene({ scene: RefSceneEnum.Enum.remove_edge, diff --git a/web/apps/web/src/components/app/edges/index.tsx b/web/apps/web/src/components/app/edges/index.tsx index 3c694775..bd528265 100644 --- a/web/apps/web/src/components/app/edges/index.tsx +++ b/web/apps/web/src/components/app/edges/index.tsx @@ -1,4 +1,5 @@ export { EdgeTypeEnum, EdgeDataTypeEnum } from './type'; export { CustomEdge } from './custom-edge'; export { DefaultEdge } from './default-edge'; +export { XCustomEdge } from './x-custom-edge'; export { type ICondition, type CustomEdgeData, type ICustomEdge } from './type'; diff --git a/web/apps/web/src/components/app/edges/type.ts b/web/apps/web/src/components/app/edges/type.ts index b4f89c6b..8a114f2d 100644 --- a/web/apps/web/src/components/app/edges/type.ts +++ b/web/apps/web/src/components/app/edges/type.ts @@ -1,4 +1,5 @@ import { Edge } from '@shellagent/flow-engine'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; export enum EdgeTypeEnum { custom = 'custom_edge', @@ -22,13 +23,14 @@ export type CustomEdgeData = { id?: string; custom?: boolean; event_key?: string; - type?: EdgeDataTypeEnum; + type?: TransitionTargetEnum; source?: string; target?: string; conditions?: { condition: string; target_inputs: Record; }[]; + display_name?: string; }; export type ICustomEdge = Edge; diff --git a/web/apps/web/src/components/app/edges/x-custom-edge.tsx b/web/apps/web/src/components/app/edges/x-custom-edge.tsx new file mode 100644 index 00000000..ed40c6fa --- /dev/null +++ b/web/apps/web/src/components/app/edges/x-custom-edge.tsx @@ -0,0 +1,120 @@ +import { + BaseEdge, + EdgeProps, + getBezierPath, + NodeIdEnum, + useReactFlowStore, + EdgeText, +} from '@shellagent/flow-engine'; +import { RefSceneEnum } from '@shellagent/shared/protocol/app-scope'; +import { useKeyPress } from 'ahooks'; +import { useInjection } from 'inversify-react'; +import React, { useEffect } from 'react'; +import { useAppState } from '@/stores/app/use-app-state'; + +import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; + +import { + TransitionTypeEnum, + TransitionData, +} from '@shellagent/shared/protocol/transition'; +export const XCustomEdge = ({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + markerEnd, + selected, + target, + source, + sourceHandleId, + data, +}: EdgeProps) => { + const appBuilder = useInjection('AppBuilderModel'); + const [edgePath] = getBezierPath({ + sourceX: sourceX - 8, + sourceY, + sourcePosition, + targetX: targetX + 2, + targetY, + targetPosition, + }); + + const { setNewTransitionSheetOpen, newTransitionSheetOpen } = useAppState( + state => ({ + setNewTransitionSheetOpen: state.setNewTransitionSheetOpen, + newTransitionSheetOpen: state.newTransitionSheetOpen, + }), + ); + + const onDelEdge = useReactFlowStore(state => state.onDelEdge); + const selectedEdges = useReactFlowStore(state => state.selectedEdges); + const edges = useReactFlowStore(state => state.edges); + const nodes = useReactFlowStore(state => state.nodes); + + useKeyPress(['delete', 'backspace'], () => { + if (selected && !newTransitionSheetOpen) { + onDelEdge({ id }); + appBuilder.handleRefScene({ + scene: RefSceneEnum.Enum.remove_edge, + params: { + edges: edges as any, + removeEdge: { + target: target as Lowercase, + source: source as Lowercase, + }, + }, + }); + } + }); + + useEffect(() => { + if (selectedEdges.length > 0 && selectedEdges[0].id === id) { + setNewTransitionSheetOpen({ open: false, source: '', sourceHandle: '' }); + setTimeout(() => { + setNewTransitionSheetOpen({ + open: true, + source, + sourceHandle: sourceHandleId || '', + id, + data: { + ...(data || {}), + target, + source, + }, + transitionType: nodes.find(node => node.id === source) + ?.type as TransitionTypeEnum, + }); + }); + } + }, [selectedEdges]); + + return ( + <> + + + + ); +}; + +XCustomEdge.displayName = 'XCustomEdge'; diff --git a/web/apps/web/src/components/app/flow-header/index.tsx b/web/apps/web/src/components/app/flow-header/index.tsx index a55e920a..ca3a97fc 100644 --- a/web/apps/web/src/components/app/flow-header/index.tsx +++ b/web/apps/web/src/components/app/flow-header/index.tsx @@ -54,13 +54,17 @@ const FlowHeader = ({ const reactflow: IFlow = appBuilder.flowInstance?.toObject() as IFlow; if ((!isEmpty(reactflow?.edges) || !isEmpty(reactflow?.nodes)) && appId) { - const automata = genAutomata(reactflow, appBuilder.nodeData); + const automata = genAutomata({ + flow: reactflow, + nodeData: appBuilder.nodeData, + appType: appBuilder.appType, + }); if (!isDeepEmpty(pick(automata, ['blocks', 'context']))) { try { const result = await appBuilder.saveApp(appId, false); if (result?.success) { setAutoSavedSuccess(true); - setAutoSavedTime(dayjs().valueOf()); + setAutoSavedTime(dayjs().valueOf().toString()); } else { setAutoSavedSuccess(false); } diff --git a/web/apps/web/src/components/app/new-transition-sheet/index.tsx b/web/apps/web/src/components/app/new-transition-sheet/index.tsx new file mode 100644 index 00000000..e17d6878 --- /dev/null +++ b/web/apps/web/src/components/app/new-transition-sheet/index.tsx @@ -0,0 +1,134 @@ +'use client'; + +import { useReactFlowStore, NodeTypeEnum } from '@shellagent/flow-engine'; +import { TValues } from '@shellagent/form-engine'; +import { Drawer } from '@shellagent/ui'; +import { useInjection } from 'inversify-react'; +import { useMemo, useCallback, memo } from 'react'; + +import NodeForm from '@/components/app/node-form'; +import { SchemaProvider } from '@/stores/app/schema-provider'; +import { getXTransitionSchema } from '@/stores/app/schema/get-transition-schema'; +import { observer } from 'mobx-react-lite'; +import { EditableTitle } from '@/components/app/editable-title'; +import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { useAppState } from '@/stores/app/use-app-state'; +import { type TransitionData } from '@shellagent/shared/protocol/transition'; +import { getNewKey } from '@shellagent/shared/utils'; + +const TransitionSheetNew: React.FC<{}> = observer(() => { + const appBuilder = useInjection('AppBuilderModel'); + + const { setEdges, onChangeEdgeData, edges } = useReactFlowStore(state => ({ + setEdges: state.setEdges, + onChangeEdgeData: state.onChangeEdgeData, + edges: state.edges, + })); + + const { + sourceHandle, + source, + currentEdgeId, + newTransitionSheetOpen, + setNewTransitionSheetOpen, + transitionType, + } = useAppState(state => ({ + sourceHandle: state.currentTransitionSourceHandle, + source: state.currentTransitionSource, + newTransitionSheetOpen: state.newTransitionSheetOpen, + setNewTransitionSheetOpen: state.setNewTransitionSheetOpen, + currentEdgeId: state.currentEdgeId, + transitionType: state.transitionType, + })); + + const handleClose = useCallback(() => { + setNewTransitionSheetOpen({ open: false, source: '', sourceHandle: '' }); + }, []); + + const edgeData = edges.find(edge => edge.id === currentEdgeId) + ?.data as TransitionData; + + const handleChange = useCallback( + (values: TransitionData) => { + if (edgeData?.target !== values.target) { + const currentEdge = edges.find(edge => edge.id === currentEdgeId); + if (currentEdge) { + setEdges( + edges.map(edge => { + if (edge.id === currentEdge.id) { + return { + ...edge, + target: values.target, + data: values, + }; + } + return edge; + }), + ); + } + } else { + onChangeEdgeData(currentEdgeId, values); + } + }, + [edgeData, edges, setEdges, onChangeEdgeData, currentEdgeId], + ); + + const schema = useMemo(() => { + return getXTransitionSchema( + appBuilder.nodeData[edgeData?.target || '']?.inputs ?? {}, + transitionType, + ); + }, [appBuilder.nodeData, edgeData, transitionType]); + + const handleTitleChange = useCallback( + (value: string) => { + const { key: newKey, name: newName } = getNewKey({ + name: value, + nameKey: 'name', + values: edges, + prefix: 'Transition', + }); + handleChange({ ...edgeData, name: newName, key: newKey }); + }, + [edgeData, edges, handleChange], + ); + + const title = useMemo( + () => , + [edgeData], + ); + + return ( + + + handleChange(values as TransitionData)} + /> + + + ); +}); + +export default memo(TransitionSheetNew, () => true); diff --git a/web/apps/web/src/components/app/node-form/index.tsx b/web/apps/web/src/components/app/node-form/index.tsx index a70afbcc..6beceb62 100644 --- a/web/apps/web/src/components/app/node-form/index.tsx +++ b/web/apps/web/src/components/app/node-form/index.tsx @@ -18,6 +18,7 @@ import { JSONView, FormRef, } from '@shellagent/ui'; +import { DatePicker } from 'antd'; import { isEmpty } from 'lodash-es'; import React, { useEffect, @@ -45,6 +46,8 @@ import { ValidateRender, ButtonValidateRender, DialogValidateRender, + StateSelector, + TimerSelector, } from './widgets'; import { OpenImageCanvas } from '../../image-canvas/open-image-canvas'; @@ -120,6 +123,9 @@ const NodeForm = forwardRef( ValidateRender, ButtonValidateRender, DialogValidateRender, + DatePicker, + StateSelector, + TimerSelector, ...components, }} /> diff --git a/web/apps/web/src/components/app/node-form/widgets/index.tsx b/web/apps/web/src/components/app/node-form/widgets/index.tsx index aa9edcd6..0c5d5c64 100644 --- a/web/apps/web/src/components/app/node-form/widgets/index.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/index.tsx @@ -10,3 +10,5 @@ export * from './variable-name-input'; export * from './validate-render'; export * from './validate-render/button'; export * from './validate-render/dialog'; +export * from './state-selector'; +export * from './timer-selector'; diff --git a/web/apps/web/src/components/app/node-form/widgets/state-selector.tsx b/web/apps/web/src/components/app/node-form/widgets/state-selector.tsx new file mode 100644 index 00000000..608f9ee3 --- /dev/null +++ b/web/apps/web/src/components/app/node-form/widgets/state-selector.tsx @@ -0,0 +1,46 @@ +import { Select } from '@shellagent/ui'; +import { useReactFlowStore, NodeTypeEnum } from '@shellagent/flow-engine'; + +interface StateSelectorProps { + value: string; + onChange: (value: string) => void; + className?: string; + excludeTargets?: string[]; + disabled?: boolean; +} + +export const StateSelector = ({ + value, + onChange, + className, + excludeTargets = [], + disabled = false, +}: StateSelectorProps) => { + const { nodes } = useReactFlowStore(state => ({ + nodes: state.nodes, + })); + + const options = nodes + .filter( + node => + node.data.type !== NodeTypeEnum.start && + !excludeTargets.includes(node.id), + ) + .map(node => ({ + label: node.data.display_name || 'Untitled State', + value: node.id, + })); + + return ( +
+ + +
+ +
+

+ End Time +

+ + + + Never + +
+ + End Date + + +
+
+ + Repeat + + + handleChange({ end: { count: value ?? undefined } }) + } + suffix="per" + /> +
+
+
+
+
+
+
+ setTimeZoneModalVisible(false)} + width={620} + className="rounded-lg" + footer={[ + , + ]}> +
+ -
-); - const ConditionItem = ({ value, onChange, onDelete, onOpen, - options, + index, + conditions, }: { value: ICondition; onChange: (value: ICondition) => void; onDelete: (value: ICondition) => void; onOpen: (open: boolean, value: ICondition) => void; - options: { label: string; value: string }[]; + index: number; + conditions: ICondition[]; }) => { const inputConRef = useRef(null); const handleChange = (field: 'condition' | 'target', newValue: string) => { @@ -60,6 +47,10 @@ const ConditionItem = ({ ); }; + const excludeTargets = conditions + .slice(0, index) + .map(condition => condition.target); + return (
@@ -76,10 +67,10 @@ const ConditionItem = ({
handleChange('target', v)} className="w-[100px]" + excludeTargets={excludeTargets} /> { - const includedTargets = value - .slice(0, index) - .map(condition => condition.target); - return nodes - .filter( - node => - node.data.type !== NodeTypeEnum.start && - !includedTargets.includes(node.id), - ) - .map(node => ({ - label: node.data.display_name || 'Untitled State', - value: node.id, - })); - }; - return ( -
+
{value?.map?.((condition, index) => ( handleChange(index, v)} onDelete={handleDelete} onOpen={() => handleOpen(true, index)} - options={getOptions(index)} + index={index} + conditions={value} /> ))} + ))} +
+ ); +}); diff --git a/web/apps/web/src/constants/timezones.ts b/web/apps/web/src/constants/timezones.ts new file mode 100644 index 00000000..e69de29b diff --git a/web/apps/web/src/services/app/index.tsx b/web/apps/web/src/services/app/index.tsx index b43778d0..60821e4e 100644 --- a/web/apps/web/src/services/app/index.tsx +++ b/web/apps/web/src/services/app/index.tsx @@ -70,6 +70,15 @@ export const initBot: Fetcher = params => { }); }; +export const convertXBotSvc: Fetcher< + InitBotRequest, + InitBotRequest +> = params => { + return APIFetch.post('/api/app/convert_x_bot', { + body: params, + }); +}; + export const runApp = ( params: RunAppRequest, cb: { diff --git a/web/apps/web/src/services/app/type.ts b/web/apps/web/src/services/app/type.ts index 7c352866..266c6c80 100644 --- a/web/apps/web/src/services/app/type.ts +++ b/web/apps/web/src/services/app/type.ts @@ -9,6 +9,7 @@ export interface AppMetadata { name: string; description: string; avatar?: string; // 默认logo,save自动截图 + app_type?: 'chat_bot' | 'x_bot'; } // 获取automata diff --git a/web/apps/web/src/services/home/type.ts b/web/apps/web/src/services/home/type.ts index 697e9466..fd05e2bf 100644 --- a/web/apps/web/src/services/home/type.ts +++ b/web/apps/web/src/services/home/type.ts @@ -3,6 +3,7 @@ export interface Metadata { description: string; avatar?: string; // 默认logo,save自动截图 categories?: string[]; + app_type?: 'chat_bot' | 'x_bot'; } export type Type = 'app' | 'workflow'; diff --git a/web/apps/web/src/stores/app/models/app-builder-utils.ts b/web/apps/web/src/stores/app/models/app-builder-utils.ts index 9761a529..8d269eb1 100644 --- a/web/apps/web/src/stores/app/models/app-builder-utils.ts +++ b/web/apps/web/src/stores/app/models/app-builder-utils.ts @@ -170,6 +170,21 @@ export function convertRefOptsToCascaderOpts( }); } + if (!isEmpty(refOpts.local.twitter?.variables)) { + localOptions.push({ + label: 'Twitter', + value: 'Twitter', + children: Object.entries(refOpts.local.twitter.variables).map( + ([key, value]) => ({ + label: value.display_name, + value: `{{payload.${key}}}`, + field_type: value.type, + parent: 'twitter', + }), + ), + }); + } + if (!isEmpty(refOpts.local.inputs.variables)) { localOptions.push({ label: 'Input', diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index b3c5ec68..1b910dd3 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -5,7 +5,7 @@ import { NodeTypeEnum, WidgetItem, } from '@shellagent/flow-engine'; -import { CustomEventName, CustomKey } from '@shellagent/pro-config'; +import { CustomKey, CustomEventName } from '@shellagent/pro-config'; import { customSnakeCase, getRefOptions, @@ -29,7 +29,7 @@ import { runInAction, } from 'mobx'; -import { defaultFlow } from '@/components/app/constants'; +import { chatDefaultFlow, xDefaultFlow } from '@/components/app/constants'; import { ComfyUIModel } from '@/components/app/plugins/comfyui/comfyui.model'; import { COMFYUI_API, @@ -71,7 +71,6 @@ import { } from '@/stores/app/utils/data-transformer'; import type { Config, Metadata, NodeDataType } from '@/types/app/types'; import { ToastModel } from '@/utils/toast.model'; - import { CascaderOption, convertRefOptsToCascaderOpts, @@ -113,6 +112,7 @@ export class AppBuilderModel { @observable metadata: Metadata = { name: '', description: '', + app_type: 'chat_bot', }; flowInstance: ReactFlowInstance | null = null; @@ -241,6 +241,10 @@ export class AppBuilderModel { return this.config.validateIssues || {}; } + get appType() { + return this.metadata.app_type || 'chat_bot'; + } + get runDisabled() { return Object.values(this.validateIssues).some(issues => issues.some(issue => issue.type === IssueTypeEnum.critical), @@ -338,7 +342,11 @@ export class AppBuilderModel { try { this.getAutomataLoading = true; const { data } = await fetchAutomata(params); - this.nodeData = genNodeData(data, nodes); + this.nodeData = genNodeData({ + automata: data, + nodes, + appType: this.appType, + }); this.config.refs = this.config.refs || fieldsModeMap2Refs(data, this.config.fieldsModeMap); } finally { @@ -365,6 +373,8 @@ export class AppBuilderModel { metadata, } = await fetchFlow(params); if (instance) { + const defaultFlow = + metadata.app_type === 'x_bot' ? xDefaultFlow : chatDefaultFlow; const flow = formatReactFlow2Flow({ edges: reactflow?.edges.length ? reactflow.edges : defaultFlow.edges, nodes: reactflow?.nodes.length ? reactflow.nodes : defaultFlow.nodes, @@ -476,7 +486,11 @@ export class AppBuilderModel { const result = await releaseApp({ app_id, reactflow, - automata: genAutomata(reactflow, this.nodeData), + automata: genAutomata({ + flow: reactflow, + nodeData: this.nodeData, + appType: this.appType, + }), config: this.config, version_name: this.versionName, metadata: this.metadata, @@ -507,7 +521,11 @@ export class AppBuilderModel { const result = await saveApp({ reactflow, config: this.config, - automata: genAutomata(reactflow, this.nodeData), + automata: genAutomata({ + flow: reactflow, + nodeData: this.nodeData, + appType: this.appType, + }), app_id, }); if (result.success) { @@ -542,7 +560,11 @@ export class AppBuilderModel { const result = await saveApp({ app_id, reactflow, - automata: genAutomata(reactflow, this.nodeData), + automata: genAutomata({ + flow: reactflow, + nodeData: this.nodeData, + appType: this.appType, + }), config: this.config, }); if (result.success) { @@ -646,7 +668,12 @@ export class AppBuilderModel { const comfyui_api = settingsDisabled ? DEFAULT_COMFYUI_API : this.settingsModel.envs.get(COMFYUI_API) || ''; - const automata = genAutomata(reactflow, this.nodeData, comfyui_api); + const automata = genAutomata({ + flow: reactflow, + nodeData: this.nodeData, + comfyui_api, + appType: this.appType, + }); try { await this.chatModel.initBot(automata); } catch (error: any) { @@ -679,10 +706,11 @@ export class AppBuilderModel { @action.bound async checkIntroIp() { - const automata = genAutomata( - this.flowInstance?.toObject() as IFlow, - this.nodeData, - ); + const automata = genAutomata({ + flow: this.flowInstance?.toObject() as IFlow, + nodeData: this.nodeData, + appType: this.appType, + }); try { const { images = [], text = '' } = await getIntroDisplay({ automata, @@ -818,4 +846,15 @@ export class AppBuilderModel { } } } + + onAddStateNode(onAddNode: FlowAction['onAddNode'], position: XYPosition) { + onAddNode({ + type: NodeTypeEnum.state, + position: this.flowInstance?.project(position) || position, + data: { + name: 'State', + display_name: 'State', + }, + }); + } } diff --git a/web/apps/web/src/stores/app/models/flow.model.ts b/web/apps/web/src/stores/app/models/flow.model.ts new file mode 100644 index 00000000..533f145f --- /dev/null +++ b/web/apps/web/src/stores/app/models/flow.model.ts @@ -0,0 +1,146 @@ +import { makeAutoObservable } from 'mobx'; +import { injectable } from 'inversify'; +import { Edge, Node } from '@shellagent/flow-engine'; +import { EdgeDataTypeEnum, CustomEdgeData } from '@/components/app/edges'; + +type SheetMode = 'button' | 'workflow' | 'widget' | ''; + +interface BaseState { + currentStateId: string; + stateConfigSheetOpen: boolean; + insideSheetOpen: boolean; + insideSheetMode: SheetMode; + transitionSheetOpen: boolean; + currentButtonId: string; + currentTaskIndex?: number; + currentTransitionSource: string; + currentTransitionSourceHandle: string; + currentEdgeData?: CustomEdgeData; + targetInputsSheetOpen: boolean; +} + +@injectable() +export class FlowModel { + currentStateId: string = ''; + stateConfigSheetOpen: boolean = false; + insideSheetOpen: boolean = false; + insideSheetMode: 'button' | 'workflow' | 'widget' | '' = ''; + transitionSheetOpen: boolean = false; + runDrawerWidth: number = 715; + selectedNode: Node | undefined = undefined; + currentTaskIndex?: number; + currentButtonId: string = ''; + currentTransitionSource: string = ''; + currentTransitionSourceHandle: string = ''; + currentEdgeData: CustomEdgeData | undefined = undefined; + targetInputsSheetOpen: boolean = false; + selectedEdge: Edge | null = null; + + constructor() { + makeAutoObservable(this); + } + + private resetSheetState() { + const baseState: BaseState = { + currentStateId: '', + stateConfigSheetOpen: false, + insideSheetOpen: false, + insideSheetMode: '', + transitionSheetOpen: false, + currentButtonId: '', + currentTaskIndex: undefined, + currentTransitionSource: '', + currentTransitionSourceHandle: '', + currentEdgeData: undefined, + targetInputsSheetOpen: false, + }; + Object.assign(this, baseState); + } + + setSelectedNode = (node: Node) => { + this.selectedNode = node; + }; + + setStateConfigSheetOpen = (stateId: string, open: boolean) => { + if (!open) { + this.resetSheetState(); + return; + } + + if (this.currentStateId === stateId) return; + + this.resetSheetState(); + this.stateConfigSheetOpen = true; + this.currentStateId = stateId; + }; + + setInsideSheetOpen = (params: { + stateId: string; + open: boolean; + mode?: SheetMode; + buttonId?: string; + currentTaskIndex?: number; + }) => { + const { stateId, open, mode, buttonId, currentTaskIndex } = params; + + if (!open) { + this.resetSheetState(); + return; + } + + this.resetSheetState(); + this.stateConfigSheetOpen = true; + this.currentStateId = stateId; + this.insideSheetOpen = true; + this.insideSheetMode = mode || ''; + this.currentButtonId = mode === 'button' ? buttonId || '' : ''; + this.currentTaskIndex = currentTaskIndex; + }; + + setTransitionSheetOpen = (params: { + open: boolean; + source: string; + sourceHandle: string; + data?: CustomEdgeData; + }) => { + const { open, source, sourceHandle, data } = params; + + if (!open) { + this.resetSheetState(); + return; + } + + this.resetSheetState(); + this.transitionSheetOpen = true; + this.currentTransitionSource = source; + this.currentTransitionSourceHandle = sourceHandle; + this.currentEdgeData = data; + }; + + setTargetInputsSheetOpen = (open: boolean) => { + this.targetInputsSheetOpen = open; + }; + + setRunDrawerWidth = (width: number) => { + this.runDrawerWidth = width; + }; + + setSelectedEdge = (edge: Edge) => { + this.selectedEdge = edge; + }; + + resetState = () => { + this.resetSheetState(); + this.runDrawerWidth = 715; + this.selectedNode = undefined; + this.currentEdgeData = { + id: '', + custom: true, + event_key: '', + type: EdgeDataTypeEnum.ALWAYS, + target: '', + conditions: [], + }; + this.selectedEdge = null; + }; +} diff --git a/web/apps/web/src/stores/app/schema-provider.tsx b/web/apps/web/src/stores/app/schema-provider.tsx index ba604c04..fe44e318 100644 --- a/web/apps/web/src/stores/app/schema-provider.tsx +++ b/web/apps/web/src/stores/app/schema-provider.tsx @@ -14,6 +14,8 @@ import { generateUUID } from '@/utils/common-helper'; import { introConfigSchema } from './schema/intro-config'; import { startSchema } from './schema/start-node'; import { stateConfigSchema } from './schema/state-config'; +import { xStateConfigSchema } from './schema/x-state-config'; +import { getConditionStateSchema } from './schema/condition-state-schema'; type SchemaState = { id: string; @@ -106,10 +108,22 @@ export const SchemaProvider: React.FC = ({ return introConfigSchema; } + if (type === 'x-intro-config') { + return stateConfigSchema; + } + if (type === 'state-config') { return stateConfigSchema; } + if (type === 'x-state-config') { + return xStateConfigSchema; + } + + if (type === 'condition-state-config') { + return getConditionStateSchema; + } + if (type === NodeTypeEnum.state) { return getStateSchema(display_name); } diff --git a/web/apps/web/src/stores/app/schema/condition-state-schema.ts b/web/apps/web/src/stores/app/schema/condition-state-schema.ts new file mode 100644 index 00000000..87873765 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/condition-state-schema.ts @@ -0,0 +1,1021 @@ +import { ISchema } from '@shellagent/form-engine'; +import { FieldModeEnum } from '@shellagent/shared/protocol/extend-config'; + +import { validateCustomKey } from '@/stores/app/utils/validator'; +import { ENABLE_MIME } from '@/utils/file-types'; + +export const getConditionStateSchema: ISchema = { + type: 'object', + 'x-type': 'Block', + 'x-title-size': 'h5', + 'x-class': 'space-y-3', + properties: { + inputs: { + type: 'object', + title: 'Input', + additionalProperties: { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + // 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'UnfocusInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-full', + 'x-component-props': { + size: '2xs', + autoFocus: false, + maxLength: 30, + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + ], + }, + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + triggerClassName: 'h-7 w-32', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-32', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + }, + user_input: { + type: 'boolean', + default: true, + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + }, + }, + 'x-key': '{{name}} Inputs_{{counter}}', + 'x-type': 'Inline', + 'x-validator-render': 'ValidateRender', + 'x-validator': [ + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value.name); + }, + }, + ], + 'x-draggable': true, + 'x-deletable': true, + 'x-edit-dialog': { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 30, + size: 'sm', + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + type: { + type: 'string', + default: 'text', + title: 'Type', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "text"', + fullfill: { + schema: { + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices 12', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: { + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "image"', + fullfill: { + schema: { + default: [{ value: 'a', label: 'A' }], + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + properties: { + label: { + title: 'Label', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'Input', + 'x-component-props': { + maxLength: 30, + size: 'sm', + }, + }, + value: { + title: 'Value', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: { + 'image/apng': ['.apng'], + 'image/png': ['.png'], + 'image/jpeg': ['.jpeg'], + 'image/jpg': ['.jpg'], + 'image/webp': ['.webp'], + 'image/gif': ['.gif'], + 'image/bmp': ['.bmp'], + 'image/tiff': ['.tiff'], + }, + }, + }, + }, + required: ['value'], + title: 'Choice Item', + 'x-type': 'Card', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-role': 'core', + 'x-deletable': true, + }, + }, + }, + }, + ], + }, + user_input: { + type: 'boolean', + default: true, + title: 'User Input', + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-reactions': [ + { + target: 'source', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'source', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + source: { + title: 'Source', + type: 'string', + default: 'IM', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'IM', value: 'IM' }, + { label: 'form', value: 'form' }, + ], + }, + 'x-reactions': [ + { + target: 'choices', + when: '$this.value === "IM"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + default_value: { + type: 'string', + title: 'Default Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + value: { + type: 'string', + title: 'Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + description: { + type: 'string', + title: 'Description', + 'x-raw': true, + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3 pb-3', + 'x-component-props': { + maxLength: 500, + placeholder: + 'Please describe how to use this input variable. This description will be displayed when the user inputs.', + }, + 'x-validator': [ + { + exclusiveMaximum: 500, + message: 'Cannot exceed 500 characters', + }, + ], + 'x-type': 'Control', + }, + choices: { + 'x-hidden': true, + type: 'array', + 'x-title-size': 'h5', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: {}, + }, + }, + validations: { + type: 'array', + title: 'Validations', + 'x-title-size': 'h5', + additionalItems: { + type: 'object', + properties: { + void_core: { + type: 'void', + 'x-role': 'core', + 'x-type': 'Grid', + 'x-class': 'grid-cols-3', + properties: { + rule_type: { + type: 'string', + enum: [ + 'max_length', + 'min_number', + 'max_number', + 'max_file_size', + ], + 'x-component': 'Select', + 'x-component-props': { + placeholder: 'Rule type', + options: [ + { label: 'Max Length', value: 'max_length' }, + { label: 'Min Number', value: 'min_number' }, + { label: 'Max Number', value: 'max_number' }, + { label: 'Max File Size', value: 'max_file_size' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'max_length', + when: '$this.value === "max_length"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'min_number', + when: '$this.value === "min_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_number', + when: '$this.value === "max_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_file_size', + when: '$this.value === "max_file_size"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + max_length: { + type: 'number', + default: 500, + 'x-component': 'NumberInput', + 'x-type': 'Control', + // 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 15000, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 15000 }, + ], + }, + min_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_file_size: { + type: 'number', + default: 10 * 1024 * 1024, + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 100 * 1024 * 1024, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 100 * 1024 * 1024 }, + ], + }, + error_message: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + placeholder: 'Error Message', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + }, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, + blocks: { + title: 'Task', + type: 'array', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-component': 'TasksConfig', + 'x-component-props': { + draggable: true, + }, + }, + outputs: { + type: 'object', + title: 'Output', + additionalProperties: { + type: 'object', + properties: { + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-hidden': true, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ref, + 'x-parent-deletable': true, + 'x-title-editable': true, + 'x-title-component-props': { + showDialog: true, + defaultKey: 'display_name', + dialogConfig: { + title: 'Edit Output', + schema: { + type: 'object', + properties: { + name_mode: { + type: 'string', + default: FieldModeEnum.Enum.ui, + title: 'Mode', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'type', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'description', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'VariableNameInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-component-props': { + maxLength: 30, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + required: true, + message: 'Please input the Variable Name', + }, + { + maxLength: 50, + message: 'Cannot exceed 50 characters', + }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + display_name: { + type: 'string', + 'x-hidden': true, + }, + type: { + type: 'string', + default: 'text', + enum: [ + 'text', + 'image', + 'audio', + 'video', + 'text_file', + 'file', + ], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-layout': 'Vertical', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + description: { + type: 'string', + title: 'Description', + default: '', + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 300, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + exclusiveMaximum: 300, + message: 'Cannot exceed 300 characters', + }, + ], + 'x-type': 'Control', + }, + }, + }, + }, + }, + 'x-component-props': { + size: '2xs', + }, + 'x-layout': 'Horizontal', + 'x-validator': [ + { required: true, message: 'The value is required' }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-hidden': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + 'x-key': '{{name}} Outputs_{{counter}}', + 'x-type': 'Inline', + 'x-collapsible': true, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, + }, +}; diff --git a/web/apps/web/src/stores/app/schema/get-condition-state-schema.ts b/web/apps/web/src/stores/app/schema/get-condition-state-schema.ts new file mode 100644 index 00000000..f06cc521 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/get-condition-state-schema.ts @@ -0,0 +1,27 @@ +import { ISchema } from '@shellagent/form-engine'; + +export const getConditionStateSchema = (name: string): ISchema => { + return { + title: name, + type: 'object', + 'x-type': 'Section', + 'x-title-size': 'h4', + 'x-title-component-props': { + className: 'bg-red-500', + }, + 'x-title-copiable': false, + properties: { + blocks: { + default: [], + type: 'array', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-component': 'TasksConfig', + 'x-component-props': { + draggable: false, + }, + }, + }, + }; +}; diff --git a/web/apps/web/src/stores/app/schema/get-transition-schema.ts b/web/apps/web/src/stores/app/schema/get-transition-schema.ts new file mode 100644 index 00000000..a1152adf --- /dev/null +++ b/web/apps/web/src/stores/app/schema/get-transition-schema.ts @@ -0,0 +1,115 @@ +import { ISchema, TValues } from '@shellagent/form-engine'; +import { getSchemaByInputs } from '@/stores/app/schema/get-workflow-schema'; +import { TransitionTypeEnum } from '@shellagent/shared/protocol/transition'; +import dayjs from 'dayjs'; + +export const getXTransitionSchema = ( + inputs: TValues, + transitionType: TransitionTypeEnum, +): ISchema => { + let options: { label: string; value: string }[] = []; + if (transitionType === TransitionTypeEnum.intro) { + options = [ + { label: 'timer', value: 'TIMER' }, + { label: 'mentioned', value: 'X.MENTIONED' }, + { label: 'pinned.commented', value: 'X.PINNED.REPLIED' }, + ]; + } else { + options = [{ label: 'always', value: 'ALWAYS' }]; + } + return { + type: 'object', + 'x-type': 'Block', + properties: { + name: { + type: 'string', + 'x-type': 'Control', + 'x-hidden': true, + }, + type: { + type: 'string', + 'x-type': 'Control', + 'x-component': 'Select', + enum: ['TIMER', 'X.MENTIONED', 'X.PINNED.REPLIED'], + 'x-component-props': { + disabled: transitionType !== TransitionTypeEnum.intro, + placeholder: 'Select your transition type', + options, + defaultValue: + transitionType !== TransitionTypeEnum.intro ? 'ALWAYS' : 'TIMER', + }, + 'x-hidden': transitionType !== TransitionTypeEnum.intro, + 'x-onchange-prop-name': 'onValueChange', + 'x-reactions': [ + { + target: 'timers', + when: '$this.value === "timer"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'timers', + when: '$this.value === "X.MENTIONED"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'timers', + when: '$this.value === "X.PINNED.REPLIED"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + timers: { + type: 'object', + 'x-type': 'Control', + 'x-component': 'TimerSelector', + 'x-hidden': transitionType !== TransitionTypeEnum.intro, + 'x-component-props': { + defaultValues: { + start: { + year: dayjs().year(), + month: dayjs().month() + 1, + date: dayjs().date(), + hour: dayjs().hour(), + minute: dayjs().minute(), + timezone: '', + }, + interval: { + hour: 1, + }, + }, + }, + }, + condition: { + title: 'Condition', + type: 'string', + 'x-type': 'Control', + 'x-component': 'ExpressionInput', + 'x-component-props': { + placeholder: 'Condition', + singleLine: true, + }, + 'x-hidden': transitionType !== TransitionTypeEnum.condition_state, + }, + target: { + title: 'Target State', + 'x-inline': true, + type: 'string', + 'x-type': 'Control', + 'x-component': 'StateSelector', + }, + target_inputs: getSchemaByInputs(inputs), + }, + }; +}; diff --git a/web/apps/web/src/stores/app/schema/get-x-state-schema.ts b/web/apps/web/src/stores/app/schema/get-x-state-schema.ts new file mode 100644 index 00000000..d12ed6e8 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/get-x-state-schema.ts @@ -0,0 +1,24 @@ +import { ISchema } from '@shellagent/form-engine'; + +export const getXStateSchema = (name: string): ISchema => { + return { + title: name, + type: 'object', + 'x-type': 'Section', + 'x-title-size': 'h4', + 'x-title-copiable': false, + properties: { + blocks: { + default: [], + type: 'array', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-component': 'TasksConfig', + 'x-component-props': { + draggable: false, + }, + }, + }, + }; +}; diff --git a/web/apps/web/src/stores/app/schema/x-state-config.ts b/web/apps/web/src/stores/app/schema/x-state-config.ts new file mode 100644 index 00000000..866f4333 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/x-state-config.ts @@ -0,0 +1,1088 @@ +import { ISchema } from '@shellagent/form-engine'; +import { FieldModeEnum } from '@shellagent/shared/protocol/extend-config'; + +import { validateCustomKey } from '@/stores/app/utils/validator'; +import { ENABLE_MIME } from '@/utils/file-types'; + +export const xStateConfigSchema: ISchema = { + type: 'object', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-class': 'space-y-3', + properties: { + inputs: { + type: 'object', + title: 'Input', + additionalProperties: { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + // 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'UnfocusInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-full', + 'x-component-props': { + size: '2xs', + autoFocus: false, + maxLength: 30, + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + ], + }, + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + triggerClassName: 'h-7 w-32', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-32', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + }, + user_input: { + type: 'boolean', + default: true, + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + }, + }, + 'x-key': '{{name}} Inputs_{{counter}}', + 'x-type': 'Inline', + 'x-validator-render': 'ValidateRender', + 'x-validator': [ + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value.name); + }, + }, + ], + 'x-draggable': true, + 'x-deletable': true, + 'x-edit-dialog': { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 30, + size: 'sm', + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + type: { + type: 'string', + default: 'text', + title: 'Type', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "text"', + fullfill: { + schema: { + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices 12', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: { + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "image"', + fullfill: { + schema: { + default: [{ value: 'a', label: 'A' }], + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + properties: { + label: { + title: 'Label', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'Input', + 'x-component-props': { + maxLength: 30, + size: 'sm', + }, + }, + value: { + title: 'Value', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: { + 'image/apng': ['.apng'], + 'image/png': ['.png'], + 'image/jpeg': ['.jpeg'], + 'image/jpg': ['.jpg'], + 'image/webp': ['.webp'], + 'image/gif': ['.gif'], + 'image/bmp': ['.bmp'], + 'image/tiff': ['.tiff'], + }, + }, + }, + }, + required: ['value'], + title: 'Choice Item', + 'x-type': 'Card', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-role': 'core', + 'x-deletable': true, + }, + }, + }, + }, + ], + }, + user_input: { + type: 'boolean', + default: true, + title: 'User Input', + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-reactions': [ + { + target: 'source', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'source', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + source: { + title: 'Source', + type: 'string', + default: 'IM', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'IM', value: 'IM' }, + { label: 'form', value: 'form' }, + ], + }, + 'x-reactions': [ + { + target: 'choices', + when: '$this.value === "IM"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + default_value: { + type: 'string', + title: 'Default Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + value: { + type: 'string', + title: 'Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + description: { + type: 'string', + title: 'Description', + 'x-raw': true, + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3 pb-3', + 'x-component-props': { + maxLength: 500, + placeholder: + 'Please describe how to use this input variable. This description will be displayed when the user inputs.', + }, + 'x-validator': [ + { + exclusiveMaximum: 500, + message: 'Cannot exceed 500 characters', + }, + ], + 'x-type': 'Control', + }, + choices: { + 'x-hidden': true, + type: 'array', + 'x-title-size': 'h5', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: {}, + }, + }, + validations: { + type: 'array', + title: 'Validations', + 'x-title-size': 'h5', + additionalItems: { + type: 'object', + properties: { + void_core: { + type: 'void', + 'x-role': 'core', + 'x-type': 'Grid', + 'x-class': 'grid-cols-3', + properties: { + rule_type: { + type: 'string', + enum: [ + 'max_length', + 'min_number', + 'max_number', + 'max_file_size', + ], + 'x-component': 'Select', + 'x-component-props': { + placeholder: 'Rule type', + options: [ + { label: 'Max Length', value: 'max_length' }, + { label: 'Min Number', value: 'min_number' }, + { label: 'Max Number', value: 'max_number' }, + { label: 'Max File Size', value: 'max_file_size' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'max_length', + when: '$this.value === "max_length"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'min_number', + when: '$this.value === "min_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_number', + when: '$this.value === "max_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_file_size', + when: '$this.value === "max_file_size"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + max_length: { + type: 'number', + default: 500, + 'x-component': 'NumberInput', + 'x-type': 'Control', + // 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 15000, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 15000 }, + ], + }, + min_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_file_size: { + type: 'number', + default: 10 * 1024 * 1024, + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 100 * 1024 * 1024, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 100 * 1024 * 1024 }, + ], + }, + error_message: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + placeholder: 'Error Message', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + }, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, + blocks: { + title: 'Task', + type: 'array', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-component': 'TasksConfig', + 'x-component-props': { + draggable: true, + }, + }, + outputs: { + type: 'object', + title: 'Output', + additionalProperties: { + type: 'object', + properties: { + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-hidden': true, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ref, + 'x-parent-deletable': true, + 'x-title-editable': true, + 'x-title-component-props': { + showDialog: true, + defaultKey: 'display_name', + dialogConfig: { + title: 'Edit Output', + schema: { + type: 'object', + properties: { + name_mode: { + type: 'string', + default: FieldModeEnum.Enum.ui, + title: 'Mode', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'type', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'description', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'VariableNameInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-component-props': { + maxLength: 30, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + required: true, + message: 'Please input the Variable Name', + }, + { + maxLength: 50, + message: 'Cannot exceed 50 characters', + }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + display_name: { + type: 'string', + 'x-hidden': true, + }, + type: { + type: 'string', + default: 'text', + enum: [ + 'text', + 'image', + 'audio', + 'video', + 'text_file', + 'file', + ], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-layout': 'Vertical', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + description: { + type: 'string', + title: 'Description', + default: '', + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 300, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + exclusiveMaximum: 300, + message: 'Cannot exceed 300 characters', + }, + ], + 'x-type': 'Control', + }, + }, + }, + }, + }, + 'x-component-props': { + size: '2xs', + }, + 'x-layout': 'Horizontal', + 'x-validator': [ + { required: true, message: 'The value is required' }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-hidden': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + 'x-key': '{{name}} Outputs_{{counter}}', + 'x-type': 'Inline', + 'x-collapsible': true, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, + render: { + type: 'object', + title: 'Message', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + properties: { + text: { + type: 'string', + title: 'Text', + 'x-component': 'ExpressionInput', + 'x-type': 'Control', + 'x-switchable': true, + 'x-switchable-default': true, + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ui, + 'x-validator': [ + { + warningOnly: true, + critical: true, + validator(rule, value) { + return new Promise((resolve, reject) => { + if (/]*>/.test(value)) { + reject( + new Error( + 'The tag is deprecated. Please use Message.Image to render images instead.', + ), + ); + } else { + resolve(true); + } + }); + }, + }, + ], + 'x-validator-render': 'ValidateRender', + }, + image: { + type: 'string', + title: 'Image', + 'x-component': 'FileUpload', + 'x-type': 'Control', + 'x-switchable': true, + 'x-switchable-default': false, + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ui, + }, + }, + }, + id: { + type: 'string', + default: '', + 'x-hidden': true, + }, + name: { + type: 'string', + default: '', + 'x-hidden': true, + }, + type: { + type: 'string', + default: 'state', + 'x-hidden': true, + }, + }, +}; diff --git a/web/apps/web/src/stores/app/schema/x-state-schema.ts b/web/apps/web/src/stores/app/schema/x-state-schema.ts new file mode 100644 index 00000000..8dc30075 --- /dev/null +++ b/web/apps/web/src/stores/app/schema/x-state-schema.ts @@ -0,0 +1,1088 @@ +import { ISchema } from '@shellagent/form-engine'; +import { FieldModeEnum } from '@shellagent/shared/protocol/extend-config'; + +import { validateCustomKey } from '@/stores/app/utils/validator'; +import { ENABLE_MIME } from '@/utils/file-types'; + +export const stateConfigSchema: ISchema = { + type: 'object', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-class': 'space-y-3', + properties: { + inputs: { + type: 'object', + title: 'Input', + additionalProperties: { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + // 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'UnfocusInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-full', + 'x-component-props': { + size: '2xs', + autoFocus: false, + maxLength: 30, + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + ], + }, + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + triggerClassName: 'h-7 w-32', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 w-32', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + }, + user_input: { + type: 'boolean', + default: true, + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + }, + }, + 'x-key': '{{name}} Inputs_{{counter}}', + 'x-type': 'Inline', + 'x-validator-render': 'ValidateRender', + 'x-validator': [ + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value.name); + }, + }, + ], + 'x-draggable': true, + 'x-deletable': true, + 'x-edit-dialog': { + type: 'object', + properties: { + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 30, + size: 'sm', + placeholder: 'Please name the event', + }, + 'x-validator': [ + { required: true, message: 'Please name the event' }, + { maxLength: 30, message: 'Cannot exceed 30 characters' }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + type: { + type: 'string', + default: 'text', + title: 'Type', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "text"', + fullfill: { + schema: { + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices 12', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: { + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + }, + }, + { + target: 'choices', + when: '$this.value === "image"', + fullfill: { + schema: { + default: [{ value: 'a', label: 'A' }], + 'x-hidden': false, + type: 'array', + 'x-title-size': 'h5', + title: 'Choices', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + properties: { + label: { + title: 'Label', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'Input', + 'x-component-props': { + maxLength: 30, + size: 'sm', + }, + }, + value: { + title: 'Value', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: { + 'image/apng': ['.apng'], + 'image/png': ['.png'], + 'image/jpeg': ['.jpeg'], + 'image/jpg': ['.jpg'], + 'image/webp': ['.webp'], + 'image/gif': ['.gif'], + 'image/bmp': ['.bmp'], + 'image/tiff': ['.tiff'], + }, + }, + }, + }, + required: ['value'], + title: 'Choice Item', + 'x-type': 'Card', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-role': 'core', + 'x-deletable': true, + }, + }, + }, + }, + ], + }, + user_input: { + type: 'boolean', + default: true, + title: 'User Input', + 'x-component': 'Switch', + 'x-type': 'Control', + 'x-value-prop-name': 'checked', + 'x-onchange-prop-name': 'onCheckedChange', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-reactions': [ + { + target: 'source', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'source', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'default_value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === true', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'value', + when: '$this.value === false', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + source: { + title: 'Source', + type: 'string', + default: 'IM', + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component': 'Select', + 'x-component-props': { + options: [ + { label: 'IM', value: 'IM' }, + { label: 'form', value: 'form' }, + ], + }, + 'x-reactions': [ + { + target: 'choices', + when: '$this.value === "IM"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + default_value: { + type: 'string', + title: 'Default Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + value: { + type: 'string', + title: 'Value', + 'x-component': 'Textarea', + 'x-raw': true, + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + }, + description: { + type: 'string', + title: 'Description', + 'x-raw': true, + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3 pb-3', + 'x-component-props': { + maxLength: 500, + placeholder: + 'Please describe how to use this input variable. This description will be displayed when the user inputs.', + }, + 'x-validator': [ + { + exclusiveMaximum: 500, + message: 'Cannot exceed 500 characters', + }, + ], + 'x-type': 'Control', + }, + choices: { + 'x-hidden': true, + type: 'array', + 'x-title-size': 'h5', + 'x-raw': true, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pb-3', + additionalItems: { + type: 'object', + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + properties: {}, + }, + }, + validations: { + type: 'array', + title: 'Validations', + 'x-title-size': 'h5', + additionalItems: { + type: 'object', + properties: { + void_core: { + type: 'void', + 'x-role': 'core', + 'x-type': 'Grid', + 'x-class': 'grid-cols-3', + properties: { + rule_type: { + type: 'string', + enum: [ + 'max_length', + 'min_number', + 'max_number', + 'max_file_size', + ], + 'x-component': 'Select', + 'x-component-props': { + placeholder: 'Rule type', + options: [ + { label: 'Max Length', value: 'max_length' }, + { label: 'Min Number', value: 'min_number' }, + { label: 'Max Number', value: 'max_number' }, + { label: 'Max File Size', value: 'max_file_size' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'max_length', + when: '$this.value === "max_length"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'min_number', + when: '$this.value === "min_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_number', + when: '$this.value === "max_number"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + { + target: 'max_file_size', + when: '$this.value === "max_file_size"', + fullfill: { + schema: { + 'x-hidden': false, + }, + }, + }, + ], + }, + max_length: { + type: 'number', + default: 500, + 'x-component': 'NumberInput', + 'x-type': 'Control', + // 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 15000, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 15000 }, + ], + }, + min_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_number: { + type: 'number', + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + max_file_size: { + type: 'number', + default: 10 * 1024 * 1024, + 'x-component': 'NumberInput', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component-props': { + min: 0, + max: 100 * 1024 * 1024, + placeholder: 'Rule limit', + size: 'sm', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-validator': [ + { required: true }, + { exclusiveMinimum: 0 }, + { exclusiveMaximum: 100 * 1024 * 1024 }, + ], + }, + error_message: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-component-props': { + size: 'sm', + placeholder: 'Error Message', + }, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Inline', + 'x-role': 'core', + 'x-deletable': true, + }, + 'x-type': 'Block', + 'x-addable': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + }, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, + blocks: { + title: 'Task', + type: 'array', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-component': 'TasksConfig', + 'x-component-props': { + draggable: true, + }, + }, + outputs: { + type: 'object', + title: 'Output', + additionalProperties: { + type: 'object', + properties: { + type: { + type: 'string', + default: 'text', + enum: ['text', 'image', 'audio', 'video', 'text_file', 'file'], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-hidden': true, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + value: { + type: 'string', + 'x-component': 'Input', + 'x-type': 'Control', + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ref, + 'x-parent-deletable': true, + 'x-title-editable': true, + 'x-title-component-props': { + showDialog: true, + defaultKey: 'display_name', + dialogConfig: { + title: 'Edit Output', + schema: { + type: 'object', + properties: { + name_mode: { + type: 'string', + default: FieldModeEnum.Enum.ui, + title: 'Mode', + 'x-type': 'Control', + 'x-hidden': true, + 'x-component': 'Input', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-reactions': [ + { + target: 'type', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + { + target: 'description', + when: '$this.value === "ref"', + fullfill: { + schema: { + 'x-hidden': true, + }, + }, + }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + title: 'Variable Name', + 'x-role': 'title', + 'x-type': 'Control', + 'x-component': 'VariableNameInput', + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + 'x-component-props': { + maxLength: 30, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + required: true, + message: 'Please input the Variable Name', + }, + { + maxLength: 50, + message: 'Cannot exceed 50 characters', + }, + { + critical: true, + validator(rule: any, value: any) { + return validateCustomKey(value); + }, + }, + ], + 'x-validator-render': 'DialogValidateRender', + }, + display_name: { + type: 'string', + 'x-hidden': true, + }, + type: { + type: 'string', + default: 'text', + enum: [ + 'text', + 'image', + 'audio', + 'video', + 'text_file', + 'file', + ], + title: 'Type', + 'x-component': 'Select', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + options: [ + { label: 'text', value: 'text' }, + { label: 'image', value: 'image' }, + { label: 'audio', value: 'audio' }, + { label: 'video', value: 'video' }, + { label: 'text_file', value: 'text_file' }, + { label: 'file', value: 'file' }, + ], + }, + 'x-value-prop-name': 'defaultValue', + 'x-onchange-prop-name': 'onValueChange', + 'x-type': 'Control', + 'x-layout': 'Vertical', + 'x-reactions': [ + { + target: 'value', + when: '$this.value === "image"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "audio"', + fullfill: { + schema: { + 'x-raw': true, + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.audio, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "video"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.video, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "text_file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.other, + }, + }, + }, + }, + { + target: 'value', + when: '$this.value === "file"', + fullfill: { + schema: { + 'x-layout': 'Vertical', + 'x-component': 'FileUpload', + 'x-component-props': { + accept: ENABLE_MIME.all, + }, + }, + }, + }, + ], + 'x-validator': [ + { + required: true, + message: 'Please select the output type', + }, + ], + }, + description: { + type: 'string', + title: 'Description', + default: '', + 'x-component': 'Textarea', + 'x-layout': 'Vertical', + 'x-class': 'border-0 bg-inherit rounded-lg p-0 pt-3', + 'x-component-props': { + maxLength: 300, + placeholder: + 'The usage of the variable. It may be displayed to the users..', + }, + 'x-validator': [ + { + exclusiveMaximum: 300, + message: 'Cannot exceed 300 characters', + }, + ], + 'x-type': 'Control', + }, + }, + }, + }, + }, + 'x-component-props': { + size: '2xs', + }, + 'x-layout': 'Horizontal', + 'x-validator': [ + { required: true, message: 'The value is required' }, + ], + }, + name: { + type: 'string', + default: 'Untitled', + 'x-type': 'Control', + 'x-component': 'Input', + 'x-hidden': true, + 'x-class': 'border-0 bg-inherit rounded-lg p-0', + }, + }, + 'x-key': '{{name}} Outputs_{{counter}}', + 'x-type': 'Inline', + 'x-collapsible': true, + }, + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + 'x-addable': true, + }, + render: { + type: 'object', + title: 'Message', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + properties: { + text: { + type: 'string', + title: 'Text', + 'x-component': 'ExpressionInput', + 'x-type': 'Control', + 'x-switchable': true, + 'x-switchable-default': true, + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ui, + 'x-validator': [ + { + warningOnly: true, + critical: true, + validator(rule, value) { + return new Promise((resolve, reject) => { + if (/]*>/.test(value)) { + reject( + new Error( + 'The tag is deprecated. Please use Message.Image to render images instead.', + ), + ); + } else { + resolve(true); + } + }); + }, + }, + ], + 'x-validator-render': 'ValidateRender', + }, + image: { + type: 'string', + title: 'Image', + 'x-component': 'FileUpload', + 'x-type': 'Control', + 'x-switchable': true, + 'x-switchable-default': false, + 'x-component-props': { + accept: ENABLE_MIME.image, + }, + 'x-raw': true, + 'x-raw-default': FieldModeEnum.Enum.ui, + }, + }, + }, + id: { + type: 'string', + default: '', + 'x-hidden': true, + }, + name: { + type: 'string', + default: '', + 'x-hidden': true, + }, + type: { + type: 'string', + default: 'state', + 'x-hidden': true, + }, + }, +}; diff --git a/web/apps/web/src/stores/app/use-app-state.tsx b/web/apps/web/src/stores/app/use-app-state.tsx index a89e1504..5ae5ec17 100644 --- a/web/apps/web/src/stores/app/use-app-state.tsx +++ b/web/apps/web/src/stores/app/use-app-state.tsx @@ -1,7 +1,10 @@ import { Node } from '@shellagent/flow-engine'; import { create } from 'zustand'; - -import { EdgeDataTypeEnum, CustomEdgeData } from '@/components/app/edges'; +import { CustomEdgeData } from '@/components/app/edges'; +import { + TransitionTypeEnum, + TransitionTargetEnum, +} from '@shellagent/shared/protocol/transition'; export type State = { currentStateId: string; @@ -9,6 +12,7 @@ export type State = { insideSheetOpen: boolean; insideSheetMode: 'button' | 'workflow' | 'widget' | ''; transitionSheetOpen: boolean; + newTransitionSheetOpen: boolean; runDrawerWidth: number; selectedNode: Node | undefined; currentTaskIndex?: number; @@ -17,6 +21,8 @@ export type State = { currentTransitionSourceHandle: string; currentEdegData: CustomEdgeData; targetInputsSheetOpen: boolean; + transitionType: TransitionTypeEnum; + currentEdgeId: string; }; type Action = { @@ -34,6 +40,16 @@ type Action = { source: string; sourceHandle: string; data?: CustomEdgeData; + transitionType?: TransitionTypeEnum; + id?: string; + }) => void; + setNewTransitionSheetOpen: (params: { + open: boolean; + source: string; + sourceHandle: string; + data?: CustomEdgeData; + transitionType?: TransitionTypeEnum; + id?: string; }) => void; setTargetInputsSheetOpen: (open: State['targetInputsSheetOpen']) => void; setRunDrawerWidth: (width: number) => void; @@ -43,8 +59,10 @@ type Action = { const initialState: State = { stateConfigSheetOpen: false, targetInputsSheetOpen: false, + newTransitionSheetOpen: false, selectedNode: undefined, currentStateId: '', + currentEdgeId: '', currentButtonId: '', insideSheetOpen: false, insideSheetMode: '', @@ -56,10 +74,11 @@ const initialState: State = { id: '', custom: true, event_key: '', - type: EdgeDataTypeEnum.ALWAYS, + type: TransitionTargetEnum.ALWAYS, target: '', conditions: [], }, + transitionType: TransitionTypeEnum.intro, }; export const useAppState = create(set => ({ @@ -76,6 +95,7 @@ export const useAppState = create(set => ({ insideSheetOpen: false, insideSheetMode: '', transitionSheetOpen: false, + newTransitionSheetOpen: false, currentTransitionSource: '', currentTransitionSourceHandle: '', currentEdegData: undefined, @@ -89,6 +109,7 @@ export const useAppState = create(set => ({ insideSheetOpen: false, insideSheetMode: '', transitionSheetOpen: false, + newTransitionSheetOpen: false, currentTransitionSource: '', currentTransitionSourceHandle: '', currentEdegData: undefined, @@ -96,7 +117,7 @@ export const useAppState = create(set => ({ } return state; }), - setTransitionSheetOpen: ({ open, source, sourceHandle, data }) => { + setTransitionSheetOpen: ({ open, source, sourceHandle, data, id }) => { set(() => { if (open) { return { @@ -106,6 +127,7 @@ export const useAppState = create(set => ({ currentEdegData: data || undefined, stateConfigSheetOpen: false, currentStateId: '', + currentEdgeId: id, insideSheetOpen: false, insideSheetMode: '', currentButtonId: '', @@ -118,6 +140,44 @@ export const useAppState = create(set => ({ currentTransitionSourceHandle: '', currentEdegData: undefined, targetInputsSheetOpen: false, + transitionType: TransitionTypeEnum.intro, + currentEdgeId: '', + }; + }); + }, + setNewTransitionSheetOpen: ({ + open, + source, + sourceHandle, + data, + transitionType, + id, + }) => { + set(() => { + if (open) { + return { + newTransitionSheetOpen: true, + currentTransitionSource: source, + currentTransitionSourceHandle: sourceHandle, + currentEdegData: data || undefined, + transitionType, + stateConfigSheetOpen: false, + currentStateId: '', + insideSheetOpen: false, + insideSheetMode: '', + currentButtonId: '', + currentTaskIndex: undefined, + currentEdgeId: id, + }; + } + return { + newTransitionSheetOpen: false, + currentTransitionSource: '', + currentTransitionSourceHandle: '', + currentEdegData: undefined, + targetInputsSheetOpen: false, + transitionType: TransitionTypeEnum.intro, + currentEdgeId: '', }; }); }, @@ -132,6 +192,7 @@ export const useAppState = create(set => ({ currentButtonId: mode === 'button' ? buttonId : '', currentTaskIndex, transitionSheetOpen: false, + newTransitionSheetOpen: false, currentTransitionSource: '', currentTransitionSourceHandle: '', currentEdegData: undefined, @@ -145,6 +206,7 @@ export const useAppState = create(set => ({ currentButtonId: '', currentTaskIndex: undefined, transitionSheetOpen: false, + newTransitionSheetOpen: false, currentTransitionSource: '', currentTransitionSourceHandle: '', currentEdegData: undefined, diff --git a/web/apps/web/src/stores/app/utils/__test__/data-transformer.spec.ts b/web/apps/web/src/stores/app/utils/__test__/data-transformer.spec.ts new file mode 100644 index 00000000..e69de29b diff --git a/web/apps/web/src/stores/app/utils/data-transformer.ts b/web/apps/web/src/stores/app/utils/data-transformer.ts index 3275fe20..a9ee7d5a 100644 --- a/web/apps/web/src/stores/app/utils/data-transformer.ts +++ b/web/apps/web/src/stores/app/utils/data-transformer.ts @@ -1,13 +1,16 @@ import { IFlow, NodeIdEnum, NodeTypeEnum } from '@shellagent/flow-engine'; import { TValue } from '@shellagent/form-engine'; import { Automata, State } from '@shellagent/pro-config'; -import { get, set } from 'lodash-es'; +import { get, isString } from 'lodash-es'; +import { ICustomEdge, CustomEdgeData } from '@/components/app/edges'; import { - EdgeDataTypeEnum, - ICustomEdge, - CustomEdgeData, -} from '@/components/app/edges'; + TransitionTargetEnum, + type TransitionData, + type Timer, + transitionTargetTypeSchema, +} from '@shellagent/shared/protocol/transition'; + import { NodeDataType } from '@/types/app/types'; import { transformChoicesToValues, @@ -49,10 +52,15 @@ function formatTemplateString(data: any): any { } // 根据automata生成nodeData -export const genNodeData = ( - automata: Automata, - nodes: IFlow['nodes'], -): NodeDataType => { +export const genNodeData = ({ + automata, + nodes, + appType, +}: { + automata: Automata; + nodes: IFlow['nodes']; + appType: 'chat_bot' | 'x_bot'; +}): NodeDataType => { const nodeData: NodeDataType = { [NodeIdEnum.start]: { id: NodeIdEnum.start, @@ -74,13 +82,19 @@ export const genNodeData = ( outputs: {}, blocks: [], }; - } else if (block.type === 'state') { + } else if ( + block.type === 'state' || + (appType === 'x_bot' && block.type === 'dispatcher') + ) { const state = block as State; const display_name = nodes.find(node => node.id === key)?.data .display_name; nodeData[key as Lowercase] = { id: key, - type: NodeTypeEnum.state, + type: + block.type === 'dispatcher' + ? NodeTypeEnum.condition_state + : NodeTypeEnum.state, name: state.name, display_name, render: state.render, @@ -103,78 +117,171 @@ export const genNodeData = ( }; // 根据生成automata -export const genAutomata: ( - flow: IFlow, - nodeData: NodeDataType, - comfyui_api?: string, -) => Automata = (flow, nodeData, comfyui_api) => { - const initial = - (flow.edges.find(edge => edge.source === NodeIdEnum.start) - ?.target as Lowercase) || ''; - const blocks: Automata['blocks'] = {}; - - flow.nodes - .filter( - node => - node.type === NodeTypeEnum.state || node.type === NodeTypeEnum.intro, - ) - .forEach(node => { - const transitions: Record = {}; - const targetEdges = (flow.edges as ICustomEdge[]).filter( - edge => edge.source === node.id, - ); - targetEdges.forEach(edge => { - const data = edge.data as CustomEdgeData; - const transitionKey = - data.type && data.type !== EdgeDataTypeEnum.STATE - ? data.type - : (data.event_key as string); - - if (!transitions[transitionKey]) { - transitions[transitionKey] = []; - } +export const genAutomata: (props: { + flow: IFlow; + nodeData: NodeDataType; + comfyui_api?: string; + appType: 'chat_bot' | 'x_bot'; +}) => Automata = ({ flow, nodeData, comfyui_api, appType }) => { + if (appType === 'chat_bot') { + const initial = + (flow.edges.find(edge => edge.source === NodeIdEnum.start) + ?.target as Lowercase) || ''; + const blocks: Automata['blocks'] = {}; - const transitionItems = data?.conditions?.length - ? data.conditions.map(item => ({ - target: edge.target, - // 空字符串不传 - condition: item?.condition || undefined, - target_inputs: item?.target_inputs, - })) - : [{ target: edge.target }]; + flow.nodes + .filter( + node => + node.type === NodeTypeEnum.state || node.type === NodeTypeEnum.intro, + ) + .forEach(node => { + const transitions: Record = {}; + const targetEdges = (flow.edges as ICustomEdge[]).filter( + edge => edge.source === node.id, + ); + targetEdges.forEach(edge => { + const data = edge.data as CustomEdgeData; + const transitionKey = + data.type && data.type !== TransitionTargetEnum.STATE + ? data.type + : (data.event_key as string); - transitions[transitionKey].push(...transitionItems); + if (!transitions[transitionKey]) { + transitions[transitionKey] = []; + } + + const transitionItems = data?.conditions?.length + ? data.conditions.map(item => ({ + target: edge.target, + // 空字符串不传 + condition: item?.condition || undefined, + target_inputs: item?.target_inputs, + })) + : [{ target: edge.target }]; + + transitions[transitionKey].push(...transitionItems); + }); + + blocks[node.id as Lowercase] = { + type: + nodeData[node.id]?.type === 'intro' + ? 'state' + : nodeData[node.id]?.type || 'state', + name: nodeData[node.id]?.name, + render: nodeData[node.id]?.render, + blocks: nodeData[node.id]?.blocks?.map((item: any) => ({ + ...item, + ...(item.widget_class_name === 'ComfyUIWidget' + ? { api: comfyui_api || item.api } + : {}), + })), + inputs: transformChoicesToValues(nodeData[node.id]?.inputs || {}), + outputs: nodeData[node.id]?.outputs, + transitions, + } as State; }); - blocks[node.id as Lowercase] = { - type: - nodeData[node.id]?.type === 'intro' - ? 'state' - : nodeData[node.id]?.type || 'state', - name: nodeData[node.id]?.name, - render: nodeData[node.id]?.render, - blocks: nodeData[node.id]?.blocks?.map((item: any) => ({ - ...item, - ...(item.widget_class_name === 'ComfyUIWidget' - ? { api: comfyui_api || item.api } - : {}), - })), - inputs: transformChoicesToValues(nodeData[node.id]?.inputs || {}), - outputs: nodeData[node.id]?.outputs, - transitions, - } as State; + return replaceContext2Api({ + type: 'automata', + properties: { + cache: false, + }, + context: nodeData[NodeIdEnum.start]?.context || {}, + initial, + blocks, + transitions: {}, }); + } else if (appType === 'x_bot') { + const initial = + (flow.edges.find(edge => edge.source === NodeIdEnum.start) + ?.target as Lowercase) || ''; + const blocks: Automata['blocks'] = {}; - return replaceContext2Api({ - type: 'automata', - properties: { - cache: false, - }, - context: nodeData[NodeIdEnum.start]?.context || {}, - initial, - blocks, - transitions: {}, - }); + // 处理所有状态节点 + flow.nodes + .filter( + node => + node.type == NodeTypeEnum.state || + node.type === NodeTypeEnum.intro || + node.type === NodeTypeEnum.condition_state, + ) + .forEach(node => { + const transitions: Record = {}; + const timers: Record = {}; + + // 获取从该节点出发的所有边 + const targetEdges = (flow.edges as ICustomEdge[]).filter( + edge => edge.source === node.id, + ); + + // 处理每条边 + targetEdges.forEach(edge => { + const transitionData = edge.data as TransitionData; + let transitionKey: string = transitionData.type; + + // 修改 timer 处理逻辑 + if (transitionData.timers) { + transitionKey = transitionData.key; + timers[transitionKey] = { + timer: transitionData.timers, + event: transitionData.key, + }; + } + + if (isString(transitionData.condition)) { + if (!transitions[transitionKey]) { + transitions[transitionKey] = []; + } + transitions[transitionKey].push({ + target: transitionData.target, + condition: transitionData.condition, + target_inputs: transitionData.target_inputs, + }); + } else { + transitions[transitionKey] = { + target: transitionData.target, + target_inputs: transitionData.target_inputs, + }; + } + }); + + // 构建 block + blocks[node.id as Lowercase] = { + type: + nodeData[node.id]?.type === 'condition_state' + ? 'dispatcher' + : 'state', + name: nodeData[node.id]?.name, + properties: { + ...(Object.keys(timers).length > 0 ? { timers } : {}), + cache: false, + }, + render: nodeData[node.id]?.render, + blocks: nodeData[node.id]?.blocks?.map((item: any) => ({ + ...item, + ...(item.widget_class_name === 'ComfyUIWidget' + ? { api: comfyui_api || item.api } + : {}), + })), + inputs: transformChoicesToValues(nodeData[node.id]?.inputs || {}), + outputs: nodeData[node.id]?.outputs, + transitions, + } as State; + }); + + return replaceContext2Api({ + type: 'automata', + properties: { + cache: false, + }, + context: nodeData[NodeIdEnum.start]?.context || {}, + initial, + blocks, + transitions: {}, + }); + } else { + return {}; + } }; export const replaceContextByAutomata = (value: TValue, automata: Automata) => { diff --git a/web/apps/web/src/stores/home/models/home-model.ts b/web/apps/web/src/stores/home/models/home-model.ts new file mode 100644 index 00000000..709bf8ce --- /dev/null +++ b/web/apps/web/src/stores/home/models/home-model.ts @@ -0,0 +1,80 @@ +import { injectable, inject } from 'inversify'; +import { action, makeObservable, observable, runInAction } from 'mobx'; +import { fetchList } from '@/services/home'; +import type { GetListRequest, GetListResponse } from '@/services/home/type'; +import { SettingsModel } from '@/components/settings/settings.model'; +import { ToastModel } from '@/utils/toast.model'; + +export type AppType = 'all' | 'chat_bot' | 'x_bot'; + +@injectable() +export class HomeModel { + @observable listLoading = true; + @observable listData: GetListResponse['data'] = []; + @observable appType: AppType = 'all'; + + get filteredData() { + if (this.appType === 'all') return this.listData; + if (this.appType === 'chat_bot') { + return this.listData.filter(item => item.metadata.app_type !== 'x_bot'); + } + if (this.appType === 'x_bot') { + return this.listData.filter( + item => item.metadata.app_type === this.appType, + ); + } + return this.listData; + } + + constructor( + @inject(ToastModel) private emitter: ToastModel, + @inject(SettingsModel) public settingsModel: SettingsModel, + ) { + makeObservable(this); + } + + @action.bound + setAppType(type: AppType) { + this.appType = type; + } + + @action.bound + async fetchList(params: GetListRequest) { + try { + runInAction(() => { + this.listLoading = true; + }); + const res = await fetchList(params); + runInAction(() => { + this.listData = res.data; + }); + } catch (error: any) { + this.emitter.emitter.emit('message.error', error.message); + } finally { + runInAction(() => { + this.listLoading = false; + }); + } + } + + @action.bound + async refreshList(params: GetListRequest) { + try { + const res = await fetchList(params); + runInAction(() => { + this.listData = res.data; + }); + } catch (error: any) { + this.emitter.emitter.emit('message.error', error.message); + } + } + + @action.bound + async autoCheck() { + await this.settingsModel.getIsBetaCheck(); + const isAutoCheck = await this.settingsModel.getAutoCheck(); + if (isAutoCheck) { + this.settingsModel.autoCheck(); + } + } +} diff --git a/web/apps/web/src/types/app/types.ts b/web/apps/web/src/types/app/types.ts index fcdd6592..13d4da53 100644 --- a/web/apps/web/src/types/app/types.ts +++ b/web/apps/web/src/types/app/types.ts @@ -3,7 +3,6 @@ import { TValues, TFieldMode } from '@shellagent/form-engine'; import { Automata } from '@shellagent/pro-config'; import { Refs } from '@shellagent/shared/protocol/app-scope'; import { Issue } from '@shellagent/shared/type'; - import { Metadata } from '@/services/home/type'; export type Config = { diff --git a/web/apps/web/src/types/index.d.ts b/web/apps/web/src/types/index.d.ts index 478c9aba..f6a5c479 100644 --- a/web/apps/web/src/types/index.d.ts +++ b/web/apps/web/src/types/index.d.ts @@ -1,4 +1,3 @@ declare module 'json-source-map'; declare module 'json5'; declare module 'react-toastify'; -declare module 'dayjs'; diff --git a/web/packages/flow-engine/src/store/flow/provider.tsx b/web/packages/flow-engine/src/store/flow/provider.tsx index 27a72930..d909490d 100644 --- a/web/packages/flow-engine/src/store/flow/provider.tsx +++ b/web/packages/flow-engine/src/store/flow/provider.tsx @@ -179,7 +179,7 @@ export const FlowStoreProvider = ({ children }: FlowStoreProviderProps) => { node.data.name?.startsWith(name), ); - const reg = new RegExp(`${name.toLowerCase()}(\\d+)$`); + const reg = new RegExp(`${customSnakeCase(name)}(\\d+)$`); // 获取最大编号 let maxIndex = 0; diff --git a/web/packages/flow-engine/src/types/node.ts b/web/packages/flow-engine/src/types/node.ts index 99ed32c7..cbc7d7d4 100644 --- a/web/packages/flow-engine/src/types/node.ts +++ b/web/packages/flow-engine/src/types/node.ts @@ -1,29 +1,10 @@ -export enum DataTypeEnum { - string = 'string', - map = 'map', - all = 'all', -} - -// 全量node类型 -export enum NodeTypeEnum { - start = 'start', - end = 'end', - widget = 'widget', - state = 'state', - note = 'note', - intro = 'intro', - workflow = 'workflow', -} +import { + NodeTypeEnum, + type NodeType, + NodeIdEnum, +} from '@shellagent/shared/protocol/node'; -export type NodeType = keyof typeof NodeTypeEnum; - -export type DataType = keyof typeof DataTypeEnum; - -export enum NodeIdEnum { - start = '@@@start', - end = '@@@end', - intro = 'intro', -} +export { NodeTypeEnum, NodeType, NodeIdEnum }; // 节点id通过uuid生成 export type WidgetNodeId = `key_${string}`; diff --git a/web/packages/pro-config-type/example/twitter_bot_hello_world.ts b/web/packages/pro-config-type/example/twitter_bot_hello_world.ts new file mode 100644 index 00000000..e8b4572b --- /dev/null +++ b/web/packages/pro-config-type/example/twitter_bot_hello_world.ts @@ -0,0 +1,117 @@ +import { Automata } from '../src/block'; +import { PayloadMap } from '../src/transition'; + +/** + * “@new” 表示和线上pro config不同的改动细节。 + * FIXME 需要根据情况手动改 + */ + +const bot = { + type: 'automata', + name: 'Twitter bot', + initial: 'idle', + context: { + global_var: '', + }, + blocks: { + idle: { + type: 'state', + properties: { + timers: { + daily: { + timer: { + start: { + year: 2025, + month: 1, + date: 1, + hour: 0, + minute: 0, + timezone: 'Asia/Beijing', + }, + interval: { + day: 1, + }, + }, + event: 'daily_tweet', + }, + }, + }, + transitions: { + 'X.MENTIONED': { + target: 'llm_condition', + target_inputs: { + /** + * @type {PayloadMap["X.MENTIONED"]} + */ + content: '{{payload.text}}', + }, + }, + 'X.PINNED.REPLIED': { + target: 'llm_condition', + target_inputs: { + /** + * @type {PayloadMap["X.PINNED.REPLIED"]} + */ + content: '{{payload.text}}', + }, + }, + daily_tweet: { + target: 'write_tweet', + }, + }, + }, + llm_condition: { + type: 'dispatcher', + inputs: { + content: { + type: 'text', + user_input: false, + }, + }, + blocks: [], + outputs: { + reply: 'Replied {{content}}', + }, + transitions: { + ALWAYS: [ + { + name: 'Transition case 1', + condition: '{{len(content) > 5}}', + target: 'reply', + target_inputs: { + reply: '{{reply}}', + }, + }, + { + name: 'Transition case 2', + target: 'reply', + target_inputs: { + reply: '{{reply}}', + }, + }, + ], + }, + }, + reply: { + type: 'state', + inputs: { + reply: { + type: 'text', + user_input: false, + }, + }, + blocks: [], + render: { + text: '{{reply}}', + }, + }, + write_tweet: { + type: 'state', // 改为required @new + inputs: {}, + outputs: {}, + render: { + text: 'write some random stuff', + }, + }, + }, +} satisfies Automata; diff --git a/web/packages/pro-config-type/src/block.ts b/web/packages/pro-config-type/src/block.ts index 8527cb78..6124ad13 100644 --- a/web/packages/pro-config-type/src/block.ts +++ b/web/packages/pro-config-type/src/block.ts @@ -8,9 +8,9 @@ import { BoolExpression, } from './variables'; import { RenderConfig } from './render'; -import { Transition, SpecialEvents } from './transition'; +import { Transition, SpecialEvents, TransitionCase } from './transition'; -type BlockType = 'automata' | 'state' | 'task' | 'workflow'; +type BlockType = 'automata' | 'state' | 'task' | 'workflow' | 'dispatcher'; interface Block { /** @@ -69,6 +69,7 @@ export interface State extends Block { properties?: { is_final?: boolean; cache?: boolean; + timers?: Record; }; // tasks?: BlockChildren; blocks?: BlockChildren; // user_input字段无效 @@ -85,7 +86,7 @@ export interface Automata extends ContainerBlock { cache?: boolean; }; initial: CustomKey; - blocks: BlockChildren; // 后续支持Automata | Task 等等 + blocks: BlockChildren; // 后续支持Automata | Task 等等 transitions?: { [key in CustomEventName | 'DONE']?: Transition }; } @@ -151,3 +152,54 @@ export interface Workflow extends ContainerBlock { }; blocks: BlockChildren; // 后续支持 Workflow } + +export interface Dispatcher extends Block { + type: 'dispatcher'; + properties?: { + cache?: boolean; + }; + blocks?: BlockChildren; + transitions: { ALWAYS: TransitionCase[] }; +} + +// Timer +type Timezone = string; +type Date = { + minute?: number; // 0-60 + hour?: number; // 0-24 + date?: number; // 1-31 + month?: number; // 1-12 + year?: number; + timezone?: Timezone | 'client'; +}; + +// 可以用秒数。只是对用户更方便 +type Interval = { + // second?: number; + minute?: number; + hour?: number; + day?: number; +}; + +type Timer = { + // start time + start?: Required | 'last interaction'; + interval?: Interval; + // TODO: 结束时间,主站应该没有支持 + end?: { + date?: Date; + count?: number; + }; + // TODO: random,主站应该未支持 + delay?: + | Interval + | { + random_mode: 'even'; + min: number; + max: number; + }; + // cron style + // recurrence?: Date & { + // day_of_week?: 0-6; + // }; +}; diff --git a/web/packages/pro-config-type/src/common.ts b/web/packages/pro-config-type/src/common.ts index 7d305b13..9c8ef9ff 100644 --- a/web/packages/pro-config-type/src/common.ts +++ b/web/packages/pro-config-type/src/common.ts @@ -15,8 +15,8 @@ export type ReservedKey = | 'transitions' | 'states' | 'context' - | 'payload'; -// TODO:blocks, result + | 'payload' + | 'blocks'; /** * User-defined keys should be: diff --git a/web/packages/pro-config-type/src/transition.ts b/web/packages/pro-config-type/src/transition.ts index 34346791..5a97f633 100644 --- a/web/packages/pro-config-type/src/transition.ts +++ b/web/packages/pro-config-type/src/transition.ts @@ -5,9 +5,9 @@ import { Button } from '.'; /* State transitions */ export type TransitionCase = { + name?: string; /** Transist to the self if unspecified */ target?: TargetPath; - // TODO(@Boyn): override the value? /** Specify the value for the inputs of target state. It will override the default value of the input, but not the value of the input. */ target_inputs?: { [input_name: CustomKey]: Value; @@ -33,11 +33,44 @@ export type AutomataSpecialEvents = 'DONE'; /** * - 'CHAT' is triggered when user inputs in IM. * - 'ALWAYS' means the state will transist as soon as its condition is satisfied after executing tasks. + * - 'X.' means X(Twitter)-related events, including being mentioned and pinned tweet being replied. */ -export type StateSpecialEvents = 'CHAT' | 'ALWAYS'; +export type StateSpecialEvents = + | 'CHAT' + | 'ALWAYS' + | 'X.MENTIONED' + | 'X.PINNED.REPLIED'; export type ActionEvents = 'COPY' | 'OPEN' | 'DOWNLOAD'; +type TwitterUser = { + creation_date: string; + user_id: string; + username: string; + name: string; + is_private: boolean; + is_verified: boolean; + is_blue_verified: boolean; + is_bot: boolean; // 主站用的字段是 bot + verified_type: 'blue' | 'business' | 'government'; +}; +type TweetDetail = { + text: string; + images: URLString[]; // 主站用的字段是 media_url + user: TwitterUser; + tweet_id: string; + creation_date: string; +}; + +export type PayloadMap = { + // 'CHAT': { + // text?: string; + // files?: URLString; + // }; + 'X.MENTIONED': TweetDetail; + 'X.PINNED.REPLIED': TweetDetail; +}; + export type SpecialEvents = | AutomataSpecialEvents | StateSpecialEvents diff --git a/web/packages/shared/package.json b/web/packages/shared/package.json index 179407c8..22cbfca6 100644 --- a/web/packages/shared/package.json +++ b/web/packages/shared/package.json @@ -17,8 +17,10 @@ "./protocol/extend-config": "./src/protocol/extend-config/index.ts", "./protocol/pro-config": "./src/protocol/pro-config/index.ts", "./protocol/node": "./src/protocol/node/index.ts", + "./protocol/transition": "./src/protocol/transition/index.ts", "./utils": "./src/utils/index.ts", - "./type": "./src/type/index.ts" + "./type": "./src/type/index.ts", + "./constants": "./src/constants/index.ts" }, "devDependencies": { "@shellagent/eslint-config": "workspace:*", @@ -43,6 +45,7 @@ "slug": "^9.1.0", "unicode": "^14.0.0", "unidecode": "^1.1.0", + "uuid": "^11.0.5", "zod": "^3.23.8" } } diff --git a/web/packages/shared/src/constants/index.ts b/web/packages/shared/src/constants/index.ts new file mode 100644 index 00000000..d0cbe8f4 --- /dev/null +++ b/web/packages/shared/src/constants/index.ts @@ -0,0 +1 @@ +export * from './timezones'; diff --git a/web/packages/shared/src/constants/timezones.ts b/web/packages/shared/src/constants/timezones.ts new file mode 100644 index 00000000..548009b5 --- /dev/null +++ b/web/packages/shared/src/constants/timezones.ts @@ -0,0 +1,190 @@ +export const TIMEZONES = [ + { + value: 'Pacific/Midway', + label: '(UTC-11:00) Midway Island, American Samoa', + }, + { value: 'America/Adak', label: '(UTC-10:00) Aleutian Islands' }, + { value: 'Pacific/Honolulu', label: '(UTC-10:00) Hawaii' }, + { value: 'Pacific/Marquesas', label: '(UTC-09:30) Marquesas Islands' }, + { value: 'America/Anchorage', label: '(UTC-09:00) Alaska' }, + { value: 'America/Tijuana', label: '(UTC-08:00) Baja California' }, + { + value: 'America/Los_Angeles', + label: '(UTC-08:00) Pacific Time (US and Canada)', + }, + { value: 'America/Phoenix', label: '(UTC-07:00) Arizona' }, + { + value: 'America/Denver', + label: '(UTC-07:00) Mountain Time (US and Canada), Navajo Nation', + }, + { value: 'America/Guatemala', label: '(UTC-06:00) Central America' }, + { + value: 'America/Chicago', + label: '(UTC-06:00) Central Time (US and Canada)', + }, + { + value: 'America/Chihuahua', + label: '(UTC-06:00) Chihuahua, La Paz, Mazatlan', + }, + { value: 'Pacific/Easter', label: '(UTC-06:00) Easter Island' }, + { + value: 'America/Mexico_City', + label: '(UTC-06:00) Guadalajara, Mexico City, Monterrey', + }, + { value: 'America/Regina', label: '(UTC-06:00) Saskatchewan' }, + { value: 'America/Bogota', label: '(UTC-05:00) Bogota, Lima, Quito' }, + { value: 'America/Cancun', label: '(UTC-05:00) Chetumal' }, + { + value: 'America/New_York', + label: '(UTC-05:00) Eastern Time (US and Canada)', + }, + { value: 'America/Port-au-Prince', label: '(UTC-05:00) Haiti' }, + { value: 'America/Havana', label: '(UTC-05:00) Havana' }, + { + value: 'America/Indiana/Indianapolis', + label: '(UTC-05:00) Indiana (East)', + }, + { value: 'America/Asuncion', label: '(UTC-04:00) Asuncion' }, + { value: 'America/Halifax', label: '(UTC-04:00) Atlantic Time (Canada)' }, + { value: 'America/Caracas', label: '(UTC-04:00) Caracas' }, + { value: 'America/Cuiaba', label: '(UTC-04:00) Cuiaba' }, + { + value: 'America/Guyana', + label: '(UTC-04:00) Georgetown, La Paz, Manaus, San Juan', + }, + { value: 'America/Santiago', label: '(UTC-04:00) Santiago' }, + { value: 'America/Grand_Turk', label: '(UTC-04:00) Turks and Caicos' }, + { value: 'America/St_Johns', label: '(UTC-03:30) Newfoundland' }, + { value: 'America/Araguaina', label: '(UTC-03:00) Araguaina' }, + { value: 'America/Sao_Paulo', label: '(UTC-03:00) Brasilia' }, + { value: 'America/Cayenne', label: '(UTC-03:00) Cayenne, Fortaleza' }, + { + value: 'America/Argentina/Buenos_Aires', + label: '(UTC-03:00) City of Buenos Aires', + }, + { value: 'America/Godthab', label: '(UTC-03:00) Greenland' }, + { value: 'America/Montevideo', label: '(UTC-03:00) Montevideo' }, + { value: 'America/Miquelon', label: '(UTC-03:00) Saint Pierre and Miquelon' }, + { value: 'America/Bahia', label: '(UTC-03:00) Salvador' }, + { value: 'America/Noronha', label: '(UTC-02:00) Fernando de Noronha' }, + { value: 'Atlantic/Azores', label: '(UTC-01:00) Azores' }, + { value: 'Atlantic/Cape_Verde', label: '(UTC-01:00) Cabo Verde Islands' }, + { value: 'Europe/London', label: '(UTC) Dublin, Edinburgh, Lisbon, London' }, + { value: 'Africa/Monrovia', label: '(UTC) Monrovia, Reykjavik' }, + { + value: 'Europe/Amsterdam', + label: '(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna', + }, + { + value: 'Europe/Belgrade', + label: '(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague', + }, + { + value: 'Europe/Brussels', + label: '(UTC+01:00) Brussels, Copenhagen, Madrid, Paris', + }, + { + value: 'Europe/Sarajevo', + label: '(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb', + }, + { value: 'Africa/West_Central', label: '(UTC+01:00) West Central Africa' }, + { value: 'Africa/Casablanca', label: '(UTC+01:00) Casablanca' }, + { value: 'Africa/Windhoek', label: '(UTC+01:00) Windhoek' }, + { value: 'Europe/Athens', label: '(UTC+02:00) Athens, Bucharest' }, + { value: 'Asia/Beirut', label: '(UTC+02:00) Beirut' }, + { value: 'Africa/Cairo', label: '(UTC+02:00) Cairo' }, + { value: 'Asia/Damascus', label: '(UTC+02:00) Damascus' }, + { value: 'Asia/Gaza', label: '(UTC+02:00) Gaza, Hebron' }, + { value: 'Africa/Harare', label: '(UTC+02:00) Harare, Pretoria' }, + { + value: 'Europe/Helsinki', + label: '(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius', + }, + { value: 'Asia/Jerusalem', label: '(UTC+02:00) Jerusalem' }, + { value: 'Europe/Kaliningrad', label: '(UTC+02:00) Kaliningrad' }, + { value: 'Africa/Tripoli', label: '(UTC+02:00) Tripoli' }, + { value: 'Asia/Amman', label: '(UTC+03:00) Amman' }, + { value: 'Asia/Baghdad', label: '(UTC+03:00) Baghdad' }, + { value: 'Europe/Istanbul', label: '(UTC+03:00) Istanbul' }, + { value: 'Asia/Kuwait', label: '(UTC+03:00) Kuwait, Riyadh' }, + { value: 'Europe/Minsk', label: '(UTC+03:00) Minsk' }, + { value: 'Europe/Moscow', label: '(UTC+03:00) Moscow, St. Petersburg' }, + { value: 'Africa/Nairobi', label: '(UTC+03:00) Nairobi' }, + { value: 'Asia/Tehran', label: '(UTC+03:30) Tehran' }, + { value: 'Asia/Muscat', label: '(UTC+04:00) Abu Dhabi, Muscat' }, + { + value: 'Europe/Astrakhan', + label: '(UTC+04:00) Astrakhan, Ulyanovsk, Volgograd', + }, + { value: 'Asia/Baku', label: '(UTC+04:00) Baku' }, + { value: 'Europe/Samara', label: '(UTC+04:00) Izhevsk, Samara' }, + { value: 'Indian/Mauritius', label: '(UTC+04:00) Port Louis' }, + { value: 'Europe/Tbilisi', label: '(UTC+04:00) Tbilisi' }, + { value: 'Asia/Yerevan', label: '(UTC+04:00) Yerevan' }, + { value: 'Asia/Kabul', label: '(UTC+04:30) Kabul' }, + { value: 'Asia/Tashkent', label: '(UTC+05:00) Tashkent, Ashgabat' }, + { value: 'Asia/Yekaterinburg', label: '(UTC+05:00) Ekaterinburg' }, + { value: 'Asia/Karachi', label: '(UTC+05:00) Islamabad, Karachi' }, + { value: 'Asia/Qyzylorda', label: '(UTC+05:00) Qyzylorda' }, + { + value: 'Asia/Kolkata', + label: '(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi', + }, + { value: 'Asia/Colombo', label: '(UTC+05:30) Sri Jayawardenepura' }, + { value: 'Asia/Kathmandu', label: '(UTC+05:45) Kathmandu' }, + { value: 'Asia/Dhaka', label: '(UTC+06:00) Dhaka' }, + { value: 'Asia/Yangon', label: '(UTC+06:30) Yangon (Rangoon)' }, + { value: 'Asia/Novosibirsk', label: '(UTC+07:00) Novosibirsk' }, + { value: 'Asia/Bangkok', label: '(UTC+07:00) Bangkok, Hanoi, Jakarta' }, + { value: 'Asia/Barnaul', label: '(UTC+07:00) Barnaul, Gorno-Altaysk' }, + { value: 'Asia/Hovd', label: '(UTC+07:00) Hovd' }, + { value: 'Asia/Krasnoyarsk', label: '(UTC+07:00) Krasnoyarsk' }, + { value: 'Asia/Tomsk', label: '(UTC+07:00) Tomsk' }, + { + value: 'Asia/Shanghai', + label: '(UTC+08:00) Beijing, Chongqing, Hong Kong SAR, Urumqi', + }, + { value: 'Asia/Irkutsk', label: '(UTC+08:00) Irkutsk' }, + { value: 'Asia/Singapore', label: '(UTC+08:00) Kuala Lumpur, Singapore' }, + { value: 'Australia/Perth', label: '(UTC+08:00) Perth' }, + { value: 'Asia/Taipei', label: '(UTC+08:00) Taipei' }, + { value: 'Asia/Ulaanbaatar', label: '(UTC+08:00) Ulaanbaatar' }, + { value: 'Asia/Pyongyang', label: '(UTC+08:30) Pyongyang' }, + { value: 'Australia/Eucla', label: '(UTC+08:45) Eucla' }, + { value: 'Asia/Chita', label: '(UTC+09:00) Chita' }, + { value: 'Asia/Tokyo', label: '(UTC+09:00) Osaka, Sapporo, Tokyo' }, + { value: 'Asia/Seoul', label: '(UTC+09:00) Seoul' }, + { value: 'Asia/Yakutsk', label: '(UTC+09:00) Yakutsk' }, + { value: 'Australia/Adelaide', label: '(UTC+09:30) Adelaide' }, + { value: 'Australia/Darwin', label: '(UTC+09:30) Darwin' }, + { value: 'Australia/Brisbane', label: '(UTC+10:00) Brisbane' }, + { + value: 'Australia/Sydney', + label: '(UTC+10:00) Canberra, Melbourne, Sydney', + }, + { value: 'Pacific/Guam', label: '(UTC+10:00) Guam, Port Moresby' }, + { value: 'Australia/Hobart', label: '(UTC+10:00) Hobart' }, + { value: 'Asia/Vladivostok', label: '(UTC+10:00) Vladivostok' }, + { value: 'Australia/Lord_Howe', label: '(UTC+10:30) Lord Howe Island' }, + { value: 'Pacific/Bougainville', label: '(UTC+11:00) Bougainville Island' }, + { value: 'Asia/Srednekolymsk', label: '(UTC+11:00) Chokurdakh' }, + { value: 'Asia/Magadan', label: '(UTC+11:00) Magadan' }, + { value: 'Pacific/Norfolk', label: '(UTC+11:00) Norfolk Island' }, + { value: 'Asia/Sakhalin', label: '(UTC+11:00) Sakhalin' }, + { + value: 'Pacific/Guadalcanal', + label: '(UTC+11:00) Solomon Islands, New Caledonia', + }, + { + value: 'Asia/Kamchatka', + label: '(UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky', + }, + { value: 'Pacific/Auckland', label: '(UTC+12:00) Auckland, Wellington' }, + { value: 'Pacific/Fiji', label: '(UTC+12:00) Fiji Islands' }, + { value: 'Pacific/Chatham', label: '(UTC+12:45) Chatham Islands' }, + { value: 'Pacific/Tongatapu', label: "(UTC+13:00) Nuku'alofa" }, + { value: 'Pacific/Apia', label: '(UTC+13:00) Samoa' }, + { value: 'Pacific/Kiritimati', label: '(UTC+14:00) Kiritimati Island' }, +]; + +export type TimezoneValue = (typeof TIMEZONES)[number]['value']; diff --git a/web/packages/shared/src/protocol/app-scope/__test__/ref-util.spec.ts b/web/packages/shared/src/protocol/app-scope/__test__/ref-util.spec.ts index 448fc84b..214b77a1 100644 --- a/web/packages/shared/src/protocol/app-scope/__test__/ref-util.spec.ts +++ b/web/packages/shared/src/protocol/app-scope/__test__/ref-util.spec.ts @@ -21,6 +21,7 @@ import { renameKeyPrefix, } from '../ref-util'; import { Edge, Edges, edgeSchema, edgesSchema, refsSchema } from '../scope'; +import { TwitterTransitionTargetEnum } from '../../transition'; describe('ref util', () => { describe('find descendants', () => { @@ -2181,3 +2182,124 @@ describe('task re order', () => { `); }); }); + +describe('target_input_twitter', () => { + it('should return twitter payload for target_input_twitter', () => { + const options = getRefOptions( + { + scopes: { + context: { variables: {} }, + edges: [], + states: { + state_1: { + name: 'state_1', + display_name: 'State 1', + children: { + inputs: { variables: {} }, + tasks: [], + outputs: { + variables: {}, + render: { buttons: {} }, + }, + }, + }, + }, + }, + }, + 'state_1', + 'target_input_twitter', + undefined, + undefined, + ); + + expect(options.local.twitter).toMatchInlineSnapshot(` + { + "variables": { + "creation_date": { + "display_name": "creation_date", + "type": "string", + }, + "images": { + "display_name": "images", + "type": "array", + }, + "text": { + "display_name": "text", + "type": "string", + }, + "tweet_id": { + "display_name": "tweet_id", + "type": "string", + }, + "user.creation_date": { + "display_name": "user.creation_date", + "type": "string", + }, + "user.is_blue_verified": { + "display_name": "user.is_blue_verified", + "type": "boolean", + }, + "user.is_bot": { + "display_name": "user.is_bot", + "type": "boolean", + }, + "user.is_private": { + "display_name": "user.is_private", + "type": "boolean", + }, + "user.is_verified": { + "display_name": "user.is_verified", + "type": "boolean", + }, + "user.name": { + "display_name": "user.name", + "type": "string", + }, + "user.user_id": { + "display_name": "user.user_id", + "type": "string", + }, + "user.username": { + "display_name": "user.username", + "type": "string", + }, + "user.verified_type": { + "display_name": "user.verified_type", + "type": "string", + }, + }, + } + `); + }); + + it('should return empty twitter payload for non-twitter refType', () => { + const options = getRefOptions( + { + scopes: { + context: { variables: {} }, + edges: [], + states: { + state_1: { + name: 'state_1', + display_name: 'State 1', + children: { + inputs: { variables: {} }, + tasks: [], + outputs: { + variables: {}, + render: { buttons: {} }, + }, + }, + }, + }, + }, + }, + 'state_1', + 'state_input', + undefined, + undefined, + ); + + expect(options.local.twitter).toBeUndefined(); + }); +}); diff --git a/web/packages/shared/src/protocol/app-scope/protocol.ts b/web/packages/shared/src/protocol/app-scope/protocol.ts index 1cfe5b84..f1e8069f 100644 --- a/web/packages/shared/src/protocol/app-scope/protocol.ts +++ b/web/packages/shared/src/protocol/app-scope/protocol.ts @@ -177,3 +177,51 @@ export const scopesSchema = z.object({ }); export type Scopes = z.infer; + +export const tweetDetailSchema = z + .object({ + text: z.object({ type: z.literal('string'), display_name: z.string() }), + images: z.object({ type: z.literal('array'), display_name: z.string() }), + tweet_id: z.object({ type: z.literal('string'), display_name: z.string() }), + creation_date: z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + 'user.creation_date': z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + 'user.user_id': z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + 'user.username': z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + 'user.name': z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + 'user.is_private': z.object({ + type: z.literal('boolean'), + display_name: z.string(), + }), + 'user.is_verified': z.object({ + type: z.literal('boolean'), + display_name: z.string(), + }), + 'user.is_blue_verified': z.object({ + type: z.literal('boolean'), + display_name: z.string(), + }), + 'user.is_bot': z.object({ + type: z.literal('boolean'), + display_name: z.string(), + }), + 'user.verified_type': z.object({ + type: z.literal('string'), + display_name: z.string(), + }), + }) + .strict(); diff --git a/web/packages/shared/src/protocol/app-scope/ref-util.ts b/web/packages/shared/src/protocol/app-scope/ref-util.ts index 4557080e..bd820c9c 100644 --- a/web/packages/shared/src/protocol/app-scope/ref-util.ts +++ b/web/packages/shared/src/protocol/app-scope/ref-util.ts @@ -22,6 +22,7 @@ import { setNodedataKeyValParamSchema, } from './scope'; import { reservedStateNameSchema } from '../node'; +import { TwitterTransitionTargetEnum } from '../transition'; import { cloneDeep, find, @@ -87,6 +88,10 @@ export function getRefOptions( assignStateRender(); assignButtonsPayload(); break; + case 'target_input_twitter': + assignStateRender(); + assignTwitterPayload(); + break; case 'state_output_key': // noops default: { @@ -95,7 +100,8 @@ export function getRefOptions( } try { - return refOptionsOutputSchema.parse(ret); + const result = refOptionsOutputSchema.parse(ret); + return result; } catch (error) { console.warn('RefOptionsOutputSchema Zod Validate Error', error); return ret; @@ -184,6 +190,38 @@ export function getRefOptions( const tasks = state.children.tasks; ret.local.tasks = tasks; } + + function assignTwitterPayload() { + ret.local.twitter = { + variables: { + text: { type: 'string', display_name: 'text' }, + images: { type: 'array', display_name: 'images' }, + tweet_id: { type: 'string', display_name: 'tweet_id' }, + creation_date: { type: 'string', display_name: 'creation_date' }, + 'user.creation_date': { + type: 'string', + display_name: 'user.creation_date', + }, + 'user.user_id': { type: 'string', display_name: 'user.user_id' }, + 'user.username': { type: 'string', display_name: 'user.username' }, + 'user.name': { type: 'string', display_name: 'user.name' }, + 'user.is_private': { type: 'boolean', display_name: 'user.is_private' }, + 'user.is_verified': { + type: 'boolean', + display_name: 'user.is_verified', + }, + 'user.is_blue_verified': { + type: 'boolean', + display_name: 'user.is_blue_verified', + }, + 'user.is_bot': { type: 'boolean', display_name: 'user.is_bot' }, + 'user.verified_type': { + type: 'string', + display_name: 'user.verified_type', + }, + }, + }; + } } export function findDescendants( diff --git a/web/packages/shared/src/protocol/app-scope/scope.ts b/web/packages/shared/src/protocol/app-scope/scope.ts index 3b653636..fc6d03da 100644 --- a/web/packages/shared/src/protocol/app-scope/scope.ts +++ b/web/packages/shared/src/protocol/app-scope/scope.ts @@ -5,6 +5,7 @@ import { outputVariablesSchema, taskSchema, variablesSchema, + tweetDetailSchema, } from './protocol'; import { reservedStateNameSchema } from '../node'; @@ -25,6 +26,7 @@ export const refTypeSchema = z.enum([ 'state_render', 'target_input', 'state_output_key', + 'target_input_twitter', ]); export type RefType = z.infer; @@ -48,6 +50,11 @@ export const refOptionsOutputSchema = z.object({ variables: outputVariablesSchema, }), buttons: buttonsSchema, + twitter: z + .object({ + variables: tweetDetailSchema, + }) + .optional(), }), }); diff --git a/web/packages/shared/src/protocol/node/index.ts b/web/packages/shared/src/protocol/node/index.ts index 511bbd9d..749d6987 100644 --- a/web/packages/shared/src/protocol/node/index.ts +++ b/web/packages/shared/src/protocol/node/index.ts @@ -6,3 +6,37 @@ export const reservedStateName = { } as const; export const reservedStateNameSchema = z.nativeEnum(reservedStateName); + +export enum NodeTypeEnum { + start = 'start', + end = 'end', + widget = 'widget', + state = 'state', + condition_state = 'condition_state', + workflow = 'workflow', + intro = 'intro', + note = 'note', +} + +export type NodeType = keyof typeof NodeTypeEnum; + +export const nodeTypeSchema = z.nativeEnum(NodeTypeEnum); + +export enum NodeIdEnum { + start = '@@@start', + end = '@@@end', + intro = 'intro', +} + +export type WidgetItem = { + display_name: string; // widget显示名称 + name: string; // widget名称(唯一标识) + icon?: string; // icon + desc?: string; // widget描述 + children?: WidgetItem[]; // 子widget + type?: NodeType; + custom?: boolean; // 是否为自定义widget + undraggable?: boolean; // 是否可以拖拽至画布中 + customRender?: () => JSX.Element; + widget_name?: string; +}; diff --git a/web/packages/shared/src/protocol/pro-config/block.ts b/web/packages/shared/src/protocol/pro-config/block.ts index 2abca96d..2f04e2cd 100644 --- a/web/packages/shared/src/protocol/pro-config/block.ts +++ b/web/packages/shared/src/protocol/pro-config/block.ts @@ -2,10 +2,55 @@ import { z } from 'zod'; import { customKeySchema } from './common'; import { variableSchema, valueSchema, inputSchema } from './variables'; import { renderConfigSchema } from './render'; -import { transitionSchema } from './transition'; +import { transitionSchema, transitionCaseSchema } from './transition'; + +// Timer related schemas +const timezoneSchema = z.string(); + +const dateSchema = z.object({ + minute: z.number().min(0).max(60).optional(), + hour: z.number().min(0).max(24).optional(), + date: z.number().min(1).max(31).optional(), + month: z.number().min(1).max(12).optional(), + year: z.number().optional(), + timezone: z.union([timezoneSchema, z.literal('client')]).optional(), +}); + +const intervalSchema = z.object({ + minute: z.number().optional(), + hour: z.number().optional(), + day: z.number().optional(), +}); + +const timerSchema = z.object({ + start: dateSchema.required(), + interval: intervalSchema.optional(), + end: z + .object({ + date: dateSchema.optional(), + count: z.number().optional(), + }) + .optional(), + delay: z + .union([ + intervalSchema, + z.object({ + random_mode: z.literal('even'), + min: z.number(), + max: z.number(), + }), + ]) + .optional(), +}); // 基础类型定义 -const blockTypeSchema = z.enum(['automata', 'state', 'task', 'workflow']); +const blockTypeSchema = z.enum([ + 'automata', + 'state', + 'task', + 'workflow', + 'dispatcher', +]); // Block 基础 Schema const blockSchema = z.object({ @@ -55,6 +100,15 @@ const stateSchema = blockSchema.extend({ .object({ is_final: z.boolean().optional(), cache: z.boolean().optional(), + timers: z + .record( + customKeySchema, + z.object({ + timer: timerSchema, + event: z.string(), + }), + ) + .optional(), }) .optional(), blocks: blockChildrenSchema.optional(), @@ -128,6 +182,19 @@ const workflowSchema = containerBlockSchema.extend({ blocks: blockChildrenSchema, }); +const dispatcherSchema = blockSchema.extend({ + type: z.literal('dispatcher'), + properties: z + .object({ + cache: z.boolean().optional(), + }) + .optional(), + blocks: blockChildrenSchema.optional(), + transitions: z.object({ + ALWAYS: z.array(transitionCaseSchema), + }), +}); + export { blockSchema, stateSchema, @@ -136,4 +203,9 @@ export { workflowSchema, blockChildrenSchema, containerBlockSchema, + dispatcherSchema, + timerSchema, + dateSchema, + intervalSchema, + timezoneSchema, }; diff --git a/web/packages/shared/src/protocol/pro-config/transition.ts b/web/packages/shared/src/protocol/pro-config/transition.ts index d2364fb4..f02c6648 100644 --- a/web/packages/shared/src/protocol/pro-config/transition.ts +++ b/web/packages/shared/src/protocol/pro-config/transition.ts @@ -4,6 +4,7 @@ import { valueSchema, boolExpressionSchema } from './variables'; // TransitionCase schema export const transitionCaseSchema = z.object({ + name: z.string().optional(), target: targetPathSchema.optional(), target_inputs: z.record(customKeySchema, valueSchema).optional(), condition: z.union([z.boolean(), boolExpressionSchema]).optional(), @@ -12,7 +13,12 @@ export const transitionCaseSchema = z.object({ // Special events schemas export const automataSpecialEventsSchema = z.enum(['DONE']); -export const stateSpecialEventsSchema = z.enum(['CHAT', 'ALWAYS']); +export const stateSpecialEventsSchema = z.enum([ + 'CHAT', + 'ALWAYS', + 'X.MENTIONED', + 'X.PINNED.REPLIED', +]); export const actionEventsSchema = z.enum(['COPY', 'OPEN', 'DOWNLOAD']); export const specialEventsSchema = z.union([ @@ -27,3 +33,35 @@ export const transitionSchema = z.union([ transitionCaseSchema, z.array(transitionCaseSchema), ]); + +// Twitter related schemas +export const twitterUserSchema = z.object({ + creation_date: z.string(), + user_id: z.string(), + username: z.string(), + name: z.string(), + is_private: z.boolean(), + is_verified: z.boolean(), + is_blue_verified: z.boolean(), + is_bot: z.boolean(), + verified_type: z.enum(['blue', 'business', 'government']), +}); + +export const tweetDetailSchema = z.object({ + text: z.string(), + images: z.array(z.string().url()), + user: twitterUserSchema, + tweet_id: z.string(), + creation_date: z.string(), +}); + +// Payload map schema +export const payloadMapSchema = z.object({ + 'X.MENTIONED': tweetDetailSchema, + 'X.PINNED.REPLIED': tweetDetailSchema, +}); + +// Export types +export type TwitterUser = z.infer; +export type TweetDetail = z.infer; +export type PayloadMap = z.infer; diff --git a/web/packages/shared/src/protocol/transition/index.ts b/web/packages/shared/src/protocol/transition/index.ts new file mode 100644 index 00000000..32dcc46b --- /dev/null +++ b/web/packages/shared/src/protocol/transition/index.ts @@ -0,0 +1,66 @@ +import { z } from 'zod'; +import { timerSchema } from '../pro-config'; +import { NodeTypeEnum } from '../node'; +import { v4 as uuidv4 } from 'uuid'; + +export type Timer = z.infer; + +export type TransitionData = { + name: string; + key: string; + type: TransitionTargetEnum; + timers: Timer; + source: string; + target: string; + target_inputs: { + [key: string]: any; + }; + condition: string; +}; + +export enum TransitionTypeEnum { + intro = NodeTypeEnum.intro, + state = NodeTypeEnum.state, + condition_state = NodeTypeEnum.condition_state, +} + +// 临时 +export enum TransitionTargetEnum { + 'X.MENTIONED' = 'X.MENTIONED', + 'X.PINNED.REPLIED' = 'X.PINNED.REPLIED', + ALWAYS = 'ALWAYS', + CHAT = 'CHAT', + TIMER = 'TIMER', + STATE = 'STATE', +} + +export enum TwitterTransitionTargetEnum { + 'X.MENTIONED' = 'X.MENTIONED', + 'X.PINNED.REPLIED' = 'X.PINNED.REPLIED', +} + +export type TransitionTargetType = + | 'X.MENTIONED' + | 'X.PINNED.REPLIED' + | 'ALWAYS' + | 'CHAT'; + +export const transitionTargetTypeSchema = z + .union([ + z.literal('X.MENTIONED'), + z.literal('X.PINNED.REPLIED'), + z.literal('ALWAYS'), + z.literal('CHAT'), + ]) + .transform(val => { + const predefinedValues = [ + 'X.MENTIONED', + 'X.PINNED.REPLIED', + 'ALWAYS', + 'CHAT', + ]; + if (predefinedValues.indexOf(val) !== -1) { + return val; + } + return uuidv4(); + }); diff --git a/web/packages/shared/src/utils/__test__/get_display_name.spec.ts b/web/packages/shared/src/utils/__test__/get_display_name.spec.ts index 639ee7f2..cb94b416 100644 --- a/web/packages/shared/src/utils/__test__/get_display_name.spec.ts +++ b/web/packages/shared/src/utils/__test__/get_display_name.spec.ts @@ -1,8 +1,6 @@ -import { - getTaskDisplayName, - getButtonDisplayName, - NodeTypeEnum, -} from '../get_display_name'; +import { getTaskDisplayName, getButtonDisplayName } from '../get_display_name'; +import { NodeTypeEnum } from '../../protocol/node'; + import { Task } from '../../protocol/task'; import { Button } from '../../protocol/render-button'; diff --git a/web/packages/shared/src/utils/get_display_name.ts b/web/packages/shared/src/utils/get_display_name.ts index 0f92ee01..f20de3f2 100644 --- a/web/packages/shared/src/utils/get_display_name.ts +++ b/web/packages/shared/src/utils/get_display_name.ts @@ -1,30 +1,6 @@ import { Task } from '../protocol/task'; import { Button } from '../protocol/render-button'; - -export enum NodeTypeEnum { - start = 'start', - end = 'end', - widget = 'widget', - state = 'state', - workflow = 'workflow', - intro = 'intro', - note = 'note', -} - -export type NodeType = keyof typeof NodeTypeEnum; - -export type WidgetItem = { - display_name: string; // widget显示名称 - name: string; // widget名称(唯一标识) - icon?: string; // icon - desc?: string; // widget描述 - children?: WidgetItem[]; // 子widget - type?: NodeType; - custom?: boolean; // 是否为自定义widget - undraggable?: boolean; // 是否可以拖拽至画布中 - customRender?: () => JSX.Element; - widget_name?: string; -}; +import { WidgetItem, NodeTypeEnum } from '../protocol/node'; export const getTaskDisplayName = (value: WidgetItem, tasks: Task[]) => { let indexMap: Record = {}; diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 7ef78f70..492e1369 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -1014,6 +1014,9 @@ importers: unidecode: specifier: ^1.1.0 version: 1.1.0 + uuid: + specifier: ^11.0.5 + version: 11.0.5 zod: specifier: ^3.23.8 version: 3.23.8 @@ -1047,13 +1050,13 @@ importers: version: 0.1.3 jest: specifier: ^29.4.1 - version: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.5.1)(typescript@5.3.3)) + version: 29.7.0(@types/node@20.16.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3)) node-emoji: specifier: ^2.1.3 version: 2.1.3 ts-jest: specifier: ^29.0.5 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.5.1)(typescript@5.3.3)))(typescript@5.3.3) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3)))(typescript@5.3.3) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -11902,6 +11905,10 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true + uuid@11.0.5: + resolution: {integrity: sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -14619,6 +14626,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3))': dependencies: @@ -16630,7 +16638,7 @@ snapshots: node-plop: 0.26.3 picocolors: 1.0.1 proxy-agent: 6.4.0 - ts-node: 10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3) + ts-node: 10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3) update-check: 1.5.4 validate-npm-package-name: 5.0.1 transitivePeerDependencies: @@ -18561,6 +18569,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true create-jest@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -21243,6 +21252,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true jest-cli@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -21289,7 +21299,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.16.1 - ts-node: 10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3) + ts-node: 10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -21324,6 +21334,7 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - supports-color + optional: true jest-config@29.7.0(@types/node@20.16.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -21386,6 +21397,7 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - supports-color + optional: true jest-config@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -21728,6 +21740,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true jest@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -23368,7 +23381,7 @@ snapshots: yaml: 2.5.0 optionalDependencies: postcss: 8.4.41 - ts-node: 10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3) + ts-node: 10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3) postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)): dependencies: @@ -25545,25 +25558,6 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.25.2) - ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.5.1)(typescript@5.3.3)))(typescript@5.3.3): - dependencies: - bs-logger: 0.2.6 - ejs: 3.1.10 - fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.5.1)(typescript@5.3.3)) - jest-util: 29.7.0 - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.6.3 - typescript: 5.3.3 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.25.2 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.25.2) - ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.4.1)(ts-node@10.9.2(@swc/core@1.7.12)(@types/node@22.4.1)(typescript@5.3.3)))(typescript@5.3.3): dependencies: bs-logger: 0.2.6 @@ -25583,41 +25577,41 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.25.2) - ts-node@10.9.2(@swc/core@1.7.12(@swc/helpers@0.5.5))(@types/node@20.16.1)(typescript@5.3.3): + ts-node@10.9.2(@swc/core@1.7.12)(@types/node@16.18.105)(typescript@5.1.6): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.16.1 + '@types/node': 16.18.105 acorn: 8.12.1 acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.3.3 + typescript: 5.1.6 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: '@swc/core': 1.7.12(@swc/helpers@0.5.5) - ts-node@10.9.2(@swc/core@1.7.12)(@types/node@16.18.105)(typescript@5.1.6): + ts-node@10.9.2(@swc/core@1.7.12)(@types/node@20.16.1)(typescript@5.3.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 16.18.105 + '@types/node': 20.16.1 acorn: 8.12.1 acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.1.6 + typescript: 5.3.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: @@ -26023,6 +26017,8 @@ snapshots: uuid@10.0.0: {} + uuid@11.0.5: {} + uuid@8.3.2: {} uuid@9.0.1: {} From 37c1503e616250c92b5d15cba5405c11cdb65302 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 15 Jan 2025 16:53:39 +0800 Subject: [PATCH 33/42] lint --- web/apps/web/src/app/app/page.tsx | 6 +++--- web/apps/web/src/app/container.ts | 2 +- .../components/app/edges/x-custom-edge.tsx | 10 +++++----- .../app/new-transition-sheet/index.tsx | 12 ++++++------ .../app/node-form/widgets/state-selector.tsx | 2 +- .../app/node-form/widgets/timer-selector.tsx | 6 +++--- .../widgets/transition-condition-editor.tsx | 1 + .../app/nodes/condition-state-node/index.tsx | 12 ++++++++---- .../app/nodes/x-intro-node/index.tsx | 6 +++--- .../app/nodes/x-state-node/index.tsx | 14 +++++++++----- .../plugins/comfyui/check-dialog/index.tsx | 6 +++--- .../components/chat/app-builder-chat.model.ts | 10 +++++----- .../src/components/home/card-list/index.tsx | 5 +++-- .../src/components/home/detail-form/index.tsx | 3 ++- .../home/detail-form/type-select.tsx | 19 ++++++++++--------- .../src/components/home/filter-tabs/index.tsx | 11 +++++++---- .../stores/app/models/app-builder.model.ts | 1 + .../web/src/stores/app/models/flow.model.ts | 5 +++-- .../web/src/stores/app/schema-provider.tsx | 2 +- .../app/schema/get-transition-schema.ts | 3 ++- web/apps/web/src/stores/app/use-app-state.tsx | 5 +++-- .../src/stores/app/utils/data-transformer.ts | 11 +++++------ .../web/src/stores/home/models/home-model.ts | 3 ++- .../web/src/stories/EditValue.stories.tsx | 6 ++++-- web/apps/web/src/stories/EditValue.tsx | 4 ++-- .../web/src/stories/FormValidate.stories.tsx | 1 + web/apps/web/src/stories/FormValidate.tsx | 2 +- web/apps/web/src/types/app/types.ts | 1 + 28 files changed, 96 insertions(+), 73 deletions(-) diff --git a/web/apps/web/src/app/app/page.tsx b/web/apps/web/src/app/app/page.tsx index cd316e01..c37a5b6e 100644 --- a/web/apps/web/src/app/app/page.tsx +++ b/web/apps/web/src/app/app/page.tsx @@ -6,12 +6,12 @@ import { useScroll } from 'ahooks'; import { useInjection } from 'inversify-react'; import { useEffect, useRef } from 'react'; -import { CreateDialog } from '@/components/home/create-dialog'; -import { ImportDialog } from '@/components/home/import-dialog'; import { CardList } from '@/components/home/card-list'; +import { CreateDialog } from '@/components/home/create-dialog'; import { FilterTabs } from '@/components/home/filter-tabs'; -import { cn } from '@/utils/cn'; +import { ImportDialog } from '@/components/home/import-dialog'; import { HomeModel } from '@/stores/home/models/home-model'; +import { cn } from '@/utils/cn'; export default function AppPage() { const contentRef = useRef(null); diff --git a/web/apps/web/src/app/container.ts b/web/apps/web/src/app/container.ts index 7cabcc8f..56b44a4d 100644 --- a/web/apps/web/src/app/container.ts +++ b/web/apps/web/src/app/container.ts @@ -8,7 +8,6 @@ import { Container, ContainerModule, interfaces } from 'inversify'; import * as Mobx from 'mobx'; import { toast } from 'react-toastify'; -import { HomeModel } from '@/stores/home/models/home-model'; import { ComfyUIModel } from '@/components/app/plugins/comfyui/comfyui.model'; import { AssistantModel } from '@/components/assistant/model'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; @@ -24,6 +23,7 @@ import { WidgetsMarketplaceModel } from '@/components/manager/manager-content/wi import { SettingsModel } from '@/components/settings/settings.model'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { FlowModel } from '@/stores/app/models/flow.model'; +import { HomeModel } from '@/stores/home/models/home-model'; import { FormEngineModel } from '@/utils/form-engine.model'; import { FormikModel } from '@/utils/formik.model'; import { ModalModel } from '@/utils/modal.model'; diff --git a/web/apps/web/src/components/app/edges/x-custom-edge.tsx b/web/apps/web/src/components/app/edges/x-custom-edge.tsx index ed40c6fa..3801c892 100644 --- a/web/apps/web/src/components/app/edges/x-custom-edge.tsx +++ b/web/apps/web/src/components/app/edges/x-custom-edge.tsx @@ -7,17 +7,17 @@ import { EdgeText, } from '@shellagent/flow-engine'; import { RefSceneEnum } from '@shellagent/shared/protocol/app-scope'; +import { + TransitionTypeEnum, + TransitionData, +} from '@shellagent/shared/protocol/transition'; import { useKeyPress } from 'ahooks'; import { useInjection } from 'inversify-react'; import React, { useEffect } from 'react'; -import { useAppState } from '@/stores/app/use-app-state'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { useAppState } from '@/stores/app/use-app-state'; -import { - TransitionTypeEnum, - TransitionData, -} from '@shellagent/shared/protocol/transition'; export const XCustomEdge = ({ id, sourceX, diff --git a/web/apps/web/src/components/app/new-transition-sheet/index.tsx b/web/apps/web/src/components/app/new-transition-sheet/index.tsx index e17d6878..f6879fe3 100644 --- a/web/apps/web/src/components/app/new-transition-sheet/index.tsx +++ b/web/apps/web/src/components/app/new-transition-sheet/index.tsx @@ -2,19 +2,19 @@ import { useReactFlowStore, NodeTypeEnum } from '@shellagent/flow-engine'; import { TValues } from '@shellagent/form-engine'; +import { type TransitionData } from '@shellagent/shared/protocol/transition'; +import { getNewKey } from '@shellagent/shared/utils'; import { Drawer } from '@shellagent/ui'; import { useInjection } from 'inversify-react'; +import { observer } from 'mobx-react-lite'; import { useMemo, useCallback, memo } from 'react'; -import NodeForm from '@/components/app/node-form'; -import { SchemaProvider } from '@/stores/app/schema-provider'; -import { getXTransitionSchema } from '@/stores/app/schema/get-transition-schema'; -import { observer } from 'mobx-react-lite'; import { EditableTitle } from '@/components/app/editable-title'; +import NodeForm from '@/components/app/node-form'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { getXTransitionSchema } from '@/stores/app/schema/get-transition-schema'; +import { SchemaProvider } from '@/stores/app/schema-provider'; import { useAppState } from '@/stores/app/use-app-state'; -import { type TransitionData } from '@shellagent/shared/protocol/transition'; -import { getNewKey } from '@shellagent/shared/utils'; const TransitionSheetNew: React.FC<{}> = observer(() => { const appBuilder = useInjection('AppBuilderModel'); diff --git a/web/apps/web/src/components/app/node-form/widgets/state-selector.tsx b/web/apps/web/src/components/app/node-form/widgets/state-selector.tsx index 608f9ee3..05acf505 100644 --- a/web/apps/web/src/components/app/node-form/widgets/state-selector.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/state-selector.tsx @@ -1,5 +1,5 @@ -import { Select } from '@shellagent/ui'; import { useReactFlowStore, NodeTypeEnum } from '@shellagent/flow-engine'; +import { Select } from '@shellagent/ui'; interface StateSelectorProps { value: string; diff --git a/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx b/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx index 89f60ac0..15135f21 100644 --- a/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/timer-selector.tsx @@ -1,4 +1,5 @@ -import React, { useState, useCallback, useEffect } from 'react'; +import { TIMEZONES } from '@shellagent/shared/constants'; +import { type Timer } from '@shellagent/shared/protocol/transition'; import { DatePicker, TimePicker, @@ -11,8 +12,7 @@ import { } from 'antd'; import type { RadioChangeEvent } from 'antd'; import dayjs from 'dayjs'; -import { TIMEZONES } from '@shellagent/shared/constants'; -import { type Timer } from '@shellagent/shared/protocol/transition'; +import React, { useState, useCallback, useEffect } from 'react'; enum IntervalUnit { minute = 'minute', diff --git a/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx b/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx index c4423783..7d2c8c8d 100644 --- a/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/transition-condition-editor.tsx @@ -16,6 +16,7 @@ import { ExpressionInput } from '@/components/app/node-form/widgets/expression-i import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { getSchemaByInputs } from '@/stores/app/schema/get-workflow-schema'; import { useAppState } from '@/stores/app/use-app-state'; + import { StateSelector } from './state-selector'; interface ITransitionConditionEditorProps { diff --git a/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx b/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx index 458d0927..afd8b292 100644 --- a/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx +++ b/web/apps/web/src/components/app/nodes/condition-state-node/index.tsx @@ -15,28 +15,32 @@ import { import { TValues } from '@shellagent/form-engine'; import { RefSceneEnum } from '@shellagent/shared/protocol/app-scope'; import { Task, TaskSchema } from '@shellagent/shared/protocol/task'; -import { customSnakeCase, getTaskDisplayName } from '@shellagent/shared/utils'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; +import { + customSnakeCase, + getTaskDisplayName, + getNewKey, +} from '@shellagent/shared/utils'; import { FormRef } from '@shellagent/ui'; import { useKeyPress } from 'ahooks'; import { useInjection } from 'inversify-react'; import React, { useCallback, useRef, useEffect, useState } from 'react'; -import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; import { EdgeTypeEnum } from '@/components/app/edges'; import NodeCard from '@/components/app/node-card'; import NodeForm from '@/components/app/node-form'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; -import { getNewKey } from '@shellagent/shared/utils'; import emitter, { EventType, useEventEmitter, } from '@/stores/app/models/emitter'; +import { getConditionStateSchema } from '@/stores/app/schema/get-condition-state-schema'; import { useAppState } from '@/stores/app/use-app-state'; import { getKeyboardKeyCodeBySystem, isEventTargetInputArea, } from '@/utils/common-helper'; -import { getConditionStateSchema } from '@/stores/app/schema/get-condition-state-schema'; + import { useDuplicateState } from './hook/use-duplicate-state'; const ConditionStateNode: React.FC> = ({ diff --git a/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx b/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx index 88730a6a..8e0da2ea 100644 --- a/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx +++ b/web/apps/web/src/components/app/nodes/x-intro-node/index.tsx @@ -10,6 +10,8 @@ import { useReactFlowStore, } from '@shellagent/flow-engine'; import { TValues } from '@shellagent/form-engine'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; +import { getNewKey } from '@shellagent/shared/utils'; import { FormRef } from '@shellagent/ui'; import { useInjection } from 'inversify-react'; import React, { useCallback, useRef, useEffect, useState } from 'react'; @@ -18,14 +20,12 @@ import { EdgeTypeEnum } from '@/components/app/edges'; import NodeCard from '@/components/app/node-card'; import NodeForm from '@/components/app/node-form'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; -import { getNewKey } from '@shellagent/shared/utils'; import emitter, { EventType, useEventEmitter, } from '@/stores/app/models/emitter'; -import { useAppState } from '@/stores/app/use-app-state'; import { getXStateSchema } from '@/stores/app/schema/get-x-state-schema'; -import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; +import { useAppState } from '@/stores/app/use-app-state'; const IntroNode: React.FC> = ({ selected, data }) => { const stateFormRef = useRef(null); diff --git a/web/apps/web/src/components/app/nodes/x-state-node/index.tsx b/web/apps/web/src/components/app/nodes/x-state-node/index.tsx index 9a687d79..cfd8f8fa 100644 --- a/web/apps/web/src/components/app/nodes/x-state-node/index.tsx +++ b/web/apps/web/src/components/app/nodes/x-state-node/index.tsx @@ -15,28 +15,32 @@ import { import { TValues } from '@shellagent/form-engine'; import { RefSceneEnum } from '@shellagent/shared/protocol/app-scope'; import { Task, TaskSchema } from '@shellagent/shared/protocol/task'; -import { customSnakeCase, getTaskDisplayName } from '@shellagent/shared/utils'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; +import { + customSnakeCase, + getTaskDisplayName, + getNewKey, +} from '@shellagent/shared/utils'; import { FormRef } from '@shellagent/ui'; import { useKeyPress } from 'ahooks'; import { useInjection } from 'inversify-react'; - import React, { useCallback, useRef, useEffect, useState } from 'react'; -import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; + import { EdgeTypeEnum } from '@/components/app/edges'; import NodeCard from '@/components/app/node-card'; import NodeForm from '@/components/app/node-form'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; -import { getNewKey } from '@shellagent/shared/utils'; import emitter, { EventType, useEventEmitter, } from '@/stores/app/models/emitter'; +import { getXStateSchema } from '@/stores/app/schema/get-x-state-schema'; import { useAppState } from '@/stores/app/use-app-state'; import { getKeyboardKeyCodeBySystem, isEventTargetInputArea, } from '@/utils/common-helper'; -import { getXStateSchema } from '@/stores/app/schema/get-x-state-schema'; + import { useDuplicateState } from './hook/use-duplicate-state'; const XStateNode: React.FC> = ({ selected, data }) => { diff --git a/web/apps/web/src/components/app/plugins/comfyui/check-dialog/index.tsx b/web/apps/web/src/components/app/plugins/comfyui/check-dialog/index.tsx index 7a0860a9..362a7158 100644 --- a/web/apps/web/src/components/app/plugins/comfyui/check-dialog/index.tsx +++ b/web/apps/web/src/components/app/plugins/comfyui/check-dialog/index.tsx @@ -3,15 +3,15 @@ import { AModal, Button, Title } from '@shellagent/ui'; import { useRequest } from 'ahooks'; import { FormInstance } from 'antd'; +import { useInjection } from 'inversify-react'; +import { observer } from 'mobx-react-lite'; import { useRef } from 'react'; import { toast } from 'react-toastify'; -import { useInjection } from 'inversify-react'; -import { observer } from 'mobx-react-lite'; +import { CheckerContent } from './content'; import { formatFormData2Dependency } from '../comfyui-utils'; import { ComfyUIModel } from '../comfyui.model'; import { updateDependency } from '../services'; -import { CheckerContent } from './content'; export const CheckDialog = observer(() => { const formRef = useRef(null); diff --git a/web/apps/web/src/components/chat/app-builder-chat.model.ts b/web/apps/web/src/components/chat/app-builder-chat.model.ts index 2932b9e9..c1ca3ce0 100644 --- a/web/apps/web/src/components/chat/app-builder-chat.model.ts +++ b/web/apps/web/src/components/chat/app-builder-chat.model.ts @@ -10,19 +10,19 @@ import { action, makeObservable, observable, runInAction } from 'mobx'; import { ButtonFnParams, IMLocalFile } from 'myshell-bundled-chat'; import { JsonSchema7 } from 'node_modules/@shellagent/form-engine/src/types/jsonSchema7'; -import { upload } from '@/services/common'; - import { convertXBotSvc } from '@/services/app'; +import { upload } from '@/services/common'; import { type AppBuilderModel } from '@/stores/app/models/app-builder.model'; -import type { ServerMessage } from '../../services/app/message-type'; -import { EventStatusEnum, RunAppRequest } from '../../services/app/type'; -import { ToastModel } from '../../utils/toast.model'; + import { patchImageUrl, patchMessageActionPopupForm, serverMessageToMessage, transformEmbedObjs, } from './app-builder-chat-utils'; +import type { ServerMessage } from '../../services/app/message-type'; +import { EventStatusEnum, RunAppRequest } from '../../services/app/type'; +import { ToastModel } from '../../utils/toast.model'; @injectable() export class AppBuilderChatModel { diff --git a/web/apps/web/src/components/home/card-list/index.tsx b/web/apps/web/src/components/home/card-list/index.tsx index 51d210ca..6a3baed3 100644 --- a/web/apps/web/src/components/home/card-list/index.tsx +++ b/web/apps/web/src/components/home/card-list/index.tsx @@ -1,10 +1,11 @@ 'use client'; import { Heading, Spinner, Text } from '@shellagent/ui'; -import { CreateDialog } from '@/components/home/create-dialog'; -import { FlowCard } from '@/components/home/flow-card'; import { useInjection } from 'inversify-react'; import { observer } from 'mobx-react-lite'; + +import { CreateDialog } from '@/components/home/create-dialog'; +import { FlowCard } from '@/components/home/flow-card'; import { HomeModel } from '@/stores/home/models/home-model'; interface CardListProps { diff --git a/web/apps/web/src/components/home/detail-form/index.tsx b/web/apps/web/src/components/home/detail-form/index.tsx index a339a292..30a1845d 100644 --- a/web/apps/web/src/components/home/detail-form/index.tsx +++ b/web/apps/web/src/components/home/detail-form/index.tsx @@ -1,8 +1,9 @@ import { ISchema, MemoizedFormEngine, TValues } from '@shellagent/form-engine'; import { Input, Textarea } from '@shellagent/ui'; -import { TypeSelect } from './type-select'; import React from 'react'; +import { TypeSelect } from './type-select'; + interface DetailFormProps { type: 'app' | 'workflow'; values: TValues; diff --git a/web/apps/web/src/components/home/detail-form/type-select.tsx b/web/apps/web/src/components/home/detail-form/type-select.tsx index 04ece4ee..128a84b3 100644 --- a/web/apps/web/src/components/home/detail-form/type-select.tsx +++ b/web/apps/web/src/components/home/detail-form/type-select.tsx @@ -1,5 +1,6 @@ import { Checkbox } from '@shellagent/ui'; import React from 'react'; + import { cn } from '@/utils/cn'; interface Option { @@ -32,22 +33,22 @@ export const TypeSelect: React.FC = ({ return (
{options.map(option => ( -
-
+ ))}
); diff --git a/web/apps/web/src/components/home/filter-tabs/index.tsx b/web/apps/web/src/components/home/filter-tabs/index.tsx index 575fde4b..2ab652b7 100644 --- a/web/apps/web/src/components/home/filter-tabs/index.tsx +++ b/web/apps/web/src/components/home/filter-tabs/index.tsx @@ -1,10 +1,11 @@ 'use client'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { Button } from '@shellagent/ui'; import { useInjection } from 'inversify-react'; import { observer } from 'mobx-react-lite'; +import { useRouter, useSearchParams } from 'next/navigation'; import { useEffect } from 'react'; -import { Button } from '@shellagent/ui'; + import { HomeModel, type AppType } from '@/stores/home/models/home-model'; import { cn } from '@/utils/cn'; @@ -14,7 +15,7 @@ const FILTER_OPTIONS: { value: AppType; label: string }[] = [ { value: 'x_bot', label: 'X Bot' }, ]; -export const FilterTabs = observer(({ className }: { className?: string }) => { +const FilterTabsBase = ({ className }: { className?: string }) => { const router = useRouter(); const searchParams = useSearchParams(); const homeModel = useInjection(HomeModel); @@ -50,4 +51,6 @@ export const FilterTabs = observer(({ className }: { className?: string }) => { ))}
); -}); +}; + +export const FilterTabs = observer(FilterTabsBase); diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 0a714347..be84b89e 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -61,6 +61,7 @@ import { } from '@/stores/app/utils/data-transformer'; import type { Config, Metadata, NodeDataType } from '@/types/app/types'; import { ToastModel } from '@/utils/toast.model'; + import { CascaderOption, convertRefOptsToCascaderOpts, diff --git a/web/apps/web/src/stores/app/models/flow.model.ts b/web/apps/web/src/stores/app/models/flow.model.ts index 533f145f..84087ba9 100644 --- a/web/apps/web/src/stores/app/models/flow.model.ts +++ b/web/apps/web/src/stores/app/models/flow.model.ts @@ -1,6 +1,7 @@ -import { makeAutoObservable } from 'mobx'; -import { injectable } from 'inversify'; import { Edge, Node } from '@shellagent/flow-engine'; +import { injectable } from 'inversify'; +import { makeAutoObservable } from 'mobx'; + import { EdgeDataTypeEnum, CustomEdgeData } from '@/components/app/edges'; type SheetMode = 'button' | 'workflow' | 'widget' | ''; diff --git a/web/apps/web/src/stores/app/schema-provider.tsx b/web/apps/web/src/stores/app/schema-provider.tsx index 973ad3d6..66077114 100644 --- a/web/apps/web/src/stores/app/schema-provider.tsx +++ b/web/apps/web/src/stores/app/schema-provider.tsx @@ -11,11 +11,11 @@ import { getSchemaByWidget } from '@/stores/app/schema/get-widget-schema'; import { useWorkflowStore } from '@/stores/workflow/workflow-provider'; import { generateUUID } from '@/utils/common-helper'; +import { getConditionStateSchema } from './schema/condition-state-schema'; import { introConfigSchema } from './schema/intro-config'; import { startSchema } from './schema/start-node'; import { stateConfigSchema } from './schema/state-config'; import { xStateConfigSchema } from './schema/x-state-config'; -import { getConditionStateSchema } from './schema/condition-state-schema'; type SchemaState = { id: string; diff --git a/web/apps/web/src/stores/app/schema/get-transition-schema.ts b/web/apps/web/src/stores/app/schema/get-transition-schema.ts index a1152adf..656b3161 100644 --- a/web/apps/web/src/stores/app/schema/get-transition-schema.ts +++ b/web/apps/web/src/stores/app/schema/get-transition-schema.ts @@ -1,8 +1,9 @@ import { ISchema, TValues } from '@shellagent/form-engine'; -import { getSchemaByInputs } from '@/stores/app/schema/get-workflow-schema'; import { TransitionTypeEnum } from '@shellagent/shared/protocol/transition'; import dayjs from 'dayjs'; +import { getSchemaByInputs } from '@/stores/app/schema/get-workflow-schema'; + export const getXTransitionSchema = ( inputs: TValues, transitionType: TransitionTypeEnum, diff --git a/web/apps/web/src/stores/app/use-app-state.tsx b/web/apps/web/src/stores/app/use-app-state.tsx index 5ae5ec17..27464003 100644 --- a/web/apps/web/src/stores/app/use-app-state.tsx +++ b/web/apps/web/src/stores/app/use-app-state.tsx @@ -1,10 +1,11 @@ import { Node } from '@shellagent/flow-engine'; -import { create } from 'zustand'; -import { CustomEdgeData } from '@/components/app/edges'; import { TransitionTypeEnum, TransitionTargetEnum, } from '@shellagent/shared/protocol/transition'; +import { create } from 'zustand'; + +import { CustomEdgeData } from '@/components/app/edges'; export type State = { currentStateId: string; diff --git a/web/apps/web/src/stores/app/utils/data-transformer.ts b/web/apps/web/src/stores/app/utils/data-transformer.ts index a9ee7d5a..184591e1 100644 --- a/web/apps/web/src/stores/app/utils/data-transformer.ts +++ b/web/apps/web/src/stores/app/utils/data-transformer.ts @@ -1,16 +1,15 @@ import { IFlow, NodeIdEnum, NodeTypeEnum } from '@shellagent/flow-engine'; import { TValue } from '@shellagent/form-engine'; import { Automata, State } from '@shellagent/pro-config'; -import { get, isString } from 'lodash-es'; - -import { ICustomEdge, CustomEdgeData } from '@/components/app/edges'; import { TransitionTargetEnum, type TransitionData, type Timer, transitionTargetTypeSchema, } from '@shellagent/shared/protocol/transition'; +import { get, isString } from 'lodash-es'; +import { ICustomEdge, CustomEdgeData } from '@/components/app/edges'; import { NodeDataType } from '@/types/app/types'; import { transformChoicesToValues, @@ -191,7 +190,8 @@ export const genAutomata: (props: { blocks, transitions: {}, }); - } else if (appType === 'x_bot') { + } + if (appType === 'x_bot') { const initial = (flow.edges.find(edge => edge.source === NodeIdEnum.start) ?.target as Lowercase) || ''; @@ -279,9 +279,8 @@ export const genAutomata: (props: { blocks, transitions: {}, }); - } else { - return {}; } + return {}; }; export const replaceContextByAutomata = (value: TValue, automata: Automata) => { diff --git a/web/apps/web/src/stores/home/models/home-model.ts b/web/apps/web/src/stores/home/models/home-model.ts index 709bf8ce..92b3f9bc 100644 --- a/web/apps/web/src/stores/home/models/home-model.ts +++ b/web/apps/web/src/stores/home/models/home-model.ts @@ -1,8 +1,9 @@ import { injectable, inject } from 'inversify'; import { action, makeObservable, observable, runInAction } from 'mobx'; + +import { SettingsModel } from '@/components/settings/settings.model'; import { fetchList } from '@/services/home'; import type { GetListRequest, GetListResponse } from '@/services/home/type'; -import { SettingsModel } from '@/components/settings/settings.model'; import { ToastModel } from '@/utils/toast.model'; export type AppType = 'all' | 'chat_bot' | 'x_bot'; diff --git a/web/apps/web/src/stories/EditValue.stories.tsx b/web/apps/web/src/stories/EditValue.stories.tsx index 7f03e83e..845a2eba 100644 --- a/web/apps/web/src/stories/EditValue.stories.tsx +++ b/web/apps/web/src/stories/EditValue.stories.tsx @@ -1,9 +1,11 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { EditValue } from './EditValue'; -import { Provider as InversifyProvider } from 'inversify-react'; import { Container } from 'inversify'; +import { Provider as InversifyProvider } from 'inversify-react'; + import { webModule } from '@/app/container'; +import { EditValue } from './EditValue'; + const container = new Container(); container.load(webModule); diff --git a/web/apps/web/src/stories/EditValue.tsx b/web/apps/web/src/stories/EditValue.tsx index baab4247..414e50d0 100644 --- a/web/apps/web/src/stories/EditValue.tsx +++ b/web/apps/web/src/stories/EditValue.tsx @@ -1,11 +1,11 @@ import { ISchema, MemoizedFormEngine } from '@shellagent/form-engine'; +import { Input, NumberInput, Select, Switch, Textarea } from '@shellagent/ui'; import React from 'react'; -import { Input, NumberInput, Select, Switch, Textarea } from '@shellagent/ui'; +import { ModeSelect } from '@/components/app/node-form/widgets'; import { ENABLE_MIME } from '@/utils/file-types'; import FileUpload from '../components/common/uploader'; -import { ModeSelect } from '@/components/app/node-form/widgets'; const schema: ISchema = { type: 'object', diff --git a/web/apps/web/src/stories/FormValidate.stories.tsx b/web/apps/web/src/stories/FormValidate.stories.tsx index 9b8549f3..c1e2fbd2 100644 --- a/web/apps/web/src/stories/FormValidate.stories.tsx +++ b/web/apps/web/src/stories/FormValidate.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react'; + import { FormValidate } from './FormValidate'; const meta = { diff --git a/web/apps/web/src/stories/FormValidate.tsx b/web/apps/web/src/stories/FormValidate.tsx index 8c3a54b8..6075fb22 100644 --- a/web/apps/web/src/stories/FormValidate.tsx +++ b/web/apps/web/src/stories/FormValidate.tsx @@ -1,9 +1,9 @@ -import { Input } from '@shellagent/ui'; import { IValidatorRules, MemoizedFormEngine, TValue, } from '@shellagent/form-engine'; +import { Input } from '@shellagent/ui'; import React from 'react'; interface ValidateRenderProps { diff --git a/web/apps/web/src/types/app/types.ts b/web/apps/web/src/types/app/types.ts index 13d4da53..fcdd6592 100644 --- a/web/apps/web/src/types/app/types.ts +++ b/web/apps/web/src/types/app/types.ts @@ -3,6 +3,7 @@ import { TValues, TFieldMode } from '@shellagent/form-engine'; import { Automata } from '@shellagent/pro-config'; import { Refs } from '@shellagent/shared/protocol/app-scope'; import { Issue } from '@shellagent/shared/type'; + import { Metadata } from '@/services/home/type'; export type Config = { From c1a97393342e5358e952cb4b724dc6a8a2ed5cce Mon Sep 17 00:00:00 2001 From: Wenliang Zhao Date: Wed, 15 Jan 2025 17:02:13 +0800 Subject: [PATCH 34/42] Dev comfyui v2 (#251) Co-authored-by: kun Co-authored-by: joe Co-authored-by: yuxumin <1090414006@qq.com> Co-authored-by: Chengwei Ouyang <118652142+Cherwayway@users.noreply.github.com> Co-authored-by: shanexi Co-authored-by: tiancheng-myshell <158017297+tiancheng-myshell@users.noreply.github.com> Co-authored-by: Chengwei Ouyang Co-authored-by: myshell-leon --- proconfig/core/block.py | 4 +- proconfig/core/common.py | 2 +- proconfig/core/render.py | 6 +- proconfig/core/state.py | 3 +- proconfig/core/variables.py | 4 +- proconfig/runners/runner.py | 55 +++++-- proconfig/utils/x_bot.py | 141 ++++++++++++++++++ .../widgets/imagen_widgets/comfyui_widget.py | 11 +- .../widgets/language_models/claude_widgets.py | 1 + proconfig/widgets/myshell_widgets/__init__.py | 3 +- .../myshell_widgets/myshell_widget_caller.py | 90 +++++++++++ .../widgets/myshell_widgets/tools/html2img.py | 3 +- .../myshell_widgets/tools/image_canvas.py | 2 +- .../myshell_widgets/tools/image_text_fuser.py | 2 +- .../myshell_widgets/tools/twitter_search.py | 3 +- proconfig/widgets/tools/code_runner.py | 2 +- servers/automata.py | 59 +++++--- servers/main.py | 1 + servers/widgets.py | 116 ++++++++++++++ servers/workflow.py | 32 ++-- 20 files changed, 475 insertions(+), 65 deletions(-) create mode 100644 proconfig/utils/x_bot.py create mode 100644 proconfig/widgets/myshell_widgets/myshell_widget_caller.py create mode 100644 servers/widgets.py diff --git a/proconfig/core/block.py b/proconfig/core/block.py index 95918c2c..e78cf475 100755 --- a/proconfig/core/block.py +++ b/proconfig/core/block.py @@ -11,7 +11,7 @@ class Block(BaseModel): type: BlockType = Field(..., description="type of the block") name: str = Field("", description="name of the block") - display_name: str = Field(None, description="the display name of the block") + display_name: str = Field("", description="the display name of the block") properties: Dict[str, Any] = Field({}, description="to specify other properties") inputs: Dict[CustomKey, Union[Input, Value]] = {} outputs: Dict[Union[CustomKey, ContextCustomKey], Union[Variable, Value]] = {} @@ -64,7 +64,7 @@ def sanity_check(self): class BaseProp(BaseModel): - cache: bool = None + cache: bool = False class TaskBase(Block): type: Literal["task"] diff --git a/proconfig/core/common.py b/proconfig/core/common.py index fb83bb72..2753637b 100755 --- a/proconfig/core/common.py +++ b/proconfig/core/common.py @@ -17,7 +17,7 @@ 'transitions', 'states', 'context', - 'payload', + # 'payload', 'condition', 'blocks', 'buttons', diff --git a/proconfig/core/render.py b/proconfig/core/render.py index b9fa02ce..a639e435 100755 --- a/proconfig/core/render.py +++ b/proconfig/core/render.py @@ -1,8 +1,8 @@ from typing import Union, Dict, List, Optional from pydantic import BaseModel, Field -from .common import CustomKey, CustomEventName -from .variables import URLString, Expression, Value +from proconfig.core.common import CustomKey, CustomEventName +from proconfig.core.variables import URLString, Expression, Value class EventPayload(BaseModel): @@ -14,7 +14,7 @@ class Button(BaseModel): content: str = "" description: Optional[str] = Field(default=None, description="Tooltip when hovering button") on_click: Union[CustomEventName, EventPayload] = Field(..., description="event name triggered") - style: Dict[str, str] = None # TODO + style: Dict[str, str] | None = None # TODO class RenderConfig(BaseModel): diff --git a/proconfig/core/state.py b/proconfig/core/state.py index b41d1333..d1a0de51 100755 --- a/proconfig/core/state.py +++ b/proconfig/core/state.py @@ -12,7 +12,7 @@ class StateProp(BaseProp): is_final: bool = False class State(Block): - type: Literal["state"] = "state" + type: Literal["state", "dispatcher"] = "state" properties: StateProp = StateProp() blocks: BlockChildren[Union[Task, Workflow]] = Field({}) render: RenderConfig = RenderConfig() @@ -38,6 +38,7 @@ def sanity_check(self): def check_input_IM(self): IM_count = 0 for k, v in self.inputs.items(): + print(k, v) if v.type in ["text", "string"] and v.source == "IM": IM_count += 1 if IM_count > 1: diff --git a/proconfig/core/variables.py b/proconfig/core/variables.py index d20c1902..06901b4f 100755 --- a/proconfig/core/variables.py +++ b/proconfig/core/variables.py @@ -13,7 +13,7 @@ class VariableBase(BaseModel): type: str - name: str = None + name: str = "" value: Any = None @@ -73,7 +73,7 @@ class InputVariableBase(VariableBase, Generic[T]): default_value: Union[T, Expression] | None = None value: Union[T, Expression] | None = None user_input: bool = False - source: Literal["IM", "form"] = None + source: Literal["IM", "form"] = "form" class InputTextVar(TextVar, InputVariableBase[str], ChoicesBase[str]): type: Literal["text", "string"] diff --git a/proconfig/runners/runner.py b/proconfig/runners/runner.py index 818a8747..92f21059 100755 --- a/proconfig/runners/runner.py +++ b/proconfig/runners/runner.py @@ -220,13 +220,18 @@ def run_block_task(self, task, environ, local_vars): local_vars[task.name] = outputs def process_comfy_extra_inputs(self, task): - comfy_extra_inputs = { "api": task.api, "comfy_workflow_id": task.comfy_workflow_id, "location": task.location, } task.inputs["comfy_extra_inputs"] = comfy_extra_inputs + + def process_myshell_extra_inputs(self, task): + task.inputs = { + "widget_id": task.widget_name.split("/")[1], + "inputs": task.inputs + } def run_task(self, container, task, environ, local_vars): @@ -234,6 +239,8 @@ def run_task(self, container, task, environ, local_vars): if task.widget_class_name == "ComfyUIWidget": # hasattr(task, "comfy_workflow_id") assert hasattr(task, "comfy_workflow_id"), "no comfy_workflow_id is founded" self.process_comfy_extra_inputs(task) + elif task.widget_class_name == "MyShellAnyWidgetCallerWidget": + self.process_myshell_extra_inputs(task) if task.mode in ["widget", "comfy_workflow"]: return self.run_widget_task(container, task, environ, local_vars) @@ -564,7 +571,7 @@ def run_automata(self, automata: Automata, sess_state: SessionState, payload: di local_transitions = automata.blocks[current_state_name].transitions or {} global_transitions = automata.transitions or {} - events = [] + events = {} for button_id, button in enumerate(render.get("buttons", [])): event_name = button["on_click"] if isinstance(event_name, dict): @@ -572,19 +579,32 @@ def run_automata(self, automata: Automata, sess_state: SessionState, payload: di event_name = event_name["event"] else: event_payload = {} - events.append({ - "event_key": f"BUTTON_{button_id}", + event_key = f"BUTTON_{button_id}" + events[event_key] = { "event_name": event_name, - "payload": event_payload - }) + "payload": event_payload, + "is_button": True + } - events.append({ - "event_key": "CHAT", - "event_name": "CHAT", - "payload": {} - }) + events["CHAT"] = { + "event_name": "CHAT", + "payload": {} + } + + skip_evaluate_events = ["X.MENTIONED", "X.PINNED.REPLIED"] + + # final event mapping, not target_inputs + for key, transition in local_transitions.items(): + # evaluate the transitions + if key not in events: + events[key] = { + "event_name": key, + "payload": {} + } + if key in skip_evaluate_events: + events[key]["skip_evaluate_target_inputs"] = True - for event_item in events: + for event_key, event_item in events.items(): event_name = event_item["event_name"] event_payload = event_item["payload"] for payload_key, payload_value in event_payload.items(): @@ -609,7 +629,10 @@ def run_automata(self, automata: Automata, sess_state: SessionState, payload: di # get the target_inputs from transition target_inputs_transition = {} for k, v in transition_case.target_inputs.items(): - target_inputs_transition[k] = calc_expression(v, {'payload': event_payload, **local_vars}) # the target_inputs defined by the transition + if event_item.get("skip_evaluate_target_inputs", False): + target_inputs_transition[k] = v + else: + target_inputs_transition[k] = calc_expression(v, {'payload': event_payload, **local_vars}) # the target_inputs defined by the transition # get the target inputs target_inputs = automata.blocks[target_state].inputs @@ -625,14 +648,16 @@ def run_automata(self, automata: Automata, sess_state: SessionState, payload: di setattr(input_var, k, tree_map(lambda x: calc_expression(x, visible_variables), getattr(input_var, k))) # target_inputs = tree_map(lambda x: calc_expression(x, local_vars), target_inputs) - event_mapping[event_item["event_key"]] = EventItem(**{ + event_mapping[event_key] = EventItem(**{ "target_state": target_state, "target_inputs": target_inputs, "target_inputs_transition": target_inputs_transition, # the target_inputs from the transition }) + + message_count = sess_state.message_count - event_mapping = {f'MESSAGE_{message_count}_{k}' if k != "CHAT" else k: v for k, v in event_mapping.items()} + event_mapping = {f'MESSAGE_{message_count}_{k}' if events[k].get("is_button", False) else k: v for k, v in event_mapping.items()} sess_state.event_mapping.update(event_mapping) return sess_state, render \ No newline at end of file diff --git a/proconfig/utils/x_bot.py b/proconfig/utils/x_bot.py new file mode 100644 index 00000000..ac2a4997 --- /dev/null +++ b/proconfig/utils/x_bot.py @@ -0,0 +1,141 @@ +import json +from pydantic import BaseModel +from typing import List, Literal +from proconfig.core import Automata, State +from proconfig.core.automata import StateWithTransitions +from proconfig.core.variables import InputTextVar +from proconfig.core.common import TransitionCase +from proconfig.core.render import Button +from proconfig.utils.misc import tree_map +from datetime import date + + +class TwitterUser(BaseModel): + creation_date: str = str(date.today()) + user_id: str = "user_id_123" + username: str = "username_default" + name: str = "Default Name" + is_private: bool = False + is_verified: bool = False + is_blue_verified: bool = False + bot: bool = False + verified_type: Literal['blue', 'business', 'government'] = 'blue' + + +class TweetDetail(BaseModel): + tweet_id: str = "tweet_id_123" + creation_date: str = str(date.today()) + text: str = "This is an example text" + user: TwitterUser = TwitterUser() + media_url: List[str] = [] + + +x_special_events = { + "X.MENTIONED": { + "button_name": "Reply to mentioned", + }, + "X.PINNED.REPLIED": { + "button_name": "Reply to pinned tweet" + }, +} + + # content: str = "" + # description: Optional[str] = Field(default=None, description="Tooltip when hovering button") + # on_click: Union[CustomEventName, EventPayload] = Field(..., description="event name triggered") + # style: Dict[str, str] = None # TODO + +def replace_payload_to_payload1(x): + if type(x) == str: + x = x.replace("payload.", "payload1.") + return x + +def convert_x_bot_to_automata(automata_x): + # build default_payload + default_payload = TweetDetail().model_dump_json(indent=2) + # user = default_payload["user"] + # default_payload["user"] = f"{{{{{user}}}}}" + + # automata_x = Automata.model_validate(automata_x) + + + new_states = {} + automata_x["blocks"] = automata_x.get("blocks", {}) + for state_name, state in automata_x["blocks"].items(): + timers = {} + state["properties"] = state.get("properties", {}) + if "timers" in state["properties"]: + for timer_key, timer_info in state["properties"]["timers"].items(): + timers[timer_info["event"]] = timer_key + + state["render"] = state.get("render", {}) + buttons = state["render"].get("buttons", []) + # buttons = state.render.buttons + # print("buttons:", buttons) + # first, handle the special events + count = 0 + state["transitions"] = state.get("transitions") or {} + for event_name, transition_cases in state["transitions"].items(): + if event_name in x_special_events: + new_button = { + "content": x_special_events[event_name]["button_name"], + "on_click": { + "event": event_name, + "payload": {} + } + } + # add a pseudo state + new_state = { + "display_name": "", + "inputs": { + "payload_str": { + "type": "text", + "name": "payload_str", + "default_value": default_payload, + "user_input": True, + "source": "form" + } + }, + "outputs": { + "payload1": "{{json.loads(payload_str)}}" + }, + "transitions": { + "ALWAYS": tree_map(replace_payload_to_payload1, transition_cases) + } + } + new_state_name = f"{state_name}_event_{count}" + # state.transitions[event_name] = TransitionCase(target=new_state_name) + # new_states[new_state_name] = StateWithTransitions.model_validate(new_state) + + state["transitions"][event_name] = { + "target": new_state_name + } + new_states[new_state_name] = new_state + elif event_name in timers: + new_button = { + "content": f"Timer [{timers[event_name]}] triggered", + "on_click": { + "event": event_name, + "payload": {} + } + } + else: + continue + count += 1 + buttons.append(new_button) + # buttons.append(Button.model_validate(new_button)) + state["render"]["buttons"] = buttons + automata_x["blocks"].update(new_states) + return automata_x + return automata_x.model_dump() + + +if __name__ == "__main__": + automata_x_hello = json.load(open("temp/automata_x.json")) + automata_hello = convert_x_bot_to_automata(automata_x_hello) + # import pdb; pdb.set_trace() + # InputTextVar.model_validate(automata_hello["blocks"]["idle_event_0"]["inputs"]["payload_str"]) + # automata_hello_load = Automata.model_validate(automata_hello) + json.dump(automata_hello, open("temp/automata_x_converted.json", "w"), indent=2) + + + import pdb; pdb.set_trace() \ No newline at end of file diff --git a/proconfig/widgets/imagen_widgets/comfyui_widget.py b/proconfig/widgets/imagen_widgets/comfyui_widget.py index a8d9a8e6..ffcde839 100644 --- a/proconfig/widgets/imagen_widgets/comfyui_widget.py +++ b/proconfig/widgets/imagen_widgets/comfyui_widget.py @@ -383,7 +383,16 @@ def comfyui_run_myshell(workflow_id, inputs, extra_headers): response = requests.post(url, headers=headers, json=data) # Parse the JSON response if response.status_code == 200: - return json.loads(response.json()['result']) + response_json = response.json() + if response_json["success "]: + return json.loads(response.json()['result']) + else: + error = { + 'error_code': 'SHELL-1109', + 'error_head': 'HTTP Request Error', + 'msg': response_json["error_message"], + } + raise ShellException(**error) else: try: response_content = response.json() diff --git a/proconfig/widgets/language_models/claude_widgets.py b/proconfig/widgets/language_models/claude_widgets.py index c39a2650..cfe97a2a 100755 --- a/proconfig/widgets/language_models/claude_widgets.py +++ b/proconfig/widgets/language_models/claude_widgets.py @@ -45,6 +45,7 @@ class MemoryItem(BaseModel): class ClaudeWidget(BaseWidget): NAME = "Claude 3.5 Sonnet" CATEGORY = "Large Language Model/Claude" + MYSHELL_WIDGET_NAME = "@myshell_llm/1744218088699596812" class InputsSchema(BaseWidget.InputsSchema): system_prompt: str = "" diff --git a/proconfig/widgets/myshell_widgets/__init__.py b/proconfig/widgets/myshell_widgets/__init__.py index cb961f94..5ea96e11 100755 --- a/proconfig/widgets/myshell_widgets/__init__.py +++ b/proconfig/widgets/myshell_widgets/__init__.py @@ -1,4 +1,5 @@ from proconfig.widgets.myshell_widgets.tools.image_text_fuser import ImageTextFuserWidget from proconfig.widgets.myshell_widgets.tools.image_canvas import ImageCanvasWidget from proconfig.widgets.myshell_widgets.tools.twitter_search import XWidget -from proconfig.widgets.myshell_widgets.tools.html2img import Html2ImgWidget \ No newline at end of file +from proconfig.widgets.myshell_widgets.tools.html2img import Html2ImgWidget +from proconfig.widgets.myshell_widgets.myshell_widget_caller import MyShellAnyWidgetCallerWidget \ No newline at end of file diff --git a/proconfig/widgets/myshell_widgets/myshell_widget_caller.py b/proconfig/widgets/myshell_widgets/myshell_widget_caller.py new file mode 100644 index 00000000..49f3c169 --- /dev/null +++ b/proconfig/widgets/myshell_widgets/myshell_widget_caller.py @@ -0,0 +1,90 @@ +from typing import Any, Literal, Optional, List +from pydantic import Field, BaseModel + +from proconfig.widgets.base import BaseWidget, WIDGETS +from proconfig.core.exception import ShellException + +# import instructor +import os +from pydantic import BaseModel, Field +import requests +import json + +@WIDGETS.register_module() +class MyShellAnyWidgetCallerWidget(BaseWidget): + NAME = "MyShellAnyWidgetCallerWidget" + # CATEGORY = "Myshell Widgets/Widget Caller" + dynamic_schema = True + + class InputsSchema(BaseWidget.InputsSchema): + widget_id: str = Field(..., description="the widget id to call") + inputs: dict = {} + + class OutputsSchema(BaseWidget.OutputsSchema): # useless + data: str | list + + def execute(self, environ, config): + # API endpoint URL + url = "https://openapi.myshell.ai/public/v1/widget/run" + widget_id = config["widget_id"] + # no myshell-test + + # Headers for the API request + headers = { + "x-myshell-openapi-key": os.environ["MYSHELL_API_KEY"], + "Content-Type": "application/json", + **environ.get("MYSHELL_HEADERS", {}) + } + + # Request payload + data = { + "widget_id": widget_id, + "input": json.dumps(config["inputs"]) + } + + # Send POST request to the API + response = requests.post(url, headers=headers, json=data) + + # Parse the JSON response + json_response = response.json() + + # Extract the 'result' field and return it as a string + if json_response.get('success') and 'result' in json_response: + result = json.loads(json_response['result']) + # handle the _url + if "_url" in result: + response = requests.get(result["_url"]) + if response.status_code == 200: + return response.json() + else: + return {"error_message": "error when retrieve the result"} + return result + else: + error = { + 'error_code': 'SHELL-1102', + 'error_head': 'Widget Execution Error', + 'msg': f'widget {widget_id} failed to execute', + 'traceback': json.dumps(json_response), + } + exception = ShellException(**error) + raise exception + + +if __name__ == "__main__": + from dotenv import load_dotenv + load_dotenv() + caller = MyShellAnyWidgetCallerWidget() + config = { + "widget_id": "1800948886988423168", + "inputs": { + "cfg_scale": 3.5, + "height": 1024, + "output_name": "result", + "prompt": "a beautiful girl", + "width": 1024 + } + } + config = caller.InputsSchema.model_validate(config) + + outputs = caller({}, config) + import pdb; pdb.set_trace() diff --git a/proconfig/widgets/myshell_widgets/tools/html2img.py b/proconfig/widgets/myshell_widgets/tools/html2img.py index 4dba3ff9..80657582 100644 --- a/proconfig/widgets/myshell_widgets/tools/html2img.py +++ b/proconfig/widgets/myshell_widgets/tools/html2img.py @@ -12,7 +12,8 @@ @WIDGETS.register_module() class Html2ImgWidget(BaseWidget): NAME = "HTML to Image" - CATEGORY = "Myshell Widgets/Tools" + CATEGORY = "Image Processing/HTML to Image" + MYSHELL_WIDGET_NAME = '@myshell/1850127954369142784' class InputsSchema(BaseWidget.InputsSchema): html_str: str = Field(default="", description="The HTML string to convert to an image") diff --git a/proconfig/widgets/myshell_widgets/tools/image_canvas.py b/proconfig/widgets/myshell_widgets/tools/image_canvas.py index 8c4215f3..11b36133 100644 --- a/proconfig/widgets/myshell_widgets/tools/image_canvas.py +++ b/proconfig/widgets/myshell_widgets/tools/image_canvas.py @@ -132,7 +132,7 @@ def get_next_image_filename(directory, prefix="ImageCanvas_", suffix=".png"): @WIDGETS.register_module() class ImageCanvasWidget(BaseWidget): NAME = "Image Canvas" - CATEGORY = 'Myshell Widgets/Tools' + CATEGORY = 'Image Processing/Image Canvas' class InputsSchema(BaseWidget.InputsSchema): config: str diff --git a/proconfig/widgets/myshell_widgets/tools/image_text_fuser.py b/proconfig/widgets/myshell_widgets/tools/image_text_fuser.py index 7b1c3981..1e2d3c1b 100755 --- a/proconfig/widgets/myshell_widgets/tools/image_text_fuser.py +++ b/proconfig/widgets/myshell_widgets/tools/image_text_fuser.py @@ -326,7 +326,7 @@ def add_content_to_image(template_path, content): @WIDGETS.register_module() class ImageTextFuserWidget(BaseWidget): NAME = "Image Text Fuser" - CATEGORY = 'Myshell Widgets/Tools' + # CATEGORY = 'Myshell Widgets/Tools' class InputsSchema(BaseWidget.InputsSchema): config: str = "" diff --git a/proconfig/widgets/myshell_widgets/tools/twitter_search.py b/proconfig/widgets/myshell_widgets/tools/twitter_search.py index d7eeae27..46247b4c 100644 --- a/proconfig/widgets/myshell_widgets/tools/twitter_search.py +++ b/proconfig/widgets/myshell_widgets/tools/twitter_search.py @@ -12,7 +12,8 @@ @WIDGETS.register_module() class XWidget(BaseWidget): NAME = "Twitter Search" - CATEGORY = "Myshell Widgets/Tools" + CATEGORY = "Tools/Twitter Search" + MYSHELL_WIDGET_NAME = '@myshell/1784206090390036480' class InputsSchema(BaseWidget.InputsSchema): action: Literal["search_tweets", "scrape_tweets", "scrape_profile"] = Field("scrape_tweets", description="The action to perform") diff --git a/proconfig/widgets/tools/code_runner.py b/proconfig/widgets/tools/code_runner.py index 8dfbe716..31e1abc8 100644 --- a/proconfig/widgets/tools/code_runner.py +++ b/proconfig/widgets/tools/code_runner.py @@ -5,7 +5,7 @@ @WIDGETS.register_module() class CodeRunnerWidget(BaseWidget): NAME = "Code Runner" - CATEGORY = "Tools/Code Runner" + # CATEGORY = "Tools/Code Runner" # hidden for now class InputsSchema(BaseWidget.InputsSchema): language: Literal["python", "javascript"] = "python" diff --git a/servers/automata.py b/servers/automata.py index b574eade..eb4854b6 100755 --- a/servers/automata.py +++ b/servers/automata.py @@ -18,8 +18,10 @@ from proconfig.widgets.tools.dependency_checker import check_dependency from proconfig.core import Automata +from proconfig.utils.expressions import calc_expression from proconfig.runners.runner import Runner from proconfig.utils.misc import convert_unserializable_display, process_local_file_path_async +from proconfig.utils.x_bot import convert_x_bot_to_automata from proconfig.utils.pytree import tree_map from proconfig.core.chat import ( ServerMessage, MessageComponentsContainer, MessageComponentsButton, @@ -404,7 +406,9 @@ class RuntimeData(BaseModel): trace_id_to_runtime_data_map: Dict[str, RuntimeData] = {} class MyShellUserInput(BaseModel): - type: str + type: str # "1": TEXT, "2": VOICE, "15": BUTTON_INTERACTION, "-1": 初始值, + # "20": X.MENTIONED, "21": "X.PINNED.REPLIED", "22": timer + form: Dict[str, Any] = {} button_id: str = "" text: str = "" @@ -412,21 +416,30 @@ class MyShellUserInput(BaseModel): def prepare_payload(automata: Automata, event_data: MyShellUserInput, sess_state: SessionState): # Decide the event_name - if event_data.type == "15": - event_name = event_data.button_id - elif event_data.type == "1": - event_name = "CHAT" - # Build the form data - event_data.form[CHAT_MESSAGE] = event_data.text - if len(event_data.embeded_objects) > 0: - # collect images - chat_images = [ - item["url"] for item in event_data.embeded_objects if item["type"] == "MESSAGE_METADATA_TYPE_IMAGE_FILE" - ] - if len(chat_images) > 0: - event_data.form[CHAT_IMAGES] = chat_images - else: - event_name = None + match event_data.type: + case "15": + event_name = event_data.button_id + case "1": + event_name = "CHAT" + # Build the form data + event_data.form[CHAT_MESSAGE] = event_data.text + if len(event_data.embeded_objects) > 0: + # collect images + chat_images = [ + item["url"] for item in event_data.embeded_objects if item["type"] == "MESSAGE_METADATA_TYPE_IMAGE_FILE" + ] + if len(chat_images) > 0: + event_data.form[CHAT_IMAGES] = chat_images + case "20" | "21": + event_name = "X.MENTIONED" if event_data.type == "20" else "X.PINNED.REPLIED" + ctx = {"payload": event_data.form} + target_inputs = getattr(sess_state.event_mapping[event_name], "target_inputs_transition", {}) + event_data.form = tree_map(lambda x: calc_expression(x, ctx), target_inputs) + case "22": + event_name = event_data.button_id + pass + case _: + event_name = None print("event_name:", event_name) target_state = automata.initial if event_name is None else sess_state.event_mapping[event_name].target_state @@ -873,7 +886,6 @@ async def run_automata_stateless(request: MyShellRunAppRequest): thread.start() thread.join() result = result_queue.get() - # result = run_automata_stateless_impl(request) return result @@ -907,4 +919,15 @@ async def get_intro_display(data: Dict): "text": data['server_message']['text'], "images": [item["url"] for item in data['server_message']['embedObjs'] if item["type"] == "MESSAGE_METADATA_TYPE_IMAGE_FILE"] } - return return_dict \ No newline at end of file + return return_dict + + +@app.post('/api/app/convert_x_bot') +async def convert_x_bot(data: Dict): + # convert x bot to normal automata + automata_x = data["automata_x"] + automata = convert_x_bot_to_automata(automata_x) + return { + "automata": automata + } + \ No newline at end of file diff --git a/servers/main.py b/servers/main.py index fe11f0d1..58f3fb87 100755 --- a/servers/main.py +++ b/servers/main.py @@ -23,6 +23,7 @@ import servers.settings import servers.comfy_runner import servers.helper + import servers.widgets diff --git a/servers/widgets.py b/servers/widgets.py new file mode 100644 index 00000000..42ed79fc --- /dev/null +++ b/servers/widgets.py @@ -0,0 +1,116 @@ +import json +from fastapi import FastAPI, HTTPException +from fastapi.responses import JSONResponse, StreamingResponse +from typing import Dict +from servers.base import app, APP_SAVE_ROOT, WORKFLOW_SAVE_ROOT, APP_RUNS_SAVE_ROOT, PROJECT_ROOT + +from proconfig.widgets.base import WIDGETS + +myshell_widget_list = json.load(open("assets/myshell_widget_list.json")) +@app.get("/api/widget/get_myshell_widget_list") +async def get_myshell_widget_list(): + # collect usage + usage_to_widget_map = {} + for widget_id, item in myshell_widget_list.items(): + item["widget_id"] = widget_id + usage = item["usage"] + if usage not in usage_to_widget_map: + usage_to_widget_map[usage] = [] + usage_to_widget_map[item["usage"]].append(item) + + data = [] + for usage, usage_widget_list in usage_to_widget_map.items(): + usage_data = { + "title": " ".join(word.capitalize() for word in usage.split("_")), + "items": [] + } + for widget_info in usage_widget_list: + widget_item = { + "name": "MyShellAnyWidgetCallerWidget", + "display_name": widget_info["name"], + "type": "widget", + "widget_name": f"@myshell/{widget_info['widget_id']}" + } + usage_data["items"].append(widget_item) + data.append(usage_data) + + response = { + "widget_list": data + } + return response + + +# @app.get("/api/widget/get_myshell_widget_schema") +def get_myshell_widget_schema(widget_id): + # widget_id = data["widget_id"] + response = { + "input_schema": myshell_widget_list[widget_id]["inputs"], + "output_schema": myshell_widget_list[widget_id]["outputs"], + } + return response + +@app.post("/api/workflow/get_widget_schema") +async def get_widget_schema(data: Dict[str, str]) -> Dict: + widget_name = data["widget_name"] + if widget_name == "MyShellAnyWidgetCallerWidget": + widget_id = data["myshell_widget_name"].split("/")[1] + print("get widget id:", widget_id) + return get_myshell_widget_schema(widget_id) + + WidgetClass = WIDGETS.module_dict[widget_name] + + response = {} + if len(WidgetClass.InputsSchemaDict) > 0: # multiple inputs schema + response["input_schema"] = { + k: v.model_json_schema() + for k, v in WidgetClass.InputsSchemaDict.items() + } + response["multi_input_schema"] = True + else: + response["input_schema"] = WidgetClass.InputsSchema.model_json_schema() + response["multi_input_schema"] = False + + response["output_schema"] = WidgetClass.OutputsSchema.model_json_schema() + + return response + + +@app.get("/api/widget/get_local_widget_list") +async def get_widget_category_list(): + # Step 1: Get categories + usage_to_categories_map = {} + + for category, widget_names in WIDGETS.category_dict.items(): + usage_name = category.split('/')[0] + category_name = category.split('/')[1] if isinstance(category, str) else category.value + if category_name == '': + continue + if usage_name not in usage_to_categories_map: + usage_to_categories_map[usage_name] = [] + + sub_widget_items = [ + { + "name": widget_name, + "display_name": WIDGETS._name_mapping.get(widget_name, "Warning: Display Name not Specified"), + "widget_name": getattr(WIDGETS._module_dict[widget_name], "MYSHELL_WIDGET_NAME", "") + } + for widget_name in widget_names + ] + + usage_to_categories_map[usage_name].append( + { + "name": category_name, + "children": sub_widget_items + } + ) + + response = { + "widget_list": [ + { + "title": usage, + "items": usage_to_categories_map[usage] + } + for usage in usage_to_categories_map + ] + } + return response \ No newline at end of file diff --git a/servers/workflow.py b/servers/workflow.py index c7535d85..cf083d46 100755 --- a/servers/workflow.py +++ b/servers/workflow.py @@ -84,25 +84,25 @@ async def get_widgets_by_category(data: Dict) -> Dict: } return response -@app.post("/api/workflow/get_widget_schema") -async def get_widget_schema(data: Dict[str, str]) -> Dict: - widget_name = data["widget_name"] - WidgetClass = WIDGETS.module_dict[widget_name] +# @app.post("/api/workflow/get_widget_schema") +# async def get_widget_schema(data: Dict[str, str]) -> Dict: +# widget_name = data["widget_name"] +# WidgetClass = WIDGETS.module_dict[widget_name] - response = {} - if len(WidgetClass.InputsSchemaDict) > 0: # multiple inputs schema - response["input_schema"] = { - k: v.model_json_schema() - for k, v in WidgetClass.InputsSchemaDict.items() - } - response["multi_input_schema"] = True - else: - response["input_schema"] = WidgetClass.InputsSchema.model_json_schema() - response["multi_input_schema"] = False +# response = {} +# if len(WidgetClass.InputsSchemaDict) > 0: # multiple inputs schema +# response["input_schema"] = { +# k: v.model_json_schema() +# for k, v in WidgetClass.InputsSchemaDict.items() +# } +# response["multi_input_schema"] = True +# else: +# response["input_schema"] = WidgetClass.InputsSchema.model_json_schema() +# response["multi_input_schema"] = False - response["output_schema"] = WidgetClass.OutputsSchema.model_json_schema() +# response["output_schema"] = WidgetClass.OutputsSchema.model_json_schema() - return response +# return response @app.post("/api/workflow/save") From 86311e308f598d7ed25153ee81077a9e65d0d565 Mon Sep 17 00:00:00 2001 From: shanexi Date: Wed, 15 Jan 2025 17:34:05 +0800 Subject: [PATCH 35/42] feat: add back Condition State --- web/apps/web/src/app/app/detail/page.tsx | 8 +++----- web/apps/web/src/stores/app/models/app-builder.model.ts | 5 +++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/web/apps/web/src/app/app/detail/page.tsx b/web/apps/web/src/app/app/detail/page.tsx index 63da99d5..8944de83 100644 --- a/web/apps/web/src/app/app/detail/page.tsx +++ b/web/apps/web/src/app/app/detail/page.tsx @@ -1,7 +1,6 @@ 'use client'; -import '../../reflect-metadata-client-side'; -import { FlowEngine, FlowRef, FlowAction } from '@shellagent/flow-engine'; +import { FlowAction, FlowEngine, FlowRef } from '@shellagent/flow-engine'; import { enableMapSet } from 'immer'; import { useInjection } from 'inversify-react'; import { observer } from 'mobx-react-lite'; @@ -10,14 +9,13 @@ import { useSearchParams } from 'next/navigation'; import { useEffect, useRef } from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; +import '../../reflect-metadata-client-side'; import { edgeTypes, - materialList, - xMaterialList, nodeTypes, - xNodeTypes, xEdgeTypes, + xNodeTypes, } from '@/components/app/constants'; import FlowHeader from '@/components/app/flow-header'; import { Header } from '@/components/app/header'; diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 34863513..d6a743b7 100644 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -99,6 +99,11 @@ const LocalWigets: MaterialListType = [ display_name: 'State', type: NodeTypeEnum.state, }, + { + name: 'Condition State', + display_name: 'Condition State', + type: NodeTypeEnum.condition_state, + }, { name: 'Note', display_name: 'Note', From 0ff1f5f873f7625704fb98d56691531171142381 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 15 Jan 2025 17:34:43 +0800 Subject: [PATCH 36/42] fix --- .../app/new-transition-sheet/index.tsx | 20 +++++++++++++++---- .../stores/app/models/app-builder-utils.ts | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/web/apps/web/src/components/app/new-transition-sheet/index.tsx b/web/apps/web/src/components/app/new-transition-sheet/index.tsx index f6879fe3..86f1e9c9 100644 --- a/web/apps/web/src/components/app/new-transition-sheet/index.tsx +++ b/web/apps/web/src/components/app/new-transition-sheet/index.tsx @@ -1,7 +1,6 @@ 'use client'; import { useReactFlowStore, NodeTypeEnum } from '@shellagent/flow-engine'; -import { TValues } from '@shellagent/form-engine'; import { type TransitionData } from '@shellagent/shared/protocol/transition'; import { getNewKey } from '@shellagent/shared/utils'; import { Drawer } from '@shellagent/ui'; @@ -15,6 +14,7 @@ import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { getXTransitionSchema } from '@/stores/app/schema/get-transition-schema'; import { SchemaProvider } from '@/stores/app/schema-provider'; import { useAppState } from '@/stores/app/use-app-state'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; const TransitionSheetNew: React.FC<{}> = observer(() => { const appBuilder = useInjection('AppBuilderModel'); @@ -94,10 +94,22 @@ const TransitionSheetNew: React.FC<{}> = observer(() => { ); const title = useMemo( - () => , + () => ( + + ), [edgeData], ); + const parentKey = useMemo(() => { + return edgeData?.type === TransitionTargetEnum['X.MENTIONED'] || + edgeData?.type === TransitionTargetEnum['X.PINNED.REPLIED'] + ? 'condition.twitter' + : 'condition.timer'; + }, [edgeData]); + return ( = observer(() => { name="" display_name=""> handleChange(values as TransitionData)} diff --git a/web/apps/web/src/stores/app/models/app-builder-utils.ts b/web/apps/web/src/stores/app/models/app-builder-utils.ts index 8d269eb1..ce537a8f 100644 --- a/web/apps/web/src/stores/app/models/app-builder-utils.ts +++ b/web/apps/web/src/stores/app/models/app-builder-utils.ts @@ -172,7 +172,7 @@ export function convertRefOptsToCascaderOpts( if (!isEmpty(refOpts.local.twitter?.variables)) { localOptions.push({ - label: 'Twitter', + label: 'Twitter Detail', value: 'Twitter', children: Object.entries(refOpts.local.twitter.variables).map( ([key, value]) => ({ From 43fe5e158de0f3a4d1badbc8fd016979d4740675 Mon Sep 17 00:00:00 2001 From: kun Date: Thu, 16 Jan 2025 00:08:03 +0800 Subject: [PATCH 37/42] feat: import --- .../src/components/app/header/extra-action/import-modal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/apps/web/src/components/app/header/extra-action/import-modal.tsx b/web/apps/web/src/components/app/header/extra-action/import-modal.tsx index da6bfebc..cdc87824 100644 --- a/web/apps/web/src/components/app/header/extra-action/import-modal.tsx +++ b/web/apps/web/src/components/app/header/extra-action/import-modal.tsx @@ -32,7 +32,8 @@ const ImportModal: React.FC<{ useEffect(() => { if (fileUrl) { setLoading(true); - APIFetch.get(getFileUrl(fileUrl)) + fetch(getFileUrl(fileUrl)) + .then(res => res.json()) .then(({ data, success }) => { if (!success) { throw new Error(''); From 8b7bb4035c15f4ae09366ed4bd11097aa329266a Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 16 Jan 2025 13:32:41 +0800 Subject: [PATCH 38/42] lint --- .../components/app/header/publish/index.tsx | 3 +++ .../app/new-transition-sheet/index.tsx | 2 +- .../app/node-form/widgets/tasks-config.tsx | 5 ++--- .../app/plugins/comfyui/comfyui.model.ts | 8 ++++---- .../app/plugins/comfyui/services/index.tsx | 3 ++- .../components/chat/app-builder-chat.model.ts | 5 ++--- .../common/uploader/display/index.tsx | 2 +- .../home/detail-form/type-select.tsx | 2 ++ .../src/components/home/filter-tabs/index.tsx | 8 +++++--- .../src/components/home/flow-card/index.tsx | 3 +-- .../src/components/settings/settings.model.ts | 18 +++++++++--------- .../web/src/components/settings/settings.tsx | 3 +++ .../components/workflow/flow-header/index.tsx | 2 +- .../src/components/workflow/header/index.tsx | 9 ++++----- web/apps/web/src/services/common/index.ts | 1 + .../web/src/stores/app/models/flow.model.ts | 5 +++-- web/apps/web/src/stores/app/utils/validator.ts | 3 ++- 17 files changed, 46 insertions(+), 36 deletions(-) diff --git a/web/apps/web/src/components/app/header/publish/index.tsx b/web/apps/web/src/components/app/header/publish/index.tsx index 8e9f6e19..119783ea 100644 --- a/web/apps/web/src/components/app/header/publish/index.tsx +++ b/web/apps/web/src/components/app/header/publish/index.tsx @@ -19,6 +19,7 @@ import { } from '@shellagent/ui'; import { Dropdown } from 'antd'; import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; import { useInjection } from 'inversify-react'; import { isEmpty } from 'lodash-es'; import { observer } from 'mobx-react-lite'; @@ -30,6 +31,8 @@ import { cn } from '@/utils/cn'; import VersionSkeleton from '../skeleton'; +dayjs.extend(relativeTime); + interface PublishProps { app_id: string; version_name: string; diff --git a/web/apps/web/src/components/app/new-transition-sheet/index.tsx b/web/apps/web/src/components/app/new-transition-sheet/index.tsx index 86f1e9c9..e9191739 100644 --- a/web/apps/web/src/components/app/new-transition-sheet/index.tsx +++ b/web/apps/web/src/components/app/new-transition-sheet/index.tsx @@ -2,6 +2,7 @@ import { useReactFlowStore, NodeTypeEnum } from '@shellagent/flow-engine'; import { type TransitionData } from '@shellagent/shared/protocol/transition'; +import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; import { getNewKey } from '@shellagent/shared/utils'; import { Drawer } from '@shellagent/ui'; import { useInjection } from 'inversify-react'; @@ -14,7 +15,6 @@ import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { getXTransitionSchema } from '@/stores/app/schema/get-transition-schema'; import { SchemaProvider } from '@/stores/app/schema-provider'; import { useAppState } from '@/stores/app/use-app-state'; -import { TransitionTargetEnum } from '@shellagent/shared/protocol/transition'; const TransitionSheetNew: React.FC<{}> = observer(() => { const appBuilder = useInjection('AppBuilderModel'); diff --git a/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx b/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx index 185fd287..591092b5 100644 --- a/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx @@ -6,14 +6,13 @@ import { customSnakeCase, getTaskDisplayName } from '@shellagent/shared/utils'; import { Button, useFormContext, Drag } from '@shellagent/ui'; import { useClickAway } from 'ahooks'; import { Dropdown } from 'antd'; +import { useInjection } from 'inversify-react'; import { useState, useRef, useCallback } from 'react'; import { useDrag, useDrop } from 'react-dnd'; -import { materialList } from '@/components/app/constants'; import { TaskList } from '@/components/app/task-list'; -import { useAppState } from '@/stores/app/use-app-state'; -import { useInjection } from 'inversify-react'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; +import { useAppState } from '@/stores/app/use-app-state'; const TaskItem = ({ name, diff --git a/web/apps/web/src/components/app/plugins/comfyui/comfyui.model.ts b/web/apps/web/src/components/app/plugins/comfyui/comfyui.model.ts index befa4cd8..3d7f5811 100644 --- a/web/apps/web/src/components/app/plugins/comfyui/comfyui.model.ts +++ b/web/apps/web/src/components/app/plugins/comfyui/comfyui.model.ts @@ -16,6 +16,10 @@ import { LOCATION_TIP, MessageType, } from '@/components/app/plugins/comfyui/constant'; +import { + defaultSchema, + getComfyUISchema, +} from '@/components/app/plugins/comfyui/schema'; import { checkJsonExist, getFile, @@ -23,10 +27,6 @@ import { getCwdSvc, updateDependency, } from '@/components/app/plugins/comfyui/services'; -import { - defaultSchema, - getComfyUISchema, -} from '@/components/app/plugins/comfyui/schema'; import { GetFileResponse, type SaveRequest, diff --git a/web/apps/web/src/components/app/plugins/comfyui/services/index.tsx b/web/apps/web/src/components/app/plugins/comfyui/services/index.tsx index 787b6576..fd27ab74 100644 --- a/web/apps/web/src/components/app/plugins/comfyui/services/index.tsx +++ b/web/apps/web/src/components/app/plugins/comfyui/services/index.tsx @@ -1,6 +1,7 @@ -import { APIFetch } from '@/services/base'; import type { Fetcher } from 'swr'; +import { APIFetch } from '@/services/base'; + import { GetFileRequest, GetFileResponse, diff --git a/web/apps/web/src/components/chat/app-builder-chat.model.ts b/web/apps/web/src/components/chat/app-builder-chat.model.ts index a7f52ad3..772cc81e 100644 --- a/web/apps/web/src/components/chat/app-builder-chat.model.ts +++ b/web/apps/web/src/components/chat/app-builder-chat.model.ts @@ -9,7 +9,8 @@ import { action, makeObservable, observable, runInAction } from 'mobx'; import { ButtonFnParams, IMLocalFile } from 'myshell-bundled-chat'; import { JsonSchema7 } from 'node_modules/@shellagent/form-engine/src/types/jsonSchema7'; -import { convertXBotSvc } from '@/services/app'; +import { convertXBotSvc, initBot } from '@/services/app'; +import { baseHeaders } from '@/services/base'; import { upload } from '@/services/common'; import { type AppBuilderModel } from '@/stores/app/models/app-builder.model'; @@ -22,8 +23,6 @@ import { import type { ServerMessage } from '../../services/app/message-type'; import { EventStatusEnum, RunAppRequest } from '../../services/app/type'; import { ToastModel } from '../../utils/toast.model'; -import { initBot } from '@/services/app'; -import { baseHeaders } from '@/services/base'; @injectable() export class AppBuilderChatModel { diff --git a/web/apps/web/src/components/common/uploader/display/index.tsx b/web/apps/web/src/components/common/uploader/display/index.tsx index 325998a6..e36960ed 100644 --- a/web/apps/web/src/components/common/uploader/display/index.tsx +++ b/web/apps/web/src/components/common/uploader/display/index.tsx @@ -7,8 +7,8 @@ import Audio from '@/components/common/uploader/display/audio'; import Image from '@/components/common/uploader/display/image'; import Other from '@/components/common/uploader/display/other'; import Video from '@/components/common/uploader/display/video'; -import { ENABLE_PREVIEW_FILE, getLocalTypeBySuffix } from '@/utils/file-types'; import { getFileUrl } from '@/utils/common-helper'; +import { ENABLE_PREVIEW_FILE, getLocalTypeBySuffix } from '@/utils/file-types'; type DisplayType = 'audio' | 'video' | 'image' | 'other'; diff --git a/web/apps/web/src/components/home/detail-form/type-select.tsx b/web/apps/web/src/components/home/detail-form/type-select.tsx index 128a84b3..aa7bc14e 100644 --- a/web/apps/web/src/components/home/detail-form/type-select.tsx +++ b/web/apps/web/src/components/home/detail-form/type-select.tsx @@ -36,6 +36,7 @@ export const TypeSelect: React.FC = ({