Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/global/support/user/login/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export type TrackRegisterParams = {
inviterId?: string;
bd_vid?: string;
fastgpt_sem?: {
keyword: string;
keyword?: string;
shortUrlSource?: string;
shortUrlMedium?: string;
shortUrlContent?: string;
};
sourceDomain?: string;
};
Expand Down
5 changes: 5 additions & 0 deletions packages/web/i18n/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,14 @@
"type.Http plugin": "HTTP Plugin",
"type.Import from json": "Import JSON",
"type.Import from json tip": "Create applications directly through JSON configuration files",
"type.Import from json_error": "Failed to get workflow data, please check the URL or manually paste the JSON data",
"type.Import from json_loading": "Workflow data is being retrieved, please wait...",
"type.Plugin": "Plugin",
"type.Simple bot": "Simple App",
"type.Workflow bot": "Workflow",
"type.error.URLempty": "The URL cannot be empty",
"type.error.Workflow data is empty": "No workflow data was obtained",
"type.error.workflowresponseempty": "Response content is empty",
"type_not_recognized": "App type not recognized",
"upload_file_max_amount": "Maximum File Quantity",
"upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation",
Expand Down
5 changes: 5 additions & 0 deletions packages/web/i18n/zh-CN/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,16 @@
"type.Http plugin": "HTTP 插件",
"type.Import from json": "导入 JSON 配置",
"type.Import from json tip": "通过 JSON 配置文件,直接创建应用",
"type.Import from json_error": "获取工作流数据失败,请检查URL或手动粘贴JSON数据",
"type.Import from json_loading": "正在获取工作流数据,请稍候...",
"type.MCP tools": "MCP 工具集",
"type.MCP_tools_url": "MCP 地址",
"type.Plugin": "插件",
"type.Simple bot": "简易应用",
"type.Workflow bot": "工作流",
"type.error.URLempty": "URL不能为空",
"type.error.Workflow data is empty": "没有获取到工作流数据",
"type.error.workflowresponseempty": "响应内容为空",
"type_not_recognized": "未识别到应用类型",
"upload_file_max_amount": "最大文件数量",
"upload_file_max_amount_tip": "单轮对话中最大上传文件数量",
Expand Down
5 changes: 5 additions & 0 deletions packages/web/i18n/zh-Hant/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,14 @@
"type.Http plugin": "HTTP 外掛",
"type.Import from json": "匯入 JSON 設定",
"type.Import from json tip": "透過 JSON 設定文件,直接建立應用",
"type.Import from json_error": "獲取工作流數據失敗,請檢查URL或手動粘貼JSON數據",
"type.Import from json_loading": "正在獲取工作流數據,請稍候...",
"type.Plugin": "外掛",
"type.Simple bot": "簡易應用程式",
"type.Workflow bot": "工作流程",
"type.error.URLempty": "URL不能為空",
"type.error.Workflow data is empty": "沒有獲取到工作流數據",
"type.error.workflowresponseempty": "響應內容為空",
"type_not_recognized": "未識別到應用程式類型",
"upload_file_max_amount": "最大檔案數量",
"upload_file_max_amount_tip": "單輪對話中最大上傳檔案數量",
Expand Down
68 changes: 65 additions & 3 deletions projects/app/src/pageComponents/dashboard/apps/JsonImportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { appTypeMap } from '@/pageComponents/app/constants';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useMemo } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { getAppType } from '@fastgpt/global/core/app/utils';
import { useContextSelector } from 'use-context-selector';
import { AppListContext } from './context';
Expand All @@ -16,17 +16,35 @@ import { postCreateApp } from '@/web/core/app/api';
import { useRouter } from 'next/router';
import { form2AppWorkflow } from '@/web/core/app/utils';
import ImportAppConfigEditor from '@/pageComponents/app/ImportAppConfigEditor';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getFetchWorkflow } from '@/web/core/app/api/app';

type FormType = {
avatar: string;
name: string;
workflowStr: string;
};

type UTMParams = {
source?: string;
medium?: string;
content?: string;
};

const getUtmParams = () => {
try {
const params = JSON.parse(sessionStorage.getItem('utm_params') || '{}');
return params as UTMParams;
} catch (error) {
return {} as UTMParams;
}
};

const JsonImportModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v);
const router = useRouter();
const { toast } = useToast();

const { register, setValue, watch, handleSubmit } = useForm<FormType>({
defaultValues: {
Expand All @@ -37,6 +55,43 @@ const JsonImportModal = ({ onClose }: { onClose: () => void }) => {
});
const workflowStr = watch('workflowStr');

const { runAsync: fetchWorkflow, loading: isFetching } = useRequest2(
async (url?: string) => {
if (!url) return Promise.reject(t('app:type.error.URLempty'));

let fetchUrl = url.trim();
if (fetchUrl.endsWith('/')) fetchUrl = fetchUrl.slice(0, -1);
if (!fetchUrl.startsWith('http')) fetchUrl = `https://${fetchUrl}`;

return getFetchWorkflow({ url: fetchUrl });
},
{ manual: false }
);

useEffect(() => {
const url = sessionStorage.getItem('utm_workflow');
if (!url) return;

toast({ title: t('app:type.Import from json_loading'), status: 'info' });

fetchWorkflow(url)
.then((workflowData) => {
if (!workflowData) return Promise.reject(t('app:type.error.Workflow data is empty'));

setValue('workflowStr', JSON.stringify(workflowData, null, 2));

const utmParams = getUtmParams();
if (utmParams.content) setValue('name', utmParams.content);

sessionStorage.removeItem('utm_params');
sessionStorage.removeItem('utm_workflow');
})
.catch(() => {
toast({ title: t('app:type.Import from json_error'), status: 'error' });
onClose();
});
}, [fetchWorkflow, onClose, t, toast]);

const avatar = watch('avatar');
const {
File,
Expand Down Expand Up @@ -65,6 +120,9 @@ const JsonImportModal = ({ onClose }: { onClose: () => void }) => {

const { runAsync: onSubmit, loading: isCreating } = useRequest2(
async ({ name, workflowStr }: FormType) => {
// 处理UTM参数
const utmParams = getUtmParams();

const { workflow, appType } = await (async () => {
try {
const workflow = JSON.parse(workflowStr);
Expand Down Expand Up @@ -97,7 +155,11 @@ const JsonImportModal = ({ onClose }: { onClose: () => void }) => {
type: appType,
modules: workflow.nodes,
edges: workflow.edges,
chatConfig: workflow.chatConfig
chatConfig: workflow.chatConfig,
utmParams: {
utm_platform: utmParams?.medium,
utm_projectcode: utmParams?.content
}
});
},
{
Expand All @@ -116,7 +178,7 @@ const JsonImportModal = ({ onClose }: { onClose: () => void }) => {
<MyModal
isOpen
onClose={onClose}
isLoading={isCreating}
isLoading={isCreating || isFetching}
title={t('app:type.Import from json')}
iconSrc="common/importLight"
iconColor={'primary.600'}
Expand Down
13 changes: 10 additions & 3 deletions projects/app/src/pages/api/core/app/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ export type CreateAppBody = {
modules: AppSchema['modules'];
edges?: AppSchema['edges'];
chatConfig?: AppSchema['chatConfig'];
utmParams?: {
utm_platform?: string;
utm_projectcode?: string;
};
};

async function handler(req: ApiRequestProps<CreateAppBody>) {
const { parentId, name, avatar, type, modules, edges, chatConfig } = req.body;
const { parentId, name, avatar, type, modules, edges, chatConfig, utmParams } = req.body;

if (!name || !type || !Array.isArray(modules)) {
return Promise.reject(CommonErrEnum.inheritPermissionError);
Expand Down Expand Up @@ -66,8 +70,11 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
type,
uid: userId,
teamId,
tmbId
});
tmbId,
appId,
shorUrlPlatform: utmParams?.utm_platform,
shorUrlProjectCode: utmParams?.utm_projectcode
} as any);

return appId;
}
Expand Down
51 changes: 51 additions & 0 deletions projects/app/src/pages/api/core/app/fetchWorkflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import axios from 'axios';
import { authCert } from '@fastgpt/service/support/permission/auth/common';

export type FetchWorkflowBody = {
url: string;
};

export type FetchWorkflowQuery = {
url: string;
};

export type FetchWorkflowResponseType = ApiResponseType<{
data: JSON;
}>;

async function handler(
req: ApiRequestProps<FetchWorkflowBody, FetchWorkflowQuery>,
res: FetchWorkflowResponseType
) {
await authCert({ req, authToken: true });

const url = req.body?.url || req.query?.url;

if (!url) {
return Promise.reject('app:type.error.URLempty');
}

const response = await axios.get(url, {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 (compatible; FastGPT/1.0)'
},
timeout: 30000,
validateStatus: (status) => status < 500
});

const contentType = response.headers['content-type'] || '';

if (!response.data || response.data.length === 0) {
return Promise.reject('app:type.error.workflowresponseempty');
}

JSON.parse(JSON.stringify(response.data));

return response.data;
}

export default NextAPI(handler);
10 changes: 9 additions & 1 deletion projects/app/src/pages/dashboard/apps/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useMemo, useState, useEffect } from 'react';
import { Box, Flex, Button, useDisclosure, Input, InputGroup } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { serviceSideProps } from '@/web/common/i18n/utils';
Expand Down Expand Up @@ -78,6 +78,14 @@ const MyApps = ({ MenuIcon }: { MenuIcon: JSX.Element }) => {
} = useDisclosure();
const [editFolder, setEditFolder] = useState<EditFolderFormType>();

//if there is a workflow url in the session storage, open the json import modal and import the workflow
useEffect(() => {
const hasWorkflowUrl = !!sessionStorage.getItem('utm_workflow');
if (hasWorkflowUrl) {
onOpenJsonImportModal();
}
}, [onOpenJsonImportModal]);

const { runAsync: onCreateFolder } = useRequest2(postCreateAppFolder, {
onSuccess() {
loadMyApps();
Expand Down
2 changes: 1 addition & 1 deletion projects/app/src/pages/login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const Login = ({ ChineseRedirectUrl }: { ChineseRedirectUrl: string }) => {
setUserInfo(res.user);

const decodeLastRoute = decodeURIComponent(lastRoute);
// 检查是否是当前的 route

const navigateTo =
decodeLastRoute && !decodeLastRoute.includes('/login')
? decodeLastRoute
Expand Down
35 changes: 27 additions & 8 deletions projects/app/src/web/context/useInitApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ import { useUserStore } from '../support/user/useUserStore';

export const useInitApp = () => {
const router = useRouter();
const { hiId, bd_vid, k, sourceDomain } = router.query as {
hiId?: string;
bd_vid?: string;
k?: string;
sourceDomain?: string;
};
const { hiId, bd_vid, k, sourceDomain, utm_source, utm_medium, utm_content, utm_workflow } =
router.query as {
hiId?: string;
bd_vid?: string;
k?: string;
sourceDomain?: string;
utm_source?: string;
utm_medium?: string;
utm_content?: string;
utm_workflow?: string;
};
const { loadGitStar, setInitd, feConfigs } = useSystemStore();
const { userInfo } = useUserStore();
const [scripts, setScripts] = useState<FastGPTFeConfigsType['scripts']>([]);
Expand Down Expand Up @@ -72,7 +77,21 @@ export const useInitApp = () => {
useEffect(() => {
hiId && localStorage.setItem('inviterId', hiId);
bd_vid && sessionStorage.setItem('bd_vid', bd_vid);
k && sessionStorage.setItem('fastgpt_sem', JSON.stringify({ keyword: k }));
utm_workflow && sessionStorage.setItem('utm_workflow', utm_workflow);

try {
const utmParams: Record<string, any> = {};
if (utm_source) utmParams.source = utm_source;
if (utm_medium) utmParams.medium = utm_medium;
if (utm_content) utmParams.content = utm_content;

if (Object.keys(utmParams).length > 0) {
sessionStorage.setItem('utm_params', JSON.stringify(utmParams));
}
k && sessionStorage.setItem('fastgpt_sem', JSON.stringify({ keyword: k, ...utmParams }));
} catch (error) {
console.error('处理UTM参数出错:', error);
}

const formatSourceDomain = (() => {
if (sourceDomain) return sourceDomain;
Expand All @@ -82,7 +101,7 @@ export const useInitApp = () => {
if (formatSourceDomain && !sessionStorage.getItem('sourceDomain')) {
sessionStorage.setItem('sourceDomain', formatSourceDomain);
}
}, [bd_vid, hiId, k, sourceDomain]);
}, [bd_vid, hiId, k, utm_content, utm_medium, utm_source, utm_workflow, sourceDomain]);

return {
feConfigs,
Expand Down
7 changes: 7 additions & 0 deletions projects/app/src/web/core/app/api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import type {
} from '@/pages/api/core/app/transitionWorkflow';
import type { copyAppQuery, copyAppResponse } from '@/pages/api/core/app/copy';

import type {
FetchWorkflowQuery,
FetchWorkflowResponseType
} from '@/pages/api/core/app/fetchWorkflow';
/* folder */
export const postCreateAppFolder = (data: CreateAppFolderBody) =>
POST('/core/app/folder/create', data);
Expand All @@ -25,3 +29,6 @@ export const postTransition2Workflow = (data: transitionWorkflowBody) =>
POST<transitionWorkflowResponse>('/core/app/transitionWorkflow', data);

export const postCopyApp = (data: copyAppQuery) => POST<copyAppResponse>('/core/app/copy', data);

export const getFetchWorkflow = (data: FetchWorkflowQuery) =>
GET<FetchWorkflowResponseType>('/core/app/fetchWorkflow', data);
Loading