Compare commits

...

5 Commits

Author SHA1 Message Date
Archer
b5f0ac3e1d Perf: read file woker (#1337)
* perf: read file worker

* fix: Http node url input

* fix: htm2md

* fix: html2md

* fix: ts

* perf: Problem classification increases the matching order

* feat: tool response answer
2024-04-30 18:12:20 +08:00
Cheer
1529c1e991 fix: issues/1334 useTransition 导致光标刷新后移问题; (#1338) 2024-04-30 15:59:39 +08:00
Archer
db6fc53840 Publish histories (#1331)
* fix http plugin edge (#95)

* fix http plugin edge

* use getHandleId

* perf: i18n file

* feat: histories list

* perf: request lock

* fix: ts

* move box components

* fix: edit form refresh

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
2024-04-30 12:42:13 +08:00
Archer
a0c1320d47 4.8-preview fix (#1324)
* feishu app release (#85)

* Revert "lafAccount add pat & re request when token invalid (#76)" (#77)

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

* perf: workflow ux

* system config

* feat: feishu app release

* chore: sovle the conflicts files; fix the feishu entry

* fix: rename Feishu interface to FeishuType

* fix: fix type problem in app.ts

* fix: type problem

* fix: style problem

---------

Co-authored-by: Archer <545436317@qq.com>

* perf: publish channel code

* change system variable position (#94)

* perf: workflow context

* perf: variable select

* hide publish

* perf: simple edit auto refresh

* perf: simple edit data refresh

* fix: target handle

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
2024-04-29 11:13:10 +08:00
Cheer
5ca4049757 feat: 增加自定义 meta description; (#1246)
* feat: 增加自定义 meta description;

* fix: 环境变量使用错误;

---------

Co-authored-by: junshun.mq <junshun.mq@alibaba-inc.com>
2024-04-28 18:31:47 +08:00
160 changed files with 2957 additions and 1584 deletions

View File

@@ -21,7 +21,7 @@ assignees: ''
- [ ] 公有云版本
- [ ] 私有部署版本, 具体版本号:
**问题描述**
**问题描述, 日志截图**
**复现步骤**

View File

@@ -17,7 +17,7 @@
"",
"type Response = {};",
"",
"async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {",
"async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<Response> {",
" $1",
" return {}",
"}",

View File

@@ -4,7 +4,7 @@
"typescript.tsdk": "node_modules/typescript/lib",
"prettier.prettierPath": "",
"i18n-ally.localesPaths": [
"projects/app/public/locales",
"projects/app/i18n",
],
"i18n-ally.enabledParsers": ["json"],
"i18n-ally.keystyle": "nested",

View File

@@ -106,6 +106,7 @@ FastGPT 商业版共包含了2个应用fastgpt, fastgpt-plus和2个数据
```
SYSTEM_NAME=FastGPT
SYSTEM_DESCRIPTION=
SYSTEM_FAVICON=/favicon.ico
HOME_URL=/app/list
```

View File

@@ -21,5 +21,7 @@ FastGPT workflow V2上线支持更加简洁的工作流模式。
2. 新增 - 工作流 Debug 模式,可以调试单个节点或者逐步调试工作流。
3. 新增 - 定时执行应用。可轻松实现定时任务。
4. 新增 - 插件自定义输入优化,可以渲染输入组件。
5. 优化 - 工作流连线,可以四向连接,方便构建循环工作流。
6. 优化 - worker进程管理并将计算 Token 任务分配给 worker 进程。
6. 优化 - 工作流连线,可以四向连接,方便构建循环工作流。
7. 优化 - 工作流上下文传递,性能🚀。
8. 优化 - 简易模式,更新配置后自动更新调试框内容,无需保存。
9. 优化 - worker进程管理并将计算 Token 任务分配给 worker 进程。

View File

@@ -37,6 +37,7 @@ export type FastGPTFeConfigsType = {
chatbotUrl?: string;
openAPIDocUrl?: string;
systemTitle?: string;
systemDescription?: string;
googleClientVerKey?: string;
isPlus?: boolean;
show_phoneLogin?: boolean;

View File

@@ -125,6 +125,7 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
defaultAppForm.selectedTools.push({
id: node.pluginId,
pluginId: node.pluginId,
name: node.name,
avatar: node.avatar,
intro: node.intro || '',

View File

@@ -2,6 +2,7 @@ import { StoreNodeItemType } from '../workflow/type';
import { StoreEdgeItemType } from '../workflow/type/edge';
export type AppVersionSchemaType = {
_id: string;
appId: string;
time: Date;
nodes: StoreNodeItemType[];

View File

@@ -14,6 +14,7 @@ import { CreateOnePluginParams } from '../controller';
import { StoreNodeItemType } from '../../workflow/type';
import { HttpImgUrl } from '../../../common/file/image/constants';
import SwaggerParser from '@apidevtools/swagger-parser';
import { getHandleId } from '../../../core/workflow/utils';
export const str2OpenApiSchema = async (yamlStr = ''): Promise<OpenApiJsonSchema> => {
try {
@@ -378,14 +379,14 @@ export const httpApiSchema2Plugins = async ({
{
source: pluginInputId,
target: httpId,
sourcePort: `${pluginInputId}-source-right`,
targetPort: `${httpId}-target-left`
sourceHandle: getHandleId(pluginInputId, 'source', 'right'),
targetHandle: getHandleId(httpId, 'target', 'left')
},
{
source: httpId,
target: pluginOutputId,
sourcePort: `${httpId}-source-right`,
targetPort: `${pluginOutputId}-target-left`
sourceHandle: getHandleId(httpId, 'source', 'right'),
targetHandle: getHandleId(pluginOutputId, 'target', 'left')
}
];

View File

@@ -83,41 +83,3 @@ export const moduleTemplatesFlat: FlowNodeTemplateType[] = [
lafModule,
ifElseNode
];
export const moduleTemplatesList: nodeTemplateListType = [
{
type: FlowNodeTemplateTypeEnum.systemInput,
label: 'core.module.template.System input module',
list: []
},
{
type: FlowNodeTemplateTypeEnum.textAnswer,
label: 'core.module.template.Response module',
list: []
},
{
type: FlowNodeTemplateTypeEnum.functionCall,
label: 'core.module.template.Function module',
list: []
},
{
type: FlowNodeTemplateTypeEnum.tools,
label: 'core.module.template.Tool module',
list: []
},
{
type: FlowNodeTemplateTypeEnum.externalCall,
label: 'core.module.template.External module',
list: []
},
{
type: FlowNodeTemplateTypeEnum.personalPlugin,
label: '',
list: []
},
{
type: FlowNodeTemplateTypeEnum.other,
label: '其他',
list: []
}
];

View File

@@ -64,5 +64,14 @@ export const ToolModule: FlowNodeTemplateType = {
Input_Template_History,
Input_Template_UserChatInput
],
outputs: []
outputs: [
{
id: NodeOutputKeyEnum.answerText,
key: NodeOutputKeyEnum.answerText,
label: 'core.module.output.label.Ai response content',
description: 'core.module.output.description.Ai response content',
valueType: WorkflowIOValueTypeEnum.string,
type: FlowNodeOutputTypeEnum.static
}
]
};

View File

@@ -1,5 +1,6 @@
export enum OutLinkTypeEnum {
export enum PublishChannelEnum {
share = 'share',
iframe = 'iframe',
apikey = 'apikey'
apikey = 'apikey',
feishu = 'feishu'
}

View File

@@ -1,31 +1,79 @@
import { AppSchema } from 'core/app/type';
import { OutLinkTypeEnum } from './constant';
import { PublishChannelEnum } from './constant';
export type OutLinkSchema = {
// Feishu Config interface
export interface FeishuType {
appId: string;
appSecret: string;
// Encrypt config
// refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/configure-encrypt-key
encryptKey?: string; // no secret if null
// Token Verification
// refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/encrypt-key-encryption-configuration-case
verificationToken: string;
}
// TODO: Unused
export interface WecomType {
ReplyLimit: Boolean;
defaultResponse: string;
immediateResponse: boolean;
WXWORK_TOKEN: string;
WXWORK_AESKEY: string;
WXWORK_SECRET: string;
WXWORD_ID: string;
}
export type OutLinkSchema<T = void> = {
_id: string;
shareId: string;
teamId: string;
tmbId: string;
appId: string;
// teamId: Schema.Types.ObjectId;
// tmbId: Schema.Types.ObjectId;
// appId: Schema.Types.ObjectId;
name: string;
usagePoints: number;
lastTime: Date;
type: `${OutLinkTypeEnum}`;
type: PublishChannelEnum;
// whether the response content is detailed
responseDetail: boolean;
// response when request
immediateResponse?: string;
// response when error or other situation
defaultResponse?: string;
limit?: {
expiredTime?: Date;
// Questions per minute
QPM: number;
maxUsagePoints: number;
// Verification message hook url
hookUrl?: string;
};
app?: T;
};
// to handle MongoDB querying
export type OutLinkWithAppType = Omit<OutLinkSchema, 'appId'> & {
appId: AppSchema;
};
export type OutLinkEditType = {
// Edit the Outlink
export type OutLinkEditType<T = void> = {
_id?: string;
name: string;
responseDetail: OutLinkSchema['responseDetail'];
limit: OutLinkSchema['limit'];
responseDetail: OutLinkSchema<T>['responseDetail'];
// response when request
immediateResponse?: string;
// response when error or other situation
defaultResponse?: string;
limit?: OutLinkSchema<T>['limit'];
// config for specific platform
app?: T;
};

View File

@@ -6,7 +6,6 @@ import { DatasetFileSchema } from '@fastgpt/global/core/dataset/type';
import { MongoFileSchema } from './schema';
import { detectFileEncoding } from '@fastgpt/global/common/file/tools';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { ReadFileByBufferParams } from '../read/type';
import { MongoRwaTextBuffer } from '../../buffer/rawText/schema';
import { readFileRawContent } from '../read/utils';
import { PassThrough } from 'stream';
@@ -197,19 +196,15 @@ export const readFileContentFromMongo = async ({
});
})();
const params: ReadFileByBufferParams = {
const { rawText } = await readFileRawContent({
extension,
csvFormat,
teamId,
buffer: fileBuffers,
encoding,
metadata: {
relatedId: fileId
}
};
const { rawText } = await readFileRawContent({
extension,
csvFormat,
params
});
if (rawText.trim()) {

View File

@@ -1,23 +0,0 @@
import { ReadFileByBufferParams, ReadFileResponse } from './type.d';
import { initMarkdownText } from './utils';
import { htmlToMarkdown } from '../../string/markdown';
import { readFileRawText } from './rawText';
export const readHtmlRawText = async (
params: ReadFileByBufferParams
): Promise<ReadFileResponse> => {
const { teamId, metadata } = params;
const { rawText: html } = readFileRawText(params);
const md = await htmlToMarkdown(html);
const rawText = await initMarkdownText({
teamId,
md,
metadata
});
return {
rawText
};
};

View File

@@ -1,18 +0,0 @@
import { ReadFileByBufferParams, ReadFileResponse } from './type.d';
import { initMarkdownText } from './utils';
import { readFileRawText } from './rawText';
export const readMarkdown = async (params: ReadFileByBufferParams): Promise<ReadFileResponse> => {
const { teamId, metadata } = params;
const { rawText: md } = readFileRawText(params);
const rawText = await initMarkdownText({
teamId,
md,
metadata
});
return {
rawText
};
};

View File

@@ -1,12 +0,0 @@
export type ReadFileByBufferParams = {
teamId: string;
buffer: Buffer;
encoding: string;
metadata?: Record<string, any>;
};
export type ReadFileResponse = {
rawText: string;
formatText?: string;
metadata?: Record<string, any>;
};

View File

@@ -1,16 +1,10 @@
import { markdownProcess } from '@fastgpt/global/common/string/markdown';
import { markdownProcess, simpleMarkdownText } from '@fastgpt/global/common/string/markdown';
import { uploadMongoImg } from '../image/controller';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { addHours } from 'date-fns';
import { ReadFileByBufferParams } from './type';
import { readFileRawText } from '../read/rawText';
import { readMarkdown } from '../read/markdown';
import { readHtmlRawText } from '../read/html';
import { readPdfFile } from '../read/pdf';
import { readWordFile } from '../read/word';
import { readCsvRawText } from '../read/csv';
import { readPptxRawText } from '../read/pptx';
import { readXlsxRawText } from '../read/xlsx';
import { WorkerNameEnum, runWorker } from '../../../worker/utils';
import { ReadFileResponse } from '../../../worker/file/type';
export const initMarkdownText = ({
teamId,
@@ -36,46 +30,39 @@ export const initMarkdownText = ({
export const readFileRawContent = async ({
extension,
csvFormat,
params
teamId,
buffer,
encoding,
metadata
}: {
csvFormat?: boolean;
extension: string;
params: ReadFileByBufferParams;
teamId: string;
buffer: Buffer;
encoding: string;
metadata?: Record<string, any>;
}) => {
switch (extension) {
case 'txt':
return readFileRawText(params);
case 'md':
return readMarkdown(params);
case 'html':
return readHtmlRawText(params);
case 'pdf':
return readPdfFile(params);
case 'docx':
return readWordFile(params);
case 'pptx':
return readPptxRawText(params);
case 'xlsx':
const xlsxResult = await readXlsxRawText(params);
if (csvFormat) {
return {
rawText: xlsxResult.formatText || ''
};
}
return {
rawText: xlsxResult.rawText
};
case 'csv':
const csvResult = await readCsvRawText(params);
if (csvFormat) {
return {
rawText: csvResult.formatText || ''
};
}
return {
rawText: csvResult.rawText
};
default:
return Promise.reject('Only support .txt, .md, .html, .pdf, .docx, pptx, .csv, .xlsx');
const result = await runWorker<ReadFileResponse>(WorkerNameEnum.readFile, {
extension,
csvFormat,
encoding,
buffer
});
// markdown data format
if (['md', 'html', 'docx'].includes(extension)) {
result.rawText = await initMarkdownText({
teamId: teamId,
md: result.rawText,
metadata: metadata
});
}
return result;
};
export const htmlToMarkdown = async (html?: string | null) => {
const md = await runWorker<string>(WorkerNameEnum.htmlStr2Md, { html: html || '' });
return simpleMarkdownText(md);
};

View File

@@ -1,35 +0,0 @@
import mammoth from 'mammoth';
import { htmlToMarkdown } from '../../string/markdown';
import { ReadFileByBufferParams, ReadFileResponse } from './type';
import { initMarkdownText } from './utils';
/**
* read docx to markdown
*/
export const readWordFile = async ({
teamId,
buffer,
metadata = {}
}: ReadFileByBufferParams): Promise<ReadFileResponse> => {
try {
const { value: html } = await mammoth.convertToHtml({
buffer
});
const md = await htmlToMarkdown(html);
const rawText = await initMarkdownText({
teamId,
md,
metadata
});
return {
rawText,
metadata: {}
};
} catch (error) {
console.log('error doc read:', error);
return Promise.reject('Can not read doc file, please convert to PDF');
}
};

View File

@@ -1,7 +1,7 @@
import { UrlFetchParams, UrlFetchResponse } from '@fastgpt/global/common/file/api';
import * as cheerio from 'cheerio';
import axios from 'axios';
import { htmlToMarkdown } from './markdown';
import { htmlToMarkdown } from '../file/read/utils';
export const cheerioToHtml = ({
fetchUrl,
@@ -77,7 +77,9 @@ export const urlsFetch = async ({
$,
selector
});
console.log('html====', html);
const md = await htmlToMarkdown(html);
console.log('html====', md);
return {
url,

View File

@@ -1,9 +0,0 @@
import { simpleMarkdownText } from '@fastgpt/global/common/string/markdown';
import { WorkerNameEnum, runWorker } from '../../worker/utils';
/* html string to markdown */
export const htmlToMarkdown = async (html?: string | null) => {
const md = await runWorker<string>(WorkerNameEnum.htmlStr2Md, { html: html || '' });
return simpleMarkdownText(md);
};

View File

@@ -23,7 +23,7 @@ export async function initPg() {
`);
await PgClient.query(
`CREATE INDEX CONCURRENTLY IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 64);`
`CREATE INDEX CONCURRENTLY IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 100);`
);
await PgClient.query(
`CREATE INDEX CONCURRENTLY IF NOT EXISTS team_dataset_collection_index ON ${PgDatasetTableName} USING btree(team_id, dataset_id, collection_id);`

View File

@@ -131,7 +131,9 @@ const completions = async ({
console.log(answer, '----');
const id =
agents.find((item) => answer.includes(item.key) || answer.includes(item.value))?.key || '';
agents.find((item) => answer.includes(item.key))?.key ||
agents.find((item) => answer.includes(item.value))?.key ||
'';
return {
tokens: await countMessagesTokens(messages),

View File

@@ -23,7 +23,9 @@ import { runToolWithPromptCall } from './promptCall';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { Prompt_Tool_Call } from './constants';
type Response = DispatchNodeResultType<{}>;
type Response = DispatchNodeResultType<{
[NodeOutputKeyEnum.answerText]: string;
}>;
export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<Response> => {
const {
@@ -129,6 +131,10 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
const flatUsages = dispatchFlowResponse.map((item) => item.flowUsages).flat();
return {
[NodeOutputKeyEnum.answerText]: assistantResponses
.filter((item) => item.text?.content)
.map((item) => item.text?.content || '')
.join(''),
[DispatchNodeResponseKeyEnum.assistantResponses]: assistantResponses,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: totalPointsUsage,

View File

@@ -142,10 +142,8 @@ export async function dispatchWorkFlow({
}
if (assistantResponses) {
chatAssistantResponse = chatAssistantResponse.concat(assistantResponses);
}
// save assistant text response
if (answerText) {
} else if (answerText) {
// save assistant text response
const isResponseAnswerText =
inputs.find((item) => item.key === NodeInputKeyEnum.aiChatIsResponseText)?.value ?? true;
if (isResponseAnswerText) {

View File

@@ -19,24 +19,24 @@ export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
res,
detail,
stream,
node: { name },
params: { text = '' }
} = props as AnswerProps;
const formatText = typeof text === 'string' ? text : JSON.stringify(text, null, 2);
const responseText = `\n${formatText}`;
if (res && stream) {
responseWrite({
res,
event: detail ? SseResponseEventEnum.fastAnswer : undefined,
data: textAdaptGptResponse({
text: `\n${formatText}`
text: responseText
})
});
}
return {
[NodeOutputKeyEnum.answerText]: formatText,
[NodeOutputKeyEnum.answerText]: responseText,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
textOutput: formatText
}

View File

@@ -1,7 +1,6 @@
import { connectionMongo, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { OutLinkSchema as SchemaType } from '@fastgpt/global/support/outLink/type';
import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant';
import {
TeamCollectionName,
TeamMemberCollectionName
@@ -30,7 +29,7 @@ const OutLinkSchema = new Schema({
},
type: {
type: String,
default: OutLinkTypeEnum.share
required: true
},
name: {
type: String,
@@ -62,6 +61,26 @@ const OutLinkSchema = new Schema({
hookUrl: {
type: String
}
},
app: {
appId: {
type: String
},
appSecret: {
type: String
},
encryptKey: {
type: String
},
verificationToken: {
type: String
}
},
immediateResponse: {
type: String
},
defaultResponse: {
type: String
}
});

View File

@@ -1,9 +1,9 @@
import Papa from 'papaparse';
import { ReadFileByBufferParams, ReadFileResponse } from './type.d';
import { ReadRawTextByBuffer, ReadFileResponse } from '../type';
import { readFileRawText } from './rawText';
// 加载源文件内容
export const readCsvRawText = async (params: ReadFileByBufferParams): Promise<ReadFileResponse> => {
export const readCsvRawText = async (params: ReadRawTextByBuffer): Promise<ReadFileResponse> => {
const { rawText } = readFileRawText(params);
const csvArr = Papa.parse(rawText).data as string[][];

View File

@@ -0,0 +1,23 @@
import mammoth from 'mammoth';
import { ReadRawTextByBuffer, ReadFileResponse } from '../type';
import { html2md } from '../../htmlStr2Md/utils';
/**
* read docx to markdown
*/
export const readDocsFile = async ({ buffer }: ReadRawTextByBuffer): Promise<ReadFileResponse> => {
try {
const { value: html } = await mammoth.convertToHtml({
buffer
});
const rawText = html2md(html);
return {
rawText
};
} catch (error) {
console.log('error doc read:', error);
return Promise.reject('Can not read doc file, please convert to PDF');
}
};

View File

@@ -0,0 +1,13 @@
import { ReadRawTextByBuffer, ReadFileResponse } from '../type';
import { readFileRawText } from './rawText';
import { html2md } from '../../htmlStr2Md/utils';
export const readHtmlRawText = async (params: ReadRawTextByBuffer): Promise<ReadFileResponse> => {
const { rawText: html } = readFileRawText(params);
const rawText = html2md(html);
return {
rawText
};
};

View File

@@ -1,7 +1,7 @@
import * as pdfjs from 'pdfjs-dist/legacy/build/pdf.mjs';
// @ts-ignore
import('pdfjs-dist/legacy/build/pdf.worker.min.mjs');
import { ReadFileByBufferParams, ReadFileResponse } from './type';
import { ReadRawTextByBuffer, ReadFileResponse } from '../type';
type TokenType = {
str: string;
@@ -13,9 +13,7 @@ type TokenType = {
hasEOL: boolean;
};
export const readPdfFile = async ({
buffer
}: ReadFileByBufferParams): Promise<ReadFileResponse> => {
export const readPdfFile = async ({ buffer }: ReadRawTextByBuffer): Promise<ReadFileResponse> => {
const readPDFPage = async (doc: any, pageNo: number) => {
const page = await doc.getPage(pageNo);
const tokenizedText = await page.getTextContent();
@@ -65,7 +63,6 @@ export const readPdfFile = async ({
loadingTask.destroy();
return {
rawText: pageTexts.join(''),
metadata: {}
rawText: pageTexts.join('')
};
};

View File

@@ -1,11 +1,11 @@
import { ReadFileByBufferParams, ReadFileResponse } from './type.d';
import { ReadRawTextByBuffer, ReadFileResponse } from '../type';
// import { parseOfficeAsync } from 'officeparser';
import { parseOffice } from './parseOffice';
import { parseOffice } from '../parseOffice';
export const readPptxRawText = async ({
buffer,
encoding
}: ReadFileByBufferParams): Promise<ReadFileResponse> => {
}: ReadRawTextByBuffer): Promise<ReadFileResponse> => {
const result = await parseOffice({
buffer,
encoding: encoding as BufferEncoding,

View File

@@ -1,5 +1,5 @@
import { ReadFileByBufferParams, ReadFileResponse } from './type.d';
import iconv from 'iconv-lite';
import { ReadRawTextByBuffer, ReadFileResponse } from '../type';
const rawEncodingList = [
'ascii',
@@ -17,7 +17,7 @@ const rawEncodingList = [
];
// 加载源文件内容
export const readFileRawText = ({ buffer, encoding }: ReadFileByBufferParams): ReadFileResponse => {
export const readFileRawText = ({ buffer, encoding }: ReadRawTextByBuffer): ReadFileResponse => {
const content = rawEncodingList.includes(encoding)
? buffer.toString(encoding as BufferEncoding)
: iconv.decode(buffer, 'gbk');

View File

@@ -1,10 +1,10 @@
import { ReadFileByBufferParams, ReadFileResponse } from './type.d';
import { ReadRawTextByBuffer, ReadFileResponse } from '../type';
import xlsx from 'node-xlsx';
import Papa from 'papaparse';
export const readXlsxRawText = async ({
buffer
}: ReadFileByBufferParams): Promise<ReadFileResponse> => {
}: ReadRawTextByBuffer): Promise<ReadFileResponse> => {
const result = xlsx.parse(buffer, {
skipHidden: false,
defval: ''

View File

@@ -2,8 +2,8 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
import fs from 'fs';
import decompress from 'decompress';
import { DOMParser } from '@xmldom/xmldom';
import { clearDirFiles } from '../utils';
import { addLog } from '../../system/log';
import { clearDirFiles } from '../../common/file/utils';
import { addLog } from '../../common/system/log';
const DEFAULTDECOMPRESSSUBLOCATION = '/tmp';

View File

@@ -0,0 +1,71 @@
import { parentPort } from 'worker_threads';
import { readFileRawText } from './extension/rawText';
import { ReadRawTextByBuffer, ReadRawTextProps } from './type';
import { readHtmlRawText } from './extension/html';
import { readPdfFile } from './extension/pdf';
import { readDocsFile } from './extension/docx';
import { readPptxRawText } from './extension/pptx';
import { readXlsxRawText } from './extension/xlsx';
import { readCsvRawText } from './extension/csv';
parentPort?.on('message', async (props: ReadRawTextProps<Uint8Array>) => {
const readFileRawContent = async (params: ReadRawTextByBuffer) => {
switch (params.extension) {
case 'txt':
case 'md':
return readFileRawText(params);
case 'html':
return readHtmlRawText(params);
case 'pdf':
return readPdfFile(params);
case 'docx':
return readDocsFile(params);
case 'pptx':
return readPptxRawText(params);
case 'xlsx':
const xlsxResult = await readXlsxRawText(params);
if (params.csvFormat) {
return {
rawText: xlsxResult.formatText || ''
};
}
return {
rawText: xlsxResult.rawText
};
case 'csv':
const csvResult = await readCsvRawText(params);
if (params.csvFormat) {
return {
rawText: csvResult.formatText || ''
};
}
return {
rawText: csvResult.rawText
};
default:
return Promise.reject('Only support .txt, .md, .html, .pdf, .docx, pptx, .csv, .xlsx');
}
};
// params.buffer: Uint8Array -> buffer
const buffer = Buffer.from(props.buffer);
const newProps: ReadRawTextByBuffer = {
...props,
buffer
};
try {
parentPort?.postMessage({
type: 'success',
data: await readFileRawContent(newProps)
});
} catch (error) {
console.log(error);
parentPort?.postMessage({
type: 'error',
data: error
});
}
global?.close?.();
});

15
packages/service/worker/file/type.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
import { ReadFileByBufferParams } from '../../common/file/read/type';
export type ReadRawTextProps<T> = {
csvFormat?: boolean;
extension: string;
buffer: T;
encoding: string;
};
export type ReadRawTextByBuffer = ReadRawTextProps<Buffer>;
export type ReadFileResponse = {
rawText: string;
formatText?: string;
};

View File

@@ -1,60 +0,0 @@
import { parentPort } from 'worker_threads';
import TurndownService from 'turndown';
//@ts-ignore
import domino from 'domino';
//@ts-ignore
import * as turndownPluginGfm from 'joplin-turndown-plugin-gfm';
const turndownService = new TurndownService({
headingStyle: 'atx',
bulletListMarker: '-',
codeBlockStyle: 'fenced',
fence: '```',
emDelimiter: '_',
strongDelimiter: '**',
linkStyle: 'inlined',
linkReferenceStyle: 'full'
});
parentPort?.on('message', (params: { html: string }) => {
const html2md = (html: string): string => {
try {
const window = domino.createWindow(html);
const document = window.document;
turndownService.remove(['i', 'script', 'iframe']);
turndownService.addRule('codeBlock', {
filter: 'pre',
replacement(_, node) {
const content = node.textContent?.trim() || '';
// @ts-ignore
const codeName = node?._attrsByQName?.class?.data?.trim() || '';
return `\n\`\`\`${codeName}\n${content}\n\`\`\`\n`;
}
});
turndownService.use(turndownPluginGfm.gfm);
// @ts-ignore
return turndownService.turndown(document);
} catch (error) {
return '';
}
};
try {
const md = html2md(params?.html || '');
parentPort?.postMessage({
type: 'success',
data: md
});
} catch (error) {
parentPort?.postMessage({
type: 'error',
data: error
});
}
global?.close?.();
});

View File

@@ -0,0 +1,20 @@
import { parentPort } from 'worker_threads';
import { html2md } from './utils';
parentPort?.on('message', (params: { html: string }) => {
try {
const md = html2md(params?.html || '');
parentPort?.postMessage({
type: 'success',
data: md
});
} catch (error) {
parentPort?.postMessage({
type: 'error',
data: error
});
}
global?.close?.();
});

View File

@@ -0,0 +1,40 @@
import TurndownService from 'turndown';
const domino = require('domino-ext');
const turndownPluginGfm = require('joplin-turndown-plugin-gfm');
export const html2md = (html: string): string => {
const turndownService = new TurndownService({
headingStyle: 'atx',
bulletListMarker: '-',
codeBlockStyle: 'fenced',
fence: '```',
emDelimiter: '_',
strongDelimiter: '**',
linkStyle: 'inlined',
linkReferenceStyle: 'full'
});
try {
const window = domino.createWindow(html);
const document = window.document;
turndownService.remove(['i', 'script', 'iframe']);
turndownService.addRule('codeBlock', {
filter: 'pre',
replacement(_, node) {
const content = node.textContent?.trim() || '';
// @ts-ignore
const codeName = node?._attrsByQName?.class?.data?.trim() || '';
return `\n\`\`\`${codeName}\n${content}\n\`\`\`\n`;
}
});
turndownService.use(turndownPluginGfm.gfm);
return turndownService.turndown(document);
} catch (error) {
console.log('html 2 markdown error', error);
return '';
}
};

View File

@@ -2,6 +2,7 @@ import { Worker } from 'worker_threads';
import path from 'path';
export enum WorkerNameEnum {
readFile = 'readFile',
htmlStr2Md = 'htmlStr2Md',
countGptMessagesTokens = 'countGptMessagesTokens'
}

8
packages/web/common/fetch/type.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
export type PaginationProps<T = {}> = T & {
current: number;
pageSize: number;
};
export type PaginationResponse<T = any> = {
total: number;
list: T[];
};

View File

@@ -65,6 +65,7 @@ export const iconPaths = {
'core/app/headphones': () => import('./icons/core/app/headphones.svg'),
'core/app/logsLight': () => import('./icons/core/app/logsLight.svg'),
'core/app/markLight': () => import('./icons/core/app/markLight.svg'),
'core/app/publish/lark': () => import('./icons/core/app/publish/lark.svg'),
'core/app/questionGuide': () => import('./icons/core/app/questionGuide.svg'),
'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'),
'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'),
@@ -141,10 +142,12 @@ export const iconPaths = {
import('./icons/core/workflow/inputType/selectLLM.svg'),
'core/workflow/inputType/switch': () => import('./icons/core/workflow/inputType/switch.svg'),
'core/workflow/inputType/textarea': () => import('./icons/core/workflow/inputType/textarea.svg'),
'core/workflow/revertVersion': () => import('./icons/core/workflow/revertVersion.svg'),
'core/workflow/runError': () => import('./icons/core/workflow/runError.svg'),
'core/workflow/runSkip': () => import('./icons/core/workflow/runSkip.svg'),
'core/workflow/runSuccess': () => import('./icons/core/workflow/runSuccess.svg'),
'core/workflow/running': () => import('./icons/core/workflow/running.svg'),
'core/workflow/versionHistories': () => import('./icons/core/workflow/versionHistories.svg'),
date: () => import('./icons/date.svg'),
delete: () => import('./icons/delete.svg'),
edit: () => import('./icons/edit.svg'),

View File

@@ -1,6 +1,4 @@
<svg t="1714186779322" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1213"
width="128" height="128">
<path
d="M957.78379975 134.65795581c-2.56331135 83.17185735-20.44423515 163.12467052-50.30317593 240.57510884-35.69958418 92.67796903-87.96862781 174.24030494-161.20251807 241.63355181-5.94396957 5.46839892-7.80783541 11.32361235-7.01830818 19.026796 2.5328431 24.92705029 4.5596486 49.91503711 7.12295994 74.84208611 5.9598665 58.12425835-14.32010468 105.71575589-61.01609761 139.47861502-50.06340414 36.20694738-102.1695094 69.6412786-153.84243498 103.55515469-32.57194606 21.38212813-72.60730269 1.98176462-76.2277327-36.80306673-4.17150847-44.53539301-7.45016355-89.16219079-10.5340864-133.78633995-1.53533793-22.33459502-0.93921859-22.52932723-22.48428636-25.38937392-57.18636407-7.56938794-101.94563325-34.8213021-132.68418171-84.27401429-15.81040175-25.46488176-23.6473811-53.61097493-26.40410034-83.18642866-0.81866989-8.86495278-4.3808133-11.13020574-12.70925895-11.59252939-46.28003447-2.57788397-92.52960201-5.3332789-138.74869996-8.8199132-38.44173211-2.92098321-57.93085045-43.61339568-37.32367695-77.56966271 14.4671474-23.86993165 29.87483771-47.17288918 44.81888136-70.76065808 16.1071371-25.38937393 32.07915418-50.85425569 48.21675953-76.21316007 29.53173846-46.41383019 72.1012638-71.48924752 126.5905208-71.93699984 31.2008721-0.23844748 62.46003078 5.27366671 93.64633157 8.64107663 5.89892998 0.64115892 10.0863341-0.16426396 14.16908774-4.73848387 72.39799786-81.24970362 162.00661664-136.08206117 263.446211-172.76457792 57.33473241-20.74096921 116.33859714-34.28479495 176.89237242-40.54271983 30.51467231-3.17400331 61.23864946-4.32120112 91.72285352 1.34060572 16.61450031 3.08392285 17.46363973 3.77012135 20.02562678 20.05609502C956.4869093 101.71509088 958.51238919 118.07524806 957.78379975 134.65795581L957.78379975 134.65795581zM813.20902898 339.81696881c0.87828208-71.05871775-57.73744384-132.1635715-127.410515-133.309445-72.35428259-1.20680999-135.02361821 51.61331338-136.73779142 134.08439962-1.44525748 68.77756914 60.61338619 131.07466028 129.88374722 131.55155523C752.69897026 472.67866149 812.29895435 413.94371248 813.20902898 339.81696881L813.20902898 339.81696881zM195.18722288 640.09538918c13.69219278 0.17883529 21.29204896 6.24335355 26.19479682 20.29321819 19.25067215 55.04033549 54.28127779 96.80443794 105.06002435 125.11479508 11.20438926 6.24335355 23.0207935 11.30904103 35.22401354 15.48187512 21.93320788 7.50845143 26.93928317 28.96476307 10.66788212 45.47328728-24.10705481 24.43558273-48.46977833 48.60357404-72.56226181 73.02590976-7.97209938 8.07542683-16.74829602 11.90383476-27.95268527 7.71775497-11.38322454-4.2470176-16.88209172-12.72383026-17.46363974-24.55480712-0.35767186-7.49520313-0.17883529-15.03544713-0.35767188-22.55846986-0.31263099-13.81274148-0.44775102-13.91739325-13.11197036-9.71541653-27.08765021 9.01464541-54.11701383 18.10347435-81.20466405 27.10354714-7.9866707 2.66796441-16.00248532 3.77012135-23.81032074-0.67030285-11.44416105-6.51226928-15.76403785-18.14851392-11.160674-32.46729429 8.99874849-27.93811395 18.43200097-55.71196266 27.64005428-83.57456879 3.99267318-12.09856827 3.7396531-12.35158706-9.26766421-12.55956629-7.83830367-0.11922438-15.70442566 0.01457133-23.54140371-0.37224318-11.4136928-0.55107846-19.5341592-6.09498652-23.75070856-16.94170392-4.14104022-10.56455466-1.49029705-19.77260668 6.19831398-27.54997384 25.15092515-25.40526957 50.42240031-50.68999172 75.6620816-76.00518086C182.68726879 642.31560258 188.64713398 639.45423026 195.18722288 640.09538918L195.18722288 640.09538918z"
p-id="1214"></path>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 19" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M13.8412 2.25558L14.04 2.28053C14.5655 2.35304 15.261 2.4505 15.6547 2.84502C15.9775 3.16702 16.1015 3.69097 16.1756 4.15878L16.2192 4.45974C16.3175 5.16925 16.3432 6.09863 16.1756 7.13093C15.845 9.16824 14.7604 11.5993 12.037 13.4814C12.0222 13.628 12.0214 13.7762 12.0245 13.9243L12.0323 14.1457C12.0448 14.4864 12.0573 14.8264 11.9622 15.1578C11.814 15.6723 11.2862 16.0115 10.7926 16.2548L10.5509 16.3694L10.2391 16.5066C9.65587 16.7545 8.86839 17.0087 8.37485 16.5144C8.07857 16.2189 7.95694 15.7854 7.85792 15.3542L7.82128 15.1928C7.77999 14.9891 7.72794 14.7876 7.66534 14.5894C7.62636 14.4755 7.58426 14.3594 7.53903 14.2424C7.48926 14.3035 7.43643 14.3621 7.38076 14.4178C7.11177 14.6868 6.71023 14.8747 6.37965 15.0065C6.01865 15.1492 5.6101 15.2739 5.23196 15.3768L5.03781 15.4283L4.66591 15.5211L4.32908 15.5991L3.92599 15.6848L3.67415 15.7339C3.54855 15.7572 3.41918 15.7495 3.29719 15.7117C3.17521 15.6738 3.06427 15.6068 2.97395 15.5165C2.88363 15.4261 2.81664 15.3152 2.77875 15.1932C2.74086 15.0712 2.73321 14.9419 2.75646 14.8163L2.82352 14.4802L2.94359 13.936L3.04027 13.5384L3.11356 13.2577C3.21648 12.8803 3.34123 12.4718 3.48469 12.1115C3.61567 11.7802 3.80358 11.3786 4.07257 11.1096L4.13494 11.0496L4.08504 11.0293C3.95179 10.9787 3.81683 10.9326 3.68039 10.8913L3.46442 10.8251C2.92332 10.6613 2.34479 10.4851 1.98536 10.1249C1.54874 9.68906 1.6961 9.02556 1.90661 8.47354L1.99238 8.25991L2.13038 7.94803L2.24499 7.70633C2.48825 7.21357 2.82742 6.68573 3.34201 6.53759C3.61489 6.45962 3.8987 6.45494 4.18406 6.46274L4.35559 6.4682C4.57858 6.47599 4.80079 6.48457 5.01832 6.46352C6.90047 3.73931 9.33152 2.65477 11.3688 2.32419C12.1861 2.19014 13.0177 2.16706 13.8412 2.25558ZM6.19096 12.138C6.06825 12.0472 5.9216 11.9944 5.76915 11.9861C5.61671 11.9777 5.46518 12.0143 5.33331 12.0913L5.24755 12.149L5.17504 12.2137L5.07758 12.3369C4.87486 12.63 4.75947 13.0355 4.6698 13.4144L4.5856 13.7793L4.54583 13.9453L4.69475 13.9095L5.02066 13.8346C5.47132 13.7294 5.97109 13.5929 6.27829 13.3161C6.41186 13.1826 6.49243 13.0052 6.50505 12.8168C6.51767 12.6284 6.46147 12.4418 6.3469 12.2916L6.28297 12.2184L6.26425 12.2004L6.19096 12.138ZM11.7914 6.70054C11.6466 6.55571 11.4748 6.44082 11.2856 6.36242C11.0964 6.28402 10.8936 6.24365 10.6888 6.24361C10.4841 6.24357 10.2813 6.28387 10.0921 6.36221C9.90285 6.44054 9.73092 6.55537 9.58609 6.70015C9.44126 6.84493 9.32636 7.01682 9.24796 7.206C9.16956 7.39518 9.12919 7.59795 9.12915 7.80274C9.12912 8.00752 9.16942 8.21031 9.24775 8.39952C9.32609 8.58873 9.44092 8.76065 9.5857 8.90548C9.87809 9.19798 10.2747 9.36235 10.6883 9.36242C11.1019 9.36249 11.4985 9.19827 11.791 8.90587C12.0835 8.61348 12.2479 8.21687 12.248 7.80329C12.248 7.38971 12.0838 6.99304 11.7914 6.70054Z" />
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,12 @@
<svg t="1714285522209" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5343"
width="128" height="128">
<path
d="M891.318857 340.845714c4.900571 0 9.728 0.292571 14.628572 0.804572a409.965714 409.965714 0 0 1 108.836571 30.061714c10.093714 4.534857 12.580571 8.192 3.949714 17.334857-24.868571 26.624-45.494857 57.051429-61.001143 89.965714-16.822857 35.328-35.108571 69.851429-52.297142 105.033143a225.28 225.28 0 0 1-52.150858 69.412572c-53.613714 48.493714-116.150857 68.973714-187.538285 59.099428-81.92-11.337143-159.451429-38.985143-232.740572-75.483428a143.506286 143.506286 0 0 1-10.459428-5.485715 5.339429 5.339429 0 0 1 0.292571-9.216l5.12-2.706285c59.245714-31.670857 108.836571-75.849143 156.525714-122.294857 20.187429-19.529143 39.497143-40.009143 59.904-59.318858A345.014857 345.014857 0 0 1 804.571429 352.256c13.165714-3.218286 26.550857-5.778286 39.789714-8.630857h0.585143l28.233143-2.56"
fill="#133C9A" p-id="5344"></path>
<path
d="M317.659429 913.846857c-8.996571-0.512-31.158857-3.584-33.865143-3.949714a536.429714 536.429714 0 0 1-165.083429-48.274286c-30.208-14.116571-59.245714-30.72-88.356571-46.957714-19.163429-10.678857-27.794286-27.282286-27.648-49.883429 0.585143-83.382857 0.585143-166.765714 0-250.148571C2.413714 461.019429 0.731429 407.405714 0 353.718857c0-4.754286 0.731429-9.508571 2.194286-13.897143 3.291429-9.728 9.947429-10.24 16.530285-3.949714 7.606857 7.314286 13.677714 16.237714 21.211429 23.405714 67.291429 66.413714 138.752 127.195429 218.770286 177.225143 45.056 28.891429 91.940571 54.710857 140.434285 77.385143 77.750857 35.328 157.549714 66.486857 241.078858 86.235429 73.874286 17.481143 145.627429 6.436571 205.458285-40.374858 18.285714-15.652571 27.282286-27.062857 48.932572-55.881142a359.862857 359.862857 0 0 1-37.376 72.850285c-13.897143 21.942857-45.348571 51.2-69.193143 74.093715-36.278857 35.108571-83.748571 63.561143-128.292572 87.552-48.566857 26.185143-99.035429 47.104-152.941714 58.514285-27.648 6.948571-67.584 14.848-81.334857 15.579429-2.413714-0.146286-10.678857 1.682286-14.848 1.389714-35.547429 2.633143-57.490286 3.657143-92.891429 0z"
fill="#3370FF" p-id="5345"></path>
<path
d="M165.083429 110.518857a52.443429 52.443429 0 0 1 7.460571 0c152.649143 0 304.128 2.486857 456.630857 2.486857 0.292571 0 0.585143 0 0.731429 0.219429 14.189714 12.361143 27.282286 25.746286 39.277714 40.155428 34.450286 34.230857 60.123429 93.622857 77.677714 129.755429 8.777143 25.014857 21.942857 48.859429 28.16 76.8v0.438857c-15.579429 5.046857-30.72 11.190857-45.348571 18.505143-44.178286 22.381714-64.219429 38.765714-100.790857 74.752-19.968 19.529143-37.010286 37.083429-63.488 62.098286a563.346286 563.346286 0 0 1-29.769143 26.916571c-7.021714-12.434286-125.732571-244.589714-364.251429-427.300571"
fill="#00D6B9" p-id="5346"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M4.31146 8.0038C4.78201 5.34167 7.10812 3.31934 9.90528 3.31934C13.0427 3.31934 15.586 5.8627 15.586 9.0001C15.586 12.1375 13.0427 14.6809 9.90528 14.6809C8.99212 14.6809 8.01118 14.3988 7.15115 13.9231C6.28734 13.4453 5.60309 12.8045 5.234 12.1428C5.0322 11.7811 4.57537 11.6514 4.21364 11.8532C3.8519 12.055 3.72225 12.5119 3.92404 12.8736C4.46219 13.8383 5.38209 14.6588 6.42511 15.2357C7.47191 15.8147 8.70047 16.1809 9.90528 16.1809C13.8711 16.1809 17.086 12.9659 17.086 9.0001C17.086 5.03428 13.8711 1.81934 9.90528 1.81934C6.43524 1.81934 3.54116 4.28003 2.87078 7.55181L2.46483 6.84336C2.2589 6.48396 1.80061 6.35956 1.44122 6.56549C1.08182 6.77142 0.957418 7.22971 1.16335 7.58911L2.4808 9.88833C2.62411 10.1384 2.8896 10.2747 3.15895 10.2651C3.29431 10.27 3.43265 10.2383 3.55876 10.1658L5.88309 8.83098C6.24228 8.6247 6.36624 8.16629 6.15996 7.80709C5.95367 7.4479 5.49526 7.32394 5.13607 7.53023L4.31146 8.0038ZM8.96233 9.52028C8.96177 9.55065 8.96306 9.58096 8.96616 9.61102C8.97694 9.71784 9.01015 9.81804 9.06098 9.90684C9.11368 9.99929 9.18665 10.0815 9.27792 10.1462C9.30081 10.1625 9.32465 10.1775 9.34934 10.1912L11.4739 11.4178C11.8326 11.6249 12.2913 11.502 12.4984 11.1433C12.7055 10.7846 12.5826 10.3259 12.2239 10.1188L10.4623 9.10176V6.92775C10.4623 6.51353 10.1265 6.17775 9.71233 6.17775C9.29811 6.17775 8.96233 6.51353 8.96233 6.92775V9.52028Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M8.99992 2.08659C5.18166 2.08659 2.08634 5.1819 2.08634 9.00017C2.08634 12.8184 5.18166 15.9137 8.99992 15.9137C12.8182 15.9137 15.9135 12.8184 15.9135 9.00017C15.9135 5.1819 12.8182 2.08659 8.99992 2.08659ZM0.419678 9.00017C0.419678 4.26143 4.26118 0.419922 8.99992 0.419922C13.7387 0.419922 17.5802 4.26143 17.5802 9.00017C17.5802 13.7389 13.7387 17.5804 8.99992 17.5804C4.26118 17.5804 0.419678 13.7389 0.419678 9.00017ZM8.99992 3.51869C9.46016 3.51869 9.83326 3.89178 9.83326 4.35202V8.48514L12.4714 9.80419C12.883 10.01 13.0499 10.5106 12.844 10.9222C12.6382 11.3339 12.1377 11.5007 11.726 11.2949L8.62725 9.74552C8.34493 9.60436 8.16659 9.31581 8.16659 9.00017V4.35202C8.16659 3.89178 8.53969 3.51869 8.99992 3.51869Z"
fill="#3370FF" />
</svg>

After

Width:  |  Height:  |  Size: 891 B

View File

@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 19" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M10 3.08634C6.18178 3.08634 3.08647 6.18166 3.08647 9.99992C3.08647 13.8182 6.18178 16.9135 10 16.9135C13.8183 16.9135 16.9136 13.8182 16.9136 9.99992C16.9136 6.18166 13.8183 3.08634 10 3.08634ZM1.4198 9.99992C1.4198 5.26118 5.26131 1.41968 10 1.41968C14.7388 1.41968 18.5803 5.26118 18.5803 9.99992C18.5803 14.7387 14.7388 18.5802 10 18.5802C5.26131 18.5802 1.4198 14.7387 1.4198 9.99992ZM10 4.51844C10.4603 4.51844 10.8334 4.89154 10.8334 5.35178V9.48489L13.4715 10.8039C13.8831 11.0098 14.05 11.5103 13.8442 11.922C13.6383 12.3336 13.1378 12.5005 12.7261 12.2947L9.62737 10.7453C9.34505 10.6041 9.16671 10.3156 9.16671 9.99992V5.35178C9.16671 4.89154 9.53981 4.51844 10 4.51844Z" />
d="M9.00005 3.27783C5.56362 3.27783 2.77783 6.06362 2.77783 9.50005C2.77783 12.9365 5.56362 15.7223 9.00005 15.7223C12.4365 15.7223 15.2223 12.9365 15.2223 9.50005C15.2223 6.06362 12.4365 3.27783 9.00005 3.27783ZM1.27783 9.50005C1.27783 5.23519 4.73519 1.77783 9.00005 1.77783C13.2649 1.77783 16.7223 5.23519 16.7223 9.50005C16.7223 13.7649 13.2649 17.2223 9.00005 17.2223C4.73519 17.2223 1.27783 13.7649 1.27783 9.50005ZM9.00005 4.56672C9.41427 4.56672 9.75005 4.90251 9.75005 5.31672V9.03653L12.1244 10.2237C12.4948 10.4089 12.645 10.8594 12.4598 11.2299C12.2745 11.6004 11.824 11.7506 11.4535 11.5653L8.66464 10.1709C8.41056 10.0438 8.25005 9.78413 8.25005 9.50005V5.31672C8.25005 4.90251 8.58584 4.56672 9.00005 4.56672Z" />
</svg>

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 866 B

View File

@@ -0,0 +1,19 @@
import React, { forwardRef } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import Loading from '../MyLoading';
type Props = BoxProps & {
isLoading?: boolean;
text?: string;
};
const MyBox = ({ text, isLoading, children, ...props }: Props, ref: any) => {
return (
<Box ref={ref} position={'relative'} {...props}>
{isLoading && <Loading fixed={false} text={text} />}
{children}
</Box>
);
};
export default forwardRef(MyBox);

View File

@@ -0,0 +1,80 @@
import React from 'react';
import MyIcon from '../Icon';
import { Flex, Image, Box, CloseButton, FlexProps } from '@chakra-ui/react';
import { useLoading } from '../../../hooks/useLoading';
type Props = FlexProps & {
onClose: () => void;
iconSrc?: string;
title?: any;
isLoading?: boolean;
showMask?: boolean;
};
const CustomRightDrawer = ({
onClose,
iconSrc,
title,
maxW = ['90vw', '30vw'],
children,
isLoading,
showMask = true,
...props
}: Props) => {
const { Loading } = useLoading();
return (
<Flex
flexDirection={'column'}
position={'fixed'}
right={0}
bg={'white'}
zIndex={100}
maxW={maxW}
w={'100%'}
h={'90vh'}
borderLeftRadius={'lg'}
border={'base'}
boxShadow={'2'}
{...props}
>
<Flex
display={'flex'}
alignItems={'center'}
fontWeight={500}
background={'#FBFBFC'}
borderBottom={'1px solid #F4F6F8'}
roundedTop={'lg'}
py={'10px'}
px={5}
>
{iconSrc && (
<>
{iconSrc.startsWith('/') ? (
<Image mr={3} objectFit={'contain'} alt="" src={iconSrc} w={'20px'} />
) : (
<MyIcon mr={3} name={iconSrc as any} w={'20px'} />
)}
</>
)}
<Box flex={'1'} fontSize={'lg'}>
{title}
</Box>
<CloseButton position={'relative'} fontSize={'sm'} top={0} right={0} onClick={onClose} />
</Flex>
<Box
flex={'1 0 0'}
py={props.py ?? 3}
px={props.px ?? 5}
overflow={props?.overflow ?? 'auto'}
display={'flex'}
flexDirection={'column'}
>
{children}
</Box>
<Loading loading={isLoading} fixed={false} />
</Flex>
);
};
export default React.memo(CustomRightDrawer);

View File

@@ -1,5 +1,8 @@
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
export type EditorVariablePickerType = {
key: string;
label: string;
icon?: string;
valueType?: WorkflowIOValueTypeEnum;
};

View File

@@ -0,0 +1,41 @@
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { nodeTemplateListType } from '@fastgpt/global/core/workflow/type';
import { TFunction } from 'next-i18next';
export const workflowNodeTemplateList = (t: TFunction): nodeTemplateListType => [
{
type: FlowNodeTemplateTypeEnum.systemInput,
label: t('core.module.template.System input module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.textAnswer,
label: t('core.module.template.Response module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.functionCall,
label: t('core.module.template.Function module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.tools,
label: t('core.module.template.Tool module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.externalCall,
label: t('core.module.template.External module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.personalPlugin,
label: '',
list: []
},
{
type: FlowNodeTemplateTypeEnum.other,
label: t('common.Other'),
list: []
}
];

View File

@@ -0,0 +1,122 @@
import { useRef, useState, useEffect } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import { useToast } from './useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { PaginationProps, PaginationResponse } from '../common/fetch/type';
import { useBoolean, useLockFn, useMemoizedFn, useMount, useScroll, useVirtualList } from 'ahooks';
import MyBox from '../components/common/MyBox';
import { useTranslation } from 'next-i18next';
export function useScrollPagination<
TParams extends PaginationProps,
TData extends PaginationResponse
>(
api: (data: TParams) => Promise<TData>,
{
itemHeight = 50,
overscan = 10,
pageSize = 10,
defaultParams = {}
}: {
itemHeight: number;
overscan?: number;
pageSize?: number;
defaultParams?: Record<string, any>;
}
) {
const { t } = useTranslation();
const containerRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef(null);
const noMore = useRef(false);
const { toast } = useToast();
const [current, setCurrent] = useState(1);
const [data, setData] = useState<TData['list']>([]);
const [isLoading, { setTrue, setFalse }] = useBoolean(false);
const [list] = useVirtualList<TData['list'][0]>(data, {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight,
overscan
});
const loadData = useLockFn(async (num: number = current) => {
if (noMore.current) return;
setTrue();
try {
const res = await api({
current: num,
pageSize,
...defaultParams
} as TParams);
setCurrent(num);
if (num === 1) {
// reload
setData(res.list);
noMore.current = res.list.length >= res.total;
} else {
const totalLength = data.length + res.list.length;
noMore.current = totalLength >= res.total;
setData((prev) => [...prev, ...res.list]);
}
} catch (error: any) {
toast({
title: getErrText(error, '获取数据异常'),
status: 'error'
});
console.log(error);
}
setFalse();
});
const ScrollList = useMemoizedFn(
({
children,
isLoading,
...props
}: { children: React.ReactNode; isLoading?: boolean } & BoxProps) => {
return (
<MyBox isLoading={isLoading} ref={containerRef} overflow={'overlay'} {...props}>
<Box ref={wrapperRef}>{children}</Box>
{noMore.current && (
<Box pb={2} textAlign={'center'} color={'myGray.600'} fontSize={'sm'}>
{t('common.No more data')}
</Box>
)}
</MyBox>
);
}
);
useMount(() => {
loadData(1);
});
const scroll = useScroll(containerRef);
useEffect(() => {
if (!containerRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
if (scrollTop + clientHeight >= scrollHeight - 100) {
loadData(current + 1);
}
}, [scroll]);
return {
containerRef,
list,
isLoading,
ScrollList,
fetchData: loadData
};
}

View File

@@ -8,8 +8,8 @@
"@chakra-ui/react": "2.8.1",
"@chakra-ui/styled-system": "2.9.1",
"@chakra-ui/system": "2.6.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
"@fastgpt/global": "workspace:*",
"@fingerprintjs/fingerprintjs": "^4.3.0",
"@lexical/react": "0.12.6",
@@ -18,6 +18,7 @@
"@lexical/utils": "0.12.6",
"@monaco-editor/react": "^4.6.0",
"@tanstack/react-query": "^4.24.10",
"ahooks": "^3.7.11",
"date-fns": "2.30.0",
"dayjs": "^1.11.7",
"i18next": "23.10.0",

34
pnpm-lock.yaml generated
View File

@@ -230,10 +230,10 @@ importers:
specifier: 2.6.1
version: 2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@emotion/react':
specifier: ^11.11.1
specifier: 11.11.1
version: 11.11.1(@types/react@18.2.0)(react@18.2.0)
'@emotion/styled':
specifier: ^11.11.0
specifier: 11.11.0
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.0)(react@18.2.0)
'@fastgpt/global':
specifier: workspace:*
@@ -259,6 +259,9 @@ importers:
'@tanstack/react-query':
specifier: ^4.24.10
version: 4.24.10(react-dom@18.2.0)(react@18.2.0)
ahooks:
specifier: ^3.7.11
version: 3.7.11(react@18.2.0)
date-fns:
specifier: 2.30.0
version: 2.30.0
@@ -336,10 +339,10 @@ importers:
specifier: 2.6.1
version: 2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@emotion/react':
specifier: ^11.11.1
specifier: 11.11.1
version: 11.11.1(@types/react@18.2.0)(react@18.2.0)
'@emotion/styled':
specifier: ^11.11.0
specifier: 11.11.0
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.0)(react@18.2.0)
'@fastgpt/global':
specifier: workspace:*
@@ -408,7 +411,7 @@ importers:
specifier: ^4.17.21
version: 4.17.21
mermaid:
specifier: 10.2.3
specifier: ^10.2.3
version: 10.2.3
nanoid:
specifier: ^4.0.1
@@ -467,6 +470,9 @@ importers:
sass:
specifier: ^1.58.3
version: 1.58.3
use-context-selector:
specifier: ^1.4.4
version: 1.4.4(react-dom@18.2.0)(react@18.2.0)(scheduler@0.23.0)
zustand:
specifier: ^4.3.5
version: 4.3.5(immer@9.0.19)(react@18.2.0)
@@ -12158,6 +12164,24 @@ packages:
tslib: 2.6.2
dev: false
/use-context-selector@1.4.4(react-dom@18.2.0)(react@18.2.0)(scheduler@0.23.0):
resolution: {integrity: sha512-pS790zwGxxe59GoBha3QYOwk8AFGp4DN6DOtH+eoqVmgBBRXVx4IlPDhJmmMiNQAgUaLlP+58aqRC3A4rdaSjg==}
peerDependencies:
react: '>=16.8.0'
react-dom: '*'
react-native: '*'
scheduler: '>=0.19.0'
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
scheduler: 0.23.0
dev: false
/use-sidecar@1.1.2(@types/react@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
engines: {node: '>=10'}

View File

@@ -14,7 +14,7 @@
],
"plugin.inlang.i18next": {
"pathPattern": {
"common": "./projects/app/public/locales/{languageTag}/common.json"
"common": "./projects/app/i18n/{languageTag}/common.json"
},
"variableReferencePattern": [
"{{", "}}"

View File

@@ -117,6 +117,7 @@
"Name is empty": "Name is empty",
"New Create": "Create",
"Next Step": "Next",
"No more data": "No more",
"Not open": "Close",
"Number of words": "{{amount}} words",
"OK": "OK",
@@ -153,6 +154,7 @@
"Submit failed": "Submit failed",
"Submit success": "Update Success",
"Sync success": "",
"System Output": "System Output",
"System version": "System version",
"Team": "Team",
"Team Tags Set": "Team Tags",
@@ -280,6 +282,7 @@
"App intro": "App intro",
"App params config": "App Config",
"Auto Save time": "Auto-saved: {{time}}",
"Change to simple mode": "Switch easy mode",
"Chat Variable": "",
"Config schedule plan": "Config schedule config",
"Config whisper": "Config whisper",
@@ -374,6 +377,11 @@
"Show History": "Show History",
"Web Link": "Web Link"
},
"publish": {
"Fei Shu Bot Desc": "Access the Lark robot",
"Fei shu bot": "Lark",
"Fei shu bot publish": "Posted to Lark Robot"
},
"schedule": {
"Default prompt": "Default prompt",
"Default prompt placeholder": "Default problem when executing the application",
@@ -957,6 +965,7 @@
"anyInput": "Any input",
"chat history": "chat history",
"switch": "Trigger",
"system params": "System params",
"textEditor textarea": "Text edit",
"user question": "User question"
},
@@ -1093,6 +1102,7 @@
"Check Failed": "Workflow verification fails. Check whether the node or connection is normal",
"Confirm stop debug": "Do you want to terminate debugging? Debugging information is not retained.",
"Copy node": "Copy node",
"Current workflow": "Current workflow",
"Custom inputs": "Inputs",
"Custom outputs": "Outputs",
"Custom variable": "Custom variable",
@@ -1108,6 +1118,7 @@
"Stop debug": "Stop",
"Success": "Running success",
"Value type": "Type",
"Variable outputs": "Variables",
"chat": {
"Quote prompt": "Quote prompt"
},
@@ -1139,6 +1150,11 @@
"target": "Target Data",
"textarea": "Textarea"
},
"publish": {
"OnRevert version": "Click back to that version",
"OnRevert version confirm": "Are you sure to roll back the version?",
"histories": "Publiish histories"
},
"tool": {
"Handle": "Tool handle",
"Select Tool": "Select Tool"

View File

@@ -117,6 +117,7 @@
"Name is empty": "名称不能为空",
"New Create": "新建",
"Next Step": "下一步",
"No more data": "没有更多了~",
"Not open": "未开启",
"Number of words": "{{amount}}字",
"OK": "好的",
@@ -153,6 +154,7 @@
"Submit failed": "提交失败",
"Submit success": "提交成功",
"Sync success": "同步成功",
"System Output": "系统输出",
"System version": "系统版本",
"Team": "团队",
"Team Tags Set": "标签",
@@ -280,6 +282,7 @@
"App intro": "应用介绍",
"App params config": "应用配置",
"Auto Save time": "自动保存: {{time}}",
"Change to simple mode": "切换简易模式",
"Chat Variable": "对话框变量",
"Config schedule plan": "配置定时执行",
"Config whisper": "配置语音输入",
@@ -374,6 +377,11 @@
"Show History": "展示历史对话",
"Web Link": "网络链接"
},
"publish": {
"Fei Shu Bot Desc": "接入到飞书机器人中",
"Fei shu bot": "飞书",
"Fei shu bot publish": "发布到飞书机器人"
},
"schedule": {
"Default prompt": "默认问题",
"Default prompt placeholder": "执行应用时的默认问题",
@@ -638,7 +646,8 @@
"success": "开始同步"
}
},
"training": {}
"training": {
}
},
"data": {
"Auxiliary Data": "辅助数据",
@@ -958,6 +967,7 @@
"anyInput": "",
"chat history": "聊天记录",
"switch": "触发器",
"system params": "系统参数",
"textEditor textarea": "文本编辑",
"user question": "用户问题"
},
@@ -1094,6 +1104,7 @@
"Check Failed": "工作流校验失败,请检查节点是否正确填值,以及连线是否正常",
"Confirm stop debug": "确认终止调试?调试信息将会不保留。",
"Copy node": "已复制节点",
"Current workflow": "当前工作流",
"Custom inputs": "自定义输入",
"Custom outputs": "自定义输出",
"Custom variable": "自定义变量",
@@ -1109,6 +1120,7 @@
"Stop debug": "停止调试",
"Success": "运行成功",
"Value type": "数据类型",
"Variable outputs": "全局变量",
"chat": {
"Quote prompt": "引用提示词"
},
@@ -1140,6 +1152,11 @@
"target": "外部数据",
"textarea": "多行输入框"
},
"publish": {
"OnRevert version": "点击回退到该版本",
"OnRevert version confirm": "确认回退该版本?",
"histories": "发布记录"
},
"tool": {
"Handle": "工具连接器",
"Select Tool": "选择工具"

View File

@@ -9,7 +9,6 @@ module.exports = {
locales: ['en', 'zh'],
localeDetection: false
},
localePath:
typeof window === 'undefined' ? require('path').resolve('./public/locales') : '/public/locales',
localePath: typeof window === 'undefined' ? require('path').resolve('./i18n') : '/i18n',
reloadOnPrerender: process.env.NODE_ENV === 'development'
};

View File

@@ -51,11 +51,15 @@ const nextConfig = {
...entries,
'worker/htmlStr2Md': path.resolve(
process.cwd(),
'../../packages/service/worker/htmlStr2Md.ts'
'../../packages/service/worker/htmlStr2Md/index.ts'
),
'worker/countGptMessagesTokens': path.resolve(
process.cwd(),
'../../packages/service/worker/tiktoken/countGptMessagesTokens.ts'
),
'worker/readFile': path.resolve(
process.cwd(),
'../../packages/service/worker/file/read.ts'
)
};
}
@@ -76,13 +80,18 @@ const nextConfig = {
return config;
},
transpilePackages: ['@fastgpt/*'],
transpilePackages: ['@fastgpt/*', 'ahooks'],
experimental: {
// 外部包独立打包
serverComponentsExternalPackages: ['mongoose', 'pg'],
// 指定导出包优化,按需引入包模块
optimizePackageImports: ['mongoose', 'pg'],
outputFileTracingRoot: path.join(__dirname, '../../')
outputFileTracingRoot: path.join(__dirname, '../../'),
outputFileTracingIncludes: {
'/api/common/file/previewContent.ts': [
path.resolve(process.cwd(), '../../packages/service/worker/**/*')
]
}
}
};

View File

@@ -1,6 +1,6 @@
{
"name": "app",
"version": "4.7.1",
"version": "4.8",
"private": false,
"scripts": {
"dev": "next dev",
@@ -16,13 +16,14 @@
"@chakra-ui/react": "2.8.1",
"@chakra-ui/styled-system": "2.9.1",
"@chakra-ui/system": "2.6.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
"@fastgpt/global": "workspace:*",
"@fastgpt/plugins": "workspace:*",
"@fastgpt/service": "workspace:*",
"@fastgpt/web": "workspace:*",
"@fortaine/fetch-event-source": "^3.0.6",
"@node-rs/jieba": "1.10.0",
"@tanstack/react-query": "^4.24.10",
"@types/nprogress": "^0.2.0",
"ahooks": "^3.7.11",
@@ -39,10 +40,11 @@
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"mermaid": "10.2.3",
"mermaid": "^10.2.3",
"nanoid": "^4.0.1",
"next": "13.5.2",
"next-i18next": "15.2.0",
"nextjs-node-loader": "^1.1.5",
"nprogress": "^0.2.0",
"react": "18.2.0",
"react-day-picker": "^8.7.1",
@@ -58,9 +60,8 @@
"remark-math": "^5.1.1",
"request-ip": "^3.3.0",
"sass": "^1.58.3",
"zustand": "^4.3.5",
"nextjs-node-loader": "^1.1.5",
"@node-rs/jieba": "1.10.0"
"use-context-selector": "^1.4.4",
"zustand": "^4.3.5"
},
"devDependencies": {
"@svgr/webpack": "^6.5.1",
@@ -76,6 +77,7 @@
"@types/request-ip": "^0.0.37",
"eslint": "8.34.0",
"eslint-config-next": "13.1.6",
"nextjs-node-loader": "^1.1.5",
"typescript": "4.9.5"
}
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { useTheme, type BoxProps } from '@chakra-ui/react';
import MyBox from '../common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
const PageContainer = ({
children,

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { Button, Input, InputGroup, InputRightElement } from '@chakra-ui/react';
import { ViewOffIcon, ViewIcon } from '@chakra-ui/icons';
function HiddenInput(props: any) {
const [show, setShow] = React.useState(false);
return (
<>
<InputGroup>
<Input {...props} type={show ? 'text' : 'password'} />
<InputRightElement width="4.5rem">
<Button h="1.75rem" size="sm" onClick={() => setShow(!show)}>
{show ? <ViewOffIcon /> : <ViewIcon />}
</Button>
</InputRightElement>
</InputGroup>
</>
);
}
export default HiddenInput;

View File

@@ -1,19 +0,0 @@
import React from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import Loading from '@fastgpt/web/components/common/MyLoading';
type Props = BoxProps & {
isLoading?: boolean;
text?: string;
};
const MyBox = ({ text, isLoading, children, ...props }: Props) => {
return (
<Box position={'relative'} {...props}>
{isLoading && <Loading fixed={false} text={text} />}
{children}
</Box>
);
};
export default MyBox;

View File

@@ -13,7 +13,6 @@ import {
Switch,
Input,
FormControl,
Image,
Table,
Thead,
Tbody,
@@ -21,7 +20,6 @@ import {
Th,
Td,
TableContainer,
BoxProps,
useDisclosure
} from '@chakra-ui/react';
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';

View File

@@ -7,7 +7,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import MyTooltip from '@/components/MyTooltip';
import dynamic from 'next/dynamic';
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constants';
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));

View File

@@ -5,7 +5,7 @@ import React, { Dispatch, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';
import { Box } from '@chakra-ui/react';
import ParentPaths from '@/components/common/ParentPaths';
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
type PathItemType = {
parentId: string;

View File

@@ -3,7 +3,8 @@ import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useFlowProviderStore } from './FlowProvider';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context';
type Props = {
onClose: () => void;
@@ -12,7 +13,8 @@ type Props = {
const ImportSettings = ({ onClose }: Props) => {
const { t } = useTranslation();
const { toast } = useToast();
const { setNodes, setEdges, initData } = useFlowProviderStore();
const initData = useContextSelector(WorkflowContext, (v) => v.initData);
const [value, setValue] = useState('');
return (

View File

@@ -1,14 +1,5 @@
import React, { useCallback, useMemo, useState } from 'react';
import {
Box,
Card,
Flex,
IconButton,
Input,
InputGroup,
InputLeftElement,
css
} from '@chakra-ui/react';
import { Box, Flex, IconButton, Input, InputGroup, InputLeftElement, css } from '@chakra-ui/react';
import type {
FlowNodeTemplateType,
nodeTemplateListType
@@ -16,8 +7,6 @@ import type {
import { useViewport, XYPosition } from 'reactflow';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import Avatar from '@/components/Avatar';
import { useFlowProviderStore } from './FlowProvider';
import { customAlphabet } from 'nanoid';
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
import { useTranslation } from 'next-i18next';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
@@ -25,7 +14,7 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getPreviewPluginModule } from '@/web/core/plugin/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { moduleTemplatesList } from '@fastgpt/global/core/workflow/template/constants';
import { workflowNodeTemplateList } from '@fastgpt/web/core/workflow/constants';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
@@ -36,6 +25,9 @@ import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import { useQuery } from '@tanstack/react-query';
import { debounce } from 'lodash';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context';
import { useCreation } from 'ahooks';
type ModuleTemplateListProps = {
isOpen: boolean;
@@ -62,7 +54,9 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
const [currentParent, setCurrentParent] = useState<RenderListProps['currentParent']>();
const [searchKey, setSearchKey] = useState('');
const { feConfigs } = useSystemStore();
const { nodes, basicNodeTemplates, hasToolNode } = useFlowProviderStore();
const basicNodeTemplates = useContextSelector(WorkflowContext, (v) => v.basicNodeTemplates);
const hasToolNode = useContextSelector(WorkflowContext, (v) => v.hasToolNode);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const {
systemNodeTemplates,
@@ -72,12 +66,12 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
} = useWorkflowStore();
const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic);
const templatesString = useMemo(() => {
const templates = useCreation(() => {
const map = {
[TemplateTypeEnum.basic]: basicNodeTemplates.filter((item) => {
// unique node filter
if (item.unique) {
const nodeExist = nodes.some((node) => node.data.flowNodeType === item.flowNodeType);
const nodeExist = nodeList.some((node) => node.flowNodeType === item.flowNodeType);
if (nodeExist) {
return false;
}
@@ -97,12 +91,12 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
searchKey ? item.pluginType !== PluginTypeEnum.folder : true
)
};
return JSON.stringify(map[templateType]);
return map[templateType];
}, [
basicNodeTemplates,
feConfigs.lafEnv,
hasToolNode,
nodes,
nodeList,
searchKey,
systemNodeTemplates,
teamPluginNodeTemplates,
@@ -132,135 +126,120 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
})
);
const Render = useMemo(() => {
const parseTemplates = JSON.parse(templatesString) as FlowNodeTemplateType[];
return (
<>
<Box
zIndex={2}
display={isOpen ? 'block' : 'none'}
position={'absolute'}
top={0}
left={0}
bottom={0}
w={`${sliderWidth}px`}
onClick={onClose}
/>
<Flex
zIndex={3}
flexDirection={'column'}
position={'absolute'}
top={'10px'}
left={0}
pt={'20px'}
pb={4}
h={isOpen ? 'calc(100% - 20px)' : '0'}
w={isOpen ? ['100%', `${sliderWidth}px`] : '0'}
bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'0 20px 20px 0'}
transition={'.2s ease'}
userSelect={'none'}
overflow={isOpen ? 'none' : 'hidden'}
>
<Box mb={2} pl={'20px'} pr={'10px'} whiteSpace={'nowrap'} overflow={'hidden'}>
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
<RowTabs
list={[
{
icon: 'core/modules/basicNode',
label: t('core.module.template.Basic Node'),
value: TemplateTypeEnum.basic
},
{
icon: 'core/modules/systemPlugin',
label: t('core.module.template.System Plugin'),
value: TemplateTypeEnum.systemPlugin
},
{
icon: 'core/modules/teamPlugin',
label: t('core.module.template.Team Plugin'),
value: TemplateTypeEnum.teamPlugin
}
]}
py={'5px'}
value={templateType}
onChange={onChangeTab}
/>
{/* close icon */}
<IconButton
size={'sm'}
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.700'} />}
w={'26px'}
h={'26px'}
borderColor={'myGray.300'}
variant={'grayBase'}
aria-label={''}
onClick={onClose}
return (
<>
<Box
zIndex={2}
display={isOpen ? 'block' : 'none'}
position={'absolute'}
top={0}
left={0}
bottom={0}
w={`${sliderWidth}px`}
onClick={onClose}
/>
<Flex
zIndex={3}
flexDirection={'column'}
position={'absolute'}
top={'10px'}
left={0}
pt={'20px'}
pb={4}
h={isOpen ? 'calc(100% - 20px)' : '0'}
w={isOpen ? ['100%', `${sliderWidth}px`] : '0'}
bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'0 20px 20px 0'}
transition={'.2s ease'}
userSelect={'none'}
overflow={isOpen ? 'none' : 'hidden'}
>
<Box mb={2} pl={'20px'} pr={'10px'} whiteSpace={'nowrap'} overflow={'hidden'}>
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
<RowTabs
list={[
{
icon: 'core/modules/basicNode',
label: t('core.module.template.Basic Node'),
value: TemplateTypeEnum.basic
},
{
icon: 'core/modules/systemPlugin',
label: t('core.module.template.System Plugin'),
value: TemplateTypeEnum.systemPlugin
},
{
icon: 'core/modules/teamPlugin',
label: t('core.module.template.Team Plugin'),
value: TemplateTypeEnum.teamPlugin
}
]}
py={'5px'}
value={templateType}
onChange={onChangeTab}
/>
{/* close icon */}
<IconButton
size={'sm'}
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.700'} />}
w={'26px'}
h={'26px'}
borderColor={'myGray.300'}
variant={'grayBase'}
aria-label={''}
onClick={onClose}
/>
</Flex>
{templateType === TemplateTypeEnum.teamPlugin && (
<Flex mt={2} alignItems={'center'} h={10}>
<InputGroup mr={4} h={'full'}>
<InputLeftElement h={'full'} alignItems={'center'} display={'flex'}>
<MyIcon name={'common/searchLight'} w={'16px'} color={'myGray.500'} ml={3} />
</InputLeftElement>
<Input
h={'full'}
bg={'myGray.50'}
placeholder={t('plugin.Search plugin')}
onChange={debounce((e) => setSearchKey(e.target.value), 200)}
/>
</InputGroup>
<Box flex={1} />
<Flex
alignItems={'center'}
cursor={'pointer'}
_hover={{
color: 'primary.600'
}}
onClick={() => router.push('/plugin/list')}
>
<Box></Box>
<MyIcon name={'common/rightArrowLight'} w={'14px'} />
</Flex>
</Flex>
)}
{templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && (
<Flex alignItems={'center'} mt={2}>
<ParentPaths
paths={[currentParent]}
FirstPathDom={null}
onClick={() => {
setCurrentParent(undefined);
}}
fontSize="md"
/>
</Flex>
{templateType === TemplateTypeEnum.teamPlugin && (
<Flex mt={2} alignItems={'center'} h={10}>
<InputGroup mr={4} h={'full'}>
<InputLeftElement h={'full'} alignItems={'center'} display={'flex'}>
<MyIcon name={'common/searchLight'} w={'16px'} color={'myGray.500'} ml={3} />
</InputLeftElement>
<Input
h={'full'}
bg={'myGray.50'}
placeholder={t('plugin.Search plugin')}
onChange={debounce((e) => setSearchKey(e.target.value), 200)}
/>
</InputGroup>
<Box flex={1} />
<Flex
alignItems={'center'}
cursor={'pointer'}
_hover={{
color: 'primary.600'
}}
onClick={() => router.push('/plugin/list')}
>
<Box></Box>
<MyIcon name={'common/rightArrowLight'} w={'14px'} />
</Flex>
</Flex>
)}
{templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && (
<Flex alignItems={'center'} mt={2}>
<ParentPaths
paths={[currentParent]}
FirstPathDom={null}
onClick={() => {
setCurrentParent(undefined);
}}
fontSize="md"
/>
</Flex>
)}
</Box>
<RenderList
templates={parseTemplates}
onClose={onClose}
currentParent={currentParent}
setCurrentParent={setCurrentParent}
/>
</Flex>
</>
);
}, [
currentParent,
isOpen,
onChangeTab,
onClose,
router,
searchKey,
t,
templateType,
templatesString
]);
return Render;
)}
</Box>
<RenderList
templates={templates}
onClose={onClose}
currentParent={currentParent}
setCurrentParent={setCurrentParent}
/>
</Flex>
</>
);
};
export default React.memo(NodeTemplatesModal);
@@ -276,10 +255,11 @@ const RenderList = React.memo(function RenderList({
const { x, y, zoom } = useViewport();
const { setLoading } = useSystemStore();
const { toast } = useToast();
const { reactFlowWrapper, setNodes } = useFlowProviderStore();
const reactFlowWrapper = useContextSelector(WorkflowContext, (v) => v.reactFlowWrapper);
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const formatTemplates = useMemo<nodeTemplateListType>(() => {
const copy: nodeTemplateListType = JSON.parse(JSON.stringify(moduleTemplatesList));
const copy: nodeTemplateListType = JSON.parse(JSON.stringify(workflowNodeTemplateList(t)));
templates.forEach((item) => {
const index = copy.findIndex((template) => template.type === item.templateType);
if (index === -1) return;

View File

@@ -1,12 +1,16 @@
import React, { useCallback, useMemo } from 'react';
import { BezierEdge, getBezierPath, EdgeLabelRenderer, EdgeProps } from 'reactflow';
import { useFlowProviderStore } from '../FlowProvider';
import { Flex } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const ButtonEdge = (props: EdgeProps) => {
const { nodes, setEdges, workflowDebugData } = useFlowProviderStore();
const nodes = useContextSelector(WorkflowContext, (v) => v.nodes);
const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
const workflowDebugData = useContextSelector(WorkflowContext, (v) => v.workflowDebugData);
const {
id,
sourceX,

View File

@@ -2,7 +2,6 @@ import { storeNodes2RuntimeNodes } from '@fastgpt/global/core/workflow/runtime/u
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { useCallback, useState } from 'react';
import { getWorkflowStore, useFlowProviderStore } from '../FlowProvider';
import { checkWorkflowNodeAndConnection } from '@/web/core/workflow/utils';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
@@ -25,6 +24,8 @@ import {
import { useForm } from 'react-hook-form';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { checkInputIsReference } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '../../context';
const MyRightDrawer = dynamic(
() => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer')
@@ -35,7 +36,10 @@ export const useDebug = () => {
const { t } = useTranslation();
const { toast } = useToast();
const { edges, setNodes, onStartNodeDebug, onUpdateNodeError } = useFlowProviderStore();
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const onStartNodeDebug = useContextSelector(WorkflowContext, (v) => v.onStartNodeDebug);
const [runtimeNodeId, setRuntimeNodeId] = useState<string>();
const [runtimeNodes, setRuntimeNodes] = useState<RuntimeNodeItemType[]>();

View File

@@ -1,14 +1,15 @@
import { useCallback, useEffect, useState } from 'react';
import { getWorkflowStore, useFlowProviderStore } from '../FlowProvider';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { useTranslation } from 'next-i18next';
import { Node } from 'reactflow';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '../../context';
export const useKeyboard = () => {
const { t } = useTranslation();
const { setNodes } = useFlowProviderStore();
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const { copyData } = useCopyData();
const [isDowningCtrl, setIsDowningCtrl] = useState(false);
@@ -29,6 +30,7 @@ export const useKeyboard = () => {
const onCopy = useCallback(async () => {
if (hasInputtingElement()) return;
const { nodes } = await getWorkflowStore();
const selectedNodes = nodes.filter(
(node) => node.selected && !node.data?.isError && node.data?.unique !== true
);

View File

@@ -21,7 +21,6 @@ import dynamic from 'next/dynamic';
import ButtonEdge from './components/ButtonEdge';
import NodeTemplatesModal from './NodeTemplatesModal';
import { useFlowProviderStore } from './FlowProvider';
import 'reactflow/dist/style.css';
import { useToast } from '@fastgpt/web/hooks/useToast';
@@ -32,6 +31,8 @@ import MyTooltip from '@/components/MyTooltip';
import { connectionLineStyle, defaultEdgeOptions } from '../constants';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useKeyboard } from './hooks/useKeyboard';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context';
const NodeSimple = dynamic(() => import('./nodes/NodeSimple'));
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
@@ -71,16 +72,13 @@ const Container = React.memo(function Container() {
});
const { isDowningCtrl } = useKeyboard();
const {
reactFlowWrapper,
nodes,
onNodesChange,
edges,
setEdges,
onEdgesChange,
setConnectingEdge
} = useFlowProviderStore();
const setConnectingEdge = useContextSelector(WorkflowContext, (v) => v.setConnectingEdge);
const reactFlowWrapper = useContextSelector(WorkflowContext, (v) => v.reactFlowWrapper);
const nodes = useContextSelector(WorkflowContext, (v) => v.nodes);
const onNodesChange = useContextSelector(WorkflowContext, (v) => v.onNodesChange);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
const onEdgesChange = useContextSelector(WorkflowContext, (v) => v.onEdgesChange);
/* node */
const handleNodesChange = useCallback(

View File

@@ -4,15 +4,16 @@ import NodeCard from './render/NodeCard';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import Container from '../components/Container';
import RenderInput from './render/RenderInput';
import { useFlowProviderStore } from '../FlowProvider';
import RenderToolInput from './render/RenderToolInput';
import { useTranslation } from 'next-i18next';
import IOTitle from '../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const NodeAnswer = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs, outputs } = data;
const { splitToolInputs } = useFlowProviderStore();
const { nodeId, inputs } = data;
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
return (

View File

@@ -11,16 +11,16 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next';
import MyTooltip from '@/components/MyTooltip';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { useFlowProviderStore } from '../FlowProvider';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { SourceHandle } from './render/Handle';
import IOTitle from '../components/IOTitle';
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const NodeCQNode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs } = data;
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const CustomComponent = useMemo(
() => ({

View File

@@ -9,19 +9,21 @@ import { useTranslation } from 'next-i18next';
import { SmallAddIcon } from '@chakra-ui/icons';
import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { getOneQuoteInputTemplate } from '@fastgpt/global/core/workflow/template/system/datasetConcat';
import { useFlowProviderStore } from '../FlowProvider';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MySlider from '@/components/Slider';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import RenderOutput from './render/RenderOutput';
import IOTitle from '../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { llmModelList } = useSystemStore();
const { nodeList, onChangeNode } = useFlowProviderStore();
const { nodeId, inputs, outputs } = data;
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const quotes = useMemo(
() => inputs.filter((item) => item.valueType === WorkflowIOValueTypeEnum.datasetQuote),

View File

@@ -26,7 +26,6 @@ import ExtractFieldModal, { defaultField } from './ExtractFieldModal';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { useFlowProviderStore } from '../../FlowProvider';
import RenderToolInput from '../render/RenderToolInput';
import {
FlowNodeInputItemType,
@@ -34,12 +33,17 @@ import {
} from '@fastgpt/global/core/workflow/type/io.d';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import IOTitle from '../../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
const { inputs, outputs, nodeId } = data;
const { splitToolInputs, onChangeNode } = useFlowProviderStore();
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
const { t } = useTranslation();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
const CustomComponent = useMemo(

View File

@@ -7,7 +7,8 @@ import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useForm } from 'react-hook-form';
import parse from '@bany/curl-to-json';
import { useFlowProviderStore } from '../../FlowProvider';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
const methodMap: { [K in RequestMethod]: string } = {
@@ -28,7 +29,8 @@ const CurlImportModal = ({
onClose: () => void;
}) => {
const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { register, handleSubmit } = useForm({
defaultValues: {
curlContent: ''

View File

@@ -20,7 +20,6 @@ import {
useDisclosure
} from '@chakra-ui/react';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useFlowProviderStore } from '../../FlowProvider';
import { useTranslation } from 'next-i18next';
import Tabs from '@/components/Tabs';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -29,18 +28,17 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import {
formatEditorVariablePickerIcon,
getGuideModule,
splitGuideModule
} from '@fastgpt/global/core/workflow/utils';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
import HttpInput from '@fastgpt/web/components/common/Input/HttpInput';
import dynamic from 'next/dynamic';
import MySelect from '@fastgpt/web/components/common/MySelect';
import RenderToolInput from '../render/RenderToolInput';
import IOTitle from '../../components/IOTitle';
import { getSystemVariables } from '@/web/core/app/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
import { useMemoizedFn } from 'ahooks';
const CurlImportModal = dynamic(() => import('./CurlImportModal'));
export const HttpHeaders = [
@@ -104,168 +102,143 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
}) {
const { t } = useTranslation();
const { toast } = useToast();
const [_, startSts] = useTransition();
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure();
const requestMethods = inputs.find((item) => item.key === NodeInputKeyEnum.httpMethod);
const requestUrl = inputs.find((item) => item.key === NodeInputKeyEnum.httpReqUrl);
const onChangeUrl = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
startSts(() => {
onChangeNode({
nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.httpReqUrl,
value: {
...requestUrl,
value: e.target.value
}
});
});
},
[nodeId, onChangeNode, requestUrl]
);
const onBlurUrl = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value;
// 拆分params和url
const url = val.split('?')[0];
const params = val.split('?')[1];
if (params) {
const paramsArr = params.split('&');
const paramsObj = paramsArr.reduce((acc, cur) => {
const [key, value] = cur.split('=');
return {
...acc,
[key]: value
};
}, {});
const inputParams = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams);
if (!inputParams || Object.keys(paramsObj).length === 0) return;
const concatParams: PropsArrType[] = inputParams?.value || [];
Object.entries(paramsObj).forEach(([key, value]) => {
if (!concatParams.find((item) => item.key === key)) {
concatParams.push({ key, value: value as string, type: 'string' });
}
});
onChangeNode({
nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.httpParams,
value: {
...inputParams,
value: concatParams
}
});
onChangeNode({
nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.httpReqUrl,
value: {
...requestUrl,
value: url
}
});
toast({
status: 'success',
title: t('core.module.http.Url and params have been split')
});
const onChangeUrl = (e: React.ChangeEvent<HTMLInputElement>) => {
onChangeNode({
nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.httpReqUrl,
value: {
...requestUrl,
value: e.target.value
}
},
[inputs, nodeId, onChangeNode, requestUrl, t, toast]
);
});
};
const onBlurUrl = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value;
// 拆分params和url
const url = val.split('?')[0];
const params = val.split('?')[1];
if (params) {
const paramsArr = params.split('&');
const paramsObj = paramsArr.reduce((acc, cur) => {
const [key, value] = cur.split('=');
return {
...acc,
[key]: value
};
}, {});
const inputParams = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams);
const Render = useMemo(() => {
return (
<Box>
<Box mb={2} display={'flex'} justifyContent={'space-between'}>
<Box fontWeight={'medium'} color={'myGray.600'}>
{t('core.module.Http request settings')}
</Box>
<Button variant={'link'} onClick={onOpenCurl}>
{t('core.module.http.curl import')}
</Button>
if (!inputParams || Object.keys(paramsObj).length === 0) return;
const concatParams: PropsArrType[] = inputParams?.value || [];
Object.entries(paramsObj).forEach(([key, value]) => {
if (!concatParams.find((item) => item.key === key)) {
concatParams.push({ key, value: value as string, type: 'string' });
}
});
onChangeNode({
nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.httpParams,
value: {
...inputParams,
value: concatParams
}
});
onChangeNode({
nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.httpReqUrl,
value: {
...requestUrl,
value: url
}
});
toast({
status: 'success',
title: t('core.module.http.Url and params have been split')
});
}
};
return (
<Box>
<Box mb={2} display={'flex'} justifyContent={'space-between'}>
<Box fontWeight={'medium'} color={'myGray.600'}>
{t('core.module.Http request settings')}
</Box>
<Flex alignItems={'center'} className="nodrag">
<MySelect
h={'34px'}
w={'88px'}
bg={'white'}
width={'100%'}
value={requestMethods?.value}
list={[
{
label: 'GET',
value: 'GET'
},
{
label: 'POST',
value: 'POST'
},
{
label: 'PUT',
value: 'PUT'
},
{
label: 'DELETE',
value: 'DELETE'
},
{
label: 'PATCH',
value: 'PATCH'
}
]}
onchange={(e) => {
onChangeNode({
nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.httpMethod,
value: {
...requestMethods,
value: e
}
});
}}
/>
<Input
flex={'1 0 0'}
ml={2}
h={'34px'}
bg={'white'}
value={requestUrl?.value}
placeholder={t('core.module.input.label.Http Request Url')}
fontSize={'xs'}
onChange={onChangeUrl}
onBlur={onBlurUrl}
/>
</Flex>
{isOpenCurl && <CurlImportModal nodeId={nodeId} inputs={inputs} onClose={onCloseCurl} />}
<Button variant={'link'} onClick={onOpenCurl}>
{t('core.module.http.curl import')}
</Button>
</Box>
);
}, [
inputs,
isOpenCurl,
nodeId,
onBlurUrl,
onChangeNode,
onChangeUrl,
onCloseCurl,
onOpenCurl,
requestMethods,
requestUrl?.value,
t
]);
<Flex alignItems={'center'} className="nodrag">
<MySelect
h={'34px'}
w={'88px'}
bg={'white'}
width={'100%'}
value={requestMethods?.value}
list={[
{
label: 'GET',
value: 'GET'
},
{
label: 'POST',
value: 'POST'
},
{
label: 'PUT',
value: 'PUT'
},
{
label: 'DELETE',
value: 'DELETE'
},
{
label: 'PATCH',
value: 'PATCH'
}
]}
onchange={(e) => {
onChangeNode({
nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.httpMethod,
value: {
...requestMethods,
value: e
}
});
}}
/>
<Input
flex={'1 0 0'}
ml={2}
h={'34px'}
bg={'white'}
value={requestUrl?.value || ''}
placeholder={t('core.module.input.label.Http Request Url')}
fontSize={'xs'}
onChange={onChangeUrl}
onBlur={onBlurUrl}
/>
</Flex>
return Render;
{isOpenCurl && <CurlImportModal nodeId={nodeId} inputs={inputs} onClose={onCloseCurl} />}
</Box>
);
});
export function RenderHttpProps({
@@ -277,7 +250,7 @@ export function RenderHttpProps({
}) {
const { t } = useTranslation();
const [selectedTab, setSelectedTab] = useState(TabEnum.params);
const { nodeList } = useFlowProviderStore();
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const requestMethods = inputs.find((item) => item.key === NodeInputKeyEnum.httpMethod)?.value;
const params = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams);
@@ -289,11 +262,7 @@ export function RenderHttpProps({
// get variable
const variables = useMemo(() => {
const globalVariables = formatEditorVariablePickerIcon(
splitGuideModule(getGuideModule(nodeList))?.variableModules || []
);
const systemVariables = getSystemVariables(t);
const globalVariables = getWorkflowGlobalVariables(nodeList, t);
const moduleVariables = formatEditorVariablePickerIcon(
inputs
@@ -304,7 +273,7 @@ export function RenderHttpProps({
}))
);
return [...moduleVariables, ...globalVariables, ...systemVariables];
return [...moduleVariables, ...globalVariables];
}, [inputs, nodeList, t]);
const variableText = useMemo(() => {
@@ -407,7 +376,7 @@ const RenderForm = ({
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [list, setList] = useState<PropsArrType[]>(input.value || []);
const [updateTrigger, setUpdateTrigger] = useState(false);
@@ -601,7 +570,7 @@ const RenderJson = ({
variables: EditorVariablePickerType[];
}) => {
const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [_, startSts] = useTransition();
const Render = useMemo(() => {
@@ -650,18 +619,20 @@ const RenderPropsItem = ({ text, num }: { text: string; num: number }) => {
const NodeHttp = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs, outputs } = data;
const { splitToolInputs } = useFlowProviderStore();
const splitToolInputs = useContextSelector(WorkflowContext, (v) => v.splitToolInputs);
const { toolInputs, commonInputs, isTool } = splitToolInputs(inputs, nodeId);
const CustomComponents = useMemo(
() => ({
[NodeInputKeyEnum.httpMethod]: () => (
<RenderHttpMethodAndUrl nodeId={nodeId} inputs={inputs} />
),
[NodeInputKeyEnum.httpHeaders]: () => <RenderHttpProps nodeId={nodeId} inputs={inputs} />
}),
[inputs, nodeId]
);
const HttpMethodAndUrl = useMemoizedFn(() => (
<RenderHttpMethodAndUrl nodeId={nodeId} inputs={inputs} />
));
const Headers = useMemoizedFn(() => <RenderHttpProps nodeId={nodeId} inputs={inputs} />);
const CustomComponents = useMemo(() => {
return {
[NodeInputKeyEnum.httpMethod]: HttpMethodAndUrl,
[NodeInputKeyEnum.httpHeaders]: Headers
};
}, [Headers, HttpMethodAndUrl]);
return (
<NodeCard minW={'350px'} selected={selected} {...data}>

View File

@@ -1,7 +1,6 @@
import React, { useCallback, useMemo } from 'react';
import NodeCard from './render/NodeCard';
import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../FlowProvider';
import { Box, Button, Flex, background } from '@chakra-ui/react';
import { SmallAddIcon } from '@chakra-ui/icons';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -25,11 +24,13 @@ import {
import { stringConditionList } from '@fastgpt/global/core/workflow/template/system/ifElse/constant';
import MySelect from '@fastgpt/web/components/common/MySelect';
import MyInput from '@/components/MyInput';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs = [], outputs } = data;
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const condition = useMemo(
() =>
@@ -274,7 +275,7 @@ const ConditionSelect = ({
variable?: ReferenceValueProps;
onSelect: (e: VariableConditionEnum) => void;
}) => {
const { nodeList } = useFlowProviderStore();
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
// get condition type
const valueType = useMemo(() => {
@@ -337,7 +338,7 @@ const ConditionValueInput = ({
condition?: VariableConditionEnum;
onChange: (e: string) => void;
}) => {
const { nodeList } = useFlowProviderStore();
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
// get value type
const valueType = useMemo(() => {

View File

@@ -5,7 +5,6 @@ import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import Container from '../components/Container';
import { Box, Button, Center, Flex, useDisclosure } from '@chakra-ui/react';
import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useFlowProviderStore } from '../FlowProvider';
import { useTranslation } from 'next-i18next';
import { getLafAppDetail } from '@/web/support/laf/api';
import MySelect from '@fastgpt/web/components/common/MySelect';
@@ -32,6 +31,8 @@ import {
} from '@fastgpt/global/core/workflow/type/io';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import IOTitle from '../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccountModal'));
@@ -42,7 +43,7 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
const { data, selected } = props;
const { nodeId, inputs, outputs } = data;
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const requestUrl = inputs.find((item) => item.key === NodeInputKeyEnum.httpReqUrl);
@@ -293,7 +294,7 @@ const ConfigLaf = () => {
const RenderIO = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs, outputs } = data;
const { splitToolInputs } = useFlowProviderStore();
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const { commonInputs, toolInputs, isTool } = splitToolInputs(inputs, nodeId);
return (

View File

@@ -2,7 +2,6 @@ import React, { useMemo, useState } from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from './render/NodeCard';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import dynamic from 'next/dynamic';
import { Box, Button, Flex } from '@chakra-ui/react';
import { SmallAddIcon } from '@chakra-ui/icons';
import {
@@ -17,7 +16,6 @@ import type {
EditNodeFieldType
} from '@fastgpt/global/core/workflow/node/type.d';
import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../FlowProvider';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import {
FlowNodeInputMap,
@@ -26,6 +24,8 @@ import {
} from '@fastgpt/global/core/workflow/node/constant';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import VariableTable from '../nodes/render/VariableTable';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const defaultCreateField: EditNodeFieldType = {
label: '',
@@ -50,7 +50,7 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs, outputs } = data;
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [createField, setCreateField] = useState<EditNodeFieldType>();
const [editField, setEditField] = useState<EditNodeFieldType>();

View File

@@ -11,8 +11,9 @@ import { EditInputFieldMapType, EditNodeFieldType } from '@fastgpt/global/core/w
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../FlowProvider';
import RenderInput from './render/RenderInput';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const FieldEditModal = dynamic(() => import('./render/FieldEditModal'));
@@ -31,7 +32,7 @@ const createEditField: EditInputFieldMapType = {
const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs } = data;
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [createField, setCreateField] = useState<EditNodeFieldType>();

View File

@@ -7,9 +7,10 @@ import RenderInput from './render/RenderInput';
import RenderOutput from './render/RenderOutput';
import RenderToolInput from './render/RenderToolInput';
import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../FlowProvider';
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import IOTitle from '../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const NodeSimple = ({
data,
@@ -18,7 +19,7 @@ const NodeSimple = ({
maxW
}: NodeProps<FlowNodeItemType> & { minW?: string | number; maxW?: string | number }) => {
const { t } = useTranslation();
const { splitToolInputs } = useFlowProviderStore();
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const { nodeId, inputs, outputs } = data;
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);

View File

@@ -1,24 +1,31 @@
import React, { useCallback, useMemo, useTransition } from 'react';
import React, { useMemo, useTransition } from 'react';
import { NodeProps } from 'reactflow';
import { Box, Flex, Textarea, useTheme } from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { FlowNodeItemType, StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { welcomeTextTip } from '@fastgpt/global/core/workflow/template/tip';
import type { VariableItemType } from '@fastgpt/global/core/app/type.d';
import QGSwitch from '@/components/core/app/QGSwitch';
import TTSSelect from '@/components/core/app/TTSSelect';
import WhisperConfig from '@/components/core/app/WhisperConfig';
import { splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { useTranslation } from 'next-i18next';
import { TTSTypeEnum } from '@/constants/app';
import { useFlowProviderStore } from '../FlowProvider';
import VariableEdit from '../../../app/VariableEdit';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@/components/MyTooltip';
import NodeCard from './render/NodeCard';
import ScheduledTriggerConfig from '@/components/core/app/ScheduledTriggerConfig';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { VariableItemType } from '@fastgpt/global/core/app/type';
import { useMemoizedFn } from 'ahooks';
import VariableEdit from '@/components/core/app/VariableEdit';
import {
FlowNodeOutputTypeEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io';
const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const theme = useTheme();
@@ -38,10 +45,10 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
>
<Box px={4} py={'10px'} position={'relative'} borderRadius={'md'} className="nodrag">
<WelcomeText data={data} />
<Box pt={4} pb={2}>
<Box pt={4}>
<ChatStartVariable data={data} />
</Box>
<Box pt={3} borderTop={theme.borders.base}>
<Box mt={3} pt={3} borderTop={theme.borders.base}>
<TTSGuide data={data} />
</Box>
<Box mt={3} pt={3} borderTop={theme.borders.base}>
@@ -65,7 +72,7 @@ function WelcomeText({ data }: { data: FlowNodeItemType }) {
const { t } = useTranslation();
const { inputs, nodeId } = data;
const [, startTst] = useTransition();
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const welcomeText = inputs.find((item) => item.key === NodeInputKeyEnum.welcomeText);
@@ -108,7 +115,7 @@ function WelcomeText({ data }: { data: FlowNodeItemType }) {
function ChatStartVariable({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data;
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const variables = useMemo(
() =>
@@ -117,27 +124,25 @@ function ChatStartVariable({ data }: { data: FlowNodeItemType }) {
[inputs]
);
const updateVariables = useCallback(
(value: VariableItemType[]) => {
onChangeNode({
nodeId,
key: NodeInputKeyEnum.variables,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === NodeInputKeyEnum.variables),
value
}
});
},
[inputs, nodeId, onChangeNode]
);
const updateVariables = useMemoizedFn((value: VariableItemType[]) => {
// update system config node
onChangeNode({
nodeId,
key: NodeInputKeyEnum.variables,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === NodeInputKeyEnum.variables),
value
}
});
});
return <VariableEdit variables={variables} onChange={(e) => updateVariables(e)} />;
}
function QuestionGuide({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data;
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const questionGuide = useMemo(
() =>
@@ -168,7 +173,7 @@ function QuestionGuide({ data }: { data: FlowNodeItemType }) {
function TTSGuide({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data;
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { ttsConfig } = splitGuideModule({ inputs } as StoreNodeItemType);
return (
@@ -191,7 +196,7 @@ function TTSGuide({ data }: { data: FlowNodeItemType }) {
function WhisperGuide({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data;
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { ttsConfig, whisperConfig } = splitGuideModule({ inputs } as StoreNodeItemType);
return (
@@ -215,7 +220,7 @@ function WhisperGuide({ data }: { data: FlowNodeItemType }) {
function ScheduledTrigger({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data;
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { scheduledTriggerConfig } = splitGuideModule({ inputs } as StoreNodeItemType);
return (

View File

@@ -10,10 +10,11 @@ import { ToolSourceHandle } from './render/Handle/ToolHandle';
import { Box } from '@chakra-ui/react';
import IOTitle from '../components/IOTitle';
import MyIcon from '@fastgpt/web/components/common/Icon';
import RenderOutput from './render/RenderOutput';
const NodeTools = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs } = data;
const { nodeId, inputs, outputs } = data;
return (
<NodeCard minW={'350px'} selected={selected} {...data}>
@@ -21,7 +22,10 @@ const NodeTools = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
<IOTitle text={t('common.Input')} />
<RenderInput nodeId={nodeId} flowInputList={inputs} />
</Container>
<Container>
<IOTitle text={t('common.Output')} />
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container>
<Box position={'relative'}>
<Box borderBottomLeftRadius={'md'} borderBottomRadius={'md'} overflow={'hidden'}>
<Divider

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from './render/NodeCard';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
@@ -6,10 +6,30 @@ import Container from '../components/Container';
import RenderOutput from './render/RenderOutput';
import IOTitle from '../components/IOTitle';
import { useTranslation } from 'next-i18next';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { useCreation } from 'ahooks';
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io';
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
const NodeStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, outputs } = data;
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const variablesOutputs = useCreation(() => {
const variables = getWorkflowGlobalVariables(nodeList, t);
return variables.map<FlowNodeOutputItemType>((item) => ({
id: item.key,
type: FlowNodeOutputTypeEnum.static,
key: item.key,
valueType: item.valueType || WorkflowIOValueTypeEnum.any,
label: item.label
}));
}, [nodeList, t]);
return (
<NodeCard
@@ -26,6 +46,10 @@ const NodeStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
<IOTitle text={t('common.Output')} />
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container>
<Container>
<IOTitle text={t('core.module.Variable')} />
<RenderOutput nodeId={nodeId} flowOutputList={variablesOutputs} />
</Container>
</NodeCard>
);
};

View File

@@ -1,12 +1,15 @@
import React, { useMemo } from 'react';
import { Position } from 'reactflow';
import { useFlowProviderStore } from '../../../FlowProvider';
import { SourceHandle, TargetHandle } from '.';
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
export const ConnectionSourceHandle = ({ nodeId }: { nodeId: string }) => {
const { nodeList, edges, connectingEdge } = useFlowProviderStore();
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const node = useMemo(() => nodeList.find((node) => node.nodeId === nodeId), [nodeList, nodeId]);
@@ -102,7 +105,8 @@ export const ConnectionSourceHandle = ({ nodeId }: { nodeId: string }) => {
};
export const ConnectionTargetHandle = ({ nodeId }: { nodeId: string }) => {
const { nodeList, connectingEdge } = useFlowProviderStore();
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const node = useMemo(() => nodeList.find((node) => node.nodeId === nodeId), [nodeList, nodeId]);

View File

@@ -1,15 +1,12 @@
import MyTooltip from '@/components/MyTooltip';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import { Box, BoxProps } from '@chakra-ui/react';
import {
WorkflowIOValueTypeEnum,
NodeOutputKeyEnum
} from '@fastgpt/global/core/workflow/constants';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next';
import { Connection, Handle, Position } from 'reactflow';
import { useFlowProviderStore } from '../../../FlowProvider';
import { useCallback, useMemo } from 'react';
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const handleSize = '14px';
type ToolHandleProps = BoxProps & {
@@ -17,7 +14,9 @@ type ToolHandleProps = BoxProps & {
};
export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => {
const { t } = useTranslation();
const { connectingEdge, edges } = useFlowProviderStore();
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const handleId = NodeOutputKeyEnum.selectedTools;
const connected = edges.some((edge) => edge.target === nodeId && edge.targetHandle === handleId);
@@ -62,7 +61,7 @@ export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => {
export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
const { t } = useTranslation();
const { setEdges } = useFlowProviderStore();
const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
/* onConnect edge, delete tool input and switch */
const onConnect = useCallback(

View File

@@ -1,9 +1,10 @@
import React, { useMemo } from 'react';
import { Handle, Position } from 'reactflow';
import { useFlowProviderStore } from '../../../FlowProvider';
import { SmallAddIcon } from '@chakra-ui/icons';
import { handleHighLightStyle, sourceCommonStyle, handleConnectedStyle, handleSize } from './style';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
type Props = {
nodeId: string;
@@ -23,7 +24,11 @@ const MySourceHandle = React.memo(function MySourceHandle({
highlightStyle: Record<string, any>;
connectedStyle: Record<string, any>;
}) {
const { nodes, hoverNodeId, edges, connectingEdge } = useFlowProviderStore();
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const nodes = useContextSelector(WorkflowContext, (v) => v.nodes);
const hoverNodeId = useContextSelector(WorkflowContext, (v) => v.hoverNodeId);
const node = useMemo(() => nodes.find((node) => node.data.nodeId === nodeId), [nodes, nodeId]);
const connected = edges.some((edge) => edge.sourceHandle === handleId);
@@ -136,8 +141,10 @@ const MyTargetHandle = React.memo(function MyTargetHandle({
highlightStyle: Record<string, any>;
connectedStyle: Record<string, any>;
}) {
const { nodeList, edges, connectingEdge } = useFlowProviderStore();
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const node = useMemo(() => nodeList.find((node) => node.nodeId === nodeId), [nodeList, nodeId]);
const connected = edges.some((edge) => edge.targetHandle === handleId);
const connectedEdges = edges.filter((edge) => edge.target === nodeId);
@@ -194,12 +201,13 @@ const MyTargetHandle = React.memo(function MyTargetHandle({
return false;
}
if (connectingEdge?.handleId && !connectingEdge.handleId?.includes('source')) return false;
// Same source node
if (connectedEdges.some((item) => item.sourceHandle === connectingEdge?.handleId)) return false;
// Same source node
if (connectedEdges.some((item) => item.target === nodeId && item.targetHandle !== handleId))
return false;
return true;
}, [connectedEdges, connectingEdge?.handleId, edges, node, nodeId]);
}, [connectedEdges, connectingEdge?.handleId, edges, handleId, node, nodeId]);
const RenderHandle = useMemo(() => {
return (

View File

@@ -6,7 +6,6 @@ import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.
import { useTranslation } from 'next-i18next';
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useFlowProviderStore } from '../../FlowProvider';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
@@ -21,6 +20,8 @@ import { getPreviewPluginModule } from '@/web/core/plugin/api';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { storeNode2FlowNode } from '@/web/core/workflow/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
type Props = FlowNodeItemType & {
children?: React.ReactNode | React.ReactNode[] | string;
@@ -55,7 +56,9 @@ const NodeCard = (props: Props) => {
pluginId
} = props;
const { nodeList, setHoverNodeId, onUpdateNodeError } = useFlowProviderStore();
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const setHoverNodeId = useContextSelector(WorkflowContext, (v) => v.setHoverNodeId);
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
const showToolHandle = useMemo(
() => isTool && !!nodeList.find((item) => item?.flowNodeType === FlowNodeTypeEnum.tools),
@@ -105,44 +108,40 @@ const NodeCard = (props: Props) => {
intro
]);
const Render = useMemo(() => {
return (
<Box
minW={minW}
maxW={maxW}
bg={'white'}
borderWidth={'1px'}
borderRadius={'md'}
boxShadow={'1'}
_hover={{
boxShadow: '4',
'& .controller-menu': {
display: 'flex'
},
'& .controller-debug': {
display: 'block'
return (
<Box
minW={minW}
maxW={maxW}
bg={'white'}
borderWidth={'1px'}
borderRadius={'md'}
boxShadow={'1'}
_hover={{
boxShadow: '4',
'& .controller-menu': {
display: 'flex'
},
'& .controller-debug': {
display: 'block'
}
}}
onMouseEnter={() => setHoverNodeId(nodeId)}
onMouseLeave={() => setHoverNodeId(undefined)}
{...(isError
? {
borderColor: 'red.500',
onMouseDownCapture: () => onUpdateNodeError(nodeId, false)
}
}}
onMouseEnter={() => setHoverNodeId(nodeId)}
onMouseLeave={() => setHoverNodeId(undefined)}
{...(isError
? {
borderColor: 'red.500',
onMouseDownCapture: () => onUpdateNodeError(nodeId, false)
}
: {
borderColor: selected ? 'primary.600' : 'borderColor.base'
})}
>
{Header}
{children}
<ConnectionSourceHandle nodeId={nodeId} />
<ConnectionTargetHandle nodeId={nodeId} />
</Box>
);
}, [Header, children, isError, maxW, minW, nodeId, onUpdateNodeError, selected, setHoverNodeId]);
return Render;
: {
borderColor: selected ? 'primary.600' : 'borderColor.base'
})}
>
{Header}
{children}
<ConnectionSourceHandle nodeId={nodeId} />
<ConnectionTargetHandle nodeId={nodeId} />
</Box>
);
};
export default React.memo(NodeCard);
@@ -180,7 +179,10 @@ const MenuRender = React.memo(function MenuRender({
type: 'delete'
});
const { setNodes, setEdges, onResetNode, onChangeNode } = useFlowProviderStore();
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const onResetNode = useContextSelector(WorkflowContext, (v) => v.onResetNode);
const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const onCopyNode = useCallback(
(nodeId: string) => {
@@ -383,7 +385,8 @@ const NodeIntro = React.memo(function NodeIntro({
intro?: string;
}) {
const { t } = useTranslation();
const { onChangeNode, splitToolInputs } = useFlowProviderStore();
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const moduleIsTool = useMemo(() => {
const { isTool } = splitToolInputs([], nodeId);
@@ -442,8 +445,12 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
debugResult: FlowNodeItemType['debugResult'];
}) {
const { t } = useTranslation();
const { onChangeNode, onStopNodeDebug, onNextNodeDebug, workflowDebugData } =
useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const onStopNodeDebug = useContextSelector(WorkflowContext, (v) => v.onStopNodeDebug);
const onNextNodeDebug = useContextSelector(WorkflowContext, (v) => v.onNextNodeDebug);
const workflowDebugData = useContextSelector(WorkflowContext, (v) => v.workflowDebugData);
const { openConfirm, ConfirmModal } = useConfirm({
content: t('core.workflow.Confirm stop debug')
});

View File

@@ -1,7 +1,6 @@
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../../../FlowProvider';
import { Box, Flex } from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
@@ -13,17 +12,20 @@ import dynamic from 'next/dynamic';
import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import ValueTypeLabel from '../ValueTypeLabel';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const FieldEditModal = dynamic(() => import('../FieldEditModal'));
type Props = {
nodeId: string;
input: FlowNodeInputItemType;
mode?: 'app' | 'plugin';
};
const InputLabel = ({ nodeId, input }: Props) => {
const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const {
description,
toolDescription,
@@ -56,7 +58,7 @@ const InputLabel = ({ nodeId, input }: Props) => {
);
const RenderLabel = useMemo(() => {
const renderType = renderTypeList[selectedTypeIndex || 0];
const renderType = renderTypeList?.[selectedTypeIndex || 0];
return (
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>

View File

@@ -5,18 +5,21 @@ import { SmallAddIcon } from '@chakra-ui/icons';
import { useTranslation } from 'next-i18next';
import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
import dynamic from 'next/dynamic';
import { useFlowProviderStore } from '../../../../FlowProvider';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import Reference from './Reference';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const FieldEditModal = dynamic(() => import('../../FieldEditModal'));
const AddInputParam = (props: RenderInputProps) => {
const { item, inputs, nodeId } = props;
const { t } = useTranslation();
const { onChangeNode, mode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const mode = useContextSelector(WorkflowContext, (ctx) => ctx.mode);
const inputValue = useMemo(() => (item.value || []) as FlowNodeInputItemType[], [item.value]);
const [editField, setEditField] = useState<EditNodeFieldType>();

View File

@@ -1,21 +1,22 @@
import React, { useCallback, useMemo } from 'react';
import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import {
formatEditorVariablePickerIcon,
getGuideModule,
splitGuideModule
} from '@fastgpt/global/core/workflow/utils';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
import { useCreation } from 'ahooks';
import { useTranslation } from 'next-i18next';
const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => {
const { nodeList, onChangeNode } = useFlowProviderStore();
const { t } = useTranslation();
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
// get variable
const variables = useMemo(() => {
const globalVariables = formatEditorVariablePickerIcon(
splitGuideModule(getGuideModule(nodeList))?.variableModules || []
);
const variables = useCreation(() => {
const globalVariables = getWorkflowGlobalVariables(nodeList, t);
const moduleVariables = formatEditorVariablePickerIcon(
inputs
.filter((input) => input.canEdit)

View File

@@ -7,10 +7,11 @@ import {
NumberInputField,
NumberInputStepper
} from '@chakra-ui/react';
import { useFlowProviderStore } from '../../../../FlowProvider';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const NumberInputRender = ({ item, nodeId }: RenderInputProps) => {
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const Render = useMemo(() => {
return (

View File

@@ -1,16 +1,19 @@
import React, { useCallback, useMemo } from 'react';
import type { RenderInputProps } from '../type';
import { Flex, Box, ButtonProps } from '@chakra-ui/react';
import { useFlowProviderStore } from '../../../../FlowProvider';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { computedNodeInputReference } from '@/web/core/workflow/utils';
import { useTranslation } from 'next-i18next';
import {
NodeOutputKeyEnum,
VARIABLE_NODE_ID,
WorkflowIOValueTypeEnum
} from '@fastgpt/global/core/workflow/constants';
import type { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io';
import dynamic from 'next/dynamic';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
const MultipleRowSelect = dynamic(
() => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect')
@@ -34,21 +37,37 @@ type SelectProps = {
const Reference = ({ item, nodeId }: RenderInputProps) => {
const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const onSelect = useCallback(
(e: any) => {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
const workflowStartNode = nodeList.find(
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
);
if (e[0] === workflowStartNode?.id && e[1] !== NodeOutputKeyEnum.userChatInput) {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: [VARIABLE_NODE_ID, e[1]]
}
});
} else {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
}
},
[item, nodeId, onChangeNode]
[item, nodeId, nodeList, onChangeNode]
);
const { referenceList, formatValue } = useReference({
@@ -79,13 +98,15 @@ export const useReference = ({
value?: any;
}) => {
const { t } = useTranslation();
const { nodeList, edges } = useFlowProviderStore();
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const referenceList = useMemo(() => {
const sourceNodes = computedNodeInputReference({
nodeId,
nodes: nodeList,
edges: edges
edges: edges,
t
});
if (!sourceNodes) return [];

View File

@@ -1,10 +1,11 @@
import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import MySelect from '@fastgpt/web/components/common/MySelect';
import { WorkflowContext } from '@/components/core/workflow/context';
import { useContextSelector } from 'use-context-selector';
const SelectRender = ({ item, nodeId }: RenderInputProps) => {
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const Render = useMemo(() => {
return (

View File

@@ -1,16 +1,18 @@
import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import { Box, Button, Flex, useDisclosure, useTheme } from '@chakra-ui/react';
import { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
import Avatar from '@/components/Avatar';
import SelectAppModal from '../../../../SelectAppModal';
import { useTranslation } from 'next-i18next';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
const { t } = useTranslation();
const theme = useTheme();
const { filterAppIds, onChangeNode } = useFlowProviderStore();
const filterAppIds = useContextSelector(WorkflowContext, (ctx) => ctx.filterAppIds);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const {
isOpen: isOpenSelectApp,
@@ -20,7 +22,7 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
const value = item.value as SelectAppItemType | undefined;
const filterAppString = useMemo(() => filterAppIds.join(','), [filterAppIds]);
const filterAppString = useMemo(() => filterAppIds?.join(',') || '', [filterAppIds]);
const Render = useMemo(() => {
return (

View File

@@ -1,6 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react';
import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import { Box, Button, Flex, Grid, useDisclosure, useTheme } from '@chakra-ui/react';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { SelectedDatasetType } from '@fastgpt/global/core/workflow/api';
@@ -8,19 +7,19 @@ import Avatar from '@/components/Avatar';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import dynamic from 'next/dynamic';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
const { t } = useTranslation();
const theme = useTheme();
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [data, setData] = useState({
searchMode: DatasetSearchModeEnum.embedding,
limit: 5,

View File

@@ -1,6 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react';
import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
@@ -10,9 +9,13 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import DatasetParamsModal, { DatasetParamsProps } from '@/components/core/app/DatasetParamsModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
const { nodeList, onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const { t } = useTranslation();
const { llmModelList } = useSystemStore();

Some files were not shown because too many files have changed in this diff Show More