mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-02 17:45:31 +00:00
feat(web): AWEL flow 2.0 frontend codes (#1898)
Co-authored-by: Fangyin Cheng <staneyffer@gmail.com> Co-authored-by: 谨欣 <echo.cmy@antgroup.com> Co-authored-by: 严志勇 <yanzhiyong@tiansuixiansheng.com> Co-authored-by: yanzhiyong <932374019@qq.com>
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -158,6 +158,7 @@ thirdparty
|
|||||||
#web
|
#web
|
||||||
# dependencies
|
# dependencies
|
||||||
/web/node_modules
|
/web/node_modules
|
||||||
|
/web/yarn.lock
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
# next.js
|
# next.js
|
||||||
@@ -184,7 +185,4 @@ thirdparty
|
|||||||
/examples/**/*.gv
|
/examples/**/*.gv
|
||||||
/examples/**/*.gv.pdf
|
/examples/**/*.gv.pdf
|
||||||
/i18n/locales/**/**/*_ai_translated.po
|
/i18n/locales/**/**/*_ai_translated.po
|
||||||
/i18n/locales/**/**/*~
|
/i18n/locales/**/**/*~
|
||||||
|
|
||||||
/web_new/node_modules
|
|
||||||
/web_new/.next
|
|
@@ -332,6 +332,17 @@ class Config(metaclass=Singleton):
|
|||||||
os.getenv("MULTI_INSTANCE", "False").lower() == "true"
|
os.getenv("MULTI_INSTANCE", "False").lower() == "true"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# file server configuration
|
||||||
|
# The host of the current file server, if None, get the host automatically
|
||||||
|
self.FILE_SERVER_HOST = os.getenv("FILE_SERVER_HOST")
|
||||||
|
self.FILE_SERVER_LOCAL_STORAGE_PATH = os.getenv(
|
||||||
|
"FILE_SERVER_LOCAL_STORAGE_PATH"
|
||||||
|
)
|
||||||
|
# multi-instance flag
|
||||||
|
self.WEBSERVER_MULTI_INSTANCE = (
|
||||||
|
os.getenv("MULTI_INSTANCE", "False").lower() == "true"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def local_db_manager(self) -> "ConnectorManager":
|
def local_db_manager(self) -> "ConnectorManager":
|
||||||
from dbgpt.datasource.manages import ConnectorManager
|
from dbgpt.datasource.manages import ConnectorManager
|
||||||
|
@@ -50,7 +50,7 @@ yarn install
|
|||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
```sh
|
```sh
|
||||||
cp .env.example .env
|
cp .env.template .env
|
||||||
```
|
```
|
||||||
edit the `API_BASE_URL` to the real address
|
edit the `API_BASE_URL` to the real address
|
||||||
|
|
||||||
|
@@ -1,5 +1,12 @@
|
|||||||
import { AppListResponse, CreateAppParams, IAgent, IApp, NativeAppScenesResponse, StrategyResponse, TeamMode } from '@/types/app';
|
import {
|
||||||
import { IFlowResponse } from '@/types/flow';
|
AppListResponse,
|
||||||
|
CreateAppParams,
|
||||||
|
IAgent,
|
||||||
|
IApp,
|
||||||
|
NativeAppScenesResponse,
|
||||||
|
StrategyResponse,
|
||||||
|
TeamMode,
|
||||||
|
} from '@/types/app';
|
||||||
|
|
||||||
import { GET, POST } from '../index';
|
import { GET, POST } from '../index';
|
||||||
|
|
||||||
@@ -45,7 +52,9 @@ export const getAppStrategy = () => {
|
|||||||
* 获取资源参数
|
* 获取资源参数
|
||||||
*/
|
*/
|
||||||
export const getResource = (data: Record<string, string>) => {
|
export const getResource = (data: Record<string, string>) => {
|
||||||
return GET<Record<string, string>, Record<string, any>[]>(`/api/v1/app/resources/list?type=${data.type}`);
|
return GET<Record<string, string>, Record<string, any>[]>(
|
||||||
|
`/api/v1/app/resources/list?type=${data.type}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 创建native_app应用
|
* 创建native_app应用
|
||||||
@@ -61,13 +70,7 @@ export const getNativeAppScenes = () => {
|
|||||||
export const getAppStrategyValues = (type: string) => {
|
export const getAppStrategyValues = (type: string) => {
|
||||||
return GET<string, string[]>(`/api/v1/llm-strategy/value/list?type=${type}`);
|
return GET<string, string[]>(`/api/v1/llm-strategy/value/list?type=${type}`);
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* 创建awel_layout应用
|
|
||||||
* 获取工作流
|
|
||||||
*/
|
|
||||||
export const getFlows = ({ page, page_size }: { page: number; page_size: number }) => {
|
|
||||||
return GET<{ page: number; page_size: number }, IFlowResponse>(`/api/v1/serve/awel/flows?page=${page}&page_size=${page_size}`);
|
|
||||||
};
|
|
||||||
/**
|
/**
|
||||||
* 查询应用权限
|
* 查询应用权限
|
||||||
*/
|
*/
|
||||||
@@ -77,6 +80,12 @@ export const getAppAdmins = (appCode: string) => {
|
|||||||
/**
|
/**
|
||||||
* 更新应用权限
|
* 更新应用权限
|
||||||
*/
|
*/
|
||||||
export const updateAppAdmins = (data: { app_code: string; admins: string[] }) => {
|
export const updateAppAdmins = (data: {
|
||||||
return POST<{ app_code: string; admins: string[] }, null>(`/api/v1/app/admins/update`, data);
|
app_code: string;
|
||||||
|
admins: string[];
|
||||||
|
}) => {
|
||||||
|
return POST<{ app_code: string; admins: string[] }, null>(
|
||||||
|
`/api/v1/app/admins/update`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,9 +1,82 @@
|
|||||||
import { IFlow, UpdateFLowAdminsParams } from '@/types/flow';
|
import {
|
||||||
import { POST } from '../index';
|
IFlow,
|
||||||
|
IFlowNode,
|
||||||
|
IFlowResponse,
|
||||||
|
IFlowUpdateParam,
|
||||||
|
IFlowRefreshParams,
|
||||||
|
IFlowExportParams,
|
||||||
|
IFlowImportParams,
|
||||||
|
IUploadFileRequestParams,
|
||||||
|
IUploadFileResponse,
|
||||||
|
} from '@/types/flow';
|
||||||
|
import { DELETE, GET, POST, PUT } from '../index';
|
||||||
|
|
||||||
/**
|
/** AWEL Flow */
|
||||||
* 更新管理员
|
export const addFlow = (data: IFlowUpdateParam) => {
|
||||||
*/
|
return POST<IFlowUpdateParam, IFlow>('/api/v2/serve/awel/flows', data);
|
||||||
export const updateFlowAdmins = (data: UpdateFLowAdminsParams) => {
|
};
|
||||||
return POST<UpdateFLowAdminsParams, IFlow>(`/api/v1/serve/awel/flow/admins`, data);
|
|
||||||
|
export const getFlows = (page?: number, page_size?: number) => {
|
||||||
|
return GET<any, IFlowResponse>('/api/v2/serve/awel/flows', {
|
||||||
|
page,
|
||||||
|
page_size,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFlowById = (id: string) => {
|
||||||
|
return GET<null, IFlow>(`/api/v2/serve/awel/flows/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateFlowById = (id: string, data: IFlowUpdateParam) => {
|
||||||
|
return PUT<IFlowUpdateParam, IFlow>(`/api/v2/serve/awel/flows/${id}`, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteFlowById = (id: string) => {
|
||||||
|
return DELETE<null, null>(`/api/v2/serve/awel/flows/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFlowNodes = () => {
|
||||||
|
return GET<null, Array<IFlowNode>>(`/api/v2/serve/awel/nodes`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const refreshFlowNodeById = (data: IFlowRefreshParams) => {
|
||||||
|
return POST<IFlowRefreshParams, IFlowNode>(
|
||||||
|
'/api/v2/serve/awel/nodes/refresh',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const debugFlow = (data: any) => {
|
||||||
|
return POST<any, IFlowNode>('/api/v2/serve/awel/flow/debug', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportFlow = (data: IFlowExportParams) => {
|
||||||
|
return GET<IFlowExportParams, any>(
|
||||||
|
`/api/v2/serve/awel/flow/export/${data.uid}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const importFlow = (data: IFlowImportParams) => {
|
||||||
|
return POST<IFlowImportParams, any>('/api/v2/serve/awel/flow/import', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uploadFile = (data: IUploadFileRequestParams) => {
|
||||||
|
return POST<IUploadFileRequestParams, Array<IUploadFileResponse>>(
|
||||||
|
'/api/v2/serve/file/files/dbgpt',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const downloadFile = (fileId: string) => {
|
||||||
|
return GET<null, any>(`/api/v2/serve/file/files/dbgpt/${fileId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO:wait for interface update
|
||||||
|
export const getFlowTemplateList = () => {
|
||||||
|
return GET<null, Array<any>>('/api/v2/serve/awel/flow/templates');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFlowTemplateById = (id: string) => {
|
||||||
|
return GET<null, any>(`/api/v2/serve/awel/flow/templates/${id}`);
|
||||||
};
|
};
|
||||||
|
@@ -5,7 +5,7 @@ import {
|
|||||||
PostAgentPluginResponse,
|
PostAgentPluginResponse,
|
||||||
PostAgentQueryParams,
|
PostAgentQueryParams,
|
||||||
} from '@/types/agent';
|
} from '@/types/agent';
|
||||||
import { GetAppInfoParams, IApp, IAgent, IAppData } from '@/types/app';
|
import { GetAppInfoParams, IApp } from '@/types/app';
|
||||||
import {
|
import {
|
||||||
ChatHistoryResponse,
|
ChatHistoryResponse,
|
||||||
DialogueListResponse,
|
DialogueListResponse,
|
||||||
@@ -17,7 +17,13 @@ import {
|
|||||||
UserParam,
|
UserParam,
|
||||||
UserParamResponse,
|
UserParamResponse,
|
||||||
} from '@/types/chat';
|
} from '@/types/chat';
|
||||||
import { ChatFeedBackSchema, DbListResponse, DbSupportTypeResponse, PostDbParams, PostDbRefreshParams } from '@/types/db';
|
import {
|
||||||
|
ChatFeedBackSchema,
|
||||||
|
DbListResponse,
|
||||||
|
DbSupportTypeResponse,
|
||||||
|
PostDbParams,
|
||||||
|
PostDbRefreshParams,
|
||||||
|
} from '@/types/db';
|
||||||
import {
|
import {
|
||||||
GetEditorSQLRoundRequest,
|
GetEditorSQLRoundRequest,
|
||||||
GetEditorySqlParams,
|
GetEditorySqlParams,
|
||||||
@@ -26,7 +32,6 @@ import {
|
|||||||
PostEditorSQLRunParams,
|
PostEditorSQLRunParams,
|
||||||
PostSQLEditorSubmitParams,
|
PostSQLEditorSubmitParams,
|
||||||
} from '@/types/editor';
|
} from '@/types/editor';
|
||||||
import { IFlow, IFlowNode, IFlowUpdateParam, IFlowResponse } from '@/types/flow';
|
|
||||||
import {
|
import {
|
||||||
AddKnowledgeParams,
|
AddKnowledgeParams,
|
||||||
ArgumentsParams,
|
ArgumentsParams,
|
||||||
@@ -42,17 +47,24 @@ import {
|
|||||||
ISyncBatchResponse,
|
ISyncBatchResponse,
|
||||||
SpaceConfig,
|
SpaceConfig,
|
||||||
} from '@/types/knowledge';
|
} from '@/types/knowledge';
|
||||||
import { BaseModelParams, IModelData, StartModelParams, SupportModel } from '@/types/model';
|
import {
|
||||||
|
BaseModelParams,
|
||||||
|
IModelData,
|
||||||
|
StartModelParams,
|
||||||
|
SupportModel,
|
||||||
|
} from '@/types/model';
|
||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
import { DELETE, GET, POST, PUT } from '.';
|
import { GET, POST } from '.';
|
||||||
|
|
||||||
|
|
||||||
/** App */
|
/** App */
|
||||||
export const postScenes = () => {
|
export const postScenes = () => {
|
||||||
return POST<null, Array<SceneResponse>>('/api/v1/chat/dialogue/scenes');
|
return POST<null, Array<SceneResponse>>('/api/v1/chat/dialogue/scenes');
|
||||||
};
|
};
|
||||||
export const newDialogue = (data: NewDialogueParam) => {
|
export const newDialogue = (data: NewDialogueParam) => {
|
||||||
return POST<NewDialogueParam, IChatDialogueSchema>(`/api/v1/chat/dialogue/new?chat_mode=${data.chat_mode}&model_name=${data.model}`, data);
|
return POST<NewDialogueParam, IChatDialogueSchema>(
|
||||||
|
`/api/v1/chat/dialogue/new?chat_mode=${data.chat_mode}&model_name=${data.model}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addUser = (data: UserParam) => {
|
export const addUser = (data: UserParam) => {
|
||||||
@@ -90,13 +102,19 @@ export const getUsableModels = () => {
|
|||||||
return GET<null, Array<string>>('/api/v1/model/types');
|
return GET<null, Array<string>>('/api/v1/model/types');
|
||||||
};
|
};
|
||||||
export const postChatModeParamsList = (chatMode: string) => {
|
export const postChatModeParamsList = (chatMode: string) => {
|
||||||
return POST<null, IDB[]>(`/api/v1/chat/mode/params/list?chat_mode=${chatMode}`);
|
return POST<null, IDB[]>(
|
||||||
|
`/api/v1/chat/mode/params/list?chat_mode=${chatMode}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const postChatModeParamsInfoList = (chatMode: string) => {
|
export const postChatModeParamsInfoList = (chatMode: string) => {
|
||||||
return POST<null, Record<string, string>>(`/api/v1/chat/mode/params/info?chat_mode=${chatMode}`);
|
return POST<null, Record<string, string>>(
|
||||||
|
`/api/v1/chat/mode/params/info?chat_mode=${chatMode}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const getChatHistory = (convId: string) => {
|
export const getChatHistory = (convId: string) => {
|
||||||
return GET<null, ChatHistoryResponse>(`/api/v1/chat/dialogue/messages/history?con_uid=${convId}`);
|
return GET<null, ChatHistoryResponse>(
|
||||||
|
`/api/v1/chat/dialogue/messages/history?con_uid=${convId}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const postChatModeParamsFileLoad = ({
|
export const postChatModeParamsFileLoad = ({
|
||||||
convUid,
|
convUid,
|
||||||
@@ -123,12 +141,14 @@ export const postChatModeParamsFileLoad = ({
|
|||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
...config,
|
...config,
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearChatHistory = (conUid: string) => {
|
export const clearChatHistory = (conUid: string) => {
|
||||||
return POST<null, Record<string, string>>(`/api/v1/chat/dialogue/clear?con_uid=${conUid}`);
|
return POST<null, Record<string, string>>(
|
||||||
|
`/api/v1/chat/dialogue/clear?con_uid=${conUid}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Menu */
|
/** Menu */
|
||||||
@@ -138,19 +158,27 @@ export const delDialogue = (conv_uid: string) => {
|
|||||||
|
|
||||||
/** Editor */
|
/** Editor */
|
||||||
export const getEditorSqlRounds = (id: string) => {
|
export const getEditorSqlRounds = (id: string) => {
|
||||||
return GET<null, GetEditorSQLRoundRequest>(`/api/v1/editor/sql/rounds?con_uid=${id}`);
|
return GET<null, GetEditorSQLRoundRequest>(
|
||||||
|
`/api/v1/editor/sql/rounds?con_uid=${id}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const postEditorSqlRun = (data: PostEditorSQLRunParams) => {
|
export const postEditorSqlRun = (data: PostEditorSQLRunParams) => {
|
||||||
return POST<PostEditorSQLRunParams>(`/api/v1/editor/sql/run`, data);
|
return POST<PostEditorSQLRunParams>(`/api/v1/editor/sql/run`, data);
|
||||||
};
|
};
|
||||||
export const postEditorChartRun = (data: PostEditorChartRunParams) => {
|
export const postEditorChartRun = (data: PostEditorChartRunParams) => {
|
||||||
return POST<PostEditorChartRunParams, PostEditorChartRunResponse>(`/api/v1/editor/chart/run`, data);
|
return POST<PostEditorChartRunParams, PostEditorChartRunResponse>(
|
||||||
|
`/api/v1/editor/chart/run`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const postSqlEditorSubmit = (data: PostSQLEditorSubmitParams) => {
|
export const postSqlEditorSubmit = (data: PostSQLEditorSubmitParams) => {
|
||||||
return POST<PostSQLEditorSubmitParams>(`/api/v1/sql/editor/submit`, data);
|
return POST<PostSQLEditorSubmitParams>(`/api/v1/sql/editor/submit`, data);
|
||||||
};
|
};
|
||||||
export const getEditorSql = (id: string, round: string | number) => {
|
export const getEditorSql = (id: string, round: string | number) => {
|
||||||
return POST<GetEditorySqlParams, string | Array<any>>('/api/v1/editor/sql', { con_uid: id, round });
|
return POST<GetEditorySqlParams, string | Array<any>>('/api/v1/editor/sql', {
|
||||||
|
con_uid: id,
|
||||||
|
round,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** knowledge */
|
/** knowledge */
|
||||||
@@ -158,21 +186,36 @@ export const getArguments = (knowledgeName: string) => {
|
|||||||
return POST<any, IArguments>(`/knowledge/${knowledgeName}/arguments`, {});
|
return POST<any, IArguments>(`/knowledge/${knowledgeName}/arguments`, {});
|
||||||
};
|
};
|
||||||
export const saveArguments = (knowledgeName: string, data: ArgumentsParams) => {
|
export const saveArguments = (knowledgeName: string, data: ArgumentsParams) => {
|
||||||
return POST<ArgumentsParams, IArguments>(`/knowledge/${knowledgeName}/argument/save`, data);
|
return POST<ArgumentsParams, IArguments>(
|
||||||
|
`/knowledge/${knowledgeName}/argument/save`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSpaceList = (data: any) => {
|
export const getSpaceList = (data: any) => {
|
||||||
return POST<any, Array<ISpace>>('/knowledge/space/list', data);
|
return POST<any, Array<ISpace>>('/knowledge/space/list', data);
|
||||||
};
|
};
|
||||||
export const getDocumentList = (spaceName: string, data: Record<string, number | Array<number>>) => {
|
export const getDocumentList = (
|
||||||
return POST<Record<string, number | Array<number>>, IDocumentResponse>(`/knowledge/${spaceName}/document/list`, data);
|
spaceName: string,
|
||||||
|
data: Record<string, number | Array<number>>
|
||||||
|
) => {
|
||||||
|
return POST<Record<string, number | Array<number>>, IDocumentResponse>(
|
||||||
|
`/knowledge/${spaceName}/document/list`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const getGraphVis = (spaceName: string, data: { limit: number }) => {
|
export const getGraphVis = (spaceName: string, data: { limit: number }) => {
|
||||||
return POST<Record<string, number>, GraphVisResult>(`/knowledge/${spaceName}/graphvis`, data);
|
return POST<Record<string, number>, GraphVisResult>(
|
||||||
|
`/knowledge/${spaceName}/graphvis`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addDocument = (knowledgeName: string, data: DocumentParams) => {
|
export const addDocument = (knowledgeName: string, data: DocumentParams) => {
|
||||||
return POST<DocumentParams, number>(`/knowledge/${knowledgeName}/document/add`, data);
|
return POST<DocumentParams, number>(
|
||||||
|
`/knowledge/${knowledgeName}/document/add`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addSpace = (data: AddKnowledgeParams) => {
|
export const addSpace = (data: AddKnowledgeParams) => {
|
||||||
@@ -180,27 +223,53 @@ export const addSpace = (data: AddKnowledgeParams) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getChunkStrategies = () => {
|
export const getChunkStrategies = () => {
|
||||||
return GET<null, Array<IChunkStrategyResponse>>('/knowledge/document/chunkstrategies');
|
return GET<null, Array<IChunkStrategyResponse>>(
|
||||||
|
'/knowledge/document/chunkstrategies'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const syncDocument = (spaceName: string, data: Record<string, Array<number>>) => {
|
export const syncDocument = (
|
||||||
return POST<Record<string, Array<number>>, string | null>(`/knowledge/${spaceName}/document/sync`, data);
|
spaceName: string,
|
||||||
|
data: Record<string, Array<number>>
|
||||||
|
) => {
|
||||||
|
return POST<Record<string, Array<number>>, string | null>(
|
||||||
|
`/knowledge/${spaceName}/document/sync`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const syncBatchDocument = (spaceName: string, data: Array<ISyncBatchParameter>) => {
|
export const syncBatchDocument = (
|
||||||
return POST<Array<ISyncBatchParameter>, ISyncBatchResponse>(`/knowledge/${spaceName}/document/sync_batch`, data);
|
spaceName: string,
|
||||||
|
data: Array<ISyncBatchParameter>
|
||||||
|
) => {
|
||||||
|
return POST<Array<ISyncBatchParameter>, ISyncBatchResponse>(
|
||||||
|
`/knowledge/${spaceName}/document/sync_batch`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadDocument = (knowLedgeName: string, data: FormData) => {
|
export const uploadDocument = (knowLedgeName: string, data: FormData) => {
|
||||||
return POST<FormData, number>(`/knowledge/${knowLedgeName}/document/upload`, data);
|
return POST<FormData, number>(
|
||||||
|
`/knowledge/${knowLedgeName}/document/upload`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getChunkList = (spaceName: string, data: ChunkListParams) => {
|
export const getChunkList = (spaceName: string, data: ChunkListParams) => {
|
||||||
return POST<ChunkListParams, IChunkList>(`/knowledge/${spaceName}/chunk/list`, data);
|
return POST<ChunkListParams, IChunkList>(
|
||||||
|
`/knowledge/${spaceName}/chunk/list`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const delDocument = (spaceName: string, data: Record<string, number>) => {
|
export const delDocument = (
|
||||||
return POST<Record<string, number>, null>(`/knowledge/${spaceName}/document/delete`, data);
|
spaceName: string,
|
||||||
|
data: Record<string, number>
|
||||||
|
) => {
|
||||||
|
return POST<Record<string, number>, null>(
|
||||||
|
`/knowledge/${spaceName}/document/delete`,
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const delSpace = (data: Record<string, string>) => {
|
export const delSpace = (data: Record<string, string>) => {
|
||||||
@@ -226,21 +295,41 @@ export const getSupportModels = () => {
|
|||||||
|
|
||||||
/** Agent */
|
/** Agent */
|
||||||
export const postAgentQuery = (data: PostAgentQueryParams) => {
|
export const postAgentQuery = (data: PostAgentQueryParams) => {
|
||||||
return POST<PostAgentQueryParams, PostAgentPluginResponse>('/api/v1/agent/query', data);
|
return POST<PostAgentQueryParams, PostAgentPluginResponse>(
|
||||||
|
'/api/v1/agent/query',
|
||||||
|
data
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const postAgentHubUpdate = (data?: PostAgentHubUpdateParams) => {
|
export const postAgentHubUpdate = (data?: PostAgentHubUpdateParams) => {
|
||||||
return POST<PostAgentHubUpdateParams>('/api/v1/agent/hub/update', data ?? { channel: '', url: '', branch: '', authorization: '' });
|
return POST<PostAgentHubUpdateParams>(
|
||||||
|
'/api/v1/agent/hub/update',
|
||||||
|
data ?? { channel: '', url: '', branch: '', authorization: '' }
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const postAgentMy = (user?: string) => {
|
export const postAgentMy = (user?: string) => {
|
||||||
return POST<undefined, PostAgentMyPluginResponse>('/api/v1/agent/my', undefined, { params: { user } });
|
return POST<undefined, PostAgentMyPluginResponse>(
|
||||||
|
'/api/v1/agent/my',
|
||||||
|
undefined,
|
||||||
|
{ params: { user } }
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const postAgentInstall = (pluginName: string, user?: string) => {
|
export const postAgentInstall = (pluginName: string, user?: string) => {
|
||||||
return POST('/api/v1/agent/install', undefined, { params: { plugin_name: pluginName, user }, timeout: 60000 });
|
return POST('/api/v1/agent/install', undefined, {
|
||||||
|
params: { plugin_name: pluginName, user },
|
||||||
|
timeout: 60000,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
export const postAgentUninstall = (pluginName: string, user?: string) => {
|
export const postAgentUninstall = (pluginName: string, user?: string) => {
|
||||||
return POST('/api/v1/agent/uninstall', undefined, { params: { plugin_name: pluginName, user }, timeout: 60000 });
|
return POST('/api/v1/agent/uninstall', undefined, {
|
||||||
|
params: { plugin_name: pluginName, user },
|
||||||
|
timeout: 60000,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
export const postAgentUpload = (user = '', data: FormData, config?: Omit<AxiosRequestConfig, 'headers'>) => {
|
export const postAgentUpload = (
|
||||||
|
user = '',
|
||||||
|
data: FormData,
|
||||||
|
config?: Omit<AxiosRequestConfig, 'headers'>
|
||||||
|
) => {
|
||||||
return POST<FormData>('/api/v1/personal/agent/upload', data, {
|
return POST<FormData>('/api/v1/personal/agent/upload', data, {
|
||||||
params: { user },
|
params: { user },
|
||||||
headers: {
|
headers: {
|
||||||
@@ -258,9 +347,18 @@ export const getChatFeedBackSelect = () => {
|
|||||||
return GET<null, FeedBack>(`/api/v1/feedback/select`, undefined);
|
return GET<null, FeedBack>(`/api/v1/feedback/select`, undefined);
|
||||||
};
|
};
|
||||||
export const getChatFeedBackItme = (conv_uid: string, conv_index: number) => {
|
export const getChatFeedBackItme = (conv_uid: string, conv_index: number) => {
|
||||||
return GET<null, Record<string, string>>(`/api/v1/feedback/find?conv_uid=${conv_uid}&conv_index=${conv_index}`, undefined);
|
return GET<null, Record<string, string>>(
|
||||||
|
`/api/v1/feedback/find?conv_uid=${conv_uid}&conv_index=${conv_index}`,
|
||||||
|
undefined
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const postChatFeedBackForm = ({ data, config }: { data: ChatFeedBackSchema; config?: Omit<AxiosRequestConfig, 'headers'> }) => {
|
export const postChatFeedBackForm = ({
|
||||||
|
data,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
data: ChatFeedBackSchema;
|
||||||
|
config?: Omit<AxiosRequestConfig, 'headers'>;
|
||||||
|
}) => {
|
||||||
return POST<ChatFeedBackSchema, any>(`/api/v1/feedback/commit`, data, {
|
return POST<ChatFeedBackSchema, any>(`/api/v1/feedback/commit`, data, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -271,27 +369,6 @@ export const postChatFeedBackForm = ({ data, config }: { data: ChatFeedBackSchem
|
|||||||
|
|
||||||
/** prompt */
|
/** prompt */
|
||||||
|
|
||||||
/** AWEL Flow */
|
|
||||||
export const addFlow = (data: IFlowUpdateParam) => {
|
|
||||||
return POST<IFlowUpdateParam, IFlow>('/api/v1/serve/awel/flows', data);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFlowById = (id: string) => {
|
|
||||||
return GET<null, IFlow>(`/api/v1/serve/awel/flows/${id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateFlowById = (id: string, data: IFlowUpdateParam) => {
|
|
||||||
return PUT<IFlowUpdateParam, IFlow>(`/api/v1/serve/awel/flows/${id}`, data);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteFlowById = (id: string) => {
|
|
||||||
return DELETE<null, null>(`/api/v1/serve/awel/flows/${id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFlowNodes = () => {
|
|
||||||
return GET<null, Array<IFlowNode>>(`/api/v1/serve/awel/nodes`);
|
|
||||||
};
|
|
||||||
|
|
||||||
/** app */
|
/** app */
|
||||||
|
|
||||||
export const collectApp = (data: Record<string, string>) => {
|
export const collectApp = (data: Record<string, string>) => {
|
||||||
@@ -322,7 +399,9 @@ export const getAppInfo = (data: GetAppInfoParams) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getSupportDBList = (db_name = '') => {
|
export const getSupportDBList = (db_name = '') => {
|
||||||
return GET<null, Record<string, any>>(`/api/v1/permission/db/list?db_name=${db_name}`);
|
return GET<null, Record<string, any>>(
|
||||||
|
`/api/v1/permission/db/list?db_name=${db_name}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const recommendApps = (data: Record<string, string>) => {
|
export const recommendApps = (data: Record<string, string>) => {
|
||||||
@@ -336,7 +415,9 @@ export const modelSearch = (data: Record<string, string>) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getKnowledgeAdmins = (spaceId: string) => {
|
export const getKnowledgeAdmins = (spaceId: string) => {
|
||||||
return GET<string, Record<string, any>>(`/knowledge/users/list?space_id=${spaceId}`);
|
return GET<string, Record<string, any>>(
|
||||||
|
`/knowledge/users/list?space_id=${spaceId}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const updateKnowledgeAdmins = (data: Record<string, string>) => {
|
export const updateKnowledgeAdmins = (data: Record<string, string>) => {
|
||||||
return POST<Record<string, any>, any[]>(`/knowledge/users/update`, data);
|
return POST<Record<string, any>, any[]>(`/knowledge/users/update`, data);
|
||||||
|
@@ -184,7 +184,7 @@ const Completion = ({ messages, onSubmit }: Props) => {
|
|||||||
question={showMessages?.filter((e) => e?.role === 'human' && e?.order === content.order)[0]?.context}
|
question={showMessages?.filter((e) => e?.role === 'human' && e?.order === content.order)[0]?.context}
|
||||||
knowledge_space={spaceNameOriginal || dbParam || ''}
|
knowledge_space={spaceNameOriginal || dbParam || ''}
|
||||||
/>
|
/>
|
||||||
<Tooltip title={t('copy')}>
|
<Tooltip title={t('Copy_Btn')}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onCopyContext(content?.context)}
|
onClick={() => onCopyContext(content?.context)}
|
||||||
slots={{ root: IconButton }}
|
slots={{ root: IconButton }}
|
||||||
|
@@ -61,6 +61,7 @@ function GPTCard({
|
|||||||
return icon;
|
return icon;
|
||||||
}, [icon]);
|
}, [icon]);
|
||||||
|
|
||||||
|
// TODO: 算子资源标签
|
||||||
const tagNode = useMemo(() => {
|
const tagNode = useMemo(() => {
|
||||||
if (!tags || !tags.length) return null;
|
if (!tags || !tags.length) return null;
|
||||||
return (
|
return (
|
||||||
|
@@ -3,7 +3,7 @@ import { Button, Form, Input, InputNumber, Modal, Select, Spin, Tooltip, message
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { addOmcDB, apiInterceptors, getSupportDBList, postDbAdd, postDbEdit, postDbTestConnect } from '@/client/api';
|
import { addOmcDB, apiInterceptors, getSupportDBList, postDbAdd, postDbEdit, postDbTestConnect } from '@/client/api';
|
||||||
import { DBOption, DBType, DbListResponse, PostDbParams } from '@/types/db';
|
import { DBOption, DBType, DbListResponse, PostDbParams } from '@/types/db';
|
||||||
import { isFileDb } from '@/pages/database';
|
import { isFileDb } from '@/pages/construct/database';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDebounceFn } from 'ahooks';
|
import { useDebounceFn } from 'ahooks';
|
||||||
|
|
||||||
|
234
web/components/flow/add-nodes-sider.tsx
Normal file
234
web/components/flow/add-nodes-sider.tsx
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
import { ChatContext } from '@/app/chat-context';
|
||||||
|
import { apiInterceptors, getFlowNodes } from '@/client/api';
|
||||||
|
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons';
|
||||||
|
import type { CollapseProps } from 'antd';
|
||||||
|
import { Badge, Collapse, Input, Layout, Space } from 'antd';
|
||||||
|
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import StaticNodes from './static-nodes';
|
||||||
|
import { IFlowNode } from '@/types/flow';
|
||||||
|
import { FLOW_NODES_KEY } from '@/utils';
|
||||||
|
|
||||||
|
const { Search } = Input;
|
||||||
|
const { Sider } = Layout;
|
||||||
|
|
||||||
|
type GroupType = {
|
||||||
|
category: string;
|
||||||
|
categoryLabel: string;
|
||||||
|
nodes: IFlowNode[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const zeroWidthTriggerDefaultStyle: React.CSSProperties = {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 16,
|
||||||
|
height: 48,
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
border: '1px solid #d6d8da',
|
||||||
|
borderRadius: 8,
|
||||||
|
right: -8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddNodesSider: React.FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { mode } = useContext(ChatContext);
|
||||||
|
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||||
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
|
const [operators, setOperators] = useState<Array<IFlowNode>>([]);
|
||||||
|
const [resources, setResources] = useState<Array<IFlowNode>>([]);
|
||||||
|
const [operatorsGroup, setOperatorsGroup] = useState<GroupType[]>([]);
|
||||||
|
const [resourcesGroup, setResourcesGroup] = useState<GroupType[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getNodes();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function getNodes() {
|
||||||
|
const [_, data] = await apiInterceptors(getFlowNodes());
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
localStorage.setItem(FLOW_NODES_KEY, JSON.stringify(data));
|
||||||
|
const operatorNodes = data.filter(
|
||||||
|
(node) => node.flow_type === 'operator'
|
||||||
|
);
|
||||||
|
const resourceNodes = data.filter(
|
||||||
|
(node) => node.flow_type === 'resource'
|
||||||
|
);
|
||||||
|
setOperators(operatorNodes);
|
||||||
|
setResources(resourceNodes);
|
||||||
|
setOperatorsGroup(groupNodes(operatorNodes));
|
||||||
|
setResourcesGroup(groupNodes(resourceNodes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerStyle: React.CSSProperties = useMemo(() => {
|
||||||
|
if (collapsed) {
|
||||||
|
return {
|
||||||
|
...zeroWidthTriggerDefaultStyle,
|
||||||
|
right: -16,
|
||||||
|
borderRadius: '0px 8px 8px 0',
|
||||||
|
borderLeft: '1px solid #d5e5f6',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...zeroWidthTriggerDefaultStyle,
|
||||||
|
borderLeft: '1px solid #d6d8da',
|
||||||
|
};
|
||||||
|
}, [collapsed]);
|
||||||
|
|
||||||
|
function groupNodes(data: IFlowNode[]) {
|
||||||
|
const groups: GroupType[] = [];
|
||||||
|
const categoryMap: Record<
|
||||||
|
string,
|
||||||
|
{ category: string; categoryLabel: string; nodes: IFlowNode[] }
|
||||||
|
> = {};
|
||||||
|
data.forEach((item) => {
|
||||||
|
const { category, category_label } = item;
|
||||||
|
if (!categoryMap[category]) {
|
||||||
|
categoryMap[category] = {
|
||||||
|
category,
|
||||||
|
categoryLabel: category_label,
|
||||||
|
nodes: [],
|
||||||
|
};
|
||||||
|
groups.push(categoryMap[category]);
|
||||||
|
}
|
||||||
|
categoryMap[category].nodes.push(item);
|
||||||
|
});
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
const operatorItems: CollapseProps['items'] = useMemo(() => {
|
||||||
|
if (!searchValue) {
|
||||||
|
return operatorsGroup.map(({ category, categoryLabel, nodes }) => ({
|
||||||
|
key: category,
|
||||||
|
label: categoryLabel,
|
||||||
|
children: <StaticNodes nodes={nodes} />,
|
||||||
|
extra: (
|
||||||
|
<Badge
|
||||||
|
showZero
|
||||||
|
count={nodes.length || 0}
|
||||||
|
style={{
|
||||||
|
backgroundColor: nodes.length > 0 ? '#52c41a' : '#7f9474',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
const searchedNodes = operators.filter((node) =>
|
||||||
|
node.label.toLowerCase().includes(searchValue.toLowerCase())
|
||||||
|
);
|
||||||
|
return groupNodes(searchedNodes).map(
|
||||||
|
({ category, categoryLabel, nodes }) => ({
|
||||||
|
key: category,
|
||||||
|
label: categoryLabel,
|
||||||
|
children: <StaticNodes nodes={nodes} />,
|
||||||
|
extra: (
|
||||||
|
<Badge
|
||||||
|
showZero
|
||||||
|
count={nodes.length || 0}
|
||||||
|
style={{
|
||||||
|
backgroundColor: nodes.length > 0 ? '#52c41a' : '#7f9474',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [operatorsGroup, searchValue]);
|
||||||
|
|
||||||
|
const resourceItems: CollapseProps['items'] = useMemo(() => {
|
||||||
|
if (!searchValue) {
|
||||||
|
return resourcesGroup.map(({ category, categoryLabel, nodes }) => ({
|
||||||
|
key: category,
|
||||||
|
label: categoryLabel,
|
||||||
|
children: <StaticNodes nodes={nodes} />,
|
||||||
|
extra: (
|
||||||
|
<Badge
|
||||||
|
showZero
|
||||||
|
count={nodes.length || 0}
|
||||||
|
style={{
|
||||||
|
backgroundColor: nodes.length > 0 ? '#52c41a' : '#7f9474',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
const searchedNodes = resources.filter((node) =>
|
||||||
|
node.label.toLowerCase().includes(searchValue.toLowerCase())
|
||||||
|
);
|
||||||
|
return groupNodes(searchedNodes).map(
|
||||||
|
({ category, categoryLabel, nodes }) => ({
|
||||||
|
key: category,
|
||||||
|
label: categoryLabel,
|
||||||
|
children: <StaticNodes nodes={nodes} />,
|
||||||
|
extra: (
|
||||||
|
<Badge
|
||||||
|
showZero
|
||||||
|
count={nodes.length || 0}
|
||||||
|
style={{
|
||||||
|
backgroundColor: nodes.length > 0 ? '#52c41a' : '#7f9474',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [resourcesGroup, searchValue]);
|
||||||
|
|
||||||
|
function searchNode(val: string) {
|
||||||
|
setSearchValue(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sider
|
||||||
|
className='flex justify-center items-start nodrag bg-[#ffffff80] border-r border-[#d5e5f6] dark:bg-[#ffffff29] dark:border-[#ffffff66]'
|
||||||
|
theme={mode}
|
||||||
|
width={280}
|
||||||
|
collapsible={true}
|
||||||
|
collapsed={collapsed}
|
||||||
|
collapsedWidth={0}
|
||||||
|
trigger={
|
||||||
|
collapsed ? (
|
||||||
|
<CaretRightOutlined className='text-base' />
|
||||||
|
) : (
|
||||||
|
<CaretLeftOutlined className='text-base' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
zeroWidthTriggerStyle={triggerStyle}
|
||||||
|
onCollapse={(collapsed) => setCollapsed(collapsed)}
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction='vertical'
|
||||||
|
className='w-[280px] pt-4 px-4 overflow-hidden overflow-y-auto scrollbar-default'
|
||||||
|
>
|
||||||
|
<p className='w-full text-base font-semibold text-[#1c2533] dark:text-[rgba(255,255,255,0.85)] line-clamp-1'>
|
||||||
|
{t('add_node')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Search placeholder='Search node' onSearch={searchNode} allowClear />
|
||||||
|
|
||||||
|
<h2 className='font-semibold'>{t('operators')}</h2>
|
||||||
|
<Collapse
|
||||||
|
size='small'
|
||||||
|
bordered={false}
|
||||||
|
className='max-h-[calc((100vh-156px)/2)] overflow-hidden overflow-y-auto scrollbar-default'
|
||||||
|
defaultActiveKey={['']}
|
||||||
|
items={operatorItems}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h2 className='font-semibold'>{t('resource')}</h2>
|
||||||
|
<Collapse
|
||||||
|
size='small'
|
||||||
|
bordered={false}
|
||||||
|
className='max-h-[calc((100vh-156px)/2)] overflow-hidden overflow-y-auto scrollbar-default'
|
||||||
|
defaultActiveKey={['']}
|
||||||
|
items={resourceItems}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Sider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddNodesSider;
|
@@ -100,6 +100,7 @@ const AddNodes: React.FC = () => {
|
|||||||
<div className="w-[320px] overflow-hidden overflow-y-auto scrollbar-default">
|
<div className="w-[320px] overflow-hidden overflow-y-auto scrollbar-default">
|
||||||
<p className="my-2 font-bold">{t('add_node')}</p>
|
<p className="my-2 font-bold">{t('add_node')}</p>
|
||||||
<Search placeholder="Search node" onSearch={searchNode} />
|
<Search placeholder="Search node" onSearch={searchNode} />
|
||||||
|
|
||||||
<h2 className="my-2 ml-2 font-semibold">{t('operators')}</h2>
|
<h2 className="my-2 ml-2 font-semibold">{t('operators')}</h2>
|
||||||
<Collapse
|
<Collapse
|
||||||
className="max-h-[300px] overflow-hidden overflow-y-auto scrollbar-default"
|
className="max-h-[300px] overflow-hidden overflow-y-auto scrollbar-default"
|
||||||
@@ -107,6 +108,7 @@ const AddNodes: React.FC = () => {
|
|||||||
defaultActiveKey={['']}
|
defaultActiveKey={['']}
|
||||||
items={operatorItems}
|
items={operatorItems}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h2 className="my-2 ml-2 font-semibold">{t('resource')}</h2>
|
<h2 className="my-2 ml-2 font-semibold">{t('resource')}</h2>
|
||||||
<Collapse
|
<Collapse
|
||||||
className="max-h-[300px] overflow-hidden overflow-y-auto scrollbar-default"
|
className="max-h-[300px] overflow-hidden overflow-y-auto scrollbar-default"
|
||||||
@@ -122,7 +124,7 @@ const AddNodes: React.FC = () => {
|
|||||||
className="flex items-center justify-center rounded-full left-4 top-4"
|
className="flex items-center justify-center rounded-full left-4 top-4"
|
||||||
style={{ zIndex: 1050 }}
|
style={{ zIndex: 1050 }}
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
></Button>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
104
web/components/flow/canvas-modal/export-flow-modal.tsx
Normal file
104
web/components/flow/canvas-modal/export-flow-modal.tsx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { Modal, Form, Input, Button, Space, Radio, message } from 'antd';
|
||||||
|
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
|
||||||
|
import { apiInterceptors, exportFlow } from '@/client/api';
|
||||||
|
import { ReactFlowInstance } from 'reactflow';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
reactFlow: ReactFlowInstance<any, any>;
|
||||||
|
flowInfo?: IFlowUpdateParam;
|
||||||
|
isExportFlowModalOpen: boolean;
|
||||||
|
setIsExportFlowModalOpen: (value: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExportFlowModal: React.FC<Props> = ({
|
||||||
|
reactFlow,
|
||||||
|
flowInfo,
|
||||||
|
isExportFlowModalOpen,
|
||||||
|
setIsExportFlowModalOpen,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [messageApi, contextHolder] = message.useMessage();
|
||||||
|
|
||||||
|
const onFlowExport = async (values: any) => {
|
||||||
|
|
||||||
|
if (values.format === 'json') {
|
||||||
|
const flowData = reactFlow.toObject() as IFlowData;
|
||||||
|
const blob = new Blob([JSON.stringify(flowData)], {
|
||||||
|
type: 'text/plain;charset=utf-8',
|
||||||
|
});
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = values.file_name || 'flow.json';
|
||||||
|
a.click();
|
||||||
|
}else{
|
||||||
|
const linkUrl = `${process.env.API_BASE_URL}/api/v2/serve/awel/flow/export/${values.uid}?export_type=${values.export_type}&format=${values.format}`
|
||||||
|
window.open(linkUrl)
|
||||||
|
}
|
||||||
|
messageApi.success(t('Export_Flow_Success'));
|
||||||
|
|
||||||
|
setIsExportFlowModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
centered
|
||||||
|
title={t('Export_Flow')}
|
||||||
|
open={isExportFlowModalOpen}
|
||||||
|
onCancel={() => setIsExportFlowModalOpen(false)}
|
||||||
|
cancelButtonProps={{ className: 'hidden' }}
|
||||||
|
okButtonProps={{ className: 'hidden' }}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
className='mt-6'
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
onFinish={onFlowExport}
|
||||||
|
initialValues={{
|
||||||
|
export_type: 'json',
|
||||||
|
format: 'file',
|
||||||
|
uid: flowInfo?.uid,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item label={t('Export_File_Type')} name='export_type'>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value='json'>JSON</Radio>
|
||||||
|
<Radio value='dbgpts'>DBGPTS</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={t('Export_File_Format')} name='format'>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value='file'>File</Radio>
|
||||||
|
<Radio value='json'>JSON</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item hidden name='uid'>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item wrapperCol={{ offset: 14, span: 8 }}>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
htmlType='button'
|
||||||
|
onClick={() => setIsExportFlowModalOpen(false)}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button type='primary' htmlType='submit'>
|
||||||
|
{t('verify')}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{contextHolder}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
113
web/components/flow/canvas-modal/import-flow-modal.tsx
Normal file
113
web/components/flow/canvas-modal/import-flow-modal.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Form,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Upload,
|
||||||
|
UploadFile,
|
||||||
|
UploadProps,
|
||||||
|
GetProp,
|
||||||
|
Radio,
|
||||||
|
Space,
|
||||||
|
} from 'antd';
|
||||||
|
import { apiInterceptors, importFlow } from '@/client/api';
|
||||||
|
import { Node, Edge } from 'reactflow';
|
||||||
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isImportModalOpen: boolean;
|
||||||
|
setNodes: React.Dispatch<
|
||||||
|
React.SetStateAction<Node<any, string | undefined>[]>
|
||||||
|
>;
|
||||||
|
setEdges: React.Dispatch<React.SetStateAction<Edge<any>[]>>;
|
||||||
|
setIsImportFlowModalOpen: (value: boolean) => void;
|
||||||
|
};
|
||||||
|
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||||||
|
|
||||||
|
export const ImportFlowModal: React.FC<Props> = ({
|
||||||
|
setNodes,
|
||||||
|
setEdges,
|
||||||
|
isImportModalOpen,
|
||||||
|
setIsImportFlowModalOpen,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [messageApi, contextHolder] = message.useMessage();
|
||||||
|
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||||
|
|
||||||
|
const onFlowImport = async (values: any) => {
|
||||||
|
values.file = values.file?.[0];
|
||||||
|
|
||||||
|
const formData: any = new FormData();
|
||||||
|
fileList.forEach((file) => {
|
||||||
|
formData.append('file', file as FileType);
|
||||||
|
});
|
||||||
|
const [, , res] = await apiInterceptors(importFlow(formData));
|
||||||
|
|
||||||
|
if (res?.success) {
|
||||||
|
messageApi.success(t('Export_Flow_Success'));
|
||||||
|
} else if (res?.err_msg) {
|
||||||
|
messageApi.error(res?.err_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsImportFlowModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
centered
|
||||||
|
title={t('Import_Flow')}
|
||||||
|
open={isImportModalOpen}
|
||||||
|
onCancel={() => setIsImportFlowModalOpen(false)}
|
||||||
|
cancelButtonProps={{ className: 'hidden' }}
|
||||||
|
okButtonProps={{ className: 'hidden' }}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
className='mt-6'
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
onFinish={onFlowImport}
|
||||||
|
initialValues={{
|
||||||
|
save_flow: false,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name='file'
|
||||||
|
label={t('Select_File')}
|
||||||
|
valuePropName='fileList'
|
||||||
|
getValueFromEvent={(e) => (Array.isArray(e) ? e : e && e.fileList)}
|
||||||
|
rules={[{ required: true, message: 'Please upload a file' }]}
|
||||||
|
>
|
||||||
|
<Upload accept='.json,.zip' beforeUpload={() => false} maxCount={1}>
|
||||||
|
<Button icon={<UploadOutlined />}> {t('Upload')}</Button>
|
||||||
|
</Upload>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name='save_flow' label={t('Save_After_Import')}>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value={true}>{t('Yes')}</Radio>
|
||||||
|
<Radio value={false}>{t('No')}</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item wrapperCol={{ offset: 14, span: 8 }}>
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => setIsImportFlowModalOpen(false)}>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button type='primary' htmlType='submit'>
|
||||||
|
{t('verify')}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{contextHolder}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
3
web/components/flow/canvas-modal/index.ts
Normal file
3
web/components/flow/canvas-modal/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './save-flow-modal';
|
||||||
|
export * from './export-flow-modal';
|
||||||
|
export * from './import-flow-modal';
|
203
web/components/flow/canvas-modal/save-flow-modal.tsx
Normal file
203
web/components/flow/canvas-modal/save-flow-modal.tsx
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Modal, Form, Input, Button, Space, message, Checkbox } from 'antd';
|
||||||
|
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
|
||||||
|
import { apiInterceptors, addFlow, updateFlowById } from '@/client/api';
|
||||||
|
import { mapHumpToUnderline } from '@/utils/flow';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ReactFlowInstance } from 'reactflow';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
reactFlow: ReactFlowInstance<any, any>;
|
||||||
|
flowInfo?: IFlowUpdateParam;
|
||||||
|
isSaveFlowModalOpen: boolean;
|
||||||
|
setIsSaveFlowModalOpen: (value: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SaveFlowModal: React.FC<Props> = ({
|
||||||
|
reactFlow,
|
||||||
|
isSaveFlowModalOpen,
|
||||||
|
flowInfo,
|
||||||
|
setIsSaveFlowModalOpen,
|
||||||
|
}) => {
|
||||||
|
const [deploy, setDeploy] = useState(true);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const id = searchParams?.get('id') || '';
|
||||||
|
const [form] = Form.useForm<IFlowUpdateParam>();
|
||||||
|
const [messageApi, contextHolder] = message.useMessage();
|
||||||
|
|
||||||
|
function onLabelChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
const label = e.target.value;
|
||||||
|
// replace spaces with underscores, convert uppercase letters to lowercase, remove characters other than digits, letters, _, and -.
|
||||||
|
let result = label
|
||||||
|
.replace(/\s+/g, '_')
|
||||||
|
.replace(/[^a-z0-9_-]/g, '')
|
||||||
|
.toLowerCase();
|
||||||
|
result = result;
|
||||||
|
form.setFieldsValue({ name: result });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSaveFlow() {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
description = '',
|
||||||
|
editable = false,
|
||||||
|
state = 'deployed',
|
||||||
|
} = form.getFieldsValue();
|
||||||
|
console.log(form.getFieldsValue());
|
||||||
|
const reactFlowObject = mapHumpToUnderline(
|
||||||
|
reactFlow.toObject() as IFlowData
|
||||||
|
);
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
const [, , res] = await apiInterceptors(
|
||||||
|
updateFlowById(id, {
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
editable,
|
||||||
|
uid: id,
|
||||||
|
flow_data: reactFlowObject,
|
||||||
|
state,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res?.success) {
|
||||||
|
messageApi.success(t('save_flow_success'));
|
||||||
|
} else if (res?.err_msg) {
|
||||||
|
messageApi.error(res?.err_msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const [_, res] = await apiInterceptors(
|
||||||
|
addFlow({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
editable,
|
||||||
|
flow_data: reactFlowObject,
|
||||||
|
state,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (res?.uid) {
|
||||||
|
messageApi.success(t('save_flow_success'));
|
||||||
|
const history = window.history;
|
||||||
|
history.pushState(null, '', `/flow/canvas?id=${res.uid}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setIsSaveFlowModalOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
centered
|
||||||
|
title={t('flow_modal_title')}
|
||||||
|
open={isSaveFlowModalOpen}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsSaveFlowModalOpen(false);
|
||||||
|
}}
|
||||||
|
cancelButtonProps={{ className: 'hidden' }}
|
||||||
|
okButtonProps={{ className: 'hidden' }}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name='flow_form'
|
||||||
|
form={form}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
className='mt-6 max-w-2xl'
|
||||||
|
initialValues={{ remember: true }}
|
||||||
|
onFinish={onSaveFlow}
|
||||||
|
autoComplete='off'
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label='Title'
|
||||||
|
name='label'
|
||||||
|
initialValue={flowInfo?.label}
|
||||||
|
rules={[{ required: true, message: 'Please input flow title!' }]}
|
||||||
|
>
|
||||||
|
<Input onChange={onLabelChange} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label='Name'
|
||||||
|
name='name'
|
||||||
|
initialValue={flowInfo?.name}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: 'Please input flow name!' },
|
||||||
|
() => ({
|
||||||
|
validator(_, value) {
|
||||||
|
const regex = /^[a-zA-Z0-9_\-]+$/;
|
||||||
|
if (!regex.test(value)) {
|
||||||
|
return Promise.reject(
|
||||||
|
'Can only contain numbers, letters, underscores, and dashes'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label='Description'
|
||||||
|
initialValue={flowInfo?.description}
|
||||||
|
name='description'
|
||||||
|
>
|
||||||
|
<TextArea rows={3} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label='Editable'
|
||||||
|
name='editable'
|
||||||
|
initialValue={flowInfo?.editable}
|
||||||
|
valuePropName='checked'
|
||||||
|
>
|
||||||
|
<Checkbox />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item hidden name='state'>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label='Deploy'>
|
||||||
|
<Checkbox
|
||||||
|
defaultChecked={
|
||||||
|
flowInfo?.state === 'deployed' || flowInfo?.state === 'running'
|
||||||
|
}
|
||||||
|
checked={deploy}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.target.checked;
|
||||||
|
form.setFieldValue('state', val ? 'deployed' : 'developing');
|
||||||
|
setDeploy(val);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item wrapperCol={{ offset: 14, span: 8 }}>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
htmlType='button'
|
||||||
|
onClick={() => {
|
||||||
|
setIsSaveFlowModalOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button type='primary' htmlType='submit'>
|
||||||
|
{t('verify')}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{contextHolder}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@@ -4,28 +4,33 @@ import NodeParamHandler from './node-param-handler';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import NodeHandler from './node-handler';
|
import NodeHandler from './node-handler';
|
||||||
import { Popover, Tooltip } from 'antd';
|
import { Form, Popover, Tooltip } from 'antd';
|
||||||
import { CopyOutlined, DeleteOutlined, InfoCircleOutlined } from '@ant-design/icons';
|
import {
|
||||||
|
CopyOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
InfoCircleOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import { useReactFlow } from 'reactflow';
|
import { useReactFlow } from 'reactflow';
|
||||||
import IconWrapper from '../common/icon-wrapper';
|
import IconWrapper from '../common/icon-wrapper';
|
||||||
import { getUniqueNodeId } from '@/utils/flow';
|
import { getUniqueNodeId, removeIndexFromNodeId } from '@/utils/flow';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { apiInterceptors, refreshFlowNodeById } from '@/client/api';
|
||||||
|
|
||||||
type CanvasNodeProps = {
|
type CanvasNodeProps = {
|
||||||
data: IFlowNode;
|
data: IFlowNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ICON_PATH_PREFIX = '/icons/node/';
|
|
||||||
|
|
||||||
function TypeLabel({ label }: { label: string }) {
|
function TypeLabel({ label }: { label: string }) {
|
||||||
return <div className="w-full h-8 bg-stone-100 dark:bg-zinc-700 px-2 flex items-center justify-center">{label}</div>;
|
return <div className='w-full h-8 align-middle font-semibold'>{label}</div>;
|
||||||
}
|
}
|
||||||
|
const forceTypeList = ['file', 'multiple_files', 'time','images','csv_file'];
|
||||||
|
|
||||||
const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
|
const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
|
||||||
const node = data;
|
const node = data;
|
||||||
const { inputs, outputs, parameters, flow_type: flowType } = node;
|
const { inputs, outputs, parameters, flow_type: flowType } = node;
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const reactFlow = useReactFlow();
|
const reactFlow = useReactFlow();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
function onHover() {
|
function onHover() {
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
@@ -68,81 +73,211 @@ const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
reactFlow.setNodes((nodes) => nodes.filter((item) => item.id !== node.id));
|
reactFlow.setNodes((nodes) => nodes.filter((item) => item.id !== node.id));
|
||||||
reactFlow.setEdges((edges) => edges.filter((edge) => edge.source !== node.id && edge.target !== node.id));
|
reactFlow.setEdges((edges) =>
|
||||||
|
edges.filter((edge) => edge.source !== node.id && edge.target !== node.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCurrentNodeValue(changedKey: string, changedVal: any) {
|
||||||
|
parameters.forEach((item) => {
|
||||||
|
if (item.name === changedKey) {
|
||||||
|
item.value = changedVal;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateDependsNodeValue(changedKey: string, changedVal: any) {
|
||||||
|
const dependParamNodes = parameters.filter(({ ui }) =>
|
||||||
|
ui?.refresh_depends?.includes(changedKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dependParamNodes?.length === 0) return;
|
||||||
|
dependParamNodes.forEach(async (item) => {
|
||||||
|
const params = {
|
||||||
|
id: removeIndexFromNodeId(data?.id),
|
||||||
|
type_name: data.type_name,
|
||||||
|
type_cls: data.type_cls,
|
||||||
|
flow_type: 'operator' as const,
|
||||||
|
refresh: [
|
||||||
|
{
|
||||||
|
name: item.name,
|
||||||
|
depends: [
|
||||||
|
{
|
||||||
|
name: changedKey,
|
||||||
|
value: changedVal,
|
||||||
|
has_value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const [_, res] = await apiInterceptors(refreshFlowNodeById(params));
|
||||||
|
|
||||||
|
// update value of the node
|
||||||
|
if (res) {
|
||||||
|
reactFlow.setNodes((nodes) =>
|
||||||
|
nodes.map((n) => {
|
||||||
|
return n.id === node.id
|
||||||
|
? {
|
||||||
|
...n,
|
||||||
|
data: {
|
||||||
|
...n.data,
|
||||||
|
parameters: res.parameters,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: n;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onParameterValuesChange(changedValues: any, allValues: any) {
|
||||||
|
const [changedKey, changedVal] = Object.entries(changedValues)[0];
|
||||||
|
|
||||||
|
if (!allValues?.force && forceTypeList.includes(changedKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateCurrentNodeValue(changedKey, changedVal);
|
||||||
|
if (changedVal) {
|
||||||
|
updateDependsNodeValue(changedKey, changedVal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOutput(data: IFlowNode) {
|
function renderOutput(data: IFlowNode) {
|
||||||
if (flowType === 'operator' && outputs?.length > 0) {
|
if (flowType === 'operator' && outputs?.length > 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className='bg-zinc-100 dark:bg-zinc-700 rounded p-2'>
|
||||||
<TypeLabel label="Outputs" />
|
<TypeLabel label='Outputs' />
|
||||||
{(outputs || []).map((output, index) => (
|
{(outputs || []).map((output, index) => (
|
||||||
<NodeHandler key={`${data.id}_input_${index}`} node={data} data={output} type="source" label="outputs" index={index} />
|
<NodeHandler
|
||||||
|
key={`${data.id}_input_${index}`}
|
||||||
|
node={data}
|
||||||
|
data={output}
|
||||||
|
type='source'
|
||||||
|
label='outputs'
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (flowType === 'resource') {
|
} else if (flowType === 'resource') {
|
||||||
// resource nodes show output default
|
// resource nodes show output default
|
||||||
return (
|
return (
|
||||||
<>
|
<div className='bg-zinc-100 dark:bg-zinc-700 rounded p-2'>
|
||||||
<TypeLabel label="Outputs" />
|
<TypeLabel label='Outputs' />
|
||||||
<NodeHandler key={`${data.id}_input_0`} node={data} data={data} type="source" label="outputs" index={0} />
|
<NodeHandler
|
||||||
</>
|
key={`${data.id}_input_0`}
|
||||||
|
node={data}
|
||||||
|
data={data}
|
||||||
|
type='source'
|
||||||
|
label='outputs'
|
||||||
|
index={0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
placement="rightTop"
|
placement='rightTop'
|
||||||
trigger={['hover']}
|
trigger={['hover']}
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
<IconWrapper className="hover:text-blue-500">
|
<IconWrapper className='hover:text-blue-500'>
|
||||||
<CopyOutlined className="h-full text-lg cursor-pointer" onClick={copyNode} />
|
<CopyOutlined
|
||||||
|
className='h-full text-lg cursor-pointer'
|
||||||
|
onClick={copyNode}
|
||||||
|
/>
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
<IconWrapper className="mt-2 hover:text-red-500">
|
|
||||||
<DeleteOutlined className="h-full text-lg cursor-pointer" onClick={deleteNode} />
|
<IconWrapper className='mt-2 hover:text-red-500'>
|
||||||
|
<DeleteOutlined
|
||||||
|
className='h-full text-lg cursor-pointer'
|
||||||
|
onClick={deleteNode}
|
||||||
|
/>
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
<IconWrapper className="mt-2">
|
|
||||||
<Tooltip title={<><p className="font-bold">{node.label}</p><p>{node.description}</p></>} placement="right">
|
<IconWrapper className='mt-2'>
|
||||||
<InfoCircleOutlined className="h-full text-lg cursor-pointer" />
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<p className='font-bold'>{node.label}</p>
|
||||||
|
<p>{node.description}</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
placement='right'
|
||||||
|
>
|
||||||
|
<InfoCircleOutlined className='h-full text-lg cursor-pointer' />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames('w-72 h-auto rounded-xl shadow-md p-0 border bg-white dark:bg-zinc-800 cursor-grab', {
|
className={classNames(
|
||||||
'border-blue-500': node.selected || isHovered,
|
'w-80 h-auto rounded-xl shadow-md px-2 py-4 border bg-white dark:bg-zinc-800 cursor-grab flex flex-col space-y-2 text-sm',
|
||||||
'border-stone-400 dark:border-white': !node.selected && !isHovered,
|
{
|
||||||
'border-dashed': flowType !== 'operator',
|
'border-blue-500': node.selected || isHovered,
|
||||||
'border-red-600': node.invalid,
|
'border-stone-400 dark:border-white': !node.selected && !isHovered,
|
||||||
})}
|
'border-dashed': flowType !== 'operator',
|
||||||
|
'border-red-600': node.invalid,
|
||||||
|
}
|
||||||
|
)}
|
||||||
onMouseEnter={onHover}
|
onMouseEnter={onHover}
|
||||||
onMouseLeave={onLeave}
|
onMouseLeave={onLeave}
|
||||||
>
|
>
|
||||||
{/* icon and label */}
|
{/* icon and label */}
|
||||||
<div className="flex flex-row items-center p-2">
|
<div className='flex flex-row items-center'>
|
||||||
<Image src={'/icons/node/vis.png'} width={24} height={24} alt="" />
|
<Image src={'/icons/node/vis.png'} width={24} height={24} alt='' />
|
||||||
<p className="ml-2 text-lg font-bold text-ellipsis overflow-hidden whitespace-nowrap">{node.label}</p>
|
<p className='ml-2 text-lg font-bold text-ellipsis overflow-hidden whitespace-nowrap'>
|
||||||
|
{node.label}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{inputs && inputs.length > 0 && (
|
|
||||||
<>
|
{inputs?.length > 0 && (
|
||||||
<TypeLabel label="Inputs" />
|
<div className='bg-zinc-100 dark:bg-zinc-700 rounded p-2'>
|
||||||
{(inputs || []).map((input, index) => (
|
<TypeLabel label='Inputs' />
|
||||||
<NodeHandler key={`${node.id}_input_${index}`} node={node} data={input} type="target" label="inputs" index={index} />
|
<div className='flex flex-col space-y-2'>
|
||||||
))}
|
{inputs?.map((item, index) => (
|
||||||
</>
|
<NodeHandler
|
||||||
|
key={`${node.id}_input_${index}`}
|
||||||
|
node={node}
|
||||||
|
data={item}
|
||||||
|
type='target'
|
||||||
|
label='inputs'
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{parameters && parameters.length > 0 && (
|
|
||||||
<>
|
{parameters?.length > 0 && (
|
||||||
<TypeLabel label="Parameters" />
|
<div className='bg-zinc-100 dark:bg-zinc-700 rounded p-2'>
|
||||||
{(parameters || []).map((parameter, index) => (
|
<TypeLabel label='Parameters' />
|
||||||
<NodeParamHandler key={`${node.id}_param_${index}`} node={node} data={parameter} label="parameters" index={index} />
|
<Form
|
||||||
))}
|
form={form}
|
||||||
</>
|
layout='vertical'
|
||||||
|
onValuesChange={onParameterValuesChange}
|
||||||
|
className='flex flex-col space-y-3 text-neutral-500'
|
||||||
|
>
|
||||||
|
{parameters?.map((item, index) => (
|
||||||
|
<NodeParamHandler
|
||||||
|
key={`${node.id}_param_${index}`}
|
||||||
|
formValuesChange={onParameterValuesChange}
|
||||||
|
node={node}
|
||||||
|
paramData={item}
|
||||||
|
label='parameters'
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{renderOutput(node)}
|
{renderOutput(node)}
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
@@ -94,15 +94,15 @@ const NodeHandler: React.FC<NodeHandlerProps> = ({ node, data, type, label, inde
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Handle
|
<Handle
|
||||||
className="w-2 h-2"
|
className={classNames('w-2 h-2', type === 'source' ? '-mr-4' : '-ml-4')}
|
||||||
type={type}
|
type={type}
|
||||||
position={type === 'source' ? Position.Right : Position.Left}
|
position={type === 'source' ? Position.Right : Position.Left}
|
||||||
id={`${node.id}|${label}|${index}`}
|
id={`${node.id}|${label}|${index}`}
|
||||||
isValidConnection={(connection) => isValidConnection(connection)}
|
isValidConnection={(connection) => isValidConnection(connection)}
|
||||||
/>
|
/>
|
||||||
<Typography
|
<Typography
|
||||||
className={classNames('p-2', {
|
className={classNames('bg-white dark:bg-[#232734] w-full px-2 py-1 rounded text-neutral-500', {
|
||||||
'pr-4': label === 'outputs',
|
'text-right': label === 'outputs',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
@@ -117,9 +117,10 @@ const NodeHandler: React.FC<NodeHandlerProps> = ({ node, data, type, label, inde
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{['inputs', 'parameters'].includes(label) && <PlusOutlined className="mr-2 cursor-pointer" onClick={showRelatedNodes} />}
|
{['inputs', 'parameters'].includes(label) && <PlusOutlined className="cursor-pointer" onClick={showRelatedNodes} />}
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
{data.type_name}:{label !== 'outputs' && <RequiredIcon optional={data.optional} />}
|
{label !== 'outputs' && <RequiredIcon optional={data.optional} />}
|
||||||
|
{data.type_name}
|
||||||
{data.description && (
|
{data.description && (
|
||||||
<Tooltip title={data.description}>
|
<Tooltip title={data.description}>
|
||||||
<InfoCircleOutlined className="ml-2 cursor-pointer" />
|
<InfoCircleOutlined className="ml-2 cursor-pointer" />
|
||||||
|
@@ -1,102 +1,153 @@
|
|||||||
import { IFlowNode, IFlowNodeParameter } from '@/types/flow';
|
import { IFlowNode, IFlowNodeParameter } from '@/types/flow';
|
||||||
import { Checkbox, Input, InputNumber, Select, Tooltip } from 'antd';
|
import { Checkbox, Form, Input, InputNumber, Select } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import RequiredIcon from './required-icon';
|
|
||||||
import NodeHandler from './node-handler';
|
import NodeHandler from './node-handler';
|
||||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
renderSelect,
|
||||||
|
renderCheckbox,
|
||||||
|
renderRadio,
|
||||||
|
renderCascader,
|
||||||
|
renderDatePicker,
|
||||||
|
renderInput,
|
||||||
|
renderSlider,
|
||||||
|
renderTreeSelect,
|
||||||
|
renderTimePicker,
|
||||||
|
renderTextArea,
|
||||||
|
renderUpload,
|
||||||
|
renderCodeEditor,
|
||||||
|
renderPassword,
|
||||||
|
renderVariables,
|
||||||
|
} from './node-renderer';
|
||||||
|
|
||||||
interface NodeParamHandlerProps {
|
interface NodeParamHandlerProps {
|
||||||
|
formValuesChange:any;
|
||||||
node: IFlowNode;
|
node: IFlowNode;
|
||||||
data: IFlowNodeParameter;
|
paramData: IFlowNodeParameter;
|
||||||
label: 'inputs' | 'outputs' | 'parameters';
|
label: 'inputs' | 'outputs' | 'parameters';
|
||||||
index: number; // index of array
|
index: number; // index of array
|
||||||
}
|
}
|
||||||
|
|
||||||
// render node parameters item
|
// render node parameters item
|
||||||
const NodeParamHandler: React.FC<NodeParamHandlerProps> = ({ node, data, label, index }) => {
|
const NodeParamHandler: React.FC<NodeParamHandlerProps> = ({ formValuesChange,node, paramData, label, index }) => {
|
||||||
function handleChange(value: any) {
|
// render node parameters based on AWEL1.0
|
||||||
data.value = value;
|
function renderNodeWithoutUiParam(data: IFlowNodeParameter) {
|
||||||
}
|
let defaultValue = data.value ?? data.default;
|
||||||
|
|
||||||
if (data.category === 'resource') {
|
|
||||||
return <NodeHandler node={node} data={data} type="target" label={label} index={index} />;
|
|
||||||
} else if (data.category === 'common') {
|
|
||||||
let defaultValue = data.value !== null && data.value !== undefined ? data.value : data.default;
|
|
||||||
switch (data.type_name) {
|
switch (data.type_name) {
|
||||||
case 'int':
|
case 'int':
|
||||||
case 'float':
|
case 'float':
|
||||||
return (
|
return (
|
||||||
<div className="p-2 text-sm">
|
<Form.Item
|
||||||
<p>
|
className="mb-2 text-sm"
|
||||||
{data.label}:<RequiredIcon optional={data.optional} />
|
name={data.name}
|
||||||
{data.description && (
|
initialValue={defaultValue}
|
||||||
<Tooltip title={data.description}>
|
rules={[{ required: !data.optional }]}
|
||||||
<InfoCircleOutlined className="ml-2 cursor-pointer" />
|
label={<span className="text-neutral-500">{data.label}</span>}
|
||||||
</Tooltip>
|
tooltip={data.description ? { title: data.description, icon: <InfoCircleOutlined /> } : ''}
|
||||||
)}
|
>
|
||||||
</p>
|
<InputNumber className="w-full nodrag" />
|
||||||
<InputNumber
|
</Form.Item>
|
||||||
className="w-full"
|
|
||||||
defaultValue={defaultValue}
|
|
||||||
onChange={(value: number | null) => {
|
|
||||||
handleChange(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'str':
|
case 'str':
|
||||||
return (
|
return (
|
||||||
<div className="p-2 text-sm">
|
<Form.Item
|
||||||
<p>
|
className="mb-2 text-sm"
|
||||||
{data.label}:<RequiredIcon optional={data.optional} />
|
name={data.name}
|
||||||
{data.description && (
|
initialValue={defaultValue}
|
||||||
<Tooltip title={data.description}>
|
rules={[{ required: !data.optional }]}
|
||||||
<InfoCircleOutlined className="ml-2 cursor-pointer" />
|
label={<span className="text-neutral-500">{data.label}</span>}
|
||||||
</Tooltip>
|
tooltip={data.description ? { title: data.description, icon: <InfoCircleOutlined /> } : ''}
|
||||||
)}
|
>
|
||||||
</p>
|
|
||||||
{data.options?.length > 0 ? (
|
{data.options?.length > 0 ? (
|
||||||
<Select
|
<Select className="w-full nodrag" options={data.options.map((item: any) => ({ label: item.label, value: item.value }))} />
|
||||||
className="w-full nodrag"
|
|
||||||
defaultValue={defaultValue}
|
|
||||||
options={data.options.map((item: any) => ({ label: item.label, value: item.value }))}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Input className="w-full" />
|
||||||
className="w-full"
|
|
||||||
defaultValue={defaultValue}
|
|
||||||
onChange={(e) => {
|
|
||||||
handleChange(e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'bool':
|
case 'bool':
|
||||||
defaultValue = defaultValue === 'False' ? false : defaultValue;
|
defaultValue = defaultValue === 'False' ? false : defaultValue;
|
||||||
defaultValue = defaultValue === 'True' ? true : defaultValue;
|
defaultValue = defaultValue === 'True' ? true : defaultValue;
|
||||||
return (
|
return (
|
||||||
<div className="p-2 text-sm">
|
<Form.Item
|
||||||
<p>
|
className="mb-2 text-sm"
|
||||||
{data.label}:<RequiredIcon optional={data.optional} />
|
name={data.name}
|
||||||
{data.description && (
|
initialValue={defaultValue}
|
||||||
<Tooltip title={data.description}>
|
rules={[{ required: !data.optional }]}
|
||||||
<InfoCircleOutlined className="ml-2 cursor-pointer" />
|
label={<span className="text-neutral-500">{data.label}</span>}
|
||||||
</Tooltip>
|
tooltip={data.description ? { title: data.description, icon: <InfoCircleOutlined /> } : ''}
|
||||||
)}
|
>
|
||||||
<Checkbox
|
<Checkbox className="ml-2" />
|
||||||
className="ml-2"
|
</Form.Item>
|
||||||
defaultChecked={defaultValue}
|
|
||||||
onChange={(e) => {
|
|
||||||
handleChange(e.target.checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderComponentByType(type: string, data: IFlowNodeParameter,formValuesChange:any) {
|
||||||
|
switch (type) {
|
||||||
|
case 'select':
|
||||||
|
return renderSelect(data);
|
||||||
|
case 'cascader':
|
||||||
|
return renderCascader(data);
|
||||||
|
case 'checkbox':
|
||||||
|
return renderCheckbox(data);
|
||||||
|
case 'radio':
|
||||||
|
return renderRadio(data);
|
||||||
|
case 'input':
|
||||||
|
return renderInput(data);
|
||||||
|
case 'text_area':
|
||||||
|
return renderTextArea(data);
|
||||||
|
case 'slider':
|
||||||
|
return renderSlider(data);
|
||||||
|
case 'date_picker':
|
||||||
|
return renderDatePicker( data );
|
||||||
|
case 'time_picker':
|
||||||
|
return renderTimePicker({ data,formValuesChange });
|
||||||
|
case 'tree_select':
|
||||||
|
return renderTreeSelect(data);
|
||||||
|
case 'password':
|
||||||
|
return renderPassword(data);
|
||||||
|
case 'upload':
|
||||||
|
return renderUpload({ data,formValuesChange });
|
||||||
|
case 'variables':
|
||||||
|
return renderVariables(data);
|
||||||
|
case 'code_editor':
|
||||||
|
return renderCodeEditor(data);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// render node parameters based on AWEL2.0
|
||||||
|
function renderNodeWithUiParam(data: IFlowNodeParameter,formValuesChange:any) {
|
||||||
|
const { refresh_depends, ui_type } = data.ui;
|
||||||
|
let defaultValue = data.value ?? data.default;
|
||||||
|
if (ui_type === 'slider' && data.is_list) {
|
||||||
|
defaultValue = [0,1]
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
className="mb-2"
|
||||||
|
initialValue={defaultValue}
|
||||||
|
name={data.name}
|
||||||
|
rules={[{ required: !data.optional }]}
|
||||||
|
label={<span className="text-neutral-500">{data.label}</span>}
|
||||||
|
{...(refresh_depends && { dependencies: refresh_depends })}
|
||||||
|
{...(data.description && { tooltip: { title: data.description, icon: <InfoCircleOutlined /> } })}
|
||||||
|
>
|
||||||
|
{renderComponentByType(ui_type, data,formValuesChange)}
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramData.category === 'resource') {
|
||||||
|
return <NodeHandler node={node} data={paramData} type="target" label={label} index={index} />;
|
||||||
|
} else if (paramData.category === 'common') {
|
||||||
|
return paramData?.ui ? renderNodeWithUiParam(paramData,formValuesChange) : renderNodeWithoutUiParam(paramData);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NodeParamHandler;
|
export default NodeParamHandler;
|
||||||
|
16
web/components/flow/node-renderer/cascader.tsx
Normal file
16
web/components/flow/node-renderer/cascader.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import { Cascader } from 'antd';
|
||||||
|
|
||||||
|
export const renderCascader = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Cascader
|
||||||
|
{...attr}
|
||||||
|
options={data.options}
|
||||||
|
placeholder="please select"
|
||||||
|
className="w-full nodrag"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
15
web/components/flow/node-renderer/checkbox.tsx
Normal file
15
web/components/flow/node-renderer/checkbox.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import { Checkbox } from 'antd';
|
||||||
|
|
||||||
|
export const renderCheckbox = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
|
||||||
|
return (
|
||||||
|
data.options?.length > 0 && (
|
||||||
|
<div className="bg-white p-2 rounded">
|
||||||
|
<Checkbox.Group {...attr} options={data.options} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
62
web/components/flow/node-renderer/code-editor.tsx
Normal file
62
web/components/flow/node-renderer/code-editor.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import React, { useState, useMemo } from 'react';
|
||||||
|
import { Button, Form, Modal } from 'antd';
|
||||||
|
import Editor from '@monaco-editor/react';
|
||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: IFlowNodeParameter;
|
||||||
|
defaultValue?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderCodeEditor = (data: IFlowNodeParameter) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const showModal = () => {
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOk = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const modalWidth = useMemo(() => {
|
||||||
|
if (data?.ui?.editor?.width) {
|
||||||
|
return data?.ui?.editor?.width + 100;
|
||||||
|
}
|
||||||
|
return '80%';
|
||||||
|
}, [data?.ui?.editor?.width]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-2 text-sm">
|
||||||
|
<Button type="default" onClick={showModal}>
|
||||||
|
{t('Open_Code_Editor')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Modal title={t('Code_Editor')} width={modalWidth} open={isModalOpen} onOk={onOk} onCancel={onCancel}>
|
||||||
|
<Form.Item name={data?.name}>
|
||||||
|
<Editor
|
||||||
|
{...attr}
|
||||||
|
width={data?.ui?.editor?.width || '100%'}
|
||||||
|
height={data?.ui?.editor?.height || 200}
|
||||||
|
defaultLanguage={data?.ui?.language}
|
||||||
|
theme="vs-dark"
|
||||||
|
options={{
|
||||||
|
minimap: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
wordWrap: 'on',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
9
web/components/flow/node-renderer/date-picker.tsx
Normal file
9
web/components/flow/node-renderer/date-picker.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import { DatePicker } from 'antd';
|
||||||
|
|
||||||
|
export const renderDatePicker = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
|
||||||
|
return <DatePicker {...attr} className="w-full" placeholder="please select a date" />;
|
||||||
|
};
|
14
web/components/flow/node-renderer/index.ts
Normal file
14
web/components/flow/node-renderer/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export * from './select';
|
||||||
|
export * from './cascader';
|
||||||
|
export * from './date-picker';
|
||||||
|
export * from './input';
|
||||||
|
export * from './checkbox';
|
||||||
|
export * from './radio';
|
||||||
|
export * from './textarea';
|
||||||
|
export * from './slider';
|
||||||
|
export * from './time-picker';
|
||||||
|
export * from './tree-select';
|
||||||
|
export * from './code-editor';
|
||||||
|
export * from './upload';
|
||||||
|
export * from './password';
|
||||||
|
export * from './variables';
|
22
web/components/flow/node-renderer/input.tsx
Normal file
22
web/components/flow/node-renderer/input.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import { Input } from 'antd';
|
||||||
|
import * as Icons from '@ant-design/icons';
|
||||||
|
|
||||||
|
const getIconComponent = (iconString: string) => {
|
||||||
|
const match = iconString.match(/^icon:(\w+)$/);
|
||||||
|
if (match) {
|
||||||
|
const iconName = match[1] as keyof typeof Icons;
|
||||||
|
const IconComponent = Icons[iconName];
|
||||||
|
// @ts-ignore
|
||||||
|
return IconComponent ? <IconComponent /> : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderInput = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
attr.prefix = getIconComponent(data.ui?.attr?.prefix || '');
|
||||||
|
|
||||||
|
return <Input {...attr} className="w-full" placeholder="please input" allowClear />;
|
||||||
|
};
|
11
web/components/flow/node-renderer/password.tsx
Normal file
11
web/components/flow/node-renderer/password.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { Input } from 'antd';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
|
||||||
|
const { Password } = Input;
|
||||||
|
|
||||||
|
export const renderPassword = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
|
||||||
|
return <Password {...attr} placeholder="input password" />;
|
||||||
|
};
|
13
web/components/flow/node-renderer/radio.tsx
Normal file
13
web/components/flow/node-renderer/radio.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import { Radio } from 'antd';
|
||||||
|
|
||||||
|
export const renderRadio = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white p-2 rounded">
|
||||||
|
<Radio.Group {...attr} options={data.options} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
9
web/components/flow/node-renderer/select.tsx
Normal file
9
web/components/flow/node-renderer/select.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { Select } from 'antd';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
|
||||||
|
export const renderSelect = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data?.ui?.attr || {});
|
||||||
|
|
||||||
|
return <Select {...attr} className="w-full nodrag" placeholder="please select" options={data.options} />;
|
||||||
|
};
|
16
web/components/flow/node-renderer/slider.tsx
Normal file
16
web/components/flow/node-renderer/slider.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import { Slider } from 'antd';
|
||||||
|
|
||||||
|
export const renderSlider = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{data.is_list?
|
||||||
|
(<Slider range className="mt-8 nodrag" {...attr} />)
|
||||||
|
:(<Slider className="mt-8 nodrag" {...attr} />)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
};
|
13
web/components/flow/node-renderer/textarea.tsx
Normal file
13
web/components/flow/node-renderer/textarea.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { Input } from 'antd';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
export const renderTextArea = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
return (
|
||||||
|
<TextArea className="nowheel mb-3" {...attr} />
|
||||||
|
);
|
||||||
|
};
|
23
web/components/flow/node-renderer/time-picker.tsx
Normal file
23
web/components/flow/node-renderer/time-picker.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TimePicker } from 'antd';
|
||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import type { TimePickerProps } from 'antd';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
formValuesChange:any,
|
||||||
|
data: IFlowNodeParameter;
|
||||||
|
};
|
||||||
|
export const renderTimePicker = (params: Props) => {
|
||||||
|
const { data ,formValuesChange} = params;
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
|
||||||
|
|
||||||
|
const onChangeTime: TimePickerProps['onChange'] = (time, timeString) => {
|
||||||
|
formValuesChange({
|
||||||
|
time:timeString
|
||||||
|
},{force:true})
|
||||||
|
};
|
||||||
|
|
||||||
|
return <TimePicker {...attr} onChange={onChangeTime} className="w-full" placeholder="please select a moment" />;
|
||||||
|
};
|
17
web/components/flow/node-renderer/tree-select.tsx
Normal file
17
web/components/flow/node-renderer/tree-select.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TreeSelect } from 'antd';
|
||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
|
||||||
|
export const renderTreeSelect = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TreeSelect
|
||||||
|
{...attr}
|
||||||
|
className="w-full nodrag"
|
||||||
|
treeDefaultExpandAll
|
||||||
|
treeData={data.options}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
90
web/components/flow/node-renderer/upload.tsx
Normal file
90
web/components/flow/node-renderer/upload.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React, { useState, useRef } from 'react';
|
||||||
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
import type { UploadProps } from 'antd';
|
||||||
|
import { Button, Upload, message,Form } from 'antd';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
formValuesChange:any,
|
||||||
|
data: IFlowNodeParameter;
|
||||||
|
onChange?: (value: any) => void;
|
||||||
|
};
|
||||||
|
export const renderUpload = (params: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const urlList = useRef<string[]>([]);
|
||||||
|
const { data ,formValuesChange} = params;
|
||||||
|
const form = Form.useFormInstance()
|
||||||
|
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [uploadType, setUploadType] = useState('');
|
||||||
|
|
||||||
|
const getUploadSuccessUrl = (url: string) => {
|
||||||
|
if (urlList.current.length === data.ui.attr.max_count) {
|
||||||
|
urlList.current.pop();
|
||||||
|
}
|
||||||
|
urlList.current.push(url);
|
||||||
|
if (data.ui.attr.max_count === 1) {
|
||||||
|
formValuesChange({file:urlList.current.toString()},{force:true})
|
||||||
|
}else{
|
||||||
|
formValuesChange({multiple_files:JSON.stringify(urlList.current)},{force:true})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileRemove = (file: any) => {
|
||||||
|
const index = urlList.current.indexOf(file.response.data[0].uri);
|
||||||
|
if (index !== -1) {
|
||||||
|
urlList.current.splice(index, 1);
|
||||||
|
}
|
||||||
|
if (data.ui.attr.max_count === 1) {
|
||||||
|
formValuesChange({file:urlList.current.toString()},{force:true})
|
||||||
|
}else{
|
||||||
|
formValuesChange({multiple_files:JSON.stringify(urlList.current)},{force:true})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const props: UploadProps = {
|
||||||
|
name: 'files',
|
||||||
|
action: process.env.API_BASE_URL + data.ui.action,
|
||||||
|
headers: {
|
||||||
|
authorization: 'authorization-text',
|
||||||
|
},
|
||||||
|
onChange(info) {
|
||||||
|
setUploading(true);
|
||||||
|
if (info.file.status !== 'uploading') {
|
||||||
|
}
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
setUploading(false);
|
||||||
|
message.success(`${info.file.response.data[0].file_name} ${t('Upload_Data_Successfully')}`);
|
||||||
|
getUploadSuccessUrl(info.file.response.data[0].uri);
|
||||||
|
} else if (info.file.status === 'error') {
|
||||||
|
setUploading(false);
|
||||||
|
message.error(`${info.file.response.data[0].file_name} ${t('Upload_Data_Failed')}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!uploadType && data.ui?.file_types && Array.isArray(data.ui?.file_types)) {
|
||||||
|
setUploadType(data.ui?.file_types.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-2 text-sm text-center">
|
||||||
|
{data.is_list ? (
|
||||||
|
<Upload onRemove={handleFileRemove} {...props} {...attr} multiple={true} accept={uploadType}>
|
||||||
|
<Button loading={uploading} icon={<UploadOutlined />}>
|
||||||
|
{t('Upload_Data')}
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
) : (
|
||||||
|
<Upload onRemove={handleFileRemove} {...props} {...attr} multiple={false} accept={uploadType}>
|
||||||
|
<Button loading={uploading} icon={<UploadOutlined />}>
|
||||||
|
{t('Upload_Data')}
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
9
web/components/flow/node-renderer/variables.tsx
Normal file
9
web/components/flow/node-renderer/variables.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { IFlowNodeParameter } from '@/types/flow';
|
||||||
|
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||||
|
import { Input } from 'antd';
|
||||||
|
|
||||||
|
export const renderVariables = (data: IFlowNodeParameter) => {
|
||||||
|
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||||
|
|
||||||
|
return <Input {...attr} className="w-full" placeholder="please input" allowClear />;
|
||||||
|
};
|
@@ -14,27 +14,28 @@ const StaticNodes: React.FC<{ nodes: IFlowNode[] }> = ({ nodes }) => {
|
|||||||
if (nodes?.length > 0) {
|
if (nodes?.length > 0) {
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
className="overflow-hidden overflow-y-auto w-full"
|
className='overflow-hidden overflow-y-auto w-full'
|
||||||
itemLayout="horizontal"
|
size='small'
|
||||||
|
itemLayout='horizontal'
|
||||||
dataSource={nodes}
|
dataSource={nodes}
|
||||||
renderItem={(node) => (
|
renderItem={(node) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
className="cursor-move hover:bg-[#F1F5F9] dark:hover:bg-theme-dark p-0 py-2"
|
className='cursor-move hover:bg-[#F1F5F9] dark:hover:bg-theme-dark p-0 py-2'
|
||||||
draggable
|
draggable
|
||||||
onDragStart={(event) => onDragStart(event, node)}
|
onDragStart={(event) => onDragStart(event, node)}
|
||||||
>
|
>
|
||||||
<List.Item.Meta
|
<List.Item.Meta
|
||||||
className="flex items-center justify-center"
|
className='flex items-center justify-center'
|
||||||
avatar={<Avatar src={'/icons/node/vis.png'} size={'large'} />}
|
avatar={<Avatar src={'/icons/node/vis.png'} size={'large'} />}
|
||||||
title={<p className="line-clamp-1 font-medium">{node.label}</p>}
|
title={<p className='line-clamp-1 font-medium'>{node.label}</p>}
|
||||||
description={<p className="line-clamp-2">{node.description}</p>}
|
description={<p className='line-clamp-2'>{node.description}</p>}
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return <Empty className="px-2" description={t('no_node')} />;
|
return <Empty className='px-2' description={t('no_node')} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ export const ChatEn = {
|
|||||||
thinking: 'Thinking',
|
thinking: 'Thinking',
|
||||||
stop_replying: 'Stop replying',
|
stop_replying: 'Stop replying',
|
||||||
erase_memory: 'Erase Memory',
|
erase_memory: 'Erase Memory',
|
||||||
copy: 'Copy',
|
|
||||||
copy_nothing: 'Content copied is empty',
|
copy_nothing: 'Content copied is empty',
|
||||||
copy_success: 'Copy success',
|
copy_success: 'Copy success',
|
||||||
copy_failed: 'Copy failed',
|
copy_failed: 'Copy failed',
|
||||||
|
@@ -15,17 +15,20 @@ export const CommonEn = {
|
|||||||
Please_select_file: 'Please select one file',
|
Please_select_file: 'Please select one file',
|
||||||
Description: 'Description',
|
Description: 'Description',
|
||||||
Storage: 'Storage',
|
Storage: 'Storage',
|
||||||
|
Domain: 'Domain',
|
||||||
Please_input_the_description: 'Please input the description',
|
Please_input_the_description: 'Please input the description',
|
||||||
Please_select_the_storage:'Please select the storage',
|
Please_select_the_storage: 'Please select the storage',
|
||||||
Please_select_the_domain_type: 'Please select the domain type',
|
Please_select_the_domain_type: 'Please select the domain type',
|
||||||
Next: 'Next',
|
Next: 'Next',
|
||||||
the_name_can_only_contain: 'the name can only contain numbers, letters, Chinese characters, "-" and "_"',
|
the_name_can_only_contain:
|
||||||
|
'the name can only contain numbers, letters, Chinese characters, "-" and "_"',
|
||||||
Text: 'Text',
|
Text: 'Text',
|
||||||
'Fill your raw text': 'Fill your raw text',
|
'Fill your raw text': 'Fill your raw text',
|
||||||
URL: 'URL',
|
URL: 'URL',
|
||||||
Fetch_the_content_of_a_URL: 'Fetch the content of a URL',
|
Fetch_the_content_of_a_URL: 'Fetch the content of a URL',
|
||||||
Document: 'Document',
|
Document: 'Document',
|
||||||
Upload_a_document: 'Upload a document, document type can be PDF, CSV, Text, PowerPoint, Word, Markdown, Zip',
|
Upload_a_document:
|
||||||
|
'Upload a document, document type can be PDF, CSV, Text, PowerPoint, Word, Markdown, Zip',
|
||||||
Name: 'Name',
|
Name: 'Name',
|
||||||
Text_Source: 'Text Source(Optional)',
|
Text_Source: 'Text Source(Optional)',
|
||||||
Please_input_the_text_source: 'Please input the text source',
|
Please_input_the_text_source: 'Please input the text source',
|
||||||
@@ -58,10 +61,12 @@ export const CommonEn = {
|
|||||||
topk: 'topk',
|
topk: 'topk',
|
||||||
the_top_k_vectors: 'the top k vectors based on similarity score',
|
the_top_k_vectors: 'the top k vectors based on similarity score',
|
||||||
recall_score: 'recall_score',
|
recall_score: 'recall_score',
|
||||||
Set_a_threshold_score: 'Set a threshold score for the retrieval of similar vectors',
|
Set_a_threshold_score:
|
||||||
|
'Set a threshold score for the retrieval of similar vectors',
|
||||||
recall_type: 'recall_type',
|
recall_type: 'recall_type',
|
||||||
model: 'model',
|
model: 'model',
|
||||||
A_model_used: 'A model used to create vector representations of text or other data',
|
A_model_used:
|
||||||
|
'A model used to create vector representations of text or other data',
|
||||||
Automatic: 'Automatic',
|
Automatic: 'Automatic',
|
||||||
Process: 'Process',
|
Process: 'Process',
|
||||||
Automatic_desc: 'Automatically set segmentation and preprocessing rules.',
|
Automatic_desc: 'Automatically set segmentation and preprocessing rules.',
|
||||||
@@ -71,14 +76,16 @@ export const CommonEn = {
|
|||||||
The_amount_of_overlap: 'The amount of overlap between adjacent data chunks',
|
The_amount_of_overlap: 'The amount of overlap between adjacent data chunks',
|
||||||
Prompt: 'Prompt',
|
Prompt: 'Prompt',
|
||||||
scene: 'scene',
|
scene: 'scene',
|
||||||
A_contextual_parameter: 'A contextual parameter used to define the setting or environment in which the prompt is being used',
|
A_contextual_parameter:
|
||||||
|
'A contextual parameter used to define the setting or environment in which the prompt is being used',
|
||||||
template: 'template',
|
template: 'template',
|
||||||
structure_or_format:
|
structure_or_format:
|
||||||
'A pre-defined structure or format for the prompt, which can help ensure that the AI system generates responses that are consistent with the desired style or tone.',
|
'A pre-defined structure or format for the prompt, which can help ensure that the AI system generates responses that are consistent with the desired style or tone.',
|
||||||
max_token: 'max_token',
|
max_token: 'max_token',
|
||||||
max_iteration: 'max_iteration',
|
max_iteration: 'max_iteration',
|
||||||
concurrency_limit: 'concurrency_limit',
|
concurrency_limit: 'concurrency_limit',
|
||||||
The_maximum_number_of_tokens: 'The maximum number of tokens or words allowed in a prompt',
|
The_maximum_number_of_tokens:
|
||||||
|
'The maximum number of tokens or words allowed in a prompt',
|
||||||
Theme: 'Theme',
|
Theme: 'Theme',
|
||||||
Port: 'Port',
|
Port: 'Port',
|
||||||
Username: 'Username',
|
Username: 'Username',
|
||||||
@@ -91,7 +98,8 @@ export const CommonEn = {
|
|||||||
Show_Sidebar: 'UnFold',
|
Show_Sidebar: 'UnFold',
|
||||||
language: 'Language',
|
language: 'Language',
|
||||||
choose_model: 'Please choose a model',
|
choose_model: 'Please choose a model',
|
||||||
data_center_desc: 'DB-GPT also offers a user-friendly data center management interface for efficient data maintenance.',
|
data_center_desc:
|
||||||
|
'DB-GPT also offers a user-friendly data center management interface for efficient data maintenance.',
|
||||||
create_database: 'Create Database',
|
create_database: 'Create Database',
|
||||||
create_knowledge: 'Create Knowledge',
|
create_knowledge: 'Create Knowledge',
|
||||||
path: 'Path',
|
path: 'Path',
|
||||||
@@ -229,7 +237,8 @@ export const CommonEn = {
|
|||||||
docs: 'Docs',
|
docs: 'Docs',
|
||||||
apps: 'All Apps',
|
apps: 'All Apps',
|
||||||
please_enter_the_keywords: 'Please enter the keywords',
|
please_enter_the_keywords: 'Please enter the keywords',
|
||||||
input_tip: 'Please select the model and enter the description to start quickly',
|
input_tip:
|
||||||
|
'Please select the model and enter the description to start quickly',
|
||||||
create_app: 'Create App',
|
create_app: 'Create App',
|
||||||
copy_url: 'Click the Copy Share link',
|
copy_url: 'Click the Copy Share link',
|
||||||
double_click_open: 'Double click on Nail nail to open',
|
double_click_open: 'Double click on Nail nail to open',
|
||||||
@@ -268,7 +277,7 @@ export const CommonEn = {
|
|||||||
details: 'Details',
|
details: 'Details',
|
||||||
choose: 'Choose',
|
choose: 'Choose',
|
||||||
please_choose: 'Please choose',
|
please_choose: 'Please choose',
|
||||||
want_delete: "Are you sure delete it?",
|
want_delete: 'Are you sure delete it?',
|
||||||
success: 'Success',
|
success: 'Success',
|
||||||
input_parameter: 'Input parameter',
|
input_parameter: 'Input parameter',
|
||||||
output_structure: 'Output structure',
|
output_structure: 'Output structure',
|
||||||
@@ -313,4 +322,6 @@ export const CommonEn = {
|
|||||||
View_details: 'View details',
|
View_details: 'View details',
|
||||||
All: 'All',
|
All: 'All',
|
||||||
Please_input_prompt_name: 'Please input prompt name',
|
Please_input_prompt_name: 'Please input prompt name',
|
||||||
|
Copy_Btn: 'Copy',
|
||||||
|
Delete_Btn: 'Delete',
|
||||||
} as const;
|
} as const;
|
||||||
|
19
web/locales/en/flow.ts
Normal file
19
web/locales/en/flow.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export const FlowEn = {
|
||||||
|
Upload_Data_Successfully: 'file uploaded successfully',
|
||||||
|
Upload_Data_Failed: 'file upload failed',
|
||||||
|
Upload_Data: 'Upload Data',
|
||||||
|
Code_Editor: 'Code Editor',
|
||||||
|
Open_Code_Editor: 'Open Code Editor',
|
||||||
|
Export_Flow_Success: 'Export flow success',
|
||||||
|
Import_Flow_Success: 'Import flow success',
|
||||||
|
Import: 'Import',
|
||||||
|
Export: 'Export',
|
||||||
|
Import_Flow: 'Import Flow',
|
||||||
|
Export_Flow: 'Export Flow',
|
||||||
|
Select_File: 'Select File',
|
||||||
|
Save_After_Import: 'Save after import',
|
||||||
|
Export_File_Type: 'File_Type',
|
||||||
|
Export_File_Format: 'File_Format',
|
||||||
|
Yes: 'Yes',
|
||||||
|
No: 'No',
|
||||||
|
};
|
@@ -1,8 +1,10 @@
|
|||||||
import { ChatEn } from './chat';
|
import { ChatEn } from "./chat";
|
||||||
import { CommonEn } from './common';
|
import { CommonEn } from "./common";
|
||||||
|
import { FlowEn } from "./flow";
|
||||||
|
|
||||||
const en = {
|
const en = {
|
||||||
...ChatEn,
|
...ChatEn,
|
||||||
|
...FlowEn,
|
||||||
...CommonEn,
|
...CommonEn,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ export const ChatZh: Resources['translation'] = {
|
|||||||
dialog_list: '对话列表',
|
dialog_list: '对话列表',
|
||||||
delete_chat: '删除会话',
|
delete_chat: '删除会话',
|
||||||
delete_chat_confirm: '您确认要删除会话吗?',
|
delete_chat_confirm: '您确认要删除会话吗?',
|
||||||
|
|
||||||
input_tips: '可以问我任何问题,shift + Enter 换行',
|
input_tips: '可以问我任何问题,shift + Enter 换行',
|
||||||
sent: '发送',
|
sent: '发送',
|
||||||
answer_again: '重新回答',
|
answer_again: '重新回答',
|
||||||
@@ -18,7 +17,6 @@ export const ChatZh: Resources['translation'] = {
|
|||||||
thinking: '正在思考中',
|
thinking: '正在思考中',
|
||||||
stop_replying: '停止回复',
|
stop_replying: '停止回复',
|
||||||
erase_memory: '清除记忆',
|
erase_memory: '清除记忆',
|
||||||
copy: '复制',
|
|
||||||
copy_success: '复制成功',
|
copy_success: '复制成功',
|
||||||
copy_failed: '复制失败',
|
copy_failed: '复制失败',
|
||||||
copy_nothing: '内容复制为空',
|
copy_nothing: '内容复制为空',
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { CommonEn } from '../en/common';
|
import { CommonEn } from "../en/common";
|
||||||
|
|
||||||
type I18nKeys = keyof typeof CommonEn;
|
type I18nKeys = keyof typeof CommonEn;
|
||||||
|
|
||||||
@@ -6,318 +6,324 @@ interface Resources {
|
|||||||
translation: Record<I18nKeys, string>;
|
translation: Record<I18nKeys, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CommonZh: Resources['translation'] = {
|
export const CommonZh: Resources["translation"] = {
|
||||||
Knowledge_Space: '知识库',
|
Knowledge_Space: "知识库",
|
||||||
space: '知识库',
|
space: "知识库",
|
||||||
Vector: '向量',
|
Vector: "向量",
|
||||||
Owner: '创建人',
|
Owner: "创建人",
|
||||||
Count: '文档数',
|
Count: "文档数",
|
||||||
File_type_Invalid: '文件类型错误',
|
File_type_Invalid: "文件类型错误",
|
||||||
Knowledge_Space_Config: '知识库配置',
|
Knowledge_Space_Config: "知识库配置",
|
||||||
Choose_a_Datasource_type: '知识库类型',
|
Choose_a_Datasource_type: "知识库类型",
|
||||||
Segmentation: '分片',
|
Segmentation: "分片",
|
||||||
No_parameter: '不需要配置分片参数',
|
No_parameter: "不需要配置分片参数",
|
||||||
Knowledge_Space_Name: '知识库名称',
|
Knowledge_Space_Name: "知识库名称",
|
||||||
Please_input_the_name: '请输入名称',
|
Please_input_the_name: "请输入名称",
|
||||||
Please_input_the_owner: '请输入创建人',
|
Please_input_the_owner: "请输入创建人",
|
||||||
Please_select_file: '请至少选择一个文件',
|
Please_select_file: "请至少选择一个文件",
|
||||||
Description: '描述',
|
Description: "描述",
|
||||||
Storage: '存储类型',
|
Storage: "存储类型",
|
||||||
Please_input_the_description: '请输入描述',
|
Domain: "领域类型",
|
||||||
Please_select_the_storage:'请选择存储类型',
|
Please_input_the_description: "请输入描述",
|
||||||
Please_select_the_domain_type: '请选择领域类型',
|
Please_select_the_storage: "请选择存储类型",
|
||||||
Next: '下一步',
|
Please_select_the_domain_type: "请选择领域类型",
|
||||||
the_name_can_only_contain: '名称只能包含数字、字母、中文字符、-或_',
|
Next: "下一步",
|
||||||
Text: '文本',
|
the_name_can_only_contain: "名称只能包含数字、字母、中文字符、-或_",
|
||||||
'Fill your raw text': '填写您的原始文本',
|
Text: "文本",
|
||||||
URL: '网址',
|
"Fill your raw text": "填写您的原始文本",
|
||||||
Fetch_the_content_of_a_URL: '获取 URL 的内容',
|
URL: "网址",
|
||||||
Document: '文档',
|
Fetch_the_content_of_a_URL: "获取 URL 的内容",
|
||||||
Upload_a_document: '上传文档,文档类型可以是PDF、CSV、Text、PowerPoint、Word、Markdown、Zip',
|
Document: "文档",
|
||||||
Name: '名称',
|
Upload_a_document:
|
||||||
Text_Source: '文本来源(可选)',
|
"上传文档,文档类型可以是PDF、CSV、Text、PowerPoint、Word、Markdown、Zip",
|
||||||
Please_input_the_text_source: '请输入文本来源',
|
Name: "名称",
|
||||||
Sync: '同步',
|
Text_Source: "文本来源(可选)",
|
||||||
Back: '上一步',
|
Please_input_the_text_source: "请输入文本来源",
|
||||||
Finish: '完成',
|
Sync: "同步",
|
||||||
Web_Page_URL: '网页网址',
|
Back: "上一步",
|
||||||
Please_input_the_Web_Page_URL: '请输入网页网址',
|
Finish: "完成",
|
||||||
Select_or_Drop_file: '选择或拖拽文件',
|
Web_Page_URL: "网页网址",
|
||||||
Documents: '文档',
|
Please_input_the_Web_Page_URL: "请输入网页网址",
|
||||||
Chat: '对话',
|
Select_or_Drop_file: "选择或拖拽文件",
|
||||||
Add_Datasource: '添加数据源',
|
Documents: "文档",
|
||||||
View_Graph: '查看图谱',
|
Chat: "对话",
|
||||||
Arguments: '参数',
|
Add_Datasource: "添加数据源",
|
||||||
Type: '类型',
|
View_Graph: "查看图谱",
|
||||||
Size: '切片',
|
Arguments: "参数",
|
||||||
Last_Sync: '上次同步时间',
|
Type: "类型",
|
||||||
Status: '状态',
|
Size: "切片",
|
||||||
Result: '结果',
|
Last_Sync: "上次同步时间",
|
||||||
Details: '明细',
|
Status: "状态",
|
||||||
Delete: '删除',
|
Result: "结果",
|
||||||
Operation: '操作',
|
Details: "明细",
|
||||||
Submit: '提交',
|
Delete: "删除",
|
||||||
close: '关闭',
|
Operation: "操作",
|
||||||
Chunks: '切片',
|
Submit: "提交",
|
||||||
Content: '内容',
|
close: "关闭",
|
||||||
Meta_Data: '元数据',
|
Chunks: "切片",
|
||||||
Please_select_a_file: '请上传一个文件',
|
Content: "内容",
|
||||||
Please_input_the_text: '请输入文本',
|
Meta_Data: "元数据",
|
||||||
Embedding: '嵌入',
|
Please_select_a_file: "请上传一个文件",
|
||||||
topk: 'TopK',
|
Please_input_the_text: "请输入文本",
|
||||||
the_top_k_vectors: '基于相似度得分的前 k 个向量',
|
Embedding: "嵌入",
|
||||||
recall_score: '召回分数',
|
topk: "TopK",
|
||||||
Set_a_threshold_score: '设置相似向量检索的阈值分数',
|
the_top_k_vectors: "基于相似度得分的前 k 个向量",
|
||||||
recall_type: '召回类型',
|
recall_score: "召回分数",
|
||||||
model: '模型',
|
Set_a_threshold_score: "设置相似向量检索的阈值分数",
|
||||||
A_model_used: '用于创建文本或其他数据的矢量表示的模型',
|
recall_type: "召回类型",
|
||||||
Automatic: '自动切片',
|
model: "模型",
|
||||||
Process: '切片处理',
|
A_model_used: "用于创建文本或其他数据的矢量表示的模型",
|
||||||
Automatic_desc: '自动设置分割和预处理规则。',
|
Automatic: "自动切片",
|
||||||
chunk_size: '块大小',
|
Process: "切片处理",
|
||||||
The_size_of_the_data_chunks: '处理中使用的数据块的大小',
|
Automatic_desc: "自动设置分割和预处理规则。",
|
||||||
chunk_overlap: '块重叠',
|
chunk_size: "块大小",
|
||||||
The_amount_of_overlap: '相邻数据块之间的重叠量',
|
The_size_of_the_data_chunks: "处理中使用的数据块的大小",
|
||||||
scene: '场景',
|
chunk_overlap: "块重叠",
|
||||||
A_contextual_parameter: '用于定义使用提示的设置或环境的上下文参数',
|
The_amount_of_overlap: "相邻数据块之间的重叠量",
|
||||||
template: '模板',
|
scene: "场景",
|
||||||
structure_or_format: '预定义的提示结构或格式,有助于确保人工智能系统生成与所需风格或语气一致的响应。',
|
A_contextual_parameter: "用于定义使用提示的设置或环境的上下文参数",
|
||||||
max_token: '最大令牌',
|
template: "模板",
|
||||||
max_iteration: '最大迭代',
|
structure_or_format:
|
||||||
concurrency_limit: '并发限制',
|
"预定义的提示结构或格式,有助于确保人工智能系统生成与所需风格或语气一致的响应。",
|
||||||
The_maximum_number_of_tokens: '提示中允许的最大标记或单词数',
|
max_token: "最大令牌",
|
||||||
Theme: '主题',
|
max_iteration: "最大迭代",
|
||||||
Port: '端口',
|
concurrency_limit: "并发限制",
|
||||||
Username: '用户名',
|
The_maximum_number_of_tokens: "提示中允许的最大标记或单词数",
|
||||||
Password: '密码',
|
Theme: "主题",
|
||||||
Remark: '备注',
|
Port: "端口",
|
||||||
Edit: '编辑',
|
Username: "用户名",
|
||||||
Database: '数据库',
|
Password: "密码",
|
||||||
Data_Source: '数据中心',
|
Remark: "备注",
|
||||||
Close_Sidebar: '收起',
|
Edit: "编辑",
|
||||||
Show_Sidebar: '展开',
|
Database: "数据库",
|
||||||
language: '语言',
|
Data_Source: "数据中心",
|
||||||
choose_model: '请选择一个模型',
|
Close_Sidebar: "收起",
|
||||||
data_center_desc: 'DB-GPT支持数据库交互和基于文档的对话,它还提供了一个用户友好的数据中心管理界面。',
|
Show_Sidebar: "展开",
|
||||||
create_database: '创建数据库',
|
language: "语言",
|
||||||
create_knowledge: '创建知识库',
|
choose_model: "请选择一个模型",
|
||||||
create_flow: '创建工作流',
|
data_center_desc:
|
||||||
path: '路径',
|
"DB-GPT支持数据库交互和基于文档的对话,它还提供了一个用户友好的数据中心管理界面。",
|
||||||
model_manage: '模型管理',
|
create_database: "创建数据库",
|
||||||
stop_model_success: '模型停止成功',
|
create_knowledge: "创建知识库",
|
||||||
create_model: '创建模型',
|
create_flow: "创建工作流",
|
||||||
model_select_tips: '请选择一个模型',
|
path: "路径",
|
||||||
submit: '提交',
|
model_manage: "模型管理",
|
||||||
start_model_success: '启动模型成功',
|
stop_model_success: "模型停止成功",
|
||||||
download_model_tip: '请先下载模型!',
|
create_model: "创建模型",
|
||||||
Plugins: '插件列表',
|
model_select_tips: "请选择一个模型",
|
||||||
try_again: '刷新重试',
|
submit: "提交",
|
||||||
no_data: '暂无数据',
|
start_model_success: "启动模型成功",
|
||||||
Prompt: '提示词',
|
download_model_tip: "请先下载模型!",
|
||||||
Open_Sidebar: '展开',
|
Plugins: "插件列表",
|
||||||
verify: '确认',
|
try_again: "刷新重试",
|
||||||
cancel: '取消',
|
no_data: "暂无数据",
|
||||||
Edit_Success: '编辑成功',
|
Prompt: "提示词",
|
||||||
Add: '新增',
|
Open_Sidebar: "展开",
|
||||||
Add_Success: '新增成功',
|
verify: "确认",
|
||||||
Error_Message: '出错了',
|
cancel: "取消",
|
||||||
Please_Input: '请输入',
|
Edit_Success: "编辑成功",
|
||||||
Prompt_Info_Scene: '场景',
|
Add: "新增",
|
||||||
Prompt_Info_Sub_Scene: '次级场景',
|
Add_Success: "新增成功",
|
||||||
Prompt_Info_Name: '名称',
|
Error_Message: "出错了",
|
||||||
Prompt_Info_Content: '内容',
|
Please_Input: "请输入",
|
||||||
Public: '公共',
|
Prompt_Info_Scene: "场景",
|
||||||
Private: '私有',
|
Prompt_Info_Sub_Scene: "次级场景",
|
||||||
Lowest: '渣渣',
|
Prompt_Info_Name: "名称",
|
||||||
Missed: '没理解',
|
Prompt_Info_Content: "内容",
|
||||||
Lost: '答不了',
|
Public: "公共",
|
||||||
Incorrect: '答错了',
|
Private: "私有",
|
||||||
Verbose: '较啰嗦',
|
Lowest: "渣渣",
|
||||||
Best: '真棒',
|
Missed: "没理解",
|
||||||
Rating: '评分',
|
Lost: "答不了",
|
||||||
Q_A_Category: '问答类别',
|
Incorrect: "答错了",
|
||||||
Q_A_Rating: '问答评分',
|
Verbose: "较啰嗦",
|
||||||
|
Best: "真棒",
|
||||||
|
Rating: "评分",
|
||||||
|
Q_A_Category: "问答类别",
|
||||||
|
Q_A_Rating: "问答评分",
|
||||||
feed_back_desc:
|
feed_back_desc:
|
||||||
'0: 无结果\n' +
|
"0: 无结果\n" +
|
||||||
'1: 有结果,但是在文不对题,没有理解问题\n' +
|
"1: 有结果,但是在文不对题,没有理解问题\n" +
|
||||||
'2: 有结果,理解了问题,但是提示回答不了这个问题\n' +
|
"2: 有结果,理解了问题,但是提示回答不了这个问题\n" +
|
||||||
'3: 有结果,理解了问题,并做出回答,但是回答的结果错误\n' +
|
"3: 有结果,理解了问题,并做出回答,但是回答的结果错误\n" +
|
||||||
'4: 有结果,理解了问题,回答结果正确,但是比较啰嗦,缺乏总结\n' +
|
"4: 有结果,理解了问题,回答结果正确,但是比较啰嗦,缺乏总结\n" +
|
||||||
'5: 有结果,理解了问题,回答结果正确,推理正确,并给出了总结,言简意赅\n',
|
"5: 有结果,理解了问题,回答结果正确,推理正确,并给出了总结,言简意赅\n",
|
||||||
input_count: '共计输入',
|
input_count: "共计输入",
|
||||||
input_unit: '字',
|
input_unit: "字",
|
||||||
Click_Select: '点击选择',
|
Click_Select: "点击选择",
|
||||||
Quick_Start: '快速开始',
|
Quick_Start: "快速开始",
|
||||||
Select_Plugins: '选择插件',
|
Select_Plugins: "选择插件",
|
||||||
Search: '搜索',
|
Search: "搜索",
|
||||||
Reset: '重置',
|
Reset: "重置",
|
||||||
Update_From_Github: '更新Github插件',
|
Update_From_Github: "更新Github插件",
|
||||||
Upload: '上传',
|
Upload: "上传",
|
||||||
Market_Plugins: '插件市场',
|
Market_Plugins: "插件市场",
|
||||||
My_Plugins: '我的插件',
|
My_Plugins: "我的插件",
|
||||||
Del_Knowledge_Tips: '你确定删除该知识库吗',
|
Del_Knowledge_Tips: "你确定删除该知识库吗",
|
||||||
Del_Document_Tips: '你确定删除该文档吗',
|
Del_Document_Tips: "你确定删除该文档吗",
|
||||||
Tips: '提示',
|
Tips: "提示",
|
||||||
Limit_Upload_File_Count_Tips: '一次只能上传一个文件',
|
Limit_Upload_File_Count_Tips: "一次只能上传一个文件",
|
||||||
To_Plugin_Market: '前往插件市场',
|
To_Plugin_Market: "前往插件市场",
|
||||||
Summary: '总结',
|
Summary: "总结",
|
||||||
stacked_column_chart: '堆叠柱状图',
|
stacked_column_chart: "堆叠柱状图",
|
||||||
column_chart: '柱状图',
|
column_chart: "柱状图",
|
||||||
percent_stacked_column_chart: '百分比堆叠柱状图',
|
percent_stacked_column_chart: "百分比堆叠柱状图",
|
||||||
grouped_column_chart: '簇形柱状图',
|
grouped_column_chart: "簇形柱状图",
|
||||||
time_column: '簇形柱状图',
|
time_column: "簇形柱状图",
|
||||||
pie_chart: '饼图',
|
pie_chart: "饼图",
|
||||||
line_chart: '折线图',
|
line_chart: "折线图",
|
||||||
area_chart: '面积图',
|
area_chart: "面积图",
|
||||||
stacked_area_chart: '堆叠面积图',
|
stacked_area_chart: "堆叠面积图",
|
||||||
scatter_plot: '散点图',
|
scatter_plot: "散点图",
|
||||||
bubble_chart: '气泡图',
|
bubble_chart: "气泡图",
|
||||||
stacked_bar_chart: '堆叠条形图',
|
stacked_bar_chart: "堆叠条形图",
|
||||||
bar_chart: '条形图',
|
bar_chart: "条形图",
|
||||||
percent_stacked_bar_chart: '百分比堆叠条形图',
|
percent_stacked_bar_chart: "百分比堆叠条形图",
|
||||||
grouped_bar_chart: '簇形条形图',
|
grouped_bar_chart: "簇形条形图",
|
||||||
water_fall_chart: '瀑布图',
|
water_fall_chart: "瀑布图",
|
||||||
table: '表格',
|
table: "表格",
|
||||||
multi_line_chart: '多折线图',
|
multi_line_chart: "多折线图",
|
||||||
multi_measure_column_chart: '多指标柱形图',
|
multi_measure_column_chart: "多指标柱形图",
|
||||||
multi_measure_line_chart: '多指标折线图',
|
multi_measure_line_chart: "多指标折线图",
|
||||||
Advices: '自动推荐',
|
Advices: "自动推荐",
|
||||||
Retry: '重试',
|
Retry: "重试",
|
||||||
Load_more: '加载更多',
|
Load_more: "加载更多",
|
||||||
new_chat: '创建会话',
|
new_chat: "创建会话",
|
||||||
choice_agent_tip: '请选择代理',
|
choice_agent_tip: "请选择代理",
|
||||||
no_context_tip: '请输入你的问题',
|
no_context_tip: "请输入你的问题",
|
||||||
Terminal: '终端',
|
Terminal: "终端",
|
||||||
used_apps: '最近使用',
|
used_apps: "最近使用",
|
||||||
app_in_mind: '没有心仪的应用?去',
|
app_in_mind: "没有心仪的应用?去",
|
||||||
explore: '探索广场',
|
explore: "探索广场",
|
||||||
Discover_more: '发现更多',
|
Discover_more: "发现更多",
|
||||||
sdk_insert: 'SDK接入',
|
sdk_insert: "SDK接入",
|
||||||
my_apps: '我的应用',
|
my_apps: "我的应用",
|
||||||
awel_flow: 'AWEL 工作流',
|
awel_flow: "AWEL 工作流",
|
||||||
save: '保存',
|
save: "保存",
|
||||||
add_node: '添加节点',
|
add_node: "添加节点",
|
||||||
no_node: '没有可编排节点',
|
no_node: "没有可编排节点",
|
||||||
connect_warning: '节点无法连接',
|
connect_warning: "节点无法连接",
|
||||||
flow_modal_title: '保存工作流',
|
flow_modal_title: "保存工作流",
|
||||||
flow_name: '工作流名称',
|
flow_name: "工作流名称",
|
||||||
flow_description: '工作流描述',
|
flow_description: "工作流描述",
|
||||||
flow_name_required: '请输入工作流名称',
|
flow_name_required: "请输入工作流名称",
|
||||||
flow_description_required: '请输入工作流描述',
|
flow_description_required: "请输入工作流描述",
|
||||||
save_flow_success: '保存工作流成功',
|
save_flow_success: "保存工作流成功",
|
||||||
delete_flow_confirm: '确定删除该工作流吗?',
|
delete_flow_confirm: "确定删除该工作流吗?",
|
||||||
related_nodes: '关联节点',
|
related_nodes: "关联节点",
|
||||||
language_select_tips: '请选择语言',
|
language_select_tips: "请选择语言",
|
||||||
add_resource: '添加资源',
|
add_resource: "添加资源",
|
||||||
team_modal: '工作模式',
|
team_modal: "工作模式",
|
||||||
App: '应用程序',
|
App: "应用程序",
|
||||||
resource: '资源',
|
resource: "资源",
|
||||||
resource_name: '资源名',
|
resource_name: "资源名",
|
||||||
resource_type: '资源类型',
|
resource_type: "资源类型",
|
||||||
resource_value: '参数',
|
resource_value: "参数",
|
||||||
resource_dynamic: '动态',
|
resource_dynamic: "动态",
|
||||||
Please_input_the_work_modal: '请选择工作模式',
|
Please_input_the_work_modal: "请选择工作模式",
|
||||||
available_resources: '可用资源',
|
available_resources: "可用资源",
|
||||||
edit_new_applications: '编辑新的应用',
|
edit_new_applications: "编辑新的应用",
|
||||||
collect: '收藏',
|
collect: "收藏",
|
||||||
collected: '已收藏',
|
collected: "已收藏",
|
||||||
create: '创建',
|
create: "创建",
|
||||||
Agents: '智能体',
|
Agents: "智能体",
|
||||||
edit_application: '编辑应用',
|
edit_application: "编辑应用",
|
||||||
add_application: '添加应用',
|
add_application: "添加应用",
|
||||||
app_name: '应用名称',
|
app_name: "应用名称",
|
||||||
input_app_name: '请输入应用名称',
|
input_app_name: "请输入应用名称",
|
||||||
LLM_strategy: '模型策略',
|
LLM_strategy: "模型策略",
|
||||||
please_select_LLM_strategy: '请选择模型策略',
|
please_select_LLM_strategy: "请选择模型策略",
|
||||||
LLM_strategy_value: '模型策略参数',
|
LLM_strategy_value: "模型策略参数",
|
||||||
please_select_LLM_strategy_value: '请选择模型策略参数',
|
please_select_LLM_strategy_value: "请选择模型策略参数",
|
||||||
operators: '算子',
|
operators: "算子",
|
||||||
Chinese: '中文',
|
Chinese: "中文",
|
||||||
English: '英文',
|
English: "英文",
|
||||||
docs: '文档',
|
docs: "文档",
|
||||||
apps: '全部',
|
apps: "全部",
|
||||||
please_enter_the_keywords: '请输入关键词',
|
please_enter_the_keywords: "请输入关键词",
|
||||||
input_tip: '请选择模型,输入描述快速开始',
|
input_tip: "请选择模型,输入描述快速开始",
|
||||||
create_app: '创建应用',
|
create_app: "创建应用",
|
||||||
copy_url: '单击复制分享链接',
|
copy_url: "单击复制分享链接",
|
||||||
double_click_open: '双击钉钉打开',
|
double_click_open: "双击钉钉打开",
|
||||||
construct: '应用管理',
|
construct: "应用管理",
|
||||||
chat_online: '在线对话',
|
chat_online: "在线对话",
|
||||||
recommend_apps: '热门推荐',
|
recommend_apps: "热门推荐",
|
||||||
all_apps: '全部应用',
|
all_apps: "全部应用",
|
||||||
latest_apps: '最新应用',
|
latest_apps: "最新应用",
|
||||||
my_collected_apps: '我的收藏',
|
my_collected_apps: "我的收藏",
|
||||||
collect_success: '收藏成功',
|
collect_success: "收藏成功",
|
||||||
cancel_success: '取消成功',
|
cancel_success: "取消成功",
|
||||||
published: '已发布',
|
published: "已发布",
|
||||||
unpublished: '未发布',
|
unpublished: "未发布",
|
||||||
start_chat: '开始对话',
|
start_chat: "开始对话",
|
||||||
native_app: '原生应用',
|
native_app: "原生应用",
|
||||||
native_type: '应用类型',
|
native_type: "应用类型",
|
||||||
temperature: '温度',
|
temperature: "温度",
|
||||||
update: '更新',
|
update: "更新",
|
||||||
refreshSuccess: '刷新成功',
|
refreshSuccess: "刷新成功",
|
||||||
Download: '下载',
|
Download: "下载",
|
||||||
app_type_select: '请选择应用类型',
|
app_type_select: "请选择应用类型",
|
||||||
please_select_param: '请选择参数',
|
please_select_param: "请选择参数",
|
||||||
please_select_model: '请选择模型',
|
please_select_model: "请选择模型",
|
||||||
please_input_temperature: '请输入temperature值',
|
please_input_temperature: "请输入temperature值",
|
||||||
select_workflow: '选择工作流',
|
select_workflow: "选择工作流",
|
||||||
please_select_workflow: '请选择工作流',
|
please_select_workflow: "请选择工作流",
|
||||||
recommended_questions: '推荐问题',
|
recommended_questions: "推荐问题",
|
||||||
question: '问题',
|
question: "问题",
|
||||||
please_input_recommended_questions: '请输入推荐问题',
|
please_input_recommended_questions: "请输入推荐问题",
|
||||||
is_effective: '是否生效',
|
is_effective: "是否生效",
|
||||||
add_question: '添加问题',
|
add_question: "添加问题",
|
||||||
update_success: '更新成功',
|
update_success: "更新成功",
|
||||||
update_failed: '更新失败',
|
update_failed: "更新失败",
|
||||||
please_select_prompt: '请选择一个提示词',
|
please_select_prompt: "请选择一个提示词",
|
||||||
details: '详情',
|
details: "详情",
|
||||||
choose: '选择',
|
choose: "选择",
|
||||||
please_choose: '请先选择',
|
please_choose: "请先选择",
|
||||||
want_delete: "你确定要删除吗?",
|
want_delete: "你确定要删除吗?",
|
||||||
success: '成功',
|
success: "成功",
|
||||||
input_parameter: '输入参数',
|
input_parameter: "输入参数",
|
||||||
output_structure: '输出结构',
|
output_structure: "输出结构",
|
||||||
User_input: '用户输入',
|
User_input: "用户输入",
|
||||||
LLM_test: 'LLM测试',
|
LLM_test: "LLM测试",
|
||||||
Output_verification: '输出验证',
|
Output_verification: "输出验证",
|
||||||
select_scene: '请选择场景',
|
select_scene: "请选择场景",
|
||||||
select_type: '请选择类型',
|
select_type: "请选择类型",
|
||||||
Please_complete_the_input_parameters: '请填写完整的输入参数',
|
Please_complete_the_input_parameters: "请填写完整的输入参数",
|
||||||
Please_fill_in_the_user_input: '请填写用户输入内容',
|
Please_fill_in_the_user_input: "请填写用户输入内容",
|
||||||
help: '我可以帮您:',
|
help: "我可以帮您:",
|
||||||
Refresh_status: '刷新状态',
|
Refresh_status: "刷新状态",
|
||||||
Recall_test: '召回测试',
|
Recall_test: "召回测试",
|
||||||
synchronization: '一键同步',
|
synchronization: "一键同步",
|
||||||
Synchronization_initiated: '同步已发起,请稍后',
|
Synchronization_initiated: "同步已发起,请稍后",
|
||||||
Edit_document: '编辑文档',
|
Edit_document: "编辑文档",
|
||||||
Document_name: '文档名',
|
Document_name: "文档名",
|
||||||
Correlation_problem: '关联问题',
|
Correlation_problem: "关联问题",
|
||||||
Add_problem: '添加问题',
|
Add_problem: "添加问题",
|
||||||
New_knowledge_base: '新增知识库',
|
New_knowledge_base: "新增知识库",
|
||||||
yuque: '语雀文档',
|
yuque: "语雀文档",
|
||||||
Get_yuque_document: '获取语雀文档的内容',
|
Get_yuque_document: "获取语雀文档的内容",
|
||||||
document_url: '文档地址',
|
document_url: "文档地址",
|
||||||
input_document_url: '请输入文档地址',
|
input_document_url: "请输入文档地址",
|
||||||
Get_token: '请先获取团队知识库token,token获取',
|
Get_token: "请先获取团队知识库token,token获取",
|
||||||
Reference_link: '参考链接',
|
Reference_link: "参考链接",
|
||||||
document_token: '文档token',
|
document_token: "文档token",
|
||||||
input_document_token: '请输入文档token',
|
input_document_token: "请输入文档token",
|
||||||
input_question: '请输入问题',
|
input_question: "请输入问题",
|
||||||
detail: '详情',
|
detail: "详情",
|
||||||
Manual_entry: '手动录入',
|
Manual_entry: "手动录入",
|
||||||
Data_content: '数据内容',
|
Data_content: "数据内容",
|
||||||
Main_content: '主要内容',
|
Main_content: "主要内容",
|
||||||
Auxiliary_data: '辅助数据',
|
Auxiliary_data: "辅助数据",
|
||||||
enter_question_first: '请先输入问题',
|
enter_question_first: "请先输入问题",
|
||||||
unpublish: '取消发布',
|
unpublish: "取消发布",
|
||||||
publish: '发布应用',
|
publish: "发布应用",
|
||||||
Update_successfully: '更新成功',
|
Update_successfully: "更新成功",
|
||||||
Create_successfully: '创建成功',
|
Create_successfully: "创建成功",
|
||||||
Update_failure: '更新失败',
|
Update_failure: "更新失败",
|
||||||
Create_failure: '创建失败',
|
Create_failure: "创建失败",
|
||||||
View_details: '查看详情',
|
View_details: "查看详情",
|
||||||
All: '全部',
|
All: "全部",
|
||||||
Please_input_prompt_name: '请输入prompt名称',
|
Please_input_prompt_name: "请输入prompt名称",
|
||||||
|
Copy_Btn: '复制',
|
||||||
|
Delete_Btn: '删除'
|
||||||
} as const;
|
} as const;
|
||||||
|
21
web/locales/zh/flow.ts
Normal file
21
web/locales/zh/flow.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Select } from 'antd';
|
||||||
|
|
||||||
|
export const FlowZn = {
|
||||||
|
Upload_Data_Successfully: '文件上传成功',
|
||||||
|
Upload_Data_Failed: '文件上传失败',
|
||||||
|
Upload_Data: '上传数据',
|
||||||
|
Code_Editor: '代码编辑器',
|
||||||
|
Open_Code_Editor: '打开代码编辑器',
|
||||||
|
Export_Flow_Success: '导出工作流成功',
|
||||||
|
Import_Flow_Success: '导入工作流成功',
|
||||||
|
Import: '导入',
|
||||||
|
Export: '导出',
|
||||||
|
Import_Flow: '导入工作流',
|
||||||
|
Export_Flow: '导出工作流',
|
||||||
|
Select_File: '选择文件',
|
||||||
|
Save_After_Import: '导入后保存',
|
||||||
|
Export_File_Type: '文件类型',
|
||||||
|
Export_File_Format: '文件格式',
|
||||||
|
Yes: '是',
|
||||||
|
No: '否',
|
||||||
|
};
|
@@ -1,9 +1,11 @@
|
|||||||
import { CommonZh } from './common';
|
import { CommonZh } from "./common";
|
||||||
import { ChatZh } from './chat';
|
import { ChatZh } from "./chat";
|
||||||
|
import { FlowZn } from "./flow";
|
||||||
|
|
||||||
const zh = {
|
const zh = {
|
||||||
...CommonZh,
|
|
||||||
...ChatZh,
|
...ChatZh,
|
||||||
|
...FlowZn,
|
||||||
|
...CommonZh,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default zh;
|
export default zh;
|
||||||
|
@@ -28,7 +28,6 @@ const zeroWidthTriggerDefaultStyle: React.CSSProperties = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* 会话项
|
* 会话项
|
||||||
*/
|
*/
|
||||||
const MenuItem: React.FC<{ item: any; refresh?: any; order: React.MutableRefObject<number>; historyLoading?: boolean }> = ({
|
const MenuItem: React.FC<{ item: any; refresh?: any; order: React.MutableRefObject<number>; historyLoading?: boolean }> = ({
|
||||||
@@ -41,7 +40,7 @@ const MenuItem: React.FC<{ item: any; refresh?: any; order: React.MutableRefObje
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const chatId = searchParams?.get('id') ?? '';
|
const chatId = searchParams?.get('id') ?? '';
|
||||||
const scene = searchParams?.get('scene') ?? '';
|
const scene = searchParams?.get('scene') ?? '';
|
||||||
|
|
||||||
const { setCurrentDialogInfo } = useContext(ChatContext);
|
const { setCurrentDialogInfo } = useContext(ChatContext);
|
||||||
|
|
||||||
|
@@ -38,7 +38,7 @@
|
|||||||
"cytoscape-euler": "^1.2.2",
|
"cytoscape-euler": "^1.2.2",
|
||||||
"framer-motion": "^10.16.4",
|
"framer-motion": "^10.16.4",
|
||||||
"google-auth-library": "^9.2.0",
|
"google-auth-library": "^9.2.0",
|
||||||
"google-one-tap": "^1.0.6",
|
"google-one-tap": "^1.0.6",
|
||||||
"i18next": "^23.4.5",
|
"i18next": "^23.4.5",
|
||||||
"iron-session": "^6.3.1",
|
"iron-session": "^6.3.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
import { ChatContext, ChatContextProvider } from '@/app/chat-context';
|
import { ChatContext, ChatContextProvider } from '@/app/chat-context';
|
||||||
import { addUser, apiInterceptors } from '@/client/api';
|
|
||||||
import SideBar from '@/components/layout/side-bar';
|
import SideBar from '@/components/layout/side-bar';
|
||||||
import TopProgressBar from '@/components/layout/top-progress-bar';
|
import TopProgressBar from '@/components/layout/top-progress-bar';
|
||||||
import { STORAGE_LANG_KEY, STORAGE_USERINFO_KEY, STORAGE_USERINFO_VALID_TIME_KEY } from '@/utils/constants/index';
|
import {
|
||||||
|
STORAGE_LANG_KEY,
|
||||||
|
STORAGE_USERINFO_KEY,
|
||||||
|
STORAGE_USERINFO_VALID_TIME_KEY,
|
||||||
|
} from '@/utils/constants/index';
|
||||||
import { App, ConfigProvider, MappingAlgorithm, theme } from 'antd';
|
import { App, ConfigProvider, MappingAlgorithm, theme } from 'antd';
|
||||||
import enUS from 'antd/locale/en_US';
|
import enUS from 'antd/locale/en_US';
|
||||||
import zhCN from 'antd/locale/zh_CN';
|
import zhCN from 'antd/locale/zh_CN';
|
||||||
@@ -43,7 +46,9 @@ function CssWrapper({ children }: { children: React.ReactElement }) {
|
|||||||
}, [mode]);
|
}, [mode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
i18n.changeLanguage && i18n.changeLanguage(window.localStorage.getItem(STORAGE_LANG_KEY) || 'zh');
|
i18n.changeLanguage?.(
|
||||||
|
window.localStorage.getItem(STORAGE_LANG_KEY) || 'zh'
|
||||||
|
);
|
||||||
}, [i18n]);
|
}, [i18n]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -61,7 +66,6 @@ function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
// 登录检测
|
// 登录检测
|
||||||
const handleAuth = async () => {
|
const handleAuth = async () => {
|
||||||
setIsLogin(false);
|
setIsLogin(false);
|
||||||
@@ -74,14 +78,17 @@ function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
|||||||
var user_not_login_url = process.env.LOGIN_URL;
|
var user_not_login_url = process.env.LOGIN_URL;
|
||||||
// MOCK User info
|
// MOCK User info
|
||||||
var user = {
|
var user = {
|
||||||
user_channel: `dbgpt`,
|
user_channel: `dbgpt`,
|
||||||
user_no: `001`,
|
user_no: `001`,
|
||||||
nick_name: `dbgpt`,
|
nick_name: `dbgpt`,
|
||||||
}
|
};
|
||||||
if (user) {
|
if (user) {
|
||||||
localStorage.setItem(STORAGE_USERINFO_KEY, JSON.stringify(user));
|
localStorage.setItem(STORAGE_USERINFO_KEY, JSON.stringify(user));
|
||||||
localStorage.setItem(STORAGE_USERINFO_VALID_TIME_KEY, Date.now().toString());
|
localStorage.setItem(
|
||||||
setIsLogin(true);
|
STORAGE_USERINFO_VALID_TIME_KEY,
|
||||||
|
Date.now().toString()
|
||||||
|
);
|
||||||
|
setIsLogin(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,16 +105,28 @@ function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
|||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="flex w-screen h-screen overflow-hidden">
|
<div className='flex w-screen h-screen overflow-hidden'>
|
||||||
<Head>
|
<Head>
|
||||||
<meta name="viewport" content="initial-scale=1.0, width=device-width, maximum-scale-1" />
|
<meta
|
||||||
|
name='viewport'
|
||||||
|
content='initial-scale=1.0, width=device-width, maximum-scale=1'
|
||||||
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
{router.pathname !== '/construct/app/extra' && (
|
{router.pathname !== '/construct/app/extra' && (
|
||||||
<div className={classNames('transition-[width]', isMenuExpand ? 'w-60' : 'w-20', 'hidden', 'md:block')}>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'transition-[width]',
|
||||||
|
isMenuExpand ? 'w-60' : 'w-20',
|
||||||
|
'hidden',
|
||||||
|
'md:block'
|
||||||
|
)}
|
||||||
|
>
|
||||||
<SideBar />
|
<SideBar />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col flex-1 relative overflow-hidden">{children}</div>
|
<div className='flex flex-col flex-1 relative overflow-hidden'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
<FloatHelper />
|
<FloatHelper />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
import MarketPlugins from '@/components/agent/market-plugins';
|
|
||||||
import MyPlugins from '@/components/agent/my-plugins';
|
|
||||||
import { Tabs } from 'antd';
|
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
function Agent() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [activeKey, setActiveKey] = useState('market');
|
|
||||||
|
|
||||||
const items: Required<Parameters<typeof Tabs>[0]['items']> = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
key: 'market',
|
|
||||||
label: t('Market_Plugins'),
|
|
||||||
children: <MarketPlugins />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'my',
|
|
||||||
label: t('My_Plugins'),
|
|
||||||
children: activeKey === 'market' ? null : <MyPlugins />,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[t, activeKey],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-screen p-4 md:p-6 overflow-y-auto">
|
|
||||||
<Tabs activeKey={activeKey} items={items} onChange={setActiveKey} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Agent;
|
|
@@ -1,114 +0,0 @@
|
|||||||
import AppModal from '@/components/app/app-modal';
|
|
||||||
import AppCard from '@/components/app/app-card';
|
|
||||||
import { Button, Spin, Tabs, TabsProps } from 'antd';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { apiInterceptors, getAppList } from '@/client/api';
|
|
||||||
import { IApp } from '@/types/app';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import MyEmpty from '@/components/common/MyEmpty';
|
|
||||||
|
|
||||||
type TabKey = 'app' | 'collected';
|
|
||||||
|
|
||||||
type ModalType = 'edit' | 'add';
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
|
||||||
const [spinning, setSpinning] = useState<boolean>(false);
|
|
||||||
const [activeKey, setActiveKey] = useState<TabKey>('app');
|
|
||||||
const [apps, setApps] = useState<IApp[]>([]);
|
|
||||||
const [curApp, setCurApp] = useState<IApp>();
|
|
||||||
const [modalType, setModalType] = useState<ModalType>('add');
|
|
||||||
|
|
||||||
const handleCreate = () => {
|
|
||||||
setModalType('add');
|
|
||||||
setOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
setOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEdit = (app: any) => {
|
|
||||||
setModalType('edit');
|
|
||||||
setCurApp(app);
|
|
||||||
setOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTabChange = (activeKey: string) => {
|
|
||||||
setActiveKey(activeKey as TabKey);
|
|
||||||
if (activeKey === 'collected') {
|
|
||||||
initData({ is_collected: true });
|
|
||||||
} else {
|
|
||||||
initData();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const initData = async (params = {}) => {
|
|
||||||
setSpinning(true);
|
|
||||||
const [error, data] = await apiInterceptors(getAppList(params));
|
|
||||||
if (error) {
|
|
||||||
setSpinning(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
setApps(data.app_list || []);
|
|
||||||
setSpinning(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
initData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const renderAppList = (data: { isCollected: boolean }) => {
|
|
||||||
const isNull = data.isCollected ? apps.every((item) => !item.is_collected) : apps.length === 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{!data.isCollected && (
|
|
||||||
<Button onClick={handleCreate} type="primary" className="mb-4" icon={<PlusOutlined />}>
|
|
||||||
{t('create')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!isNull ? (
|
|
||||||
<div className=" w-full flex flex-wrap pb-0 gap-4">
|
|
||||||
{apps.map((app, index) => {
|
|
||||||
return <AppCard handleEdit={handleEdit} key={index} app={app} updateApps={initData} isCollected={activeKey === 'collected'} />;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<MyEmpty />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const items: TabsProps['items'] = [
|
|
||||||
{
|
|
||||||
key: 'app',
|
|
||||||
label: t('App'),
|
|
||||||
children: renderAppList({ isCollected: false }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'collected',
|
|
||||||
label: t('collected'),
|
|
||||||
children: renderAppList({ isCollected: true }),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Spin spinning={spinning}>
|
|
||||||
<div className="h-screen w-full p-4 md:p-6 overflow-y-auto">
|
|
||||||
<Tabs defaultActiveKey="app" items={items} onChange={handleTabChange} />
|
|
||||||
{open && (
|
|
||||||
<AppModal app={modalType === 'edit' ? curApp : {}} type={modalType} updateApps={initData} open={open} handleCancel={handleCancel} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Spin>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -174,7 +174,7 @@ function Database() {
|
|||||||
onDelete(item);
|
onDelete(item);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
删除
|
{t('Delete_Btn')}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@@ -1,20 +1,48 @@
|
|||||||
import { addFlow, apiInterceptors, getFlowById, updateFlowById } from '@/client/api';
|
import { apiInterceptors, getFlowById } from '@/client/api';
|
||||||
import MuiLoading from '@/components/common/loading';
|
import MuiLoading from '@/components/common/loading';
|
||||||
import AddNodes from '@/components/flow/add-nodes';
|
import AddNodes from '@/components/flow/add-nodes';
|
||||||
|
import AddNodesSider from '@/components/flow/add-nodes-sider';
|
||||||
import ButtonEdge from '@/components/flow/button-edge';
|
import ButtonEdge from '@/components/flow/button-edge';
|
||||||
import CanvasNode from '@/components/flow/canvas-node';
|
import CanvasNode from '@/components/flow/canvas-node';
|
||||||
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
|
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
|
||||||
import { checkFlowDataRequied, getUniqueNodeId, mapHumpToUnderline, mapUnderlineToHump } from '@/utils/flow';
|
import {
|
||||||
import { FrownOutlined, SaveOutlined } from '@ant-design/icons';
|
checkFlowDataRequied,
|
||||||
import { App, Button, Checkbox, Divider, Form, Input, Modal, Space, message, notification } from 'antd';
|
getUniqueNodeId,
|
||||||
|
mapUnderlineToHump,
|
||||||
|
} from '@/utils/flow';
|
||||||
|
import {
|
||||||
|
ExportOutlined,
|
||||||
|
FrownOutlined,
|
||||||
|
ImportOutlined,
|
||||||
|
SaveOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Divider, Space, Tooltip, message, notification } from 'antd';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import React, { DragEvent, useCallback, useEffect, useRef, useState } from 'react';
|
import React, {
|
||||||
|
DragEvent,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import ReactFlow, { Background, Connection, Controls, ReactFlowProvider, addEdge, useEdgesState, useNodesState, useReactFlow, Node } from 'reactflow';
|
import ReactFlow, {
|
||||||
|
Background,
|
||||||
|
Connection,
|
||||||
|
Controls,
|
||||||
|
ReactFlowProvider,
|
||||||
|
addEdge,
|
||||||
|
useEdgesState,
|
||||||
|
useNodesState,
|
||||||
|
useReactFlow,
|
||||||
|
Node,
|
||||||
|
} from 'reactflow';
|
||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import { useRouter } from 'next/router';
|
import {
|
||||||
|
SaveFlowModal,
|
||||||
const { TextArea } = Input;
|
ExportFlowModal,
|
||||||
|
ImportFlowModal,
|
||||||
|
} from '@/components/flow/canvas-modal';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// Define your component props here
|
// Define your component props here
|
||||||
@@ -24,9 +52,7 @@ const edgeTypes = { buttonedge: ButtonEdge };
|
|||||||
|
|
||||||
const Canvas: React.FC<Props> = () => {
|
const Canvas: React.FC<Props> = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { message } = App.useApp();
|
|
||||||
const { replace } = useRouter();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const id = searchParams?.get('id') || '';
|
const id = searchParams?.get('id') || '';
|
||||||
const reactFlow = useReactFlow();
|
const reactFlow = useReactFlow();
|
||||||
@@ -35,8 +61,10 @@ const Canvas: React.FC<Props> = () => {
|
|||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
|
||||||
const [flowInfo, setFlowInfo] = useState<IFlowUpdateParam>();
|
const [flowInfo, setFlowInfo] = useState<IFlowUpdateParam>();
|
||||||
|
const [isSaveFlowModalOpen, setIsSaveFlowModalOpen] = useState(false);
|
||||||
|
const [isExportFlowModalOpen, setIsExportFlowModalOpen] = useState(false);
|
||||||
|
const [isImportModalOpen, setIsImportFlowModalOpen] = useState(false);
|
||||||
|
|
||||||
async function getFlowData() {
|
async function getFlowData() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -81,7 +109,7 @@ const Canvas: React.FC<Props> = () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,13 +126,18 @@ const Canvas: React.FC<Props> = () => {
|
|||||||
(event: DragEvent) => {
|
(event: DragEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const reactFlowBounds = reactFlowWrapper.current!.getBoundingClientRect();
|
const reactFlowBounds = reactFlowWrapper.current!.getBoundingClientRect();
|
||||||
|
const sidebarWidth = (
|
||||||
|
document.getElementsByClassName('ant-layout-sider')?.[0] as HTMLElement
|
||||||
|
)?.offsetWidth; // get sidebar width
|
||||||
|
|
||||||
let nodeStr = event.dataTransfer.getData('application/reactflow');
|
let nodeStr = event.dataTransfer.getData('application/reactflow');
|
||||||
if (!nodeStr || typeof nodeStr === 'undefined') {
|
if (!nodeStr || typeof nodeStr === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeData = JSON.parse(nodeStr);
|
const nodeData = JSON.parse(nodeStr);
|
||||||
const position = reactFlow.screenToFlowPosition({
|
const position = reactFlow.screenToFlowPosition({
|
||||||
x: event.clientX - reactFlowBounds.left,
|
x: event.clientX - reactFlowBounds.left + sidebarWidth,
|
||||||
y: event.clientY - reactFlowBounds.top,
|
y: event.clientY - reactFlowBounds.top,
|
||||||
});
|
});
|
||||||
const nodeId = getUniqueNodeId(nodeData, reactFlow.getNodes());
|
const nodeId = getUniqueNodeId(nodeData, reactFlow.getNodes());
|
||||||
@@ -129,10 +162,10 @@ const Canvas: React.FC<Props> = () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[reactFlow],
|
[reactFlow]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDragOver = useCallback((event: DragEvent) => {
|
const onDragOver = useCallback((event: DragEvent) => {
|
||||||
@@ -140,18 +173,7 @@ const Canvas: React.FC<Props> = () => {
|
|||||||
event.dataTransfer.dropEffect = 'move';
|
event.dataTransfer.dropEffect = 'move';
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function labelChange(e: React.ChangeEvent<HTMLInputElement>) {
|
function onSave() {
|
||||||
const label = e.target.value;
|
|
||||||
// replace spaces with underscores, convert uppercase letters to lowercase, remove characters other than digits, letters, _, and -.
|
|
||||||
let result = label
|
|
||||||
.replace(/\s+/g, '_')
|
|
||||||
.replace(/[^a-z0-9_-]/g, '')
|
|
||||||
.toLowerCase();
|
|
||||||
result = result;
|
|
||||||
form.setFieldsValue({ name: result });
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickSave() {
|
|
||||||
const flowData = reactFlow.toObject() as IFlowData;
|
const flowData = reactFlow.toObject() as IFlowData;
|
||||||
const [check, node, message] = checkFlowDataRequied(flowData);
|
const [check, node, message] = checkFlowDataRequied(flowData);
|
||||||
if (!check && message) {
|
if (!check && message) {
|
||||||
@@ -169,139 +191,116 @@ const Canvas: React.FC<Props> = () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
return notification.error({ message: 'Error', description: message, icon: <FrownOutlined className="text-red-600" /> });
|
return notification.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: message,
|
||||||
|
icon: <FrownOutlined className='text-red-600' />,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setIsModalVisible(true);
|
setIsSaveFlowModalOpen(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSaveFlow() {
|
function onExport() {
|
||||||
const { name, label, description = '', editable = false, deploy = false } = form.getFieldsValue();
|
setIsExportFlowModalOpen(true);
|
||||||
const reactFlowObject = mapHumpToUnderline(reactFlow.toObject() as IFlowData);
|
|
||||||
if (id) {
|
|
||||||
const [, , res] = await apiInterceptors(updateFlowById(id, {
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
description,
|
|
||||||
editable,
|
|
||||||
uid: id,
|
|
||||||
flow_data: reactFlowObject,
|
|
||||||
state: deploy ? 'deployed' : 'developing',
|
|
||||||
}));
|
|
||||||
setIsModalVisible(false);
|
|
||||||
if (res?.success) {
|
|
||||||
message.success('编辑成功');
|
|
||||||
replace('/construct/flow');
|
|
||||||
setIsModalVisible(false);
|
|
||||||
} else if (res?.err_msg) {
|
|
||||||
message.error(res?.err_msg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const [_, res] = await apiInterceptors(addFlow({ name, label, description, editable, flow_data: reactFlowObject, state: deploy ? 'deployed' : 'developing' }));
|
|
||||||
setIsModalVisible(false);
|
|
||||||
replace('/construct/flow');
|
|
||||||
message.success('创建成功');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onImport() {
|
||||||
|
setIsImportFlowModalOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getButtonList = () => {
|
||||||
|
const buttonList = [
|
||||||
|
{
|
||||||
|
title: t('Import'),
|
||||||
|
icon: <ImportOutlined className='block text-xl' onClick={onImport} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('save'),
|
||||||
|
icon: <SaveOutlined className='block text-xl' onClick={onSave} />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (id !== '') {
|
||||||
|
buttonList.unshift({
|
||||||
|
title: t('Export'),
|
||||||
|
icon: <ExportOutlined className='block text-xl' onClick={onExport} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttonList;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MuiLoading visible={loading} />
|
<div className='flex flex-row'>
|
||||||
<div className="my-2 mx-4 flex flex-row justify-end items-center">
|
<AddNodesSider />
|
||||||
<div className="w-8 h-8 rounded-md bg-stone-300 dark:bg-zinc-700 dark:text-zinc-200 flext justify-center items-center hover:text-blue-500 dark:hover:text-zinc-100">
|
|
||||||
<SaveOutlined className="block text-xl" onClick={clickSave} />
|
<div className='flex flex-col flex-1'>
|
||||||
|
<Space className='my-2 mx-4 flex flex-row justify-end'>
|
||||||
|
{getButtonList().map(({ title, icon }) => (
|
||||||
|
<Tooltip
|
||||||
|
key={title}
|
||||||
|
title={title}
|
||||||
|
className='w-8 h-8 rounded-md bg-stone-300 dark:bg-zinc-700 dark:text-zinc-200 hover:text-blue-500 dark:hover:text-zinc-100'
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Divider className='mt-0 mb-0' />
|
||||||
|
|
||||||
|
<div className='h-[calc(100vh-48px)] w-full' ref={reactFlowWrapper}>
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
nodeTypes={nodeTypes}
|
||||||
|
edgeTypes={edgeTypes}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
onNodeClick={onNodesClick}
|
||||||
|
onConnect={onConnect}
|
||||||
|
onDrop={onDrop}
|
||||||
|
onDragOver={onDragOver}
|
||||||
|
minZoom={0.1}
|
||||||
|
fitView
|
||||||
|
deleteKeyCode={['Backspace', 'Delete']}
|
||||||
|
>
|
||||||
|
<Controls
|
||||||
|
className='flex flex-row items-center'
|
||||||
|
position='bottom-center'
|
||||||
|
/>
|
||||||
|
<Background color='#aaa' gap={16} />
|
||||||
|
{/* <AddNodes /> */}
|
||||||
|
</ReactFlow>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Divider className="mt-0 mb-0" />
|
|
||||||
<div className="h-[calc(100vh-60px)] w-full" ref={reactFlowWrapper}>
|
<MuiLoading visible={loading} />
|
||||||
<ReactFlow
|
|
||||||
nodes={nodes}
|
<SaveFlowModal
|
||||||
edges={edges}
|
reactFlow={reactFlow}
|
||||||
nodeTypes={nodeTypes}
|
flowInfo={flowInfo}
|
||||||
edgeTypes={edgeTypes}
|
isSaveFlowModalOpen={isSaveFlowModalOpen}
|
||||||
onNodesChange={onNodesChange}
|
setIsSaveFlowModalOpen={setIsSaveFlowModalOpen}
|
||||||
onEdgesChange={onEdgesChange}
|
/>
|
||||||
onNodeClick={onNodesClick}
|
|
||||||
onConnect={onConnect}
|
<ExportFlowModal
|
||||||
onDrop={onDrop}
|
reactFlow={reactFlow}
|
||||||
onDragOver={onDragOver}
|
flowInfo={flowInfo}
|
||||||
minZoom={0.1}
|
isExportFlowModalOpen={isExportFlowModalOpen}
|
||||||
fitView
|
setIsExportFlowModalOpen={setIsExportFlowModalOpen}
|
||||||
deleteKeyCode={['Backspace', 'Delete']}
|
/>
|
||||||
>
|
|
||||||
<Controls className="flex flex-row items-center" position="bottom-center" />
|
<ImportFlowModal
|
||||||
<Background color="#aaa" gap={16} />
|
setNodes={setNodes}
|
||||||
<AddNodes />
|
setEdges={setEdges}
|
||||||
</ReactFlow>
|
isImportModalOpen={isImportModalOpen}
|
||||||
</div>
|
setIsImportFlowModalOpen={setIsImportFlowModalOpen}
|
||||||
<Modal
|
/>
|
||||||
title={t('flow_modal_title')}
|
|
||||||
open={isModalVisible}
|
|
||||||
onCancel={() => {
|
|
||||||
setIsModalVisible(false);
|
|
||||||
}}
|
|
||||||
cancelButtonProps={{ className: 'hidden' }}
|
|
||||||
okButtonProps={{ className: 'hidden' }}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
name="flow_form"
|
|
||||||
form={form}
|
|
||||||
labelCol={{ span: 8 }}
|
|
||||||
wrapperCol={{ span: 16 }}
|
|
||||||
style={{ maxWidth: 600 }}
|
|
||||||
initialValues={{ remember: true }}
|
|
||||||
onFinish={handleSaveFlow}
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<Form.Item label="Title" name="label" initialValue={flowInfo?.label} rules={[{ required: true, message: 'Please input flow title!' }]}>
|
|
||||||
<Input onChange={labelChange} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="Name"
|
|
||||||
name="name"
|
|
||||||
initialValue={flowInfo?.name}
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: 'Please input flow name!' },
|
|
||||||
() => ({
|
|
||||||
validator(_, value) {
|
|
||||||
const regex = /^[a-zA-Z0-9_\-]+$/;
|
|
||||||
if (!regex.test(value)) {
|
|
||||||
return Promise.reject('Can only contain numbers, letters, underscores, and dashes');
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="Description" initialValue={flowInfo?.description} name="description">
|
|
||||||
<TextArea rows={3} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="Editable" name="editable" initialValue={id ? flowInfo?.editable : true} valuePropName="checked">
|
|
||||||
<Checkbox></Checkbox>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="Deploy" name="deploy" initialValue={id ? (flowInfo?.state === 'deployed' || flowInfo?.state === 'running') : true} valuePropName="checked">
|
|
||||||
<Checkbox></Checkbox>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
|
||||||
<Space>
|
|
||||||
<Button
|
|
||||||
htmlType="button"
|
|
||||||
onClick={() => {
|
|
||||||
setIsModalVisible(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button type="primary" htmlType="submit">
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,12 +1,31 @@
|
|||||||
import BlurredCard, { ChatButton, InnerDropdown } from '@/new-components/common/blurredCard';
|
import BlurredCard, {
|
||||||
|
ChatButton,
|
||||||
|
InnerDropdown,
|
||||||
|
} from '@/new-components/common/blurredCard';
|
||||||
import ConstructLayout from '@/new-components/layout/Construct';
|
import ConstructLayout from '@/new-components/layout/Construct';
|
||||||
import { ChatContext } from '@/app/chat-context';
|
import { ChatContext } from '@/app/chat-context';
|
||||||
import { apiInterceptors, deleteFlowById, getFlows, newDialogue, updateFlowAdmins, addFlow } from '@/client/api';
|
import {
|
||||||
|
apiInterceptors,
|
||||||
|
deleteFlowById,
|
||||||
|
getFlows,
|
||||||
|
newDialogue,
|
||||||
|
addFlow,
|
||||||
|
} from '@/client/api';
|
||||||
import MyEmpty from '@/components/common/MyEmpty';
|
import MyEmpty from '@/components/common/MyEmpty';
|
||||||
import { IFlow, IFlowUpdateParam } from '@/types/flow';
|
import { IFlow, IFlowUpdateParam } from '@/types/flow';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { useRequest } from 'ahooks';
|
import { useRequest } from 'ahooks';
|
||||||
import { Button, Modal, Popconfirm, Select, Spin, Tag, message, Form, Input, Checkbox } from 'antd';
|
import {
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
Popconfirm,
|
||||||
|
Spin,
|
||||||
|
Tag,
|
||||||
|
message,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Checkbox,
|
||||||
|
} from 'antd';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { concat, debounce } from 'lodash';
|
import { concat, debounce } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@@ -19,18 +38,14 @@ function Flow() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { model } = useContext(ChatContext);
|
const { model } = useContext(ChatContext);
|
||||||
const [messageApi, contextHolder] = message.useMessage();
|
const [messageApi, contextHolder] = message.useMessage();
|
||||||
|
const [form] = Form.useForm<Pick<IFlow, 'label' | 'name'>>();
|
||||||
|
|
||||||
const [flowList, setFlowList] = useState<Array<IFlow>>([]);
|
const [flowList, setFlowList] = useState<Array<IFlow>>([]);
|
||||||
const [adminOpen, setAdminOpen] = useState<boolean>(false);
|
|
||||||
const [curFlow, setCurFLow] = useState<IFlow>();
|
|
||||||
const [admins, setAdmins] = useState<string[]>([]);
|
|
||||||
const copyFlowTemp = useRef<IFlow>();
|
const copyFlowTemp = useRef<IFlow>();
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [deploy, setDeploy] = useState(false);
|
const [deploy, setDeploy] = useState(false);
|
||||||
const [editable, setEditable] = useState(false);
|
const [editable, setEditable] = useState(false);
|
||||||
|
|
||||||
const [form] = Form.useForm<Pick<IFlow, 'label' | 'name'>>();
|
|
||||||
|
|
||||||
// 分页信息
|
// 分页信息
|
||||||
const totalRef = useRef<{
|
const totalRef = useRef<{
|
||||||
current_page: number;
|
current_page: number;
|
||||||
@@ -41,18 +56,14 @@ function Flow() {
|
|||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// 获取列表
|
// 获取列表
|
||||||
const {
|
const { run: getFlowListRun, loading } = useRequest(
|
||||||
run: getFlowListRun,
|
|
||||||
loading,
|
|
||||||
refresh: refreshFlowList,
|
|
||||||
} = useRequest(
|
|
||||||
async (params: any) =>
|
async (params: any) =>
|
||||||
await apiInterceptors(
|
await apiInterceptors(
|
||||||
getFlows({
|
getFlows({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 12,
|
page_size: 12,
|
||||||
...params,
|
...params,
|
||||||
}),
|
})
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
cacheKey: 'query-flow-list',
|
cacheKey: 'query-flow-list',
|
||||||
@@ -66,7 +77,7 @@ function Flow() {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
throttleWait: 300,
|
throttleWait: 300,
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
@@ -112,7 +123,9 @@ function Flow() {
|
|||||||
}, [loading, handleScroll, loadMoreData]);
|
}, [loading, handleScroll, loadMoreData]);
|
||||||
|
|
||||||
const handleChat = async (flow: IFlow) => {
|
const handleChat = async (flow: IFlow) => {
|
||||||
const [, res] = await apiInterceptors(newDialogue({ chat_mode: 'chat_agent' }));
|
const [, res] = await apiInterceptors(
|
||||||
|
newDialogue({ chat_mode: 'chat_agent' })
|
||||||
|
);
|
||||||
if (res) {
|
if (res) {
|
||||||
const queryStr = qs.stringify({
|
const queryStr = qs.stringify({
|
||||||
scene: 'chat_flow',
|
scene: 'chat_flow',
|
||||||
@@ -131,30 +144,6 @@ function Flow() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (curFlow?.admins?.length) {
|
|
||||||
setAdmins(curFlow?.admins);
|
|
||||||
} else {
|
|
||||||
setAdmins([]);
|
|
||||||
}
|
|
||||||
}, [curFlow]);
|
|
||||||
|
|
||||||
// 更新管理员
|
|
||||||
const { run: updateAdmins, loading: adminLoading } = useRequest(
|
|
||||||
async (value: string[]) => await apiInterceptors(updateFlowAdmins({ uid: curFlow?.uid || '', admins: value })),
|
|
||||||
{
|
|
||||||
manual: true,
|
|
||||||
onSuccess: (data) => {
|
|
||||||
const [error] = data;
|
|
||||||
if (!error) {
|
|
||||||
message.success('更新成功');
|
|
||||||
} else {
|
|
||||||
message.error('更新失败');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCopy = (flow: IFlow) => {
|
const handleCopy = (flow: IFlow) => {
|
||||||
copyFlowTemp.current = flow;
|
copyFlowTemp.current = flow;
|
||||||
form.setFieldValue('label', `${flow.label} Copy`);
|
form.setFieldValue('label', `${flow.label} Copy`);
|
||||||
@@ -166,7 +155,8 @@ function Flow() {
|
|||||||
|
|
||||||
const onFinish = async (val: { name: string; label: string }) => {
|
const onFinish = async (val: { name: string; label: string }) => {
|
||||||
if (!copyFlowTemp.current) return;
|
if (!copyFlowTemp.current) return;
|
||||||
const { source, uid, dag_id, gmt_created, gmt_modified, state, ...params } = copyFlowTemp.current;
|
const { source, uid, dag_id, gmt_created, gmt_modified, state, ...params } =
|
||||||
|
copyFlowTemp.current;
|
||||||
const data: IFlowUpdateParam = {
|
const data: IFlowUpdateParam = {
|
||||||
...params,
|
...params,
|
||||||
editable,
|
editable,
|
||||||
@@ -181,18 +171,15 @@ function Flow() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = async (value: string[]) => {
|
|
||||||
setAdmins(value);
|
|
||||||
await updateAdmins(value);
|
|
||||||
await refreshFlowList();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConstructLayout>
|
<ConstructLayout>
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<div className="relative h-screen w-full p-4 md:p-6 overflow-y-auto" ref={scrollRef}>
|
<div
|
||||||
<div className="flex justify-between items-center mb-6">
|
className='relative h-screen w-full p-4 md:p-6 overflow-y-auto'
|
||||||
<div className="flex items-center gap-4">
|
ref={scrollRef}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between items-center mb-6'>
|
||||||
|
<div className='flex items-center gap-4'>
|
||||||
{/* <Input
|
{/* <Input
|
||||||
variant="filled"
|
variant="filled"
|
||||||
prefix={<SearchOutlined />}
|
prefix={<SearchOutlined />}
|
||||||
@@ -204,9 +191,9 @@ function Flow() {
|
|||||||
/> */}
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className='flex items-center gap-4'>
|
||||||
<Button
|
<Button
|
||||||
className="border-none text-white bg-button-gradient"
|
className='border-none text-white bg-button-gradient'
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push('/construct/flow/canvas');
|
router.push('/construct/flow/canvas');
|
||||||
@@ -216,13 +203,13 @@ function Flow() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap mx-[-8px] pb-12 justify-start items-stretch">
|
<div className='flex flex-wrap mx-[-8px] pb-12 justify-start items-stretch'>
|
||||||
{flowList.map((flow) => (
|
{flowList.map((flow) => (
|
||||||
<BlurredCard
|
<BlurredCard
|
||||||
description={flow.description}
|
description={flow.description}
|
||||||
name={flow.name}
|
name={flow.name}
|
||||||
key={flow.uid}
|
key={flow.uid}
|
||||||
logo="/pictures/flow.png"
|
logo='/pictures/flow.png'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push('/construct/flow/canvas?id=' + flow.uid);
|
router.push('/construct/flow/canvas?id=' + flow.uid);
|
||||||
}}
|
}}
|
||||||
@@ -230,19 +217,6 @@ function Flow() {
|
|||||||
<InnerDropdown
|
<InnerDropdown
|
||||||
menu={{
|
menu={{
|
||||||
items: [
|
items: [
|
||||||
// {
|
|
||||||
// key: 'edit',
|
|
||||||
// label: (
|
|
||||||
// <span
|
|
||||||
// onClick={() => {
|
|
||||||
// setAdminOpen(true);
|
|
||||||
// setCurFLow(flow);
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// 权限管理
|
|
||||||
// </span>
|
|
||||||
// ),
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
key: 'copy',
|
key: 'copy',
|
||||||
label: (
|
label: (
|
||||||
@@ -251,15 +225,20 @@ function Flow() {
|
|||||||
handleCopy(flow);
|
handleCopy(flow);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('copy')}
|
{t('Copy_Btn')}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'del',
|
key: 'del',
|
||||||
label: (
|
label: (
|
||||||
<Popconfirm title="Are you sure to delete this flow?" onConfirm={() => deleteFlow(flow)}>
|
<Popconfirm
|
||||||
<span className="text-red-400">删除</span>
|
title='Are you sure to delete this flow?'
|
||||||
|
onConfirm={() => deleteFlow(flow)}
|
||||||
|
>
|
||||||
|
<span className='text-red-400'>
|
||||||
|
{t('Delete_Btn')}
|
||||||
|
</span>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -270,16 +249,36 @@ function Flow() {
|
|||||||
rightTopHover={false}
|
rightTopHover={false}
|
||||||
Tags={
|
Tags={
|
||||||
<div>
|
<div>
|
||||||
<Tag color={flow.source === 'DBGPT-WEB' ? 'green' : 'blue'}>{flow.source}</Tag>
|
<Tag color={flow.source === 'DBGPT-WEB' ? 'green' : 'blue'}>
|
||||||
<Tag color={flow.editable ? 'green' : 'gray'}>{flow.editable ? 'Editable' : 'Can not Edit'}</Tag>
|
{flow.source}
|
||||||
<Tag color={flow.state === 'load_failed' ? 'red' : flow.state === 'running' ? 'green' : 'blue'}>{flow.state}</Tag>
|
</Tag>
|
||||||
|
<Tag color={flow.editable ? 'green' : 'gray'}>
|
||||||
|
{flow.editable ? 'Editable' : 'Can not Edit'}
|
||||||
|
</Tag>
|
||||||
|
<Tag
|
||||||
|
color={
|
||||||
|
flow.state === 'load_failed'
|
||||||
|
? 'red'
|
||||||
|
: flow.state === 'running'
|
||||||
|
? 'green'
|
||||||
|
: 'blue'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{flow.state}
|
||||||
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
LeftBottom={
|
LeftBottom={
|
||||||
<div key={i18n.language + 'flow'} className="flex gap-2">
|
<div key={i18n.language + 'flow'} className='flex gap-2'>
|
||||||
<span>{flow?.nick_name}</span>
|
<span>{flow?.nick_name}</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
{flow?.gmt_modified && <span>{moment(flow?.gmt_modified).fromNow() + ' ' + t('update')}</span>}
|
{flow?.gmt_modified && (
|
||||||
|
<span>
|
||||||
|
{moment(flow?.gmt_modified).fromNow() +
|
||||||
|
' ' +
|
||||||
|
t('update')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
RightBottom={
|
RightBottom={
|
||||||
@@ -292,40 +291,26 @@ function Flow() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{flowList.length === 0 && <MyEmpty description="No flow found" />}
|
{flowList.length === 0 && <MyEmpty description='No flow found' />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Spin>
|
</Spin>
|
||||||
<Modal title="权限管理" open={adminOpen} onCancel={() => setAdminOpen(false)} footer={null}>
|
|
||||||
<div className="py-4">
|
|
||||||
<div className="mb-1">管理员(工号,去前缀0):</div>
|
|
||||||
<Select
|
|
||||||
mode="tags"
|
|
||||||
value={admins}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
onChange={handleChange}
|
|
||||||
tokenSeparators={[',']}
|
|
||||||
options={admins?.map((item: string) => ({ label: item, value: item }))}
|
|
||||||
loading={adminLoading}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
<Modal
|
||||||
open={showModal}
|
open={showModal}
|
||||||
title="Copy AWEL Flow"
|
title='Copy AWEL Flow'
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
}}
|
}}
|
||||||
footer={false}
|
footer={false}
|
||||||
>
|
>
|
||||||
<Form form={form} onFinish={onFinish} className="mt-6">
|
<Form form={form} onFinish={onFinish} className='mt-6'>
|
||||||
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
|
<Form.Item name='name' label='Name' rules={[{ required: true }]}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="label" label="Label" rules={[{ required: true }]}>
|
<Form.Item name='label' label='Label' rules={[{ required: true }]}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="editable">
|
<Form.Item label='editable'>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value={editable}
|
value={editable}
|
||||||
checked={editable}
|
checked={editable}
|
||||||
@@ -335,7 +320,7 @@ function Flow() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="deploy">
|
<Form.Item label='deploy'>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value={deploy}
|
value={deploy}
|
||||||
checked={deploy}
|
checked={deploy}
|
||||||
@@ -345,8 +330,8 @@ function Flow() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<div className="flex justify-end">
|
<div className='flex justify-end'>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type='primary' htmlType='submit'>
|
||||||
{t('Submit')}
|
{t('Submit')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,233 +0,0 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
|
||||||
import { useAsyncEffect } from 'ahooks';
|
|
||||||
import { Badge, Button, Card, Drawer, Empty, Modal, message, Spin } from 'antd';
|
|
||||||
import FormDialog from '@/components/database/form-dialog';
|
|
||||||
import { apiInterceptors, getDbList, getDbSupportType, postDbDelete, postDbRefresh } from '@/client/api';
|
|
||||||
import { DeleteFilled, EditFilled, PlusOutlined, RedoOutlined } from '@ant-design/icons';
|
|
||||||
import { DBOption, DBType, DbListResponse, DbSupportTypeResponse } from '@/types/db';
|
|
||||||
import MuiLoading from '@/components/common/loading';
|
|
||||||
import { dbMapper } from '@/utils';
|
|
||||||
import GPTCard from '@/components/common/gpt-card';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type DBItem = DbListResponse[0];
|
|
||||||
|
|
||||||
export function isFileDb(dbTypeList: DBOption[], dbType: DBType) {
|
|
||||||
return dbTypeList.find((item) => item.value === dbType)?.isFileDb;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Database() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [dbList, setDbList] = useState<DbListResponse>([]);
|
|
||||||
const [dbSupportList, setDbSupportList] = useState<DbSupportTypeResponse>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [modal, setModal] = useState<{ open: boolean; info?: DBItem; dbType?: DBType }>({ open: false });
|
|
||||||
const [draw, setDraw] = useState<{ open: boolean; dbList?: DbListResponse; name?: string; type?: DBType }>({ open: false });
|
|
||||||
const [refreshLoading, setRefreshLoading] = useState(false);
|
|
||||||
|
|
||||||
const getDbSupportList = async () => {
|
|
||||||
const [, data] = await apiInterceptors(getDbSupportType());
|
|
||||||
setDbSupportList(data ?? []);
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshDbList = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
const [, data] = await apiInterceptors(getDbList());
|
|
||||||
setDbList(data ?? []);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const dbTypeList = useMemo(() => {
|
|
||||||
const supportDbList = dbSupportList.map((item) => {
|
|
||||||
const { db_type, is_file_db } = item;
|
|
||||||
|
|
||||||
return { ...dbMapper[db_type], value: db_type, isFileDb: is_file_db };
|
|
||||||
}) as DBOption[];
|
|
||||||
const unSupportDbList = Object.keys(dbMapper)
|
|
||||||
.filter((item) => !supportDbList.some((db) => db.value === item))
|
|
||||||
.map((item) => ({ ...dbMapper[item], value: dbMapper[item].label, disabled: true })) as DBOption[];
|
|
||||||
return [...supportDbList, ...unSupportDbList];
|
|
||||||
}, [dbSupportList]);
|
|
||||||
|
|
||||||
const onModify = (item: DBItem) => {
|
|
||||||
setModal({ open: true, info: item });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDelete = (item: DBItem) => {
|
|
||||||
Modal.confirm({
|
|
||||||
title: 'Tips',
|
|
||||||
content: `Do you Want to delete the ${item.db_name}?`,
|
|
||||||
onOk() {
|
|
||||||
return new Promise<void>(async (resolve, reject) => {
|
|
||||||
try {
|
|
||||||
const [err] = await apiInterceptors(postDbDelete(item.db_name));
|
|
||||||
if (err) {
|
|
||||||
message.error(err.message);
|
|
||||||
reject();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
message.success('success');
|
|
||||||
refreshDbList();
|
|
||||||
resolve();
|
|
||||||
} catch (e: any) {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRefresh = async (item: DBItem) => {
|
|
||||||
setRefreshLoading(true);
|
|
||||||
const [, res] = await apiInterceptors(postDbRefresh({ db_name: item.db_name, db_type: item.db_type }));
|
|
||||||
if (res) message.success(t('refreshSuccess'));
|
|
||||||
setRefreshLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const dbListByType = useMemo(() => {
|
|
||||||
const mapper = dbTypeList.reduce((acc, item) => {
|
|
||||||
acc[item.value] = dbList.filter((dbConn) => dbConn.db_type === item.value);
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<DBType, DbListResponse>);
|
|
||||||
return mapper;
|
|
||||||
}, [dbList, dbTypeList]);
|
|
||||||
|
|
||||||
useAsyncEffect(async () => {
|
|
||||||
await refreshDbList();
|
|
||||||
await getDbSupportList();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDbTypeClick = (info: DBOption) => {
|
|
||||||
const dbItems = dbList.filter((item) => item.db_type === info.value);
|
|
||||||
setDraw({ open: true, dbList: dbItems, name: info.label, type: info.value });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative p-4 md:p-6 min-h-full overflow-y-auto">
|
|
||||||
<MuiLoading visible={loading} />
|
|
||||||
<div className="mb-4">
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
className="flex items-center"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={() => {
|
|
||||||
setModal({ open: true });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('create')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2 md:gap-4">
|
|
||||||
{dbTypeList.map((item) => (
|
|
||||||
<Badge key={item.value} count={dbListByType[item.value].length} className="min-h-fit">
|
|
||||||
<GPTCard
|
|
||||||
className="h-full"
|
|
||||||
title={item.label}
|
|
||||||
desc={item.desc ?? ''}
|
|
||||||
disabled={item.disabled}
|
|
||||||
icon={item.icon}
|
|
||||||
onClick={() => {
|
|
||||||
if (item.disabled) return;
|
|
||||||
handleDbTypeClick(item);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<FormDialog
|
|
||||||
open={modal.open}
|
|
||||||
dbTypeList={dbTypeList}
|
|
||||||
choiceDBType={modal.dbType}
|
|
||||||
editValue={modal.info}
|
|
||||||
dbNames={dbList.map((item) => item.db_name)}
|
|
||||||
onSuccess={() => {
|
|
||||||
setModal({ open: false });
|
|
||||||
refreshDbList();
|
|
||||||
}}
|
|
||||||
onClose={() => {
|
|
||||||
setModal({ open: false });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Drawer
|
|
||||||
title={draw.name}
|
|
||||||
placement="right"
|
|
||||||
onClose={() => {
|
|
||||||
setDraw({ open: false });
|
|
||||||
}}
|
|
||||||
open={draw.open}
|
|
||||||
>
|
|
||||||
{draw.type && dbListByType[draw.type] && dbListByType[draw.type].length ? (
|
|
||||||
<Spin spinning={refreshLoading}>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
className="mb-4 flex items-center"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={() => {
|
|
||||||
setModal({ open: true, dbType: draw.type });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
{dbListByType[draw.type].map((item) => (
|
|
||||||
<Card
|
|
||||||
key={item.db_name}
|
|
||||||
title={item.db_name}
|
|
||||||
extra={
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<RedoOutlined
|
|
||||||
style={{ color: 'gray' }}
|
|
||||||
onClick={() => {
|
|
||||||
onRefresh(item);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<EditFilled
|
|
||||||
className="mr-2"
|
|
||||||
style={{ color: '#1b7eff' }}
|
|
||||||
onClick={() => {
|
|
||||||
onModify(item);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<DeleteFilled
|
|
||||||
style={{ color: '#ff1b2e' }}
|
|
||||||
onClick={() => {
|
|
||||||
onDelete(item);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
{item.db_path ? (
|
|
||||||
<p>path: {item.db_path}</p>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<p>host: {item.db_host}</p>
|
|
||||||
<p>username: {item.db_user}</p>
|
|
||||||
<p>port: {item.db_port}</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<p>remark: {item.comment}</p>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</Spin>
|
|
||||||
) : (
|
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_DEFAULT}>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
className="flex items-center mx-auto"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={() => {
|
|
||||||
setModal({ open: true, dbType: draw.type });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Create Now
|
|
||||||
</Button>
|
|
||||||
</Empty>
|
|
||||||
)}
|
|
||||||
</Drawer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Database;
|
|
@@ -1,319 +0,0 @@
|
|||||||
import { addFlow, apiInterceptors, getFlowById, updateFlowById } from '@/client/api';
|
|
||||||
import MuiLoading from '@/components/common/loading';
|
|
||||||
import AddNodes from '@/components/flow/add-nodes';
|
|
||||||
import ButtonEdge from '@/components/flow/button-edge';
|
|
||||||
import CanvasNode from '@/components/flow/canvas-node';
|
|
||||||
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
|
|
||||||
import { checkFlowDataRequied, getUniqueNodeId, mapHumpToUnderline, mapUnderlineToHump } from '@/utils/flow';
|
|
||||||
import { FrownOutlined, SaveOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Checkbox, Divider, Form, Input, Modal, Space, message, notification } from 'antd';
|
|
||||||
import { useSearchParams } from 'next/navigation';
|
|
||||||
import React, { DragEvent, useCallback, useEffect, useRef, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import ReactFlow, { Background, Connection, Controls, ReactFlowProvider, addEdge, useEdgesState, useNodesState, useReactFlow, Node } from 'reactflow';
|
|
||||||
import 'reactflow/dist/style.css';
|
|
||||||
|
|
||||||
const { TextArea } = Input;
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
// Define your component props here
|
|
||||||
}
|
|
||||||
const nodeTypes = { customNode: CanvasNode };
|
|
||||||
const edgeTypes = { buttonedge: ButtonEdge };
|
|
||||||
|
|
||||||
const Canvas: React.FC<Props> = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [messageApi, contextHolder] = message.useMessage();
|
|
||||||
const [form] = Form.useForm<IFlowUpdateParam>();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const id = searchParams?.get('id') || '';
|
|
||||||
const reactFlow = useReactFlow();
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
|
||||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
|
||||||
const [flowInfo, setFlowInfo] = useState<IFlowUpdateParam>();
|
|
||||||
const [deploy, setDeploy] = useState(true);
|
|
||||||
|
|
||||||
async function getFlowData() {
|
|
||||||
setLoading(true);
|
|
||||||
const [_, data] = await apiInterceptors(getFlowById(id));
|
|
||||||
if (data) {
|
|
||||||
const flowData = mapUnderlineToHump(data.flow_data);
|
|
||||||
setFlowInfo(data);
|
|
||||||
setNodes(flowData.nodes);
|
|
||||||
setEdges(flowData.edges);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
id && getFlowData();
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
|
||||||
event.returnValue = message;
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function onNodesClick(event: any, clickedNode: Node) {
|
|
||||||
reactFlow.setNodes((nds) =>
|
|
||||||
nds.map((node) => {
|
|
||||||
if (node.id === clickedNode.id) {
|
|
||||||
node.data = {
|
|
||||||
...node.data,
|
|
||||||
selected: true,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
node.data = {
|
|
||||||
...node.data,
|
|
||||||
selected: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onConnect(connection: Connection) {
|
|
||||||
const newEdge = {
|
|
||||||
...connection,
|
|
||||||
type: 'buttonedge',
|
|
||||||
id: `${connection.source}|${connection.target}`,
|
|
||||||
};
|
|
||||||
setEdges((eds) => addEdge(newEdge, eds));
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDrop = useCallback(
|
|
||||||
(event: DragEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
const reactFlowBounds = reactFlowWrapper.current!.getBoundingClientRect();
|
|
||||||
let nodeStr = event.dataTransfer.getData('application/reactflow');
|
|
||||||
if (!nodeStr || typeof nodeStr === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const nodeData = JSON.parse(nodeStr);
|
|
||||||
const position = reactFlow.screenToFlowPosition({
|
|
||||||
x: event.clientX - reactFlowBounds.left,
|
|
||||||
y: event.clientY - reactFlowBounds.top,
|
|
||||||
});
|
|
||||||
const nodeId = getUniqueNodeId(nodeData, reactFlow.getNodes());
|
|
||||||
nodeData.id = nodeId;
|
|
||||||
const newNode = {
|
|
||||||
id: nodeId,
|
|
||||||
position,
|
|
||||||
type: 'customNode',
|
|
||||||
data: nodeData,
|
|
||||||
};
|
|
||||||
setNodes((nds) =>
|
|
||||||
nds.concat(newNode).map((node) => {
|
|
||||||
if (node.id === newNode.id) {
|
|
||||||
node.data = {
|
|
||||||
...node.data,
|
|
||||||
selected: true,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
node.data = {
|
|
||||||
...node.data,
|
|
||||||
selected: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[reactFlow],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onDragOver = useCallback((event: DragEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.dataTransfer.dropEffect = 'move';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function labelChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
||||||
const label = e.target.value;
|
|
||||||
// replace spaces with underscores, convert uppercase letters to lowercase, remove characters other than digits, letters, _, and -.
|
|
||||||
let result = label
|
|
||||||
.replace(/\s+/g, '_')
|
|
||||||
.replace(/[^a-z0-9_-]/g, '')
|
|
||||||
.toLowerCase();
|
|
||||||
result = result;
|
|
||||||
form.setFieldsValue({ name: result });
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickSave() {
|
|
||||||
const flowData = reactFlow.toObject() as IFlowData;
|
|
||||||
const [check, node, message] = checkFlowDataRequied(flowData);
|
|
||||||
if (!check && message) {
|
|
||||||
setNodes((nds) =>
|
|
||||||
nds.map((item) => {
|
|
||||||
if (item.id === node?.id) {
|
|
||||||
item.data = {
|
|
||||||
...item.data,
|
|
||||||
invalid: true,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
item.data = {
|
|
||||||
...item.data,
|
|
||||||
invalid: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return notification.error({ message: 'Error', description: message, icon: <FrownOutlined className="text-red-600" /> });
|
|
||||||
}
|
|
||||||
setIsModalVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSaveFlow() {
|
|
||||||
const { name, label, description = '', editable = false, state = 'deployed' } = form.getFieldsValue();
|
|
||||||
const reactFlowObject = mapHumpToUnderline(reactFlow.toObject() as IFlowData);
|
|
||||||
if (id) {
|
|
||||||
const [, , res] = await apiInterceptors(updateFlowById(id, { name, label, description, editable, uid: id, flow_data: reactFlowObject, state }));
|
|
||||||
setIsModalVisible(false);
|
|
||||||
if (res?.success) {
|
|
||||||
messageApi.success(t('save_flow_success'));
|
|
||||||
} else if (res?.err_msg) {
|
|
||||||
messageApi.error(res?.err_msg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const [_, res] = await apiInterceptors(addFlow({ name, label, description, editable, flow_data: reactFlowObject, state }));
|
|
||||||
if (res?.uid) {
|
|
||||||
messageApi.success(t('save_flow_success'));
|
|
||||||
const history = window.history;
|
|
||||||
history.pushState(null, '', `/flow/canvas?id=${res.uid}`);
|
|
||||||
}
|
|
||||||
setIsModalVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MuiLoading visible={loading} />
|
|
||||||
<div className="my-2 mx-4 flex flex-row justify-end items-center">
|
|
||||||
<div className="w-8 h-8 rounded-md bg-stone-300 dark:bg-zinc-700 dark:text-zinc-200 flext justify-center items-center hover:text-blue-500 dark:hover:text-zinc-100">
|
|
||||||
<SaveOutlined className="block text-xl" onClick={clickSave} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Divider className="mt-0 mb-0" />
|
|
||||||
<div className="h-[calc(100vh-60px)] w-full" ref={reactFlowWrapper}>
|
|
||||||
<ReactFlow
|
|
||||||
nodes={nodes}
|
|
||||||
edges={edges}
|
|
||||||
nodeTypes={nodeTypes}
|
|
||||||
edgeTypes={edgeTypes}
|
|
||||||
onNodesChange={onNodesChange}
|
|
||||||
onEdgesChange={onEdgesChange}
|
|
||||||
onNodeClick={onNodesClick}
|
|
||||||
onConnect={onConnect}
|
|
||||||
onDrop={onDrop}
|
|
||||||
onDragOver={onDragOver}
|
|
||||||
minZoom={0.1}
|
|
||||||
fitView
|
|
||||||
deleteKeyCode={['Backspace', 'Delete']}
|
|
||||||
>
|
|
||||||
<Controls className="flex flex-row items-center" position="bottom-center" />
|
|
||||||
<Background color="#aaa" gap={16} />
|
|
||||||
<AddNodes />
|
|
||||||
</ReactFlow>
|
|
||||||
</div>
|
|
||||||
<Modal
|
|
||||||
title={t('flow_modal_title')}
|
|
||||||
open={isModalVisible}
|
|
||||||
onCancel={() => {
|
|
||||||
setIsModalVisible(false);
|
|
||||||
}}
|
|
||||||
cancelButtonProps={{ className: 'hidden' }}
|
|
||||||
okButtonProps={{ className: 'hidden' }}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
name="flow_form"
|
|
||||||
form={form}
|
|
||||||
labelCol={{ span: 8 }}
|
|
||||||
wrapperCol={{ span: 16 }}
|
|
||||||
style={{ maxWidth: 600 }}
|
|
||||||
initialValues={{ remember: true }}
|
|
||||||
onFinish={handleSaveFlow}
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<Form.Item label="Title" name="label" initialValue={flowInfo?.label} rules={[{ required: true, message: 'Please input flow title!' }]}>
|
|
||||||
<Input onChange={labelChange} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="Name"
|
|
||||||
name="name"
|
|
||||||
initialValue={flowInfo?.name}
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: 'Please input flow name!' },
|
|
||||||
() => ({
|
|
||||||
validator(_, value) {
|
|
||||||
const regex = /^[a-zA-Z0-9_\-]+$/;
|
|
||||||
if (!regex.test(value)) {
|
|
||||||
return Promise.reject('Can only contain numbers, letters, underscores, and dashes');
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="Description" initialValue={flowInfo?.description} name="description">
|
|
||||||
<TextArea rows={3} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="Editable" name="editable" initialValue={flowInfo?.editable} valuePropName="checked">
|
|
||||||
<Checkbox />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item hidden name="state">
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="Deploy">
|
|
||||||
<Checkbox
|
|
||||||
defaultChecked={flowInfo?.state === 'deployed' || flowInfo?.state === 'running'}
|
|
||||||
value={deploy}
|
|
||||||
onChange={(e) => {
|
|
||||||
const val = e.target.checked;
|
|
||||||
form.setFieldValue('state', val ? 'deployed' : 'developing');
|
|
||||||
setDeploy(val);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
|
||||||
<Space>
|
|
||||||
<Button
|
|
||||||
htmlType="button"
|
|
||||||
onClick={() => {
|
|
||||||
setIsModalVisible(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button type="primary" htmlType="submit">
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
{contextHolder}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function CanvasWrapper() {
|
|
||||||
return (
|
|
||||||
<ReactFlowProvider>
|
|
||||||
<Canvas />
|
|
||||||
</ReactFlowProvider>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,117 +0,0 @@
|
|||||||
import { apiInterceptors, getFlows, addFlow } from '@/client/api';
|
|
||||||
import MyEmpty from '@/components/common/MyEmpty';
|
|
||||||
import MuiLoading from '@/components/common/loading';
|
|
||||||
import FlowCard from '@/components/flow/flow-card';
|
|
||||||
import { IFlow, IFlowUpdateParam } from '@/types/flow';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Checkbox, Form, Input, Modal, message } from 'antd';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import React, { useEffect, useState, useRef } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
function Flow() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [showModal, setShowModal] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [flowList, setFlowList] = useState<Array<IFlow>>([]);
|
|
||||||
const [deploy, setDeploy] = useState(false);
|
|
||||||
|
|
||||||
const [messageApi, contextHolder] = message.useMessage();
|
|
||||||
const [form] = Form.useForm<Pick<IFlow, 'label' | 'name'>>();
|
|
||||||
|
|
||||||
const copyFlowTemp = useRef<IFlow>();
|
|
||||||
|
|
||||||
async function getFlowList() {
|
|
||||||
setLoading(true);
|
|
||||||
const [_, data] = await apiInterceptors(getFlows());
|
|
||||||
setLoading(false);
|
|
||||||
setFlowList(data?.items ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getFlowList();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function updateFlowList(uid: string) {
|
|
||||||
setFlowList((flows) => flows.filter((flow) => flow.uid !== uid));
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCopy = (flow: IFlow) => {
|
|
||||||
copyFlowTemp.current = flow;
|
|
||||||
form.setFieldValue('label', `${flow.label} Copy`);
|
|
||||||
form.setFieldValue('name', `${flow.name}_copy`);
|
|
||||||
setDeploy(false);
|
|
||||||
setShowModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFinish = async (val: { name: string; label: string }) => {
|
|
||||||
if (!copyFlowTemp.current) return;
|
|
||||||
const { source, uid, dag_id, gmt_created, gmt_modified, state, ...params } = copyFlowTemp.current;
|
|
||||||
const data: IFlowUpdateParam = {
|
|
||||||
...params,
|
|
||||||
editable: true,
|
|
||||||
state: deploy ? 'deployed' : 'developing',
|
|
||||||
...val,
|
|
||||||
};
|
|
||||||
const [err] = await apiInterceptors(addFlow(data));
|
|
||||||
if (!err) {
|
|
||||||
messageApi.success(t('save_flow_success'));
|
|
||||||
setShowModal(false);
|
|
||||||
getFlowList();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative p-4 md:p-6 min-h-full overflow-y-auto">
|
|
||||||
{contextHolder}
|
|
||||||
<MuiLoading visible={loading} />
|
|
||||||
<div className="mb-4">
|
|
||||||
<Link href="/flow/canvas">
|
|
||||||
<Button type="primary" className="flex items-center" icon={<PlusOutlined />}>
|
|
||||||
New AWEL Flow
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2 md:gap-4 justify-start items-stretch">
|
|
||||||
{flowList.map((flow) => (
|
|
||||||
<FlowCard key={flow.uid} flow={flow} deleteCallback={updateFlowList} onCopy={handleCopy} />
|
|
||||||
))}
|
|
||||||
{flowList.length === 0 && <MyEmpty description="No flow found" />}
|
|
||||||
</div>
|
|
||||||
<Modal
|
|
||||||
open={showModal}
|
|
||||||
title="Copy AWEL Flow"
|
|
||||||
onCancel={() => {
|
|
||||||
setShowModal(false);
|
|
||||||
}}
|
|
||||||
footer={false}
|
|
||||||
>
|
|
||||||
<Form form={form} onFinish={onFinish} className="mt-6">
|
|
||||||
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="label" label="Label" rules={[{ required: true }]}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="Deploy">
|
|
||||||
<Checkbox
|
|
||||||
value={deploy}
|
|
||||||
onChange={(e) => {
|
|
||||||
const val = e.target.checked;
|
|
||||||
setDeploy(val);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button type="primary" htmlType="submit">
|
|
||||||
{t('Submit')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Flow;
|
|
@@ -1,106 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { Breadcrumb, Card, Empty, Pagination, Spin } from 'antd';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { apiInterceptors, getChunkList } from '@/client/api';
|
|
||||||
import DocIcon from '@/components/knowledge/doc-icon';
|
|
||||||
|
|
||||||
const DEDAULT_PAGE_SIZE = 10;
|
|
||||||
|
|
||||||
function ChunkList() {
|
|
||||||
const router = useRouter();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [chunkList, setChunkList] = useState<any>([]);
|
|
||||||
const [total, setTotal] = useState<number>(0);
|
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
|
||||||
const {
|
|
||||||
query: { id, spaceName },
|
|
||||||
} = useRouter();
|
|
||||||
|
|
||||||
const fetchChunks = async () => {
|
|
||||||
const [_, data] = await apiInterceptors(
|
|
||||||
getChunkList(spaceName as string, {
|
|
||||||
document_id: id as string,
|
|
||||||
page: 1,
|
|
||||||
page_size: DEDAULT_PAGE_SIZE,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
setChunkList(data?.data);
|
|
||||||
setTotal(data?.total!);
|
|
||||||
};
|
|
||||||
|
|
||||||
const loaderMore = async (page: number, page_size: number) => {
|
|
||||||
setLoading(true);
|
|
||||||
const [_, data] = await apiInterceptors(
|
|
||||||
getChunkList(spaceName as string, {
|
|
||||||
document_id: id as string,
|
|
||||||
page,
|
|
||||||
page_size,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
setChunkList(data?.data || []);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
spaceName && id && fetchChunks();
|
|
||||||
}, [id, spaceName]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-full overflow-y-scroll relative px-2">
|
|
||||||
<Breadcrumb
|
|
||||||
className="m-6"
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
title: 'Knowledge',
|
|
||||||
onClick() {
|
|
||||||
router.back();
|
|
||||||
},
|
|
||||||
path: '/knowledge',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: spaceName,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Spin spinning={loading}>
|
|
||||||
<div className="flex justify-center flex-col">
|
|
||||||
{chunkList?.length > 0 ? (
|
|
||||||
chunkList?.map((chunk: any) => {
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
key={chunk.id}
|
|
||||||
className="mt-2"
|
|
||||||
title={
|
|
||||||
<>
|
|
||||||
<DocIcon type={chunk.doc_type} />
|
|
||||||
<span>{chunk.doc_name}</span>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<p className="font-semibold">{t('Content')}:</p>
|
|
||||||
<p>{chunk?.content}</p>
|
|
||||||
<p className="font-semibold">{t('Meta_Data')}: </p>
|
|
||||||
<p>{chunk?.meta_info}</p>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_DEFAULT}></Empty>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Spin>
|
|
||||||
<Pagination
|
|
||||||
className="mx-2 my-4 float-right right-6 bottom-4"
|
|
||||||
defaultCurrent={1}
|
|
||||||
defaultPageSize={DEDAULT_PAGE_SIZE}
|
|
||||||
total={total}
|
|
||||||
showTotal={(total) => `Total ${total} items`}
|
|
||||||
onChange={loaderMore}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ChunkList;
|
|
@@ -1,137 +0,0 @@
|
|||||||
import React, { useEffect,useRef, useState } from 'react';
|
|
||||||
import cytoscape from 'cytoscape';
|
|
||||||
import euler from 'cytoscape-euler';
|
|
||||||
import { Button } from 'antd';
|
|
||||||
import { RollbackOutlined } from '@ant-design/icons';
|
|
||||||
cytoscape.use(euler)
|
|
||||||
import { apiInterceptors,getGraphVis } from '@/client/api';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
|
|
||||||
const LAYOUTCONFIG = {
|
|
||||||
name: 'euler',
|
|
||||||
springLength: 340,
|
|
||||||
fit: false,
|
|
||||||
springCoeff: 0.0008,
|
|
||||||
mass: 20,
|
|
||||||
dragCoeff: 1,
|
|
||||||
gravity: -20,
|
|
||||||
pull: 0.009,
|
|
||||||
randomize: false,
|
|
||||||
padding: 0,
|
|
||||||
maxIterations: 1000,
|
|
||||||
maxSimulationTime: 4000,
|
|
||||||
}
|
|
||||||
|
|
||||||
function GraphVis() {
|
|
||||||
const myRef = useRef<HTMLDivElement>(null);
|
|
||||||
const LIMIT = 500
|
|
||||||
const router = useRouter();
|
|
||||||
const fetchGraphVis = async () => {
|
|
||||||
const [_, data] = await apiInterceptors(getGraphVis(spaceName as string,{limit:LIMIT}))
|
|
||||||
if(myRef.current && data){
|
|
||||||
let processedData = processResult(data)
|
|
||||||
renderGraphVis(processedData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const processResult = (data:{nodes:Array<any>,edges:Array<any>}) => {
|
|
||||||
let nodes:any[] = []
|
|
||||||
let edges:any[] = []
|
|
||||||
data.nodes.forEach((node:any)=>{
|
|
||||||
let n = {
|
|
||||||
data:{
|
|
||||||
id:node.vid,
|
|
||||||
displayName:node.vid,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nodes.push(n)
|
|
||||||
})
|
|
||||||
data.edges.forEach((edge:any)=>{
|
|
||||||
let e = {
|
|
||||||
data:{
|
|
||||||
id:edge.src+'_'+edge.dst+'_'+edge.label,
|
|
||||||
source:edge.src,
|
|
||||||
target:edge.dst,
|
|
||||||
displayName:edge.label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edges.push(e)
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
nodes,
|
|
||||||
edges
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const renderGraphVis = (data:any)=> {
|
|
||||||
let dom = myRef.current as HTMLDivElement
|
|
||||||
let cy = cytoscape(
|
|
||||||
{
|
|
||||||
container:myRef.current,
|
|
||||||
elements:data,
|
|
||||||
zoom:0.3,
|
|
||||||
pixelRatio: 'auto',
|
|
||||||
style:[
|
|
||||||
{
|
|
||||||
selector: 'node',
|
|
||||||
style: {
|
|
||||||
width: 60,
|
|
||||||
height: 60,
|
|
||||||
color: '#fff',
|
|
||||||
'text-outline-color': '#37D4BE',
|
|
||||||
'text-outline-width': 2,
|
|
||||||
'text-valign': 'center',
|
|
||||||
'text-halign': 'center',
|
|
||||||
'background-color': '#37D4BE',
|
|
||||||
'label': 'data(displayName)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector: 'edge',
|
|
||||||
style: {
|
|
||||||
'width': 1,
|
|
||||||
color: '#fff',
|
|
||||||
'label': 'data(displayName)',
|
|
||||||
'line-color': '#66ADFF',
|
|
||||||
'font-size': 14,
|
|
||||||
'target-arrow-shape': 'vee',
|
|
||||||
'control-point-step-size': 40,
|
|
||||||
'curve-style': 'bezier',
|
|
||||||
'text-background-opacity': 1,
|
|
||||||
'text-background-color': '#66ADFF',
|
|
||||||
'target-arrow-color': '#66ADFF',
|
|
||||||
'text-background-shape': 'roundrectangle',
|
|
||||||
'text-border-color': '#000',
|
|
||||||
'text-wrap': 'wrap',
|
|
||||||
'text-valign': 'top',
|
|
||||||
'text-halign': 'center',
|
|
||||||
'text-background-padding':'5',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
cy.layout(LAYOUTCONFIG).run()
|
|
||||||
cy.pan({
|
|
||||||
x: dom.clientWidth / 2,
|
|
||||||
y: dom.clientHeight / 2
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const back = ()=>{
|
|
||||||
router.push(`/knowledge`);
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
query: { spaceName },
|
|
||||||
} = useRouter();
|
|
||||||
useEffect(()=>{
|
|
||||||
spaceName && fetchGraphVis()
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<div className="p-4 h-full overflow-y-scroll relative px-2">
|
|
||||||
<div>
|
|
||||||
<Button onClick={back} icon={<RollbackOutlined />}> Back </Button>
|
|
||||||
</div>
|
|
||||||
<div className='h-full w-full' ref={myRef}></div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GraphVis;
|
|
@@ -1,123 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Modal, Steps } from 'antd';
|
|
||||||
import SpaceCard from '@/components/knowledge/space-card';
|
|
||||||
import { File, ISpace, StepChangeParams, IStorage, SpaceConfig } from '@/types/knowledge';
|
|
||||||
import { apiInterceptors, getSpaceList, getSpaceConfig } from '@/client/api';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import DocUploadForm from '@/components/knowledge/doc-upload-form';
|
|
||||||
import SpaceForm from '@/components/knowledge/space-form';
|
|
||||||
import DocTypeForm from '@/components/knowledge/doc-type-form';
|
|
||||||
import Segmentation from '@/components/knowledge/segmentation';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
const Knowledge = () => {
|
|
||||||
const [spaceList, setSpaceList] = useState<Array<ISpace> | null>([]);
|
|
||||||
const [isAddShow, setIsAddShow] = useState<boolean>(false);
|
|
||||||
const [activeStep, setActiveStep] = useState<number>(0);
|
|
||||||
const [spaceName, setSpaceName] = useState<string>('');
|
|
||||||
const [files, setFiles] = useState<Array<File>>([]);
|
|
||||||
const [docType, setDocType] = useState<string>('');
|
|
||||||
const [spaceConfig, setSpaceConfig] = useState<IStorage | null>(null);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const addKnowledgeSteps = [
|
|
||||||
{ title: t('Knowledge_Space_Config') },
|
|
||||||
{ title: t('Choose_a_Datasource_type') },
|
|
||||||
{ title: t('Upload') },
|
|
||||||
{ title: t('Segmentation') },
|
|
||||||
];
|
|
||||||
|
|
||||||
async function getSpaces() {
|
|
||||||
const [_, data] = await apiInterceptors(getSpaceList());
|
|
||||||
setSpaceList(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSpaceConfigs() {
|
|
||||||
const [_, data] = await apiInterceptors(getSpaceConfig());
|
|
||||||
if (!data) return null;
|
|
||||||
setSpaceConfig(data.storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getSpaces();
|
|
||||||
getSpaceConfigs();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleStepChange = ({ label, spaceName, docType = '', files, pace = 1 }: StepChangeParams) => {
|
|
||||||
if (label === 'finish') {
|
|
||||||
setIsAddShow(false);
|
|
||||||
getSpaces();
|
|
||||||
setSpaceName('');
|
|
||||||
setDocType('');
|
|
||||||
} else if (label === 'forward') {
|
|
||||||
activeStep === 0 && getSpaces();
|
|
||||||
setActiveStep((step) => step + pace);
|
|
||||||
} else {
|
|
||||||
setActiveStep((step) => step - pace);
|
|
||||||
}
|
|
||||||
files && setFiles(files);
|
|
||||||
spaceName && setSpaceName(spaceName);
|
|
||||||
docType && setDocType(docType);
|
|
||||||
};
|
|
||||||
|
|
||||||
function onAddDoc(spaceName: string) {
|
|
||||||
const space = spaceList?.find((item) => item?.name === spaceName);
|
|
||||||
setSpaceName(spaceName);
|
|
||||||
setActiveStep(space?.domain_type === 'FinancialReport' ? 2 : 1);
|
|
||||||
setIsAddShow(true);
|
|
||||||
if (space?.domain_type === 'FinancialReport') {
|
|
||||||
setDocType('DOCUMENT');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bg-[#FAFAFA] dark:bg-transparent w-full h-full">
|
|
||||||
<div className="page-body p-4 md:p-6 h-full overflow-auto">
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
className="flex items-center"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={() => {
|
|
||||||
setIsAddShow(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
<div className="flex flex-wrap mt-4 gap-2 md:gap-4">
|
|
||||||
{spaceList?.map((space: ISpace) => (
|
|
||||||
<SpaceCard key={space.id} space={space} onAddDoc={onAddDoc} getSpaces={getSpaces} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Modal
|
|
||||||
title="Add Knowledge"
|
|
||||||
centered
|
|
||||||
open={isAddShow}
|
|
||||||
destroyOnClose={true}
|
|
||||||
onCancel={() => {
|
|
||||||
setIsAddShow(false);
|
|
||||||
}}
|
|
||||||
width={1000}
|
|
||||||
afterClose={() => {
|
|
||||||
setActiveStep(0);
|
|
||||||
getSpaces();
|
|
||||||
}}
|
|
||||||
footer={null}
|
|
||||||
>
|
|
||||||
<Steps current={activeStep} items={addKnowledgeSteps} />
|
|
||||||
{activeStep === 0 && <SpaceForm handleStepChange={handleStepChange} spaceConfig={spaceConfig} />}
|
|
||||||
{activeStep === 1 && <DocTypeForm handleStepChange={handleStepChange} />}
|
|
||||||
<DocUploadForm
|
|
||||||
className={classNames({ hidden: activeStep !== 2 })}
|
|
||||||
spaceName={spaceName}
|
|
||||||
docType={docType}
|
|
||||||
handleStepChange={handleStepChange}
|
|
||||||
/>
|
|
||||||
{activeStep === 3 && <Segmentation spaceName={spaceName} docType={docType} uploadFiles={files} handleStepChange={handleStepChange} />}
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Knowledge;
|
|
@@ -1,62 +0,0 @@
|
|||||||
import { apiInterceptors, getModelList } from '@/client/api';
|
|
||||||
import ModelCard from '@/components/model/model-card';
|
|
||||||
import ModelForm from '@/components/model/model-form';
|
|
||||||
import { IModelData } from '@/types/model';
|
|
||||||
import { Button, Modal } from 'antd';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
function Models() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [models, setModels] = useState<Array<IModelData>>([]);
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
||||||
|
|
||||||
async function getModels() {
|
|
||||||
const [, res] = await apiInterceptors(getModelList());
|
|
||||||
setModels(res ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getModels();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-4 md:p-6 overflow-y-auto">
|
|
||||||
<Button
|
|
||||||
className="mb-4"
|
|
||||||
type="primary"
|
|
||||||
onClick={() => {
|
|
||||||
setIsModalOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('create_model')}
|
|
||||||
</Button>
|
|
||||||
<div className="flex flex-wrap gap-2 md:gap-4">
|
|
||||||
{models.map((item) => (
|
|
||||||
<ModelCard info={item} key={item.model_name} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<Modal
|
|
||||||
width={800}
|
|
||||||
open={isModalOpen}
|
|
||||||
title={t('create_model')}
|
|
||||||
onCancel={() => {
|
|
||||||
setIsModalOpen(false);
|
|
||||||
}}
|
|
||||||
footer={null}
|
|
||||||
>
|
|
||||||
<ModelForm
|
|
||||||
onCancel={() => {
|
|
||||||
setIsModalOpen(false);
|
|
||||||
}}
|
|
||||||
onSuccess={() => {
|
|
||||||
setIsModalOpen(false);
|
|
||||||
getModels();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Models;
|
|
@@ -1,181 +0,0 @@
|
|||||||
import { useState, useEffect, useRef, Ref } from 'react';
|
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
|
||||||
import type { FormInstance, MenuProps } from 'antd';
|
|
||||||
import { Menu, Table, Button, Tooltip, Modal } from 'antd';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import GroupsIcon from '@mui/icons-material/Groups';
|
|
||||||
import PersonIcon from '@mui/icons-material/Person';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { addPrompt, apiInterceptors, getPromptList, postScenes, updatePrompt } from '@/client/api';
|
|
||||||
import { IPrompt } from '@/types/prompt';
|
|
||||||
import PromptForm from '@/components/prompt/prompt-form';
|
|
||||||
import { TFunction } from 'i18next';
|
|
||||||
|
|
||||||
const getItems = (t: TFunction) => [
|
|
||||||
{
|
|
||||||
label: t('Public') + ' Prompts',
|
|
||||||
key: 'common',
|
|
||||||
icon: <GroupsIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('Private') + ' Prompts',
|
|
||||||
key: 'private',
|
|
||||||
icon: <PersonIcon />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const getColumns = (t: TFunction, handleEdit: (prompt: IPrompt) => void): ColumnsType<IPrompt> => [
|
|
||||||
{
|
|
||||||
title: t('Prompt_Info_Name'),
|
|
||||||
dataIndex: 'prompt_name',
|
|
||||||
key: 'prompt_name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('Prompt_Info_Scene'),
|
|
||||||
dataIndex: 'chat_scene',
|
|
||||||
key: 'chat_scene',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('Prompt_Info_Sub_Scene'),
|
|
||||||
dataIndex: 'sub_chat_scene',
|
|
||||||
key: 'sub_chat_scene',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('Prompt_Info_Content'),
|
|
||||||
dataIndex: 'content',
|
|
||||||
key: 'content',
|
|
||||||
render: (content) => (
|
|
||||||
<Tooltip placement="topLeft" title={content}>
|
|
||||||
{content}
|
|
||||||
</Tooltip>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('Operation'),
|
|
||||||
dataIndex: 'operate',
|
|
||||||
key: 'operate',
|
|
||||||
render: (_, record) => (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
handleEdit(record);
|
|
||||||
}}
|
|
||||||
type="primary"
|
|
||||||
>
|
|
||||||
{t('Edit')}
|
|
||||||
</Button>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
type FormType = Ref<FormInstance<any>> | undefined;
|
|
||||||
|
|
||||||
const Prompt = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [promptType, setPromptType] = useState<string>('common');
|
|
||||||
const [promptList, setPromptList] = useState<Array<IPrompt>>();
|
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
|
||||||
const [prompt, setPrompt] = useState<IPrompt>();
|
|
||||||
const [showModal, setShowModal] = useState<boolean>(false);
|
|
||||||
const [scenes, setScenes] = useState<Array<Record<string, string>>>();
|
|
||||||
const formRef = useRef<FormType>();
|
|
||||||
|
|
||||||
const getPrompts = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
const body = {
|
|
||||||
prompt_type: promptType,
|
|
||||||
current: 1,
|
|
||||||
pageSize: 1000,
|
|
||||||
hideOnSinglePage: true,
|
|
||||||
showQuickJumper: true,
|
|
||||||
};
|
|
||||||
const [_, data] = await apiInterceptors(getPromptList(body));
|
|
||||||
setPromptList(data!);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getScenes = async () => {
|
|
||||||
const [, res] = await apiInterceptors(postScenes());
|
|
||||||
setScenes(res?.map((scene) => ({ value: scene.chat_scene, label: scene.scene_name })));
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFinish = async (newPrompt: IPrompt) => {
|
|
||||||
if (prompt) {
|
|
||||||
await apiInterceptors(updatePrompt({ ...newPrompt, prompt_type: promptType }));
|
|
||||||
} else {
|
|
||||||
await apiInterceptors(addPrompt({ ...newPrompt, prompt_type: promptType }));
|
|
||||||
}
|
|
||||||
getPrompts();
|
|
||||||
handleClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditBtn = (prompt: IPrompt) => {
|
|
||||||
setPrompt(prompt);
|
|
||||||
setShowModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddBtn = () => {
|
|
||||||
setShowModal(true);
|
|
||||||
setPrompt(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setShowModal(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMenuChange: MenuProps['onClick'] = (e) => {
|
|
||||||
const type = e.key;
|
|
||||||
setPromptType(type);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getPrompts();
|
|
||||||
}, [promptType]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getScenes();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Menu onClick={handleMenuChange} selectedKeys={[promptType]} mode="horizontal" items={getItems(t)} />
|
|
||||||
<div className="px-6 py-4">
|
|
||||||
<div className="flex flex-row-reverse mb-4">
|
|
||||||
<Button className="flex items-center" onClick={handleAddBtn}>
|
|
||||||
<PlusOutlined />
|
|
||||||
{t('Add')} Prompts
|
|
||||||
</Button>
|
|
||||||
{promptType === 'common' && (
|
|
||||||
<Button className="mr-2 flex items-center" disabled>
|
|
||||||
<PlusOutlined />
|
|
||||||
{t('Add')} Prompts {t('template')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Table
|
|
||||||
columns={getColumns(t, handleEditBtn)}
|
|
||||||
dataSource={promptList}
|
|
||||||
loading={loading}
|
|
||||||
rowKey={(record) => record.prompt_name}
|
|
||||||
scroll={{ y: 600 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Modal
|
|
||||||
title={`${prompt ? t('Edit') : t('Add')} Prompts`}
|
|
||||||
destroyOnClose
|
|
||||||
open={showModal}
|
|
||||||
onCancel={handleClose}
|
|
||||||
cancelText={t('cancel')}
|
|
||||||
okText={t('submit')}
|
|
||||||
onOk={() => {
|
|
||||||
// @ts-ignore
|
|
||||||
formRef.current?.submit();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PromptForm scenes={scenes} ref={formRef as FormType} prompt={prompt} onFinish={onFinish} />
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Prompt;
|
|
@@ -1,6 +1,14 @@
|
|||||||
|
import { File } from 'buffer';
|
||||||
import { Node } from 'reactflow';
|
import { Node } from 'reactflow';
|
||||||
|
|
||||||
export type FlowState = 'deployed' | 'developing' | 'initializing' | 'testing' | 'disabled' | 'running' | 'load_failed';
|
export type FlowState =
|
||||||
|
| 'deployed'
|
||||||
|
| 'developing'
|
||||||
|
| 'initializing'
|
||||||
|
| 'testing'
|
||||||
|
| 'disabled'
|
||||||
|
| 'running'
|
||||||
|
| 'load_failed';
|
||||||
|
|
||||||
export type IFlowUpdateParam = {
|
export type IFlowUpdateParam = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -13,6 +21,21 @@ export type IFlowUpdateParam = {
|
|||||||
state?: FlowState;
|
state?: FlowState;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IFlowRefreshParams = {
|
||||||
|
id: string;
|
||||||
|
type_name: string;
|
||||||
|
type_cls: string;
|
||||||
|
flow_type: 'resource' | 'operator';
|
||||||
|
refresh: {
|
||||||
|
name: string;
|
||||||
|
depends?: Array<{
|
||||||
|
name: string;
|
||||||
|
value: any;
|
||||||
|
has_value: boolean;
|
||||||
|
}>;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
export type IFlow = {
|
export type IFlow = {
|
||||||
dag_id: string;
|
dag_id: string;
|
||||||
gmt_created: string;
|
gmt_created: string;
|
||||||
@@ -52,6 +75,25 @@ export type IFlowNodeParameter = {
|
|||||||
options?: any;
|
options?: any;
|
||||||
value: any;
|
value: any;
|
||||||
is_list?: boolean;
|
is_list?: boolean;
|
||||||
|
ui: IFlowNodeParameterUI;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IFlowNodeParameterUI = {
|
||||||
|
ui_type: string;
|
||||||
|
language: string;
|
||||||
|
file_types: string;
|
||||||
|
action: string;
|
||||||
|
attr: {
|
||||||
|
disabled: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
editor?: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
show_input?: boolean;
|
||||||
|
refresh?: boolean;
|
||||||
|
refresh_depends?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IFlowNodeInput = {
|
export type IFlowNodeInput = {
|
||||||
@@ -139,7 +181,29 @@ export type IFlowData = {
|
|||||||
viewport: IFlowDataViewport;
|
viewport: IFlowDataViewport;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface UpdateFLowAdminsParams {
|
export type IFlowExportParams = {
|
||||||
uid: string;
|
uid: string;
|
||||||
admins: string[];
|
export_type?: 'json' | 'dbgpts';
|
||||||
}
|
format?: 'json' | 'file';
|
||||||
|
file_name?: string;
|
||||||
|
user_name?: string;
|
||||||
|
sys_code?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IFlowImportParams = {
|
||||||
|
file: File;
|
||||||
|
save_flow?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IUploadFileRequestParams = {
|
||||||
|
files: Array<File>;
|
||||||
|
user_name?: string;
|
||||||
|
sys_code?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IUploadFileResponse = {
|
||||||
|
file_name: string;
|
||||||
|
file_id: string;
|
||||||
|
bucket: string;
|
||||||
|
uri?: string;
|
||||||
|
};
|
||||||
|
@@ -11,6 +11,12 @@ export const getUniqueNodeId = (nodeData: IFlowNode, nodes: Node[]) => {
|
|||||||
return `${nodeData.id}_${count}`;
|
return `${nodeData.id}_${count}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// function getUniqueNodeId will add '_${count}' to id, so we need to remove it when we want to get the original id
|
||||||
|
export const removeIndexFromNodeId = (id: string) => {
|
||||||
|
const indexPattern = /_\d+$/;
|
||||||
|
return id.replace(indexPattern, '');
|
||||||
|
};
|
||||||
|
|
||||||
// 驼峰转下划线,接口协议字段命名规范
|
// 驼峰转下划线,接口协议字段命名规范
|
||||||
export const mapHumpToUnderline = (flowData: IFlowData) => {
|
export const mapHumpToUnderline = (flowData: IFlowData) => {
|
||||||
/**
|
/**
|
||||||
@@ -98,3 +104,31 @@ export const checkFlowDataRequied = (flowData: IFlowData) => {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const convertKeysToCamelCase = (obj: Record<string, any>): Record<string, any> => {
|
||||||
|
function toCamelCase(str: string): string {
|
||||||
|
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObject(value: any): boolean {
|
||||||
|
return value && typeof value === 'object' && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function convert(obj: any): any {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map((item) => convert(item));
|
||||||
|
} else if (isObject(obj)) {
|
||||||
|
const newObj: Record<string, any> = {};
|
||||||
|
for (const key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
const newKey = toCamelCase(key);
|
||||||
|
newObj[newKey] = convert(obj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newObj;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
return convert(obj);
|
||||||
|
};
|
Reference in New Issue
Block a user