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:
Archer
2024-11-13 11:29:53 +08:00
committed by GitHub
parent e1f5483432
commit e9d52ada73
449 changed files with 7626 additions and 4180 deletions

View File

@@ -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';

View File

@@ -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
};
}
};

View File

@@ -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 */

View File

@@ -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>
`;

View File

@@ -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 {

View File

@@ -126,6 +126,7 @@ export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatIt
moduleName?: string;
ttsBuffer?: Uint8Array;
responseData?: ChatHistoryItemResType[];
time?: Date;
} & ChatBoxInputType &
ResponseTagItemType;

View File

@@ -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 `![Input an image](${item.file.url.slice(0, 100)}...)`;
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,

View File

@@ -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
};

View File

@@ -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]: {

View File

@@ -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[];
};

View File

@@ -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 = '',

View File

@@ -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 = {

View File

@@ -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') }
],

View File

@@ -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: [

View File

@@ -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,

View File

@@ -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: [
{

View File

@@ -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: [
{

View File

@@ -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,

View File

@@ -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: [
{

View File

@@ -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: [
{

View File

@@ -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: [
{

View File

@@ -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;
};

View File

@@ -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,

View File

@@ -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: [
{

View File

@@ -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: [
{

View File

@@ -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
}
]
};

View File

@@ -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,

View File

@@ -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: [
{

View File

@@ -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],

View File

@@ -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: [

View File

@@ -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,

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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;

View File

@@ -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,

View File

@@ -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