mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-27 00:17:31 +00:00
4.8.11 test fix (#2746)
* fix: refresh tool param * perf: load image message * fix: cannot remoce externalReadUrl * perf: variables update dom * perf: empty response tip * fix: conflict
This commit is contained in:
@@ -114,7 +114,7 @@ ${content}
|
|||||||
它接收一个`string`类型的输入,除了可以引用文档解析结果外,还可以实现自定义内容引用,最终会进行提示词拼接,放置在 role=system 的消息中。提示词模板如下:
|
它接收一个`string`类型的输入,除了可以引用文档解析结果外,还可以实现自定义内容引用,最终会进行提示词拼接,放置在 role=system 的消息中。提示词模板如下:
|
||||||
|
|
||||||
```
|
```
|
||||||
将 <Quote></Quote> 中的内容作为本次对话的参考内容:
|
将 <Quote></Quote> 中的内容作为本次对话的参考:
|
||||||
<Quote>
|
<Quote>
|
||||||
{{quote}}
|
{{quote}}
|
||||||
</Quote>
|
</Quote>
|
||||||
|
@@ -227,7 +227,7 @@ curl --location --request POST '{{host}}/shareAuth/finish' \
|
|||||||
"historyPreview": [
|
"historyPreview": [
|
||||||
{
|
{
|
||||||
"obj": "Human",
|
"obj": "Human",
|
||||||
"value": "使用 <Data></Data> 标记中的内容作为本次对话的参考内容:\n\n<Data>\n导演是谁?\n电影《铃芽之旅》的导演是新海诚。\n------\n电影《铃芽之旅》的编剧是谁?22\n新海诚是本片的编剧。\n------\n电影《铃芽之旅》的女主角是谁?\n电影的女主角是铃芽。\n------\n电影《铃芽之旅》的制作团队中有哪位著名人士?2\n川村元气是本片的制作团队成员之一。\n------\n你是谁?\n我是电影《铃芽之旅》助手\n------\n电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。\n------\n电影《铃芽之旅》的作者新海诚写了一本小说,叫什么名字?\n小说名字叫《铃芽之旅》。\n------\n电影《铃芽之旅》的女主角是谁?\n电影《铃芽之旅》的女主角是岩户铃芽,由原菜乃华配音。\n------\n电影《铃芽之旅》的故事背景是什么?\n日本\n------\n谁担任电影《铃芽之旅》中岩户环的配音?\n深津绘里担任电影《铃芽之旅》中岩户环的配音。\n</Data>\n\n回答要求:\n- 如果你不清楚答案,你需要澄清。\n- 避免提及你是从 <Data></Data> 获取的知识。\n- 保持答案与 <Data></Data> 中描述的一致。\n- 使用 Markdown 语法优化回答格式。\n- 使用与问题相同的语言回答。\n\n问题:\"\"\"导演是谁\"\"\""
|
"value": "使用 <Data></Data> 标记中的内容作为本次对话的参考:\n\n<Data>\n导演是谁?\n电影《铃芽之旅》的导演是新海诚。\n------\n电影《铃芽之旅》的编剧是谁?22\n新海诚是本片的编剧。\n------\n电影《铃芽之旅》的女主角是谁?\n电影的女主角是铃芽。\n------\n电影《铃芽之旅》的制作团队中有哪位著名人士?2\n川村元气是本片的制作团队成员之一。\n------\n你是谁?\n我是电影《铃芽之旅》助手\n------\n电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。\n------\n电影《铃芽之旅》的作者新海诚写了一本小说,叫什么名字?\n小说名字叫《铃芽之旅》。\n------\n电影《铃芽之旅》的女主角是谁?\n电影《铃芽之旅》的女主角是岩户铃芽,由原菜乃华配音。\n------\n电影《铃芽之旅》的故事背景是什么?\n日本\n------\n谁担任电影《铃芽之旅》中岩户环的配音?\n深津绘里担任电影《铃芽之旅》中岩户环的配音。\n</Data>\n\n回答要求:\n- 如果你不清楚答案,你需要澄清。\n- 避免提及你是从 <Data></Data> 获取的知识。\n- 保持答案与 <Data></Data> 中描述的一致。\n- 使用 Markdown 语法优化回答格式。\n- 使用与问题相同的语言回答。\n\n问题:\"\"\"导演是谁\"\"\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"obj": "AI",
|
"obj": "AI",
|
||||||
|
@@ -965,7 +965,7 @@ export default async function (ctx: FunctionContext) {
|
|||||||
"required": true,
|
"required": true,
|
||||||
"description": "",
|
"description": "",
|
||||||
"canEdit": false,
|
"canEdit": false,
|
||||||
"value": "请使用下面<data> </data>中的数据作为本次对话的参考内容。请直接输出答案,不要提及你是从<data> </data>中获取的知识。\n\n当前时间:{{cTime}}\n\n<data>\n{{response}}\n</data>\n\n我的问题:\"{{q}}\"",
|
"value": "请使用下面<data> </data>中的数据作为本次对话的参考。请直接输出答案,不要提及你是从<data> </data>中获取的知识。\n\n当前时间:{{cTime}}\n\n<data>\n{{response}}\n</data>\n\n我的问题:\"{{q}}\"",
|
||||||
"editField": {
|
"editField": {
|
||||||
"key": true
|
"key": true
|
||||||
},
|
},
|
||||||
|
@@ -48,7 +48,7 @@ export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
|
|||||||
{
|
{
|
||||||
title: i18nT('app:template.standard_template'),
|
title: i18nT('app:template.standard_template'),
|
||||||
desc: '',
|
desc: '',
|
||||||
value: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考内容:
|
value: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
|
||||||
|
|
||||||
<Reference>
|
<Reference>
|
||||||
{{quote}}
|
{{quote}}
|
||||||
@@ -83,7 +83,7 @@ export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
|
|||||||
{
|
{
|
||||||
title: i18nT('app:template.standard_strict'),
|
title: i18nT('app:template.standard_strict'),
|
||||||
desc: '',
|
desc: '',
|
||||||
value: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考内容:
|
value: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
|
||||||
|
|
||||||
<Reference>
|
<Reference>
|
||||||
{{quote}}
|
{{quote}}
|
||||||
@@ -133,7 +133,7 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
|
|||||||
{
|
{
|
||||||
title: i18nT('app:template.standard_template'),
|
title: i18nT('app:template.standard_template'),
|
||||||
desc: '',
|
desc: '',
|
||||||
value: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考内容:
|
value: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
|
||||||
|
|
||||||
<Reference>
|
<Reference>
|
||||||
{{quote}}
|
{{quote}}
|
||||||
@@ -164,7 +164,7 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
|
|||||||
{
|
{
|
||||||
title: i18nT('app:template.standard_strict'),
|
title: i18nT('app:template.standard_strict'),
|
||||||
desc: '',
|
desc: '',
|
||||||
value: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考内容:
|
value: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
|
||||||
|
|
||||||
<Reference>
|
<Reference>
|
||||||
{{quote}}
|
{{quote}}
|
||||||
@@ -207,7 +207,7 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Document quote prompt
|
// Document quote prompt
|
||||||
export const Prompt_DocumentQuote = `将 <Reference></Reference> 中的内容作为本次对话的参考内容:
|
export const Prompt_DocumentQuote = `将 <Reference></Reference> 中的内容作为本次对话的参考:
|
||||||
<Reference>
|
<Reference>
|
||||||
{{quote}}
|
{{quote}}
|
||||||
</Reference>
|
</Reference>
|
||||||
|
@@ -8,6 +8,8 @@ import axios from 'axios';
|
|||||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||||
import { getFileContentTypeFromHeader, guessBase64ImageType } from '../../common/file/utils';
|
import { getFileContentTypeFromHeader, guessBase64ImageType } from '../../common/file/utils';
|
||||||
import { serverRequestBaseUrl } from '../../common/api/serverRequest';
|
import { serverRequestBaseUrl } from '../../common/api/serverRequest';
|
||||||
|
import { i18nT } from '../../../web/i18n/utils';
|
||||||
|
import { addLog } from '../../common/system/log';
|
||||||
|
|
||||||
/* slice chat context by tokens */
|
/* slice chat context by tokens */
|
||||||
const filterEmptyMessages = (messages: ChatCompletionMessageParam[]) => {
|
const filterEmptyMessages = (messages: ChatCompletionMessageParam[]) => {
|
||||||
@@ -111,20 +113,62 @@ export const loadRequestMessages = async ({
|
|||||||
useVision?: boolean;
|
useVision?: boolean;
|
||||||
origin?: string;
|
origin?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
// Load image to base64
|
||||||
|
const loadImageToBase64 = async (messages: ChatCompletionContentPart[]) => {
|
||||||
|
return Promise.all(
|
||||||
|
messages.map(async (item) => {
|
||||||
|
if (item.type === 'image_url') {
|
||||||
|
// Remove url origin
|
||||||
|
const imgUrl = (() => {
|
||||||
|
if (origin && item.image_url.url.startsWith(origin)) {
|
||||||
|
return item.image_url.url.replace(origin, '');
|
||||||
|
}
|
||||||
|
return item.image_url.url;
|
||||||
|
})();
|
||||||
|
|
||||||
|
// If imgUrl is a local path, load image from local, and set url to base64
|
||||||
|
if (imgUrl.startsWith('/') || process.env.VISION_FOCUS_BASE64 === 'true') {
|
||||||
|
addLog.debug('Load image from local server', {
|
||||||
|
baseUrl: serverRequestBaseUrl,
|
||||||
|
requestUrl: imgUrl
|
||||||
|
});
|
||||||
|
const response = await axios.get(imgUrl, {
|
||||||
|
baseURL: serverRequestBaseUrl,
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
proxy: false
|
||||||
|
});
|
||||||
|
const base64 = Buffer.from(response.data, 'binary').toString('base64');
|
||||||
|
const imageType =
|
||||||
|
getFileContentTypeFromHeader(response.headers['content-type']) ||
|
||||||
|
guessBase64ImageType(base64);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
image_url: {
|
||||||
|
...item.image_url,
|
||||||
|
url: `data:${imageType};base64,${base64}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
// Split question text and image
|
// Split question text and image
|
||||||
function parseStringWithImages(input: string): ChatCompletionContentPart[] {
|
const parseStringWithImages = (input: string): ChatCompletionContentPart[] => {
|
||||||
if (!useVision) {
|
if (!useVision) {
|
||||||
return [{ type: 'text', text: input || '' }];
|
return [{ type: 'text', text: input || '' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 正则表达式匹配图片URL
|
// 正则表达式匹配图片URL
|
||||||
const imageRegex =
|
const imageRegex =
|
||||||
/(https?:\/\/[^\s/$.?#].[^\s]*\.(?:png|jpe?g|gif|webp|bmp|tiff?|svg|ico|heic|avif))/i;
|
/(https?:\/\/[^\s/$.?#].[^\s]*\.(?:png|jpe?g|gif|webp|bmp|tiff?|svg|ico|heic|avif))/gi;
|
||||||
|
|
||||||
const result: ChatCompletionContentPart[] = [];
|
const result: ChatCompletionContentPart[] = [];
|
||||||
|
|
||||||
// 提取所有HTTPS图片URL并添加到result开头
|
// 提取所有HTTPS图片URL并添加到result开头
|
||||||
const httpsImages = input.match(imageRegex) || [];
|
const httpsImages = [...new Set(Array.from(input.matchAll(imageRegex), (m) => m[0]))];
|
||||||
httpsImages.forEach((url) => {
|
httpsImages.forEach((url) => {
|
||||||
result.push({
|
result.push({
|
||||||
type: 'image_url',
|
type: 'image_url',
|
||||||
@@ -137,54 +181,27 @@ export const loadRequestMessages = async ({
|
|||||||
// 添加原始input作为文本
|
// 添加原始input作为文本
|
||||||
result.push({ type: 'text', text: input });
|
result.push({ type: 'text', text: input });
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
// Load image
|
// Parse user content(text and img)
|
||||||
const parseUserContent = async (content: string | ChatCompletionContentPart[]) => {
|
const parseUserContent = async (content: string | ChatCompletionContentPart[]) => {
|
||||||
if (typeof content === 'string') {
|
if (typeof content === 'string') {
|
||||||
return parseStringWithImages(content);
|
return loadImageToBase64(parseStringWithImages(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await Promise.all(
|
const result = await Promise.all(
|
||||||
content.map(async (item) => {
|
content.map(async (item) => {
|
||||||
if (item.type === 'text') return parseStringWithImages(item.text);
|
if (item.type === 'text') return parseStringWithImages(item.text);
|
||||||
if (item.type === 'file_url') return;
|
if (item.type === 'file_url') return; // LLM not support file_url
|
||||||
|
|
||||||
if (!item.image_url.url) return item;
|
if (!item.image_url.url) return item;
|
||||||
|
|
||||||
// Remove url origin
|
|
||||||
const imgUrl = (() => {
|
|
||||||
if (origin && item.image_url.url.startsWith(origin)) {
|
|
||||||
return item.image_url.url.replace(origin, '');
|
|
||||||
}
|
|
||||||
return item.image_url.url;
|
|
||||||
})();
|
|
||||||
|
|
||||||
/* Load local image */
|
|
||||||
if (imgUrl.startsWith('/')) {
|
|
||||||
const response = await axios.get(imgUrl, {
|
|
||||||
baseURL: serverRequestBaseUrl,
|
|
||||||
responseType: 'arraybuffer'
|
|
||||||
});
|
|
||||||
const base64 = Buffer.from(response.data, 'binary').toString('base64');
|
|
||||||
const imageType =
|
|
||||||
getFileContentTypeFromHeader(response.headers['content-type']) ||
|
|
||||||
guessBase64ImageType(base64);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
image_url: {
|
|
||||||
...item.image_url,
|
|
||||||
url: `data:${imageType};base64,${base64}`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.flat().filter(Boolean);
|
return loadImageToBase64(result.flat().filter(Boolean) as ChatCompletionContentPart[]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// format GPT messages, concat text messages
|
// format GPT messages, concat text messages
|
||||||
const clearInvalidMessages = (messages: ChatCompletionMessageParam[]) => {
|
const clearInvalidMessages = (messages: ChatCompletionMessageParam[]) => {
|
||||||
return messages
|
return messages
|
||||||
@@ -247,7 +264,7 @@ export const loadRequestMessages = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
return Promise.reject('core.chat.error.Messages empty');
|
return Promise.reject(i18nT('common:core.chat.error.Messages empty'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter messages file
|
// filter messages file
|
||||||
@@ -275,6 +292,7 @@ export const loadRequestMessages = async ({
|
|||||||
content: await parseUserContent(item.content)
|
content: await parseUserContent(item.content)
|
||||||
};
|
};
|
||||||
} else if (item.role === ChatCompletionRequestMessageRoleEnum.Assistant) {
|
} else if (item.role === ChatCompletionRequestMessageRoleEnum.Assistant) {
|
||||||
|
// remove invalid field
|
||||||
return {
|
return {
|
||||||
role: item.role,
|
role: item.role,
|
||||||
content: item.content,
|
content: item.content,
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
"input_guide": "Input Guide",
|
"input_guide": "Input Guide",
|
||||||
"input_guide_lexicon": "Lexicon",
|
"input_guide_lexicon": "Lexicon",
|
||||||
"input_guide_tip": "You can set up some preset questions. When the user inputs a question, related questions from these presets will be suggested.",
|
"input_guide_tip": "You can set up some preset questions. When the user inputs a question, related questions from these presets will be suggested.",
|
||||||
|
"input_placeholder_phone": "Please enter your question",
|
||||||
"insert_input_guide,_some_data_already_exists": "Duplicate data detected, automatically filtered, {{len}} items inserted",
|
"insert_input_guide,_some_data_already_exists": "Duplicate data detected, automatically filtered, {{len}} items inserted",
|
||||||
"is_chatting": "Chatting in progress... please wait until it finishes",
|
"is_chatting": "Chatting in progress... please wait until it finishes",
|
||||||
"items": "Items",
|
"items": "Items",
|
||||||
@@ -36,4 +37,4 @@
|
|||||||
"stream_output": "Stream Output",
|
"stream_output": "Stream Output",
|
||||||
"view_citations": "View References",
|
"view_citations": "View References",
|
||||||
"web_site_sync": "Web Site Sync"
|
"web_site_sync": "Web Site Sync"
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
"input_guide": "输入引导",
|
"input_guide": "输入引导",
|
||||||
"input_guide_lexicon": "词库",
|
"input_guide_lexicon": "词库",
|
||||||
"input_guide_tip": "可以配置一些预设的问题。在用户输入问题时,会从这些预设问题中获取相关问题进行提示。",
|
"input_guide_tip": "可以配置一些预设的问题。在用户输入问题时,会从这些预设问题中获取相关问题进行提示。",
|
||||||
|
"input_placeholder_phone": "输入问题",
|
||||||
"insert_input_guide,_some_data_already_exists": "有重复数据,已自动过滤,共插入 {{len}} 条数据",
|
"insert_input_guide,_some_data_already_exists": "有重复数据,已自动过滤,共插入 {{len}} 条数据",
|
||||||
"is_chatting": "正在聊天中...请等待结束",
|
"is_chatting": "正在聊天中...请等待结束",
|
||||||
"items": "条",
|
"items": "条",
|
||||||
@@ -36,4 +37,4 @@
|
|||||||
"stream_output": "流输出",
|
"stream_output": "流输出",
|
||||||
"view_citations": "查看引用",
|
"view_citations": "查看引用",
|
||||||
"web_site_sync": "Web站点同步"
|
"web_site_sync": "Web站点同步"
|
||||||
}
|
}
|
||||||
|
@@ -449,7 +449,11 @@ const ChatInput = ({
|
|||||||
border: 'none'
|
border: 'none'
|
||||||
}}
|
}}
|
||||||
placeholder={
|
placeholder={
|
||||||
isSpeaking ? t('common:core.chat.Speaking') : t('common:core.chat.Type a message')
|
isSpeaking
|
||||||
|
? t('common:core.chat.Speaking')
|
||||||
|
: isPc
|
||||||
|
? t('common:core.chat.Type a message')
|
||||||
|
: t('chat:input_placeholder_phone')
|
||||||
}
|
}
|
||||||
resize={'none'}
|
resize={'none'}
|
||||||
rows={1}
|
rows={1}
|
||||||
|
@@ -174,7 +174,7 @@ const ChatItem = (props: Props) => {
|
|||||||
|
|
||||||
// Check last group is interactive, Auto add a empty text node(animation)
|
// Check last group is interactive, Auto add a empty text node(animation)
|
||||||
const lastGroup = groupedValues[groupedValues.length - 1];
|
const lastGroup = groupedValues[groupedValues.length - 1];
|
||||||
if (isChatting) {
|
if (isChatting || groupedValues.length === 0) {
|
||||||
if (
|
if (
|
||||||
(lastGroup &&
|
(lastGroup &&
|
||||||
lastGroup[lastGroup.length - 1] &&
|
lastGroup[lastGroup.length - 1] &&
|
||||||
|
@@ -72,7 +72,7 @@ async function handler(
|
|||||||
...(websiteConfig && { websiteConfig }),
|
...(websiteConfig && { websiteConfig }),
|
||||||
...(status && { status }),
|
...(status && { status }),
|
||||||
...(intro !== undefined && { intro }),
|
...(intro !== undefined && { intro }),
|
||||||
...(externalReadUrl && { externalReadUrl }),
|
...(externalReadUrl !== undefined && { externalReadUrl }),
|
||||||
// move
|
// move
|
||||||
...(updatedDefaultPermission !== undefined && {
|
...(updatedDefaultPermission !== undefined && {
|
||||||
defaultPermission: updatedDefaultPermission
|
defaultPermission: updatedDefaultPermission
|
||||||
|
@@ -266,7 +266,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
workflowStreamResponse: workflowResponseWrite
|
workflowStreamResponse: workflowResponseWrite
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.reject('请升级工作流');
|
return Promise.reject('您的工作流版本过低,请重新发布一次');
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// save chat
|
// save chat
|
||||||
|
@@ -67,7 +67,7 @@ const Header = () => {
|
|||||||
future,
|
future,
|
||||||
setPast
|
setPast
|
||||||
} = useContextSelector(WorkflowContext, (v) => v);
|
} = useContextSelector(WorkflowContext, (v) => v);
|
||||||
const { appType } = useSystemStore();
|
const { lastAppListRouteType } = useSystemStore();
|
||||||
|
|
||||||
const [isPublished, setIsPublished] = useState(false);
|
const [isPublished, setIsPublished] = useState(false);
|
||||||
useDebounceEffect(
|
useDebounceEffect(
|
||||||
@@ -138,11 +138,11 @@ const Header = () => {
|
|||||||
pathname: '/app/list',
|
pathname: '/app/list',
|
||||||
query: {
|
query: {
|
||||||
parentId: appDetail.parentId,
|
parentId: appDetail.parentId,
|
||||||
type: appType
|
type: lastAppListRouteType
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}, [appDetail._id, appDetail.parentId, router]);
|
}, [appDetail._id, appDetail.parentId, lastAppListRouteType, router]);
|
||||||
|
|
||||||
const Render = useMemo(() => {
|
const Render = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
|
@@ -37,7 +37,7 @@ const Header = ({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { appId, appDetail, onSaveApp, currentTab } = useContextSelector(AppContext, (v) => v);
|
const { appId, appDetail, onSaveApp, currentTab } = useContextSelector(AppContext, (v) => v);
|
||||||
const { appType } = useSystemStore();
|
const { lastAppListRouteType } = useSystemStore();
|
||||||
|
|
||||||
const { data: paths = [] } = useRequest2(() => getAppFolderPath(appId), {
|
const { data: paths = [] } = useRequest2(() => getAppFolderPath(appId), {
|
||||||
manual: false,
|
manual: false,
|
||||||
@@ -49,11 +49,11 @@ const Header = ({
|
|||||||
pathname: '/app/list',
|
pathname: '/app/list',
|
||||||
query: {
|
query: {
|
||||||
parentId,
|
parentId,
|
||||||
type: appType
|
type: lastAppListRouteType
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[router, appType]
|
[router, lastAppListRouteType]
|
||||||
);
|
);
|
||||||
|
|
||||||
const isPublished = useMemo(() => {
|
const isPublished = useMemo(() => {
|
||||||
|
@@ -67,7 +67,8 @@ const Header = () => {
|
|||||||
future,
|
future,
|
||||||
setPast
|
setPast
|
||||||
} = useContextSelector(WorkflowContext, (v) => v);
|
} = useContextSelector(WorkflowContext, (v) => v);
|
||||||
const { appType } = useSystemStore();
|
|
||||||
|
const { lastAppListRouteType } = useSystemStore();
|
||||||
|
|
||||||
// Check if the workflow is published
|
// Check if the workflow is published
|
||||||
const [isPublished, setIsPublished] = useState(false);
|
const [isPublished, setIsPublished] = useState(false);
|
||||||
@@ -139,11 +140,11 @@ const Header = () => {
|
|||||||
pathname: '/app/list',
|
pathname: '/app/list',
|
||||||
query: {
|
query: {
|
||||||
parentId: appDetail.parentId,
|
parentId: appDetail.parentId,
|
||||||
type: appType
|
type: lastAppListRouteType
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}, [appDetail._id, appDetail.parentId, router]);
|
}, [appDetail._id, appDetail.parentId, lastAppListRouteType, router]);
|
||||||
|
|
||||||
const Render = useMemo(() => {
|
const Render = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import NodeCard from './render/NodeCard';
|
import NodeCard from './render/NodeCard';
|
||||||
import { NodeProps } from 'reactflow';
|
import { NodeProps } from 'reactflow';
|
||||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||||
@@ -12,7 +12,8 @@ import {
|
|||||||
NumberInput,
|
NumberInput,
|
||||||
NumberInputField,
|
NumberInputField,
|
||||||
NumberInputStepper,
|
NumberInputStepper,
|
||||||
Switch
|
Switch,
|
||||||
|
Textarea
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type';
|
import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type';
|
||||||
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
@@ -33,7 +34,7 @@ import { getRefData } from '@/web/core/workflow/utils';
|
|||||||
import { isReferenceValue } from '@fastgpt/global/core/workflow/utils';
|
import { isReferenceValue } from '@fastgpt/global/core/workflow/utils';
|
||||||
import { AppContext } from '@/pages/app/detail/components/context';
|
import { AppContext } from '@/pages/app/detail/components/context';
|
||||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||||
import { useCreation } from 'ahooks';
|
import { useCreation, useMemoizedFn } from 'ahooks';
|
||||||
import { getEditorVariables } from '../../utils';
|
import { getEditorVariables } from '../../utils';
|
||||||
|
|
||||||
const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||||
@@ -45,6 +46,19 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
|||||||
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||||
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
||||||
|
|
||||||
|
const menuList = useRef([
|
||||||
|
{
|
||||||
|
renderType: FlowNodeInputTypeEnum.input,
|
||||||
|
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.input].icon,
|
||||||
|
label: t('common:core.workflow.inputType.Manual input')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderType: FlowNodeInputTypeEnum.reference,
|
||||||
|
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.reference].icon,
|
||||||
|
label: t('common:core.workflow.inputType.Reference')
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
const variables = useCreation(() => {
|
const variables = useCreation(() => {
|
||||||
return getEditorVariables({
|
return getEditorVariables({
|
||||||
nodeId,
|
nodeId,
|
||||||
@@ -80,195 +94,182 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
|||||||
[inputs, nodeId, onChangeNode]
|
[inputs, nodeId, onChangeNode]
|
||||||
);
|
);
|
||||||
|
|
||||||
const Render = useMemo(() => {
|
const ValueRender = useMemoizedFn(
|
||||||
const menuList = [
|
({ updateItem, index }: { updateItem: TUpdateListItem; index: number }) => {
|
||||||
{
|
const { valueType } = getRefData({
|
||||||
renderType: FlowNodeInputTypeEnum.input,
|
variable: updateItem.variable,
|
||||||
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.input].icon,
|
nodeList,
|
||||||
label: t('common:core.workflow.inputType.Manual input')
|
chatConfig: appDetail.chatConfig
|
||||||
},
|
});
|
||||||
{
|
const renderTypeData = menuList.current.find(
|
||||||
renderType: FlowNodeInputTypeEnum.reference,
|
(item) => item.renderType === updateItem.renderType
|
||||||
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.reference].icon,
|
);
|
||||||
label: t('common:core.workflow.inputType.Reference')
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
const handleUpdate = (newValue: ReferenceValueProps | string) => {
|
||||||
<>
|
if (isReferenceValue(newValue)) {
|
||||||
{updateList.map((updateItem, index) => {
|
onUpdateList(
|
||||||
const { valueType } = getRefData({
|
updateList.map((update, i) =>
|
||||||
variable: updateItem.variable,
|
i === index ? { ...update, value: newValue as ReferenceValueProps } : update
|
||||||
nodeList,
|
)
|
||||||
chatConfig: appDetail.chatConfig
|
);
|
||||||
});
|
} else {
|
||||||
const renderTypeData = menuList.find((item) => item.renderType === updateItem.renderType);
|
onUpdateList(
|
||||||
const handleUpdate = (newValue: ReferenceValueProps | string) => {
|
updateList.map((update, i) =>
|
||||||
if (isReferenceValue(newValue)) {
|
i === index ? { ...update, value: ['', newValue as string] } : update
|
||||||
onUpdateList(
|
)
|
||||||
updateList.map((update, i) =>
|
);
|
||||||
i === index ? { ...update, value: newValue as ReferenceValueProps } : update
|
}
|
||||||
)
|
};
|
||||||
);
|
|
||||||
} else {
|
|
||||||
onUpdateList(
|
|
||||||
updateList.map((update, i) =>
|
|
||||||
i === index ? { ...update, value: ['', newValue as string] } : update
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container key={index} mt={4} w={'full'} mx={0}>
|
<Container key={index} mt={4} w={'full'} mx={0}>
|
||||||
<Flex alignItems={'center'}>
|
<Flex alignItems={'center'}>
|
||||||
<Flex w={'60px'}>{t('common:core.workflow.variable')}</Flex>
|
<Flex w={'60px'}>{t('common:core.workflow.variable')}</Flex>
|
||||||
<Reference
|
<Reference
|
||||||
nodeId={nodeId}
|
nodeId={nodeId}
|
||||||
variable={updateItem.variable}
|
variable={updateItem.variable}
|
||||||
onSelect={(value) => {
|
onSelect={(value) => {
|
||||||
|
onUpdateList(
|
||||||
|
updateList.map((update, i) => {
|
||||||
|
if (i === index) {
|
||||||
|
return {
|
||||||
|
...update,
|
||||||
|
value: ['', ''],
|
||||||
|
valueType,
|
||||||
|
variable: value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return update;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box flex={1} />
|
||||||
|
{updateList.length > 1 && (
|
||||||
|
<MyIcon
|
||||||
|
className="delete"
|
||||||
|
name={'delete'}
|
||||||
|
w={'14px'}
|
||||||
|
color={'myGray.600'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
_hover={{ color: 'red.500' }}
|
||||||
|
position={'absolute'}
|
||||||
|
top={3}
|
||||||
|
right={3}
|
||||||
|
onClick={() => {
|
||||||
|
onUpdateList(updateList.filter((_, i) => i !== index));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={2} w={'full'} alignItems={'center'} className="nodrag">
|
||||||
|
<Flex w={'60px'}>
|
||||||
|
<Box>{t('common:core.workflow.value')}</Box>
|
||||||
|
<MyTooltip
|
||||||
|
label={
|
||||||
|
menuList.current.find((item) => item.renderType === updateItem.renderType)?.label
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size={'xs'}
|
||||||
|
bg={'white'}
|
||||||
|
borderRadius={'xs'}
|
||||||
|
mx={2}
|
||||||
|
color={'primary.600'}
|
||||||
|
onClick={() => {
|
||||||
onUpdateList(
|
onUpdateList(
|
||||||
updateList.map((update, i) => {
|
updateList.map((update, i) => {
|
||||||
if (i === index) {
|
if (i === index) {
|
||||||
return {
|
return {
|
||||||
...update,
|
...update,
|
||||||
value: ['', ''],
|
value: ['', ''],
|
||||||
valueType,
|
renderType:
|
||||||
variable: value
|
updateItem.renderType === FlowNodeInputTypeEnum.input
|
||||||
|
? FlowNodeInputTypeEnum.reference
|
||||||
|
: FlowNodeInputTypeEnum.input
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return update;
|
return update;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
<Box flex={1} />
|
<MyIcon name={renderTypeData?.icon as any} w={'14px'} />
|
||||||
{updateList.length > 1 && (
|
</Button>
|
||||||
<MyIcon
|
</MyTooltip>
|
||||||
className="delete"
|
</Flex>
|
||||||
name={'delete'}
|
|
||||||
w={'14px'}
|
{/* Render input components */}
|
||||||
color={'myGray.600'}
|
{(() => {
|
||||||
cursor={'pointer'}
|
if (updateItem.renderType === FlowNodeInputTypeEnum.reference) {
|
||||||
_hover={{ color: 'red.500' }}
|
return (
|
||||||
position={'absolute'}
|
<Reference
|
||||||
top={3}
|
nodeId={nodeId}
|
||||||
right={3}
|
variable={updateItem.value}
|
||||||
onClick={() => {
|
valueType={valueType}
|
||||||
onUpdateList(updateList.filter((_, i) => i !== index));
|
onSelect={handleUpdate}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
);
|
||||||
</Flex>
|
}
|
||||||
<Flex mt={2} w={'full'} alignItems={'center'} className="nodrag">
|
if (valueType === WorkflowIOValueTypeEnum.string) {
|
||||||
<Flex w={'60px'}>
|
return (
|
||||||
<Box>{t('common:core.workflow.value')}</Box>
|
<Box w={'300px'}>
|
||||||
<MyTooltip
|
<PromptEditor
|
||||||
label={
|
value={updateItem.value?.[1] || ''}
|
||||||
menuList.find((item) => item.renderType === updateItem.renderType)?.label
|
onChange={handleUpdate}
|
||||||
}
|
showOpenModal={false}
|
||||||
>
|
variableLabels={variables}
|
||||||
<Button
|
h={100}
|
||||||
size={'xs'}
|
|
||||||
bg={'white'}
|
|
||||||
borderRadius={'xs'}
|
|
||||||
mx={2}
|
|
||||||
color={'primary.600'}
|
|
||||||
onClick={() => {
|
|
||||||
onUpdateList(
|
|
||||||
updateList.map((update, i) => {
|
|
||||||
if (i === index) {
|
|
||||||
return {
|
|
||||||
...update,
|
|
||||||
value: ['', ''],
|
|
||||||
renderType:
|
|
||||||
updateItem.renderType === FlowNodeInputTypeEnum.input
|
|
||||||
? FlowNodeInputTypeEnum.reference
|
|
||||||
: FlowNodeInputTypeEnum.input
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return update;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MyIcon name={renderTypeData?.icon as any} w={'14px'} />
|
|
||||||
</Button>
|
|
||||||
</MyTooltip>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* Render input components */}
|
|
||||||
{(() => {
|
|
||||||
if (updateItem.renderType === FlowNodeInputTypeEnum.reference) {
|
|
||||||
return (
|
|
||||||
<Reference
|
|
||||||
nodeId={nodeId}
|
|
||||||
variable={updateItem.value}
|
|
||||||
valueType={valueType}
|
|
||||||
onSelect={handleUpdate}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (valueType === WorkflowIOValueTypeEnum.string) {
|
|
||||||
return (
|
|
||||||
<Box w={'300px'}>
|
|
||||||
<PromptEditor
|
|
||||||
value={updateItem.value?.[1] || ''}
|
|
||||||
onChange={handleUpdate}
|
|
||||||
showOpenModal={false}
|
|
||||||
variableLabels={variables}
|
|
||||||
h={100}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (valueType === WorkflowIOValueTypeEnum.number) {
|
|
||||||
return (
|
|
||||||
<NumberInput value={Number(updateItem.value?.[1]) || 0}>
|
|
||||||
<NumberInputField
|
|
||||||
bg="white"
|
|
||||||
onChange={(e) => handleUpdate(e.target.value)}
|
|
||||||
/>
|
|
||||||
<NumberInputStepper>
|
|
||||||
<NumberIncrementStepper />
|
|
||||||
<NumberDecrementStepper />
|
|
||||||
</NumberInputStepper>
|
|
||||||
</NumberInput>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (valueType === WorkflowIOValueTypeEnum.boolean) {
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
defaultChecked={updateItem.value?.[1] === 'true'}
|
|
||||||
onChange={(e) => handleUpdate(String(e.target.checked))}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<JsonEditor
|
|
||||||
bg="white"
|
|
||||||
resize
|
|
||||||
w="300px"
|
|
||||||
value={String(updateItem.value?.[1] || '')}
|
|
||||||
onChange={(e) => {
|
|
||||||
handleUpdate(e);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
</Box>
|
||||||
})()}
|
);
|
||||||
</Flex>
|
}
|
||||||
</Container>
|
if (valueType === WorkflowIOValueTypeEnum.number) {
|
||||||
);
|
return (
|
||||||
})}
|
<NumberInput value={Number(updateItem.value?.[1]) || 0}>
|
||||||
</>
|
<NumberInputField bg="white" onChange={(e) => handleUpdate(e.target.value)} />
|
||||||
);
|
<NumberInputStepper>
|
||||||
}, [appDetail.chatConfig, nodeId, nodeList, onUpdateList, t, updateList, variables]);
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={updateItem.value?.[1] === 'true'}
|
||||||
|
onChange={(e) => handleUpdate(String(e.target.checked))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box w={'300px'}>
|
||||||
|
<PromptEditor
|
||||||
|
value={updateItem.value?.[1] || ''}
|
||||||
|
onChange={handleUpdate}
|
||||||
|
showOpenModal={false}
|
||||||
|
variableLabels={variables}
|
||||||
|
h={100}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</Flex>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeCard selected={selected} maxW={'1000px'} {...data}>
|
<NodeCard selected={selected} maxW={'1000px'} {...data}>
|
||||||
<Box px={4} pb={4}>
|
<Box px={4} pb={4}>
|
||||||
{Render}
|
<>
|
||||||
|
{updateList.map((updateItem, index) => (
|
||||||
|
<ValueRender key={index} updateItem={updateItem} index={index} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||||
<Button
|
<Button
|
||||||
variant={'whiteBase'}
|
variant={'whiteBase'}
|
||||||
|
@@ -549,20 +549,23 @@ const WorkflowContextProvider = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
/* If the module is connected by a tool, the tool input and the normal input are separated */
|
/* If the module is connected by a tool, the tool input and the normal input are separated */
|
||||||
const splitToolInputs = useMemoizedFn((inputs: FlowNodeInputItemType[], nodeId: string) => {
|
const splitToolInputs = useCallback(
|
||||||
const isTool = !!edges.find(
|
(inputs: FlowNodeInputItemType[], nodeId: string) => {
|
||||||
(edge) => edge.targetHandle === NodeOutputKeyEnum.selectedTools && edge.target === nodeId
|
const isTool = !!edges.find(
|
||||||
);
|
(edge) => edge.targetHandle === NodeOutputKeyEnum.selectedTools && edge.target === nodeId
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isTool,
|
isTool,
|
||||||
toolInputs: inputs.filter((item) => isTool && item.toolDescription),
|
toolInputs: inputs.filter((item) => isTool && item.toolDescription),
|
||||||
commonInputs: inputs.filter((item) => {
|
commonInputs: inputs.filter((item) => {
|
||||||
if (!isTool) return true;
|
if (!isTool) return true;
|
||||||
return !item.toolDescription;
|
return !item.toolDescription;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
});
|
},
|
||||||
|
[edges]
|
||||||
|
);
|
||||||
|
|
||||||
/* ui flow to store data */
|
/* ui flow to store data */
|
||||||
const flowData2StoreDataAndCheck = useMemoizedFn((hideTip = false) => {
|
const flowData2StoreDataAndCheck = useMemoizedFn((hideTip = false) => {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { ReactNode, useCallback, useState } from 'react';
|
import React, { ReactNode, useCallback, useEffect, useState } from 'react';
|
||||||
import { createContext } from 'use-context-selector';
|
import { createContext } from 'use-context-selector';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
@@ -14,6 +14,7 @@ import { AppUpdateParams } from '@/global/core/app/api';
|
|||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useI18n } from '@/web/context/I18n';
|
import { useI18n } from '@/web/context/I18n';
|
||||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
const MoveModal = dynamic(() => import('@/components/common/folder/MoveModal'));
|
const MoveModal = dynamic(() => import('@/components/common/folder/MoveModal'));
|
||||||
|
|
||||||
type AppListContextType = {
|
type AppListContextType = {
|
||||||
@@ -135,6 +136,11 @@ const AppListContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const { setLastAppListRouteType } = useSystemStore();
|
||||||
|
useEffect(() => {
|
||||||
|
setLastAppListRouteType(type);
|
||||||
|
}, [setLastAppListRouteType, type]);
|
||||||
|
|
||||||
const contextValue: AppListContextType = {
|
const contextValue: AppListContextType = {
|
||||||
parentId,
|
parentId,
|
||||||
appType: type,
|
appType: type,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Flex,
|
Flex,
|
||||||
|
@@ -19,8 +19,12 @@ type LoginStoreType = { provider: `${OAuthEnum}`; lastRoute: string; state: stri
|
|||||||
type State = {
|
type State = {
|
||||||
initd: boolean;
|
initd: boolean;
|
||||||
setInitd: () => void;
|
setInitd: () => void;
|
||||||
|
|
||||||
lastRoute: string;
|
lastRoute: string;
|
||||||
setLastRoute: (e: string) => void;
|
setLastRoute: (e: string) => void;
|
||||||
|
lastAppListRouteType?: string;
|
||||||
|
setLastAppListRouteType: (e?: string) => void;
|
||||||
|
|
||||||
loginStore?: LoginStoreType;
|
loginStore?: LoginStoreType;
|
||||||
setLoginStore: (e: LoginStoreType) => void;
|
setLoginStore: (e: LoginStoreType) => void;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -67,6 +71,12 @@ export const useSystemStore = create<State>()(
|
|||||||
state.lastRoute = e;
|
state.lastRoute = e;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
lastAppListRouteType: undefined,
|
||||||
|
setLastAppListRouteType(e) {
|
||||||
|
set((state) => {
|
||||||
|
state.lastAppListRouteType = e;
|
||||||
|
});
|
||||||
|
},
|
||||||
loginStore: undefined,
|
loginStore: undefined,
|
||||||
setLoginStore(e) {
|
setLoginStore(e) {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
|
Reference in New Issue
Block a user