mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 21:13:50 +00:00
4.8.13 feature (#3118)
* chore(ui): login page & workflow page (#3046) * login page & number input & multirow select & llm select * workflow * adjust nodes * New file upload (#3058) * feat: toolNode aiNode readFileNode adapt new version * update docker-compose * update tip * feat: adapt new file version * perf: file input * fix: ts * feat: add chat history time label (#3024) * feat:add chat and logs time * feat: add chat history time label * code perf * code perf --------- Co-authored-by: 勤劳上班的卑微小张 <jiazhan.zhang@ggimage.com> * add chatType (#3060) * pref: slow query of full text search (#3044) * Adapt findLast api;perf: markdown zh format. (#3066) * perf: context code * fix: adapt findLast api * perf: commercial plugin run error * perf: markdown zh format * perf: dockerfile proxy (#3067) * fix ui (#3065) * fix ui * fix * feat: support array reference multi-select (#3041) * feat: support array reference multi-select * fix build * fix * fix loop multi-select * adjust condition * fix get value * array and non-array conversion * fix plugin input * merge func * feat: iframe code block;perf: workflow selector type (#3076) * feat: iframe code block * perf: workflow selector type * node pluginoutput check (#3074) * feat: View will move when workflow check error;fix: ui refresh error when continuous file upload (#3077) * fix: plugin output check * fix: ui refresh error when continuous file upload * feat: View will move when workflow check error * add dispatch try catch (#3075) * perf: workflow context split (#3083) * perf: workflow context split * perf: context * 4.8.13 test (#3085) * perf: workflow node ui * chat iframe url * feat: support sub route config (#3071) * feat: support sub route config * dockerfile * fix upload * delete unused code * 4.8.13 test (#3087) * fix: image expired * fix: datacard navbar ui * perf: build action * fix: workflow file upload refresh (#3088) * fix: http tool response (#3097) * loop node dynamic height (#3092) * loop node dynamic height * fix * fix * feat: support push chat log (#3093) * feat: custom uid/metadata * to: custom info * fix: chat push latest * feat: add chat log envs * refactor: move timer to pushChatLog * fix: using precise log --------- Co-authored-by: Finley Ge <m13203533462@163.com> * 4.8.13 test (#3098) * perf: loop node refresh * rename context * comment * fix: ts * perf: push chat log * array reference check & node ui (#3100) * feat: loop start add index (#3101) * feat: loop start add index * update doc * 4.8.13 test (#3102) * fix: loop index;edge parent check * perf: reference invalid check * fix: ts * fix: plugin select files and ai response check (#3104) * fix: plugin select files and ai response check * perf: text editor selector;tool call tip;remove invalid image url; * perf: select file * perf: drop files * feat: source id prefix env (#3103) * 4.8.13 test (#3106) * perf: select file * perf: drop files * perf: env template * 4.8.13 test (#3107) * perf: select file * perf: drop files * fix: imple mode adapt files * perf: push chat log (#3109) * fix: share page load title error (#3111) * 4.8.13 perf (#3112) * fix: share page load title error * update file input doc * perf: auto add file urls * perf: auto ser loop node offset height * 4.8.13 test (#3117) * perf: plugin * updat eaction * feat: add more share config (#3120) * feat: add more share config * add i18n en * fix: missing subroute (#3121) * perf: outlink config (#3128) * update action * perf: outlink config * fix: ts (#3129) * 更新 docSite 文档内容 (#3131) * fix: null pointer (#3130) * fix: null pointer * perf: not input text * update doc url * perf: outlink default value (#3134) * update doc (#3136) * 4.8.13 test (#3137) * update doc * perf: completions chat api * Restore docSite content based on upstream/4.8.13-dev (#3138) * Restore docSite content based on upstream/4.8.13-dev * 4813.md缺少更正 * update doc (#3141) --------- Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: papapatrick <109422393+Patrickill@users.noreply.github.com> Co-authored-by: 勤劳上班的卑微小张 <jiazhan.zhang@ggimage.com> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: a.e. <49438478+I-Info@users.noreply.github.com> Co-authored-by: Finley Ge <m13203533462@163.com> Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com>
This commit is contained in:
@@ -16,6 +16,8 @@ export const bucketNameMap = {
|
||||
}
|
||||
};
|
||||
|
||||
export const ReadFileBaseUrl = `${process.env.FE_DOMAIN || ''}/api/common/file/read`;
|
||||
export const ReadFileBaseUrl = `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL}/api/common/file/read`;
|
||||
|
||||
export const documentFileType = '.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .pptx';
|
||||
export const imageFileType =
|
||||
'.jpg, .jpeg, .png, .gif, .bmp, .webp, .svg, .tiff, .tif, .ico, .heic, .heif, .avif';
|
||||
|
@@ -1,4 +1,7 @@
|
||||
import { detect } from 'jschardet';
|
||||
import { documentFileType, imageFileType } from './constants';
|
||||
import { ChatFileTypeEnum } from '../../core/chat/constants';
|
||||
import { UserChatItemValueItemType } from '../../core/chat/type';
|
||||
|
||||
export const formatFileSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B';
|
||||
@@ -13,3 +16,40 @@ export const formatFileSize = (bytes: number): string => {
|
||||
export const detectFileEncoding = (buffer: Buffer) => {
|
||||
return detect(buffer.slice(0, 200))?.encoding?.toLocaleLowerCase();
|
||||
};
|
||||
|
||||
// Url => user upload file type
|
||||
export const parseUrlToFileType = (url: string): UserChatItemValueItemType['file'] | undefined => {
|
||||
if (typeof url !== 'string') return;
|
||||
const parseUrl = new URL(url, 'https://locaohost:3000');
|
||||
|
||||
const filename = (() => {
|
||||
// Old version file url: https://xxx.com/file/read?filename=xxx.pdf
|
||||
const filenameQuery = parseUrl.searchParams.get('filename');
|
||||
if (filenameQuery) return filenameQuery;
|
||||
|
||||
// Common file: https://xxx.com/xxx.pdf?xxxx=xxx
|
||||
const pathname = parseUrl.pathname;
|
||||
if (pathname) return pathname.split('/').pop();
|
||||
})();
|
||||
|
||||
if (!filename) return;
|
||||
|
||||
const extension = filename.split('.').pop()?.toLowerCase() || '';
|
||||
|
||||
if (!extension) return;
|
||||
|
||||
if (documentFileType.includes(extension)) {
|
||||
return {
|
||||
type: ChatFileTypeEnum.file,
|
||||
name: filename,
|
||||
url
|
||||
};
|
||||
}
|
||||
if (imageFileType.includes(extension)) {
|
||||
return {
|
||||
type: ChatFileTypeEnum.image,
|
||||
name: filename,
|
||||
url
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@@ -2,6 +2,7 @@ import dayjs from 'dayjs';
|
||||
import cronParser from 'cron-parser';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import { i18nT } from '../../../web/i18n/utils';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
@@ -23,31 +24,51 @@ export const formatTimeToChatTime = (time: Date) => {
|
||||
|
||||
// 如果传入时间小于60秒,返回刚刚
|
||||
if (now.diff(target, 'second') < 60) {
|
||||
return '刚刚';
|
||||
return i18nT('common:just_now');
|
||||
}
|
||||
|
||||
// 如果时间是今天,展示几时:几分
|
||||
//用#占位,i18n生效后replace成:
|
||||
if (now.isSame(target, 'day')) {
|
||||
return target.format('HH : mm');
|
||||
return target.format('HH#mm');
|
||||
}
|
||||
|
||||
// 如果是昨天,展示昨天
|
||||
if (now.subtract(1, 'day').isSame(target, 'day')) {
|
||||
return '昨天';
|
||||
}
|
||||
|
||||
// 如果是前天,展示前天
|
||||
if (now.subtract(2, 'day').isSame(target, 'day')) {
|
||||
return '前天';
|
||||
return i18nT('common:yesterday');
|
||||
}
|
||||
|
||||
// 如果是今年,展示某月某日
|
||||
if (now.isSame(target, 'year')) {
|
||||
return target.format('MM/DD');
|
||||
return target.format('MM-DD');
|
||||
}
|
||||
|
||||
// 如果是更久之前,展示某年某月某日
|
||||
return target.format('YYYY/M/D');
|
||||
return target.format('YYYY-M-D');
|
||||
};
|
||||
|
||||
export const formatTimeToChatItemTime = (time: Date) => {
|
||||
const now = dayjs();
|
||||
const target = dayjs(time);
|
||||
const detailTime = target.format('HH#mm');
|
||||
|
||||
// 如果时间是今天,展示几时:几分
|
||||
if (now.isSame(target, 'day')) {
|
||||
return detailTime;
|
||||
}
|
||||
|
||||
// 如果是昨天,展示昨天+几时:几分
|
||||
if (now.subtract(1, 'day').isSame(target, 'day')) {
|
||||
return i18nT('common:yesterday_detail_time');
|
||||
}
|
||||
|
||||
// 如果是今年,展示某月某日+几时:几分
|
||||
if (now.isSame(target, 'year')) {
|
||||
return target.format('MM-DD') + ' ' + detailTime;
|
||||
}
|
||||
|
||||
// 如果是更久之前,展示某年某月某日+几时:几分
|
||||
return target.format('YYYY-M-D') + ' ' + detailTime;
|
||||
};
|
||||
|
||||
/* cron time parse */
|
||||
|
@@ -207,8 +207,8 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
|
||||
];
|
||||
|
||||
// Document quote prompt
|
||||
export const Prompt_DocumentQuote = `将 <Reference></Reference> 中的内容作为本次对话的参考:
|
||||
<Reference>
|
||||
export const Prompt_DocumentQuote = `将 <FilesContent></FilesContent> 中的内容作为本次对话的参考:
|
||||
<FilesContent>
|
||||
{{quote}}
|
||||
</Reference>
|
||||
</FilesContent>
|
||||
`;
|
||||
|
@@ -14,7 +14,6 @@ import type {
|
||||
ChatCompletionToolMessageParam
|
||||
} from '../../core/ai/type.d';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '../../core/ai/constants';
|
||||
|
||||
const GPT2Chat = {
|
||||
[ChatCompletionRequestMessageRoleEnum.System]: ChatRoleEnum.System,
|
||||
[ChatCompletionRequestMessageRoleEnum.User]: ChatRoleEnum.Human,
|
||||
@@ -61,14 +60,14 @@ export const chats2GPTMessages = ({
|
||||
return {
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: item.file?.url || ''
|
||||
url: item.file.url
|
||||
}
|
||||
};
|
||||
} else if (item.file?.type === ChatFileTypeEnum.file) {
|
||||
return {
|
||||
type: 'file_url',
|
||||
name: item.file?.name || '',
|
||||
url: item.file?.url || ''
|
||||
url: item.file.url
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -91,6 +90,7 @@ export const chats2GPTMessages = ({
|
||||
}
|
||||
} else {
|
||||
const aiResults: ChatCompletionMessageParam[] = [];
|
||||
|
||||
//AI
|
||||
item.value.forEach((value, i) => {
|
||||
if (value.type === ChatItemValueTypeEnum.tool && value.tools && reserveTool) {
|
||||
@@ -131,7 +131,7 @@ export const chats2GPTMessages = ({
|
||||
if (
|
||||
lastValue &&
|
||||
lastValue.type === ChatItemValueTypeEnum.text &&
|
||||
typeof lastResult.content === 'string'
|
||||
typeof lastResult?.content === 'string'
|
||||
) {
|
||||
lastResult.content += value.text.content;
|
||||
} else {
|
||||
|
1
packages/global/core/chat/type.d.ts
vendored
1
packages/global/core/chat/type.d.ts
vendored
@@ -126,6 +126,7 @@ export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatIt
|
||||
moduleName?: string;
|
||||
ttsBuffer?: Uint8Array;
|
||||
responseData?: ChatHistoryItemResType[];
|
||||
time?: Date;
|
||||
} & ChatBoxInputType &
|
||||
ResponseTagItemType;
|
||||
|
||||
|
@@ -30,7 +30,8 @@ export const getChatTitleFromChatMessage = (message?: ChatItemType, defaultValue
|
||||
// Keep the first n and last n characters
|
||||
export const getHistoryPreview = (
|
||||
completeMessages: ChatItemType[],
|
||||
size = 100
|
||||
size = 100,
|
||||
useVision = false
|
||||
): {
|
||||
obj: `${ChatRoleEnum}`;
|
||||
value: string;
|
||||
@@ -48,7 +49,8 @@ export const getHistoryPreview = (
|
||||
item.value
|
||||
?.map((item) => {
|
||||
if (item?.text?.content) return item?.text?.content;
|
||||
if (item.file?.type === 'image') return 'Input an image';
|
||||
if (item.file?.type === 'image' && useVision)
|
||||
return `}...)`;
|
||||
return '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
@@ -80,7 +82,7 @@ export const filterPublicNodeResponseData = ({
|
||||
}: {
|
||||
flowResponses?: ChatHistoryItemResType[];
|
||||
}) => {
|
||||
const filedList = ['quoteList', 'moduleType', 'pluginOutput'];
|
||||
const filedList = ['quoteList', 'moduleType', 'pluginOutput', 'runningTime'];
|
||||
const filterModuleTypeList: any[] = [
|
||||
FlowNodeTypeEnum.pluginModule,
|
||||
FlowNodeTypeEnum.datasetSearchNode,
|
||||
|
@@ -199,8 +199,10 @@ export enum NodeInputKeyEnum {
|
||||
childrenNodeIdList = 'childrenNodeIdList',
|
||||
nodeWidth = 'nodeWidth',
|
||||
nodeHeight = 'nodeHeight',
|
||||
loopNodeInputHeight = 'loopNodeInputHeight',
|
||||
// loop start
|
||||
loopStartInput = 'loopStartInput',
|
||||
loopStartIndex = 'loopStartIndex',
|
||||
// loop end
|
||||
loopEndInput = 'loopEndInput',
|
||||
|
||||
@@ -256,9 +258,9 @@ export enum NodeOutputKeyEnum {
|
||||
|
||||
// loop
|
||||
loopArray = 'loopArray',
|
||||
|
||||
// loop start
|
||||
loopStartInput = 'loopStartInput',
|
||||
loopStartIndex = 'loopStartIndex',
|
||||
|
||||
// form input
|
||||
formInputResult = 'formInputResult'
|
||||
@@ -334,3 +336,21 @@ export enum ContentTypes {
|
||||
xml = 'xml',
|
||||
raw = 'raw-text'
|
||||
}
|
||||
|
||||
export const ArrayTypeMap: Record<WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum> = {
|
||||
[WorkflowIOValueTypeEnum.string]: WorkflowIOValueTypeEnum.arrayString,
|
||||
[WorkflowIOValueTypeEnum.number]: WorkflowIOValueTypeEnum.arrayNumber,
|
||||
[WorkflowIOValueTypeEnum.boolean]: WorkflowIOValueTypeEnum.arrayBoolean,
|
||||
[WorkflowIOValueTypeEnum.object]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.arrayString]: WorkflowIOValueTypeEnum.arrayString,
|
||||
[WorkflowIOValueTypeEnum.arrayNumber]: WorkflowIOValueTypeEnum.arrayNumber,
|
||||
[WorkflowIOValueTypeEnum.arrayBoolean]: WorkflowIOValueTypeEnum.arrayBoolean,
|
||||
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.chatHistory]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.datasetQuote]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.dynamic]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.selectDataset]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.selectApp]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.arrayAny]: WorkflowIOValueTypeEnum.arrayAny,
|
||||
[WorkflowIOValueTypeEnum.any]: WorkflowIOValueTypeEnum.arrayAny
|
||||
};
|
||||
|
@@ -27,7 +27,9 @@ export enum FlowNodeInputTypeEnum { // render ui
|
||||
settingDatasetQuotePrompt = 'settingDatasetQuotePrompt',
|
||||
|
||||
hidden = 'hidden',
|
||||
custom = 'custom'
|
||||
custom = 'custom',
|
||||
|
||||
fileSelect = 'fileSelect'
|
||||
}
|
||||
export const FlowNodeInputMap: Record<
|
||||
FlowNodeInputTypeEnum,
|
||||
@@ -85,6 +87,9 @@ export const FlowNodeInputMap: Record<
|
||||
},
|
||||
[FlowNodeInputTypeEnum.textarea]: {
|
||||
icon: 'core/workflow/inputType/textarea'
|
||||
},
|
||||
[FlowNodeInputTypeEnum.fileSelect]: {
|
||||
icon: 'core/workflow/inputType/file'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,43 +142,43 @@ export enum FlowNodeTypeEnum {
|
||||
// node IO value type
|
||||
export const FlowValueTypeMap = {
|
||||
[WorkflowIOValueTypeEnum.string]: {
|
||||
label: 'string',
|
||||
label: 'String',
|
||||
value: WorkflowIOValueTypeEnum.string
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.number]: {
|
||||
label: 'number',
|
||||
label: 'Number',
|
||||
value: WorkflowIOValueTypeEnum.number
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.boolean]: {
|
||||
label: 'boolean',
|
||||
label: 'Boolean',
|
||||
value: WorkflowIOValueTypeEnum.boolean
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.object]: {
|
||||
label: 'object',
|
||||
label: 'Object',
|
||||
value: WorkflowIOValueTypeEnum.object
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.arrayString]: {
|
||||
label: 'array<string>',
|
||||
label: 'Array<string>',
|
||||
value: WorkflowIOValueTypeEnum.arrayString
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.arrayNumber]: {
|
||||
label: 'array<number>',
|
||||
label: 'Array<number>',
|
||||
value: WorkflowIOValueTypeEnum.arrayNumber
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.arrayBoolean]: {
|
||||
label: 'array<boolean>',
|
||||
label: 'Array<boolean>',
|
||||
value: WorkflowIOValueTypeEnum.arrayBoolean
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.arrayObject]: {
|
||||
label: 'array<object>',
|
||||
label: 'Array<object>',
|
||||
value: WorkflowIOValueTypeEnum.arrayObject
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.arrayAny]: {
|
||||
label: 'array',
|
||||
label: 'Array',
|
||||
value: WorkflowIOValueTypeEnum.arrayAny
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.any]: {
|
||||
label: 'any',
|
||||
label: 'Any',
|
||||
value: WorkflowIOValueTypeEnum.any
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.chatHistory]: {
|
||||
|
@@ -135,6 +135,9 @@ export type DispatchNodeResponseType = {
|
||||
extensionResult?: string;
|
||||
extensionTokens?: number;
|
||||
|
||||
// dataset concat
|
||||
concatLength?: number;
|
||||
|
||||
// cq
|
||||
cqList?: ClassifyQuestionAgentItemType[];
|
||||
cqResult?: string;
|
||||
@@ -216,5 +219,7 @@ export type AIChatNodeProps = {
|
||||
[NodeInputKeyEnum.aiChatQuoteTemplate]?: string;
|
||||
[NodeInputKeyEnum.aiChatQuotePrompt]?: string;
|
||||
[NodeInputKeyEnum.aiChatVision]?: boolean;
|
||||
|
||||
[NodeInputKeyEnum.stringQuoteText]?: string;
|
||||
[NodeInputKeyEnum.fileUrlList]?: string[];
|
||||
};
|
||||
|
@@ -5,8 +5,8 @@ import { StoreNodeItemType } from '../type/node';
|
||||
import { StoreEdgeItemType } from '../type/edge';
|
||||
import { RuntimeEdgeItemType, RuntimeNodeItemType } from './type';
|
||||
import { VARIABLE_NODE_ID } from '../constants';
|
||||
import { isReferenceValue } from '../utils';
|
||||
import { FlowNodeOutputItemType, ReferenceValueProps } from '../type/io';
|
||||
import { isValidReferenceValueFormat } from '../utils';
|
||||
import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io';
|
||||
import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants';
|
||||
|
||||
@@ -34,7 +34,7 @@ export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number
|
||||
2. Check that the workflow starts at the interaction node
|
||||
*/
|
||||
export const getLastInteractiveValue = (histories: ChatItemType[]) => {
|
||||
const lastAIMessage = histories.findLast((item) => item.obj === ChatRoleEnum.AI);
|
||||
const lastAIMessage = [...histories].reverse().find((item) => item.obj === ChatRoleEnum.AI);
|
||||
|
||||
if (lastAIMessage) {
|
||||
const lastValue = lastAIMessage.value[lastAIMessage.value.length - 1];
|
||||
@@ -225,37 +225,129 @@ export const checkNodeRunStatus = ({
|
||||
return 'wait';
|
||||
};
|
||||
|
||||
/*
|
||||
Get the value of the reference variable/node output
|
||||
1. [string,string]
|
||||
2. [string,string][]
|
||||
*/
|
||||
export const getReferenceVariableValue = ({
|
||||
value,
|
||||
nodes,
|
||||
variables
|
||||
}: {
|
||||
value: ReferenceValueProps;
|
||||
value?: ReferenceValueType;
|
||||
nodes: RuntimeNodeItemType[];
|
||||
variables: Record<string, any>;
|
||||
}) => {
|
||||
const nodeIds = nodes.map((node) => node.nodeId);
|
||||
if (!isReferenceValue(value, nodeIds)) {
|
||||
return value;
|
||||
}
|
||||
const sourceNodeId = value[0];
|
||||
const outputId = value[1];
|
||||
if (!value) return value;
|
||||
|
||||
if (sourceNodeId === VARIABLE_NODE_ID && outputId) {
|
||||
return variables[outputId];
|
||||
// handle single reference value
|
||||
if (isValidReferenceValueFormat(value)) {
|
||||
const sourceNodeId = value[0];
|
||||
const outputId = value[1];
|
||||
|
||||
if (sourceNodeId === VARIABLE_NODE_ID) {
|
||||
if (!outputId) return undefined;
|
||||
return variables[outputId];
|
||||
}
|
||||
|
||||
const node = nodes.find((node) => node.nodeId === sourceNodeId);
|
||||
if (!node) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return node.outputs.find((output) => output.id === outputId)?.value;
|
||||
}
|
||||
|
||||
const node = nodes.find((node) => node.nodeId === sourceNodeId);
|
||||
// handle reference array
|
||||
if (
|
||||
Array.isArray(value) &&
|
||||
value.length > 0 &&
|
||||
value.every((item) => isValidReferenceValueFormat(item))
|
||||
) {
|
||||
const result = value.map<any>((val) => {
|
||||
return getReferenceVariableValue({
|
||||
value: val,
|
||||
nodes,
|
||||
variables
|
||||
});
|
||||
});
|
||||
|
||||
if (!node) {
|
||||
return undefined;
|
||||
return result.flat().filter((item) => item !== undefined);
|
||||
}
|
||||
|
||||
const outputValue = node.outputs.find((output) => output.id === outputId)?.value;
|
||||
|
||||
return outputValue;
|
||||
return value;
|
||||
};
|
||||
|
||||
// replace {{$xx.xx$}} variables for text
|
||||
export function replaceEditorVariable({
|
||||
text,
|
||||
nodes,
|
||||
variables,
|
||||
runningNode
|
||||
}: {
|
||||
text: any;
|
||||
nodes: RuntimeNodeItemType[];
|
||||
variables: Record<string, any>; // global variables
|
||||
runningNode: RuntimeNodeItemType;
|
||||
}) {
|
||||
if (typeof text !== 'string') return text;
|
||||
|
||||
const globalVariables = Object.keys(variables).map((key) => {
|
||||
return {
|
||||
nodeId: VARIABLE_NODE_ID,
|
||||
id: key,
|
||||
value: variables[key]
|
||||
};
|
||||
});
|
||||
|
||||
// Upstream node outputs
|
||||
const nodeVariables = nodes
|
||||
.map((node) => {
|
||||
return node.outputs.map((output) => {
|
||||
return {
|
||||
nodeId: node.nodeId,
|
||||
id: output.id,
|
||||
value: output.value
|
||||
};
|
||||
});
|
||||
})
|
||||
.flat();
|
||||
|
||||
// Get runningNode inputs(Will be replaced with reference)
|
||||
const customInputs = runningNode.inputs.flatMap((item) => {
|
||||
return [
|
||||
{
|
||||
id: item.key,
|
||||
value: getReferenceVariableValue({
|
||||
value: item.value,
|
||||
nodes,
|
||||
variables
|
||||
}),
|
||||
nodeId: runningNode.nodeId
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
const allVariables = [...globalVariables, ...nodeVariables, ...customInputs];
|
||||
|
||||
// Replace {{$xxx.xxx$}} to value
|
||||
for (const key in allVariables) {
|
||||
const variable = allVariables[key];
|
||||
const val = variable.value;
|
||||
const formatVal = (() => {
|
||||
if (val === undefined) return '';
|
||||
if (val === null) return 'null';
|
||||
|
||||
return typeof val === 'object' ? JSON.stringify(val) : String(val);
|
||||
})();
|
||||
|
||||
const regex = new RegExp(`\\{\\{\\$(${variable.nodeId}\\.${variable.id})\\$\\}\\}`, 'g');
|
||||
text = text.replace(regex, formatVal);
|
||||
}
|
||||
return text || '';
|
||||
}
|
||||
|
||||
export const textAdaptGptResponse = ({
|
||||
text,
|
||||
model = '',
|
||||
|
@@ -75,10 +75,17 @@ export const Input_Template_Text_Quote: FlowNodeInputItemType = {
|
||||
description: i18nT('app:document_quote_tip'),
|
||||
valueType: WorkflowIOValueTypeEnum.string
|
||||
};
|
||||
|
||||
export const Input_Template_File_Link_Prompt: FlowNodeInputItemType = {
|
||||
key: NodeInputKeyEnum.fileUrlList,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.input],
|
||||
label: i18nT('app:file_quote_link'),
|
||||
debugLabel: i18nT('app:file_quote_link'),
|
||||
valueType: WorkflowIOValueTypeEnum.arrayString
|
||||
};
|
||||
export const Input_Template_File_Link: FlowNodeInputItemType = {
|
||||
key: NodeInputKeyEnum.fileUrlList,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
required: true,
|
||||
label: i18nT('app:workflow.user_file_input'),
|
||||
debugLabel: i18nT('app:workflow.user_file_input'),
|
||||
description: i18nT('app:workflow.user_file_input_desc'),
|
||||
@@ -104,7 +111,14 @@ export const Input_Template_Node_Height: FlowNodeInputItemType = {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.number,
|
||||
label: '',
|
||||
value: 900
|
||||
value: 600
|
||||
};
|
||||
export const Input_Template_LOOP_NODE_OFFSET: FlowNodeInputItemType = {
|
||||
key: NodeInputKeyEnum.loopNodeInputHeight,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.number,
|
||||
label: '',
|
||||
value: 320
|
||||
};
|
||||
|
||||
export const Input_Template_Stream_MODE: FlowNodeInputItemType = {
|
||||
|
@@ -17,7 +17,7 @@ import {
|
||||
Input_Template_History,
|
||||
Input_Template_System_Prompt,
|
||||
Input_Template_UserChatInput,
|
||||
Input_Template_Text_Quote
|
||||
Input_Template_File_Link_Prompt
|
||||
} from '../../input';
|
||||
import { chatNodeSystemPromptTip, systemPromptTip } from '../../tip';
|
||||
import { getHandleConfig } from '../../utils';
|
||||
@@ -54,8 +54,8 @@ export const AiChatModule: FlowNodeTemplateType = {
|
||||
intro: i18nT('workflow:template.ai_chat_intro'),
|
||||
showStatus: true,
|
||||
isTool: true,
|
||||
courseUrl: '/docs/workflow/modules/ai_chat/',
|
||||
version: '481',
|
||||
courseUrl: '/docs/guide/workbench/workflow/ai_chat/',
|
||||
version: '4813',
|
||||
inputs: [
|
||||
Input_Template_SettingAiModel,
|
||||
// --- settings modal
|
||||
@@ -89,7 +89,7 @@ export const AiChatModule: FlowNodeTemplateType = {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
label: '',
|
||||
valueType: WorkflowIOValueTypeEnum.boolean,
|
||||
value: false
|
||||
value: true
|
||||
},
|
||||
// settings modal ---
|
||||
{
|
||||
@@ -100,7 +100,7 @@ export const AiChatModule: FlowNodeTemplateType = {
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_Dataset_Quote,
|
||||
Input_Template_Text_Quote,
|
||||
Input_Template_File_Link_Prompt,
|
||||
|
||||
{ ...Input_Template_UserChatInput, toolDescription: i18nT('workflow:user_question') }
|
||||
],
|
||||
|
@@ -17,7 +17,7 @@ export const AssignedAnswerModule: FlowNodeTemplateType = {
|
||||
avatar: 'core/workflow/template/reply',
|
||||
name: i18nT('workflow:assigned_reply'),
|
||||
intro: i18nT('workflow:intro_assigned_reply'),
|
||||
courseUrl: '/docs/workflow/modules/reply/',
|
||||
courseUrl: '/docs/guide/workbench/workflow/reply/',
|
||||
version: '481',
|
||||
isTool: true,
|
||||
inputs: [
|
||||
|
@@ -31,7 +31,7 @@ export const ClassifyQuestionModule: FlowNodeTemplateType = {
|
||||
intro: i18nT('workflow:intro_question_classification'),
|
||||
showStatus: true,
|
||||
version: '481',
|
||||
courseUrl: '/docs/workflow/modules/question_classify/',
|
||||
courseUrl: '/docs/guide/workbench/workflow/question_classify/',
|
||||
inputs: [
|
||||
{
|
||||
...Input_Template_SelectAIModel,
|
||||
|
@@ -26,7 +26,7 @@ export const ContextExtractModule: FlowNodeTemplateType = {
|
||||
intro: i18nT('workflow:intro_text_content_extraction'),
|
||||
showStatus: true,
|
||||
isTool: true,
|
||||
courseUrl: '/docs/workflow/modules/content_extract/',
|
||||
courseUrl: '/docs/guide/workbench/workflow/content_extract/',
|
||||
version: '481',
|
||||
inputs: [
|
||||
{
|
||||
|
@@ -17,7 +17,7 @@ export const CustomFeedbackNode: FlowNodeTemplateType = {
|
||||
avatar: 'core/workflow/template/customFeedback',
|
||||
name: i18nT('workflow:custom_feedback'),
|
||||
intro: i18nT('workflow:intro_custom_feedback'),
|
||||
courseUrl: '/docs/workflow/modules/custom_feedback/',
|
||||
courseUrl: '/docs/guide/workbench/workflow/custom_feedback/',
|
||||
version: '486',
|
||||
inputs: [
|
||||
{
|
||||
|
@@ -25,7 +25,7 @@ export const getOneQuoteInputTemplate = ({
|
||||
}): FlowNodeInputItemType => ({
|
||||
key,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
label: `${i18nT('workflow:quote_num')},{ num: ${index} }`,
|
||||
label: `${i18nT('workflow:quote_num')}-${index}`,
|
||||
debugLabel: i18nT('workflow:knowledge_base_reference'),
|
||||
canEdit: true,
|
||||
valueType: WorkflowIOValueTypeEnum.datasetQuote
|
||||
@@ -43,6 +43,7 @@ export const DatasetConcatModule: FlowNodeTemplateType = {
|
||||
|
||||
showStatus: false,
|
||||
version: '486',
|
||||
courseUrl: '/docs/guide/workbench/workflow/knowledge_base_search_merge/',
|
||||
inputs: [
|
||||
{
|
||||
key: NodeInputKeyEnum.datasetMaxTokens,
|
||||
|
@@ -29,7 +29,7 @@ export const DatasetSearchModule: FlowNodeTemplateType = {
|
||||
intro: Dataset_SEARCH_DESC,
|
||||
showStatus: true,
|
||||
isTool: true,
|
||||
courseUrl: '/docs/workflow/modules/dataset_search/',
|
||||
courseUrl: '/docs/guide/workbench/workflow/dataset_search/',
|
||||
version: '481',
|
||||
inputs: [
|
||||
{
|
||||
|
@@ -27,7 +27,7 @@ export const HttpNode468: FlowNodeTemplateType = {
|
||||
intro: i18nT('workflow:intro_http_request'),
|
||||
showStatus: true,
|
||||
isTool: true,
|
||||
courseUrl: '/docs/workflow/modules/http/',
|
||||
courseUrl: '/docs/guide/workbench/workflow/http/',
|
||||
version: '481',
|
||||
inputs: [
|
||||
{
|
||||
|
@@ -23,7 +23,7 @@ export const IfElseNode: FlowNodeTemplateType = {
|
||||
name: i18nT('workflow:condition_checker'),
|
||||
intro: i18nT('workflow:execute_different_branches_based_on_conditions'),
|
||||
showStatus: true,
|
||||
courseUrl: '/docs/workflow/modules/tfswitch/',
|
||||
courseUrl: '/docs/guide/workbench/workflow/tfswitch/',
|
||||
version: '481',
|
||||
inputs: [
|
||||
{
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { ReferenceValueProps } from 'core/workflow/type/io';
|
||||
import { ReferenceItemValueType } from '../../../type/io';
|
||||
import { VariableConditionEnum } from './constant';
|
||||
|
||||
export type IfElseConditionType = 'AND' | 'OR';
|
||||
export type ConditionListItemType = {
|
||||
variable?: ReferenceValueProps;
|
||||
variable?: ReferenceItemValueType;
|
||||
condition?: VariableConditionEnum;
|
||||
value?: string;
|
||||
};
|
||||
|
@@ -25,6 +25,7 @@ export const UserSelectNode: FlowNodeTemplateType = {
|
||||
intro: i18nT(`app:workflow.user_select_tip`),
|
||||
isTool: true,
|
||||
version: '489',
|
||||
courseUrl: '/docs/guide/workbench/workflow/user-selection/',
|
||||
inputs: [
|
||||
{
|
||||
key: NodeInputKeyEnum.description,
|
||||
|
@@ -32,7 +32,7 @@ export const LafModule: FlowNodeTemplateType = {
|
||||
intro: i18nT('workflow:intro_laf_function_call'),
|
||||
showStatus: true,
|
||||
isTool: true,
|
||||
courseUrl: '/docs/workflow/modules/laf/',
|
||||
courseUrl: '/docs/guide/workbench/workflow/laf/',
|
||||
version: '481',
|
||||
inputs: [
|
||||
{
|
||||
|
@@ -14,6 +14,7 @@ import { getHandleConfig } from '../../utils';
|
||||
import { i18nT } from '../../../../../../web/i18n/utils';
|
||||
import {
|
||||
Input_Template_Children_Node_List,
|
||||
Input_Template_LOOP_NODE_OFFSET,
|
||||
Input_Template_Node_Height,
|
||||
Input_Template_Node_Width
|
||||
} from '../../input';
|
||||
@@ -29,6 +30,7 @@ export const LoopNode: FlowNodeTemplateType = {
|
||||
intro: i18nT('workflow:intro_loop'),
|
||||
showStatus: true,
|
||||
version: '4811',
|
||||
courseUrl: '/docs/guide/workbench/workflow/loop/',
|
||||
inputs: [
|
||||
{
|
||||
key: NodeInputKeyEnum.loopInputArray,
|
||||
@@ -40,7 +42,8 @@ export const LoopNode: FlowNodeTemplateType = {
|
||||
},
|
||||
Input_Template_Children_Node_List,
|
||||
Input_Template_Node_Width,
|
||||
Input_Template_Node_Height
|
||||
Input_Template_Node_Height,
|
||||
Input_Template_LOOP_NODE_OFFSET
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
|
@@ -1,8 +1,13 @@
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../../node/constant';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '../../../node/constant';
|
||||
import { FlowNodeTemplateType } from '../../../type/node.d';
|
||||
import {
|
||||
FlowNodeTemplateTypeEnum,
|
||||
NodeInputKeyEnum,
|
||||
NodeOutputKeyEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} from '../../../constants';
|
||||
import { getHandleConfig } from '../../utils';
|
||||
@@ -28,7 +33,21 @@ export const LoopStartNode: FlowNodeTemplateType = {
|
||||
label: '',
|
||||
required: true,
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: NodeInputKeyEnum.loopStartIndex,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.number,
|
||||
label: i18nT('workflow:Array_element_index')
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
outputs: [
|
||||
{
|
||||
id: NodeOutputKeyEnum.loopStartIndex,
|
||||
key: NodeOutputKeyEnum.loopStartIndex,
|
||||
label: i18nT('workflow:Array_element_index'),
|
||||
type: FlowNodeOutputTypeEnum.static,
|
||||
valueType: WorkflowIOValueTypeEnum.number
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@@ -23,8 +23,9 @@ export const ReadFilesNode: FlowNodeTemplateType = {
|
||||
name: i18nT('app:workflow.read_files'),
|
||||
intro: i18nT('app:workflow.read_files_tip'),
|
||||
showStatus: true,
|
||||
version: '489',
|
||||
isTool: true,
|
||||
version: '4812',
|
||||
isTool: false,
|
||||
courseUrl: '/docs/guide/course/fileinput/',
|
||||
inputs: [
|
||||
{
|
||||
key: NodeInputKeyEnum.fileUrlList,
|
||||
|
@@ -26,7 +26,7 @@ export const CodeNode: FlowNodeTemplateType = {
|
||||
name: i18nT('workflow:code_execution'),
|
||||
intro: i18nT('workflow:execute_a_simple_script_code_usually_for_complex_data_processing'),
|
||||
showStatus: true,
|
||||
courseUrl: '/docs/workflow/modules/sandbox/',
|
||||
courseUrl: '/docs/guide/workbench/workflow/sandbox/',
|
||||
version: '482',
|
||||
inputs: [
|
||||
{
|
||||
|
@@ -23,18 +23,9 @@ export const TextEditorNode: FlowNodeTemplateType = {
|
||||
avatar: 'core/workflow/template/textConcat',
|
||||
name: i18nT('workflow:text_concatenation'),
|
||||
intro: i18nT('workflow:intro_text_concatenation'),
|
||||
courseUrl: '/docs/workflow/modules/text_editor/',
|
||||
version: '486',
|
||||
courseUrl: '/docs/guide/workbench/workflow/text_editor/',
|
||||
version: '4813',
|
||||
inputs: [
|
||||
{
|
||||
...Input_Template_DynamicInput,
|
||||
description: i18nT('workflow:dynamic_input_description_concat'),
|
||||
customInputConfig: {
|
||||
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
|
||||
showDescription: false,
|
||||
showDefaultValue: false
|
||||
}
|
||||
},
|
||||
{
|
||||
key: NodeInputKeyEnum.textareaInput,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.textarea],
|
||||
|
@@ -20,6 +20,7 @@ import { chatNodeSystemPromptTip, systemPromptTip } from '../tip';
|
||||
import { LLMModelTypeEnum } from '../../../ai/constants';
|
||||
import { getHandleConfig } from '../utils';
|
||||
import { i18nT } from '../../../../../web/i18n/utils';
|
||||
import { Input_Template_File_Link_Prompt } from '../input';
|
||||
|
||||
export const ToolModule: FlowNodeTemplateType = {
|
||||
id: FlowNodeTypeEnum.tools,
|
||||
@@ -31,8 +32,8 @@ export const ToolModule: FlowNodeTemplateType = {
|
||||
name: i18nT('workflow:template.tool_call'),
|
||||
intro: i18nT('workflow:template.tool_call_intro'),
|
||||
showStatus: true,
|
||||
courseUrl: '/docs/workflow/modules/tool/',
|
||||
version: '481',
|
||||
courseUrl: '/docs/guide/workbench/workflow/tool/',
|
||||
version: '4813',
|
||||
inputs: [
|
||||
{
|
||||
...Input_Template_SettingAiModel,
|
||||
@@ -67,6 +68,7 @@ export const ToolModule: FlowNodeTemplateType = {
|
||||
placeholder: chatNodeSystemPromptTip
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_File_Link_Prompt,
|
||||
Input_Template_UserChatInput
|
||||
],
|
||||
outputs: [
|
||||
|
@@ -20,6 +20,7 @@ export const VariableUpdateNode: FlowNodeTemplateType = {
|
||||
showStatus: false,
|
||||
isTool: true,
|
||||
version: '481',
|
||||
courseUrl: '/docs/guide/workbench/workflow/variable_update/',
|
||||
inputs: [
|
||||
{
|
||||
key: NodeInputKeyEnum.updateList,
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { FlowNodeInputTypeEnum } from '../../../node/constant';
|
||||
import { ReferenceValueProps } from '../../..//type/io';
|
||||
import { ReferenceItemValueType, ReferenceValueType } from '../../..//type/io';
|
||||
import { WorkflowIOValueTypeEnum } from '../../../constants';
|
||||
|
||||
export type TUpdateListItem = {
|
||||
variable?: ReferenceValueProps;
|
||||
value: ReferenceValueProps;
|
||||
variable?: ReferenceItemValueType;
|
||||
value?: ReferenceValueType; // input: ['',value], reference: [nodeId,outputId]
|
||||
valueType?: WorkflowIOValueTypeEnum;
|
||||
renderType: FlowNodeInputTypeEnum.input | FlowNodeInputTypeEnum.reference;
|
||||
};
|
||||
|
@@ -30,7 +30,7 @@ export const WorkflowStart: FlowNodeTemplateType = {
|
||||
intro: '',
|
||||
forbidDelete: true,
|
||||
unique: true,
|
||||
courseUrl: '/docs/workflow/modules/input/',
|
||||
courseUrl: '/docs/guide/workbench/workflow/input/',
|
||||
version: '481',
|
||||
inputs: [{ ...Input_Template_UserChatInput, toolDescription: i18nT('workflow:user_question') }],
|
||||
outputs: [
|
||||
@@ -43,6 +43,3 @@ export const WorkflowStart: FlowNodeTemplateType = {
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const isWorkflowStartOutput = (key?: string) =>
|
||||
!!WorkflowStart.outputs.find((output) => output.key === key);
|
||||
|
9
packages/global/core/workflow/type/io.d.ts
vendored
9
packages/global/core/workflow/type/io.d.ts
vendored
@@ -56,6 +56,11 @@ export type FlowNodeInputItemType = InputComponentPropsType & {
|
||||
canEdit?: boolean; // dynamic inputs
|
||||
isPro?: boolean; // Pro version field
|
||||
isToolOutput?: boolean;
|
||||
|
||||
// file
|
||||
canSelectFile?: boolean;
|
||||
canSelectImg?: boolean;
|
||||
maxFiles?: number;
|
||||
};
|
||||
|
||||
export type FlowNodeOutputItemType = {
|
||||
@@ -75,4 +80,6 @@ export type FlowNodeOutputItemType = {
|
||||
customFieldConfig?: CustomFieldConfigType;
|
||||
};
|
||||
|
||||
export type ReferenceValueProps = [string, string | undefined];
|
||||
export type ReferenceItemValueType = [string, string | undefined];
|
||||
export type ReferenceArrayValueType = ReferenceItemValueType[];
|
||||
export type ReferenceValueType = ReferenceItemValueType | ReferenceArrayValueType;
|
||||
|
@@ -12,7 +12,12 @@ import {
|
||||
VARIABLE_NODE_ID,
|
||||
NodeOutputKeyEnum
|
||||
} from './constants';
|
||||
import { FlowNodeInputItemType, FlowNodeOutputItemType, ReferenceValueProps } from './type/io.d';
|
||||
import {
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType,
|
||||
ReferenceArrayValueType,
|
||||
ReferenceItemValueType
|
||||
} from './type/io.d';
|
||||
import { StoreNodeItemType } from './type/node';
|
||||
import type {
|
||||
VariableItemType,
|
||||
@@ -30,8 +35,8 @@ import {
|
||||
} from '../app/constants';
|
||||
import { IfElseResultEnum } from './template/system/ifElse/constant';
|
||||
import { RuntimeNodeItemType } from './runtime/type';
|
||||
import { getReferenceVariableValue } from './runtime/utils';
|
||||
import {
|
||||
Input_Template_File_Link,
|
||||
Input_Template_History,
|
||||
Input_Template_Stream_MODE,
|
||||
Input_Template_UserChatInput
|
||||
@@ -261,8 +266,10 @@ export const appData2FlowNodeIO = ({
|
||||
inputs: [
|
||||
Input_Template_Stream_MODE,
|
||||
Input_Template_History,
|
||||
...(chatConfig?.fileSelectConfig?.canSelectFile || chatConfig?.fileSelectConfig?.canSelectImg
|
||||
? [Input_Template_File_Link]
|
||||
: []),
|
||||
Input_Template_UserChatInput,
|
||||
// ...(showFileLink ? [Input_Template_File_Link] : []),
|
||||
...variableInput
|
||||
],
|
||||
outputs: [
|
||||
@@ -298,9 +305,37 @@ export const formatEditorVariablePickerIcon = (
|
||||
}));
|
||||
};
|
||||
|
||||
export const isReferenceValue = (value: any, nodeIds: string[]): boolean => {
|
||||
const validIdList = [VARIABLE_NODE_ID, ...nodeIds];
|
||||
return Array.isArray(value) && value.length === 2 && validIdList.includes(value[0]);
|
||||
// Check the value is a valid reference value format: [variableId, outputId]
|
||||
export const isValidReferenceValueFormat = (value: any): value is ReferenceItemValueType => {
|
||||
return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string';
|
||||
};
|
||||
/*
|
||||
Check whether the value([variableId, outputId]) value is a valid reference value:
|
||||
1. The value must be an array of length 2
|
||||
2. The first item of the array must be one of VARIABLE_NODE_ID or nodeIds
|
||||
*/
|
||||
export const isValidReferenceValue = (
|
||||
value: any,
|
||||
nodeIds: string[]
|
||||
): value is ReferenceItemValueType => {
|
||||
if (!isValidReferenceValueFormat(value)) return false;
|
||||
|
||||
const validIdSet = new Set([VARIABLE_NODE_ID, ...nodeIds]);
|
||||
return validIdSet.has(value[0]);
|
||||
};
|
||||
/*
|
||||
Check whether the value([variableId, outputId][]) value is a valid reference value array:
|
||||
1. The value must be an array
|
||||
2. The array must contain at least one element
|
||||
3. Each element in the array must be a valid reference value
|
||||
*/
|
||||
export const isValidArrayReferenceValue = (
|
||||
value: any,
|
||||
nodeIds: string[]
|
||||
): value is ReferenceArrayValueType => {
|
||||
if (!Array.isArray(value)) return false;
|
||||
|
||||
return value.every((item) => isValidReferenceValue(item, nodeIds));
|
||||
};
|
||||
|
||||
export const getElseIFLabel = (i: number) => {
|
||||
@@ -342,79 +377,6 @@ export const updatePluginInputByVariables = (
|
||||
);
|
||||
};
|
||||
|
||||
// replace {{$xx.xx$}} variables for text
|
||||
export function replaceEditorVariable({
|
||||
text,
|
||||
nodes,
|
||||
variables,
|
||||
runningNode
|
||||
}: {
|
||||
text: any;
|
||||
nodes: RuntimeNodeItemType[];
|
||||
variables: Record<string, any>; // global variables
|
||||
runningNode: RuntimeNodeItemType;
|
||||
}) {
|
||||
if (typeof text !== 'string') return text;
|
||||
|
||||
const globalVariables = Object.keys(variables).map((key) => {
|
||||
return {
|
||||
nodeId: VARIABLE_NODE_ID,
|
||||
id: key,
|
||||
value: variables[key]
|
||||
};
|
||||
});
|
||||
|
||||
// Upstream node outputs
|
||||
const nodeVariables = nodes
|
||||
.map((node) => {
|
||||
return node.outputs.map((output) => {
|
||||
return {
|
||||
nodeId: node.nodeId,
|
||||
id: output.id,
|
||||
value: output.value
|
||||
};
|
||||
});
|
||||
})
|
||||
.flat();
|
||||
|
||||
// Get runningNode inputs(Will be replaced with reference)
|
||||
const customInputs = runningNode.inputs.flatMap((item) => {
|
||||
if (Array.isArray(item.value)) {
|
||||
return [
|
||||
{
|
||||
id: item.key,
|
||||
value: getReferenceVariableValue({
|
||||
value: item.value as ReferenceValueProps,
|
||||
nodes,
|
||||
variables
|
||||
}),
|
||||
nodeId: runningNode.nodeId
|
||||
}
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: item.key,
|
||||
value: item.value,
|
||||
nodeId: runningNode.nodeId
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
const allVariables = [...globalVariables, ...nodeVariables, ...customInputs];
|
||||
|
||||
// Replace {{$xxx.xxx$}} to value
|
||||
for (const key in allVariables) {
|
||||
const variable = allVariables[key];
|
||||
const val = variable.value;
|
||||
const formatVal = typeof val === 'object' ? JSON.stringify(val) : String(val);
|
||||
|
||||
const regex = new RegExp(`\\{\\{\\$(${variable.nodeId}\\.${variable.id})\\$\\}\\}`, 'g');
|
||||
text = text.replace(regex, formatVal);
|
||||
}
|
||||
return text || '';
|
||||
}
|
||||
|
||||
/* Get plugin runtime input user query */
|
||||
export const getPluginRunUserQuery = ({
|
||||
pluginInputs,
|
||||
|
6
packages/global/support/outLink/type.d.ts
vendored
6
packages/global/support/outLink/type.d.ts
vendored
@@ -51,6 +51,10 @@ export type OutLinkSchema<T extends OutlinkAppType = undefined> = {
|
||||
|
||||
// whether the response content is detailed
|
||||
responseDetail: boolean;
|
||||
// whether to hide the node status
|
||||
showNodeStatus?: boolean;
|
||||
// whether to show the complete quote
|
||||
showRawSource?: boolean;
|
||||
|
||||
// response when request
|
||||
immediateResponse?: string;
|
||||
@@ -79,6 +83,8 @@ export type OutLinkEditType<T = undefined> = {
|
||||
_id?: string;
|
||||
name: string;
|
||||
responseDetail?: OutLinkSchema<T>['responseDetail'];
|
||||
showNodeStatus?: OutLinkSchema<T>['showNodeStatus'];
|
||||
showRawSource?: OutLinkSchema<T>['showRawSource'];
|
||||
// response when request
|
||||
immediateResponse?: string;
|
||||
// response when error or other situation
|
||||
|
Reference in New Issue
Block a user