4.7.1-alpha (#1120)

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-04-03 18:14:09 +08:00
committed by GitHub
parent 9ae581e09b
commit 8a46372418
76 changed files with 3129 additions and 2104 deletions

View File

@@ -1,5 +1,7 @@
export const fileImgs = [
{ suffix: 'pdf', src: 'file/fill/pdf' },
{ suffix: 'ppt', src: 'file/fill/ppt' },
{ suffix: 'xlsx', src: 'file/fill/xlsx' },
{ suffix: 'csv', src: 'file/fill/csv' },
{ suffix: '(doc|docs)', src: 'file/fill/doc' },
{ suffix: 'txt', src: 'file/fill/txt' },

View File

@@ -11,5 +11,5 @@ export const formatFileSize = (bytes: number): string => {
};
export const detectFileEncoding = (buffers: string | Buffer) => {
return detect(buffers)?.encoding || 'utf-8';
return (detect(buffers)?.encoding || 'utf-8') as BufferEncoding;
};

View File

@@ -57,6 +57,7 @@ export type FastGPTFeConfigsType = {
uploadFileMaxAmount?: number;
uploadFileMaxSize?: number;
lafEnv?: string;
};
export type SystemEnvType = {

View File

@@ -61,7 +61,8 @@ export enum FlowNodeTypeEnum {
pluginOutput = 'pluginOutput',
queryExtension = 'cfr',
tools = 'tools',
stopTool = 'stopTool'
stopTool = 'stopTool',
lafModule = 'lafModule'
// abandon
}

View File

@@ -20,6 +20,7 @@ import { AiQueryExtension } from './system/queryExtension';
import type { FlowNodeTemplateType, moduleTemplateListType } from '../../module/type.d';
import { FlowNodeTemplateTypeEnum } from '../../module/constants';
import { lafModule } from './system/laf';
/* app flow module templates */
export const appSystemModuleTemplates: FlowNodeTemplateType[] = [
@@ -35,7 +36,8 @@ export const appSystemModuleTemplates: FlowNodeTemplateType[] = [
ClassifyQuestionModule,
ContextExtractModule,
HttpModule468,
AiQueryExtension
AiQueryExtension,
lafModule
];
/* plugin flow module templates */
export const pluginSystemModuleTemplates: FlowNodeTemplateType[] = [
@@ -51,7 +53,8 @@ export const pluginSystemModuleTemplates: FlowNodeTemplateType[] = [
ClassifyQuestionModule,
ContextExtractModule,
HttpModule468,
AiQueryExtension
AiQueryExtension,
lafModule
];
/* all module */
@@ -73,7 +76,8 @@ export const moduleTemplatesFlat: FlowNodeTemplateType[] = [
PluginInputModule,
PluginOutputModule,
RunPluginModule,
AiQueryExtension
AiQueryExtension,
lafModule
];
export const moduleTemplatesList: moduleTemplateListType = [

View File

@@ -0,0 +1,86 @@
import {
FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum,
FlowNodeTypeEnum
} from '../../node/constant';
import { FlowNodeTemplateType } from '../../type';
import {
ModuleIOValueTypeEnum,
ModuleInputKeyEnum,
ModuleOutputKeyEnum,
FlowNodeTemplateTypeEnum
} from '../../constants';
import {
Input_Template_DynamicInput,
Input_Template_Switch,
Input_Template_AddInputParam
} from '../input';
import { Output_Template_Finish, Output_Template_AddOutput } from '../output';
export const lafModule: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.lafModule,
templateType: FlowNodeTemplateTypeEnum.externalCall,
flowType: FlowNodeTypeEnum.lafModule,
avatar: '/imgs/module/laf.png',
name: 'Laf 函数调用(测试)',
intro: '可以调用Laf账号下的云函数。',
showStatus: true,
isTool: true,
inputs: [
Input_Template_Switch,
{
key: ModuleInputKeyEnum.httpReqUrl,
type: FlowNodeInputTypeEnum.hidden,
valueType: ModuleIOValueTypeEnum.string,
label: '',
description: 'core.module.input.description.Http Request Url',
placeholder: 'https://api.ai.com/getInventory',
required: false,
showTargetInApp: false,
showTargetInPlugin: false
},
Input_Template_DynamicInput,
{
...Input_Template_AddInputParam,
editField: {
key: true,
description: true,
dataType: true
},
defaultEditField: {
label: '',
key: '',
description: '',
inputType: FlowNodeInputTypeEnum.target,
valueType: ModuleIOValueTypeEnum.string
}
}
],
outputs: [
{
key: ModuleOutputKeyEnum.httpRawResponse,
label: '原始响应',
description: 'HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。',
valueType: ModuleIOValueTypeEnum.any,
type: FlowNodeOutputTypeEnum.source,
targets: []
},
{
...Output_Template_AddOutput,
editField: {
key: true,
description: true,
dataType: true,
defaultValue: true
},
defaultEditField: {
label: '',
key: '',
description: '',
outputType: FlowNodeOutputTypeEnum.source,
valueType: ModuleIOValueTypeEnum.string
}
},
Output_Template_Finish
]
};

View File

@@ -9,6 +9,7 @@ import { DispatchNodeResponseKeyEnum } from './runtime/constants';
import { FlowNodeInputItemType, FlowNodeOutputItemType } from './node/type';
import { UserModelSchema } from 'support/user/type';
import {
ChatItemType,
ChatItemValueItemType,
ToolRunResponseItemType,
UserChatItemValueItemType

View File

@@ -41,7 +41,7 @@ export const str2OpenApiSchema = async (yamlStr = ''): Promise<OpenApiJsonSchema
path,
method,
name: methodInfo.operationId || path,
description: methodInfo.description,
description: methodInfo.description || methodInfo.summary,
params: methodInfo.parameters,
request: methodInfo?.requestBody
};

View File

@@ -1,5 +1,5 @@
import { TeamMemberRoleEnum } from './constant';
import { TeamMemberSchema } from './type';
import { LafAccountType, TeamMemberSchema } from './type';
export type AuthTeamRoleProps = {
teamId: string;
@@ -10,12 +10,14 @@ export type CreateTeamProps = {
name: string;
avatar?: string;
defaultTeam?: boolean;
lafAccount?: LafAccountType;
};
export type UpdateTeamProps = {
teamId: string;
name?: string;
avatar?: string;
teamDomain?: string;
lafAccount?: null | LafAccountType;
};
/* ------------- member ----------- */

View File

@@ -1,5 +1,6 @@
import type { UserModelSchema } from '../type';
import type { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant';
import { LafAccountType } from './type';
export type TeamSchema = {
_id: string;
@@ -13,6 +14,7 @@ export type TeamSchema = {
lastExportDatasetTime: Date;
lastWebsiteSyncTime: Date;
};
lafAccount: LafAccountType;
};
export type tagsType = {
label: string;
@@ -58,6 +60,7 @@ export type TeamItemType = {
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
canWrite: boolean;
lafAccount?: LafAccountType;
};
export type TeamMemberItemType = {
@@ -74,3 +77,8 @@ export type TeamTagItemType = {
label: string;
key: string;
};
export type LafAccountType = {
token: string;
appid: string;
};

View File

@@ -6,16 +6,9 @@ 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 { readFileRawText } from '../read/rawText';
import { ReadFileByBufferParams } from '../read/type';
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 { MongoRwaTextBuffer } from '../../buffer/rawText/schema';
import { readPptxRawText } from '../read/pptx';
import { readXlsxRawText } from '../read/xlsx';
import { readFileRawContent } from '../read/utils';
export function getGFSCollection(bucket: `${BucketNameEnum}`) {
MongoFileSchema;
@@ -146,7 +139,7 @@ export const readFileEncode = async ({
return encoding as BufferEncoding;
};
export const readFileContent = async ({
export const readFileContentFromMongo = async ({
teamId,
bucketName,
fileId,
@@ -205,47 +198,14 @@ export const readFileContent = async ({
}
};
const { rawText } = await (async () => {
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 { rawText } = await readFileRawContent({
extension,
csvFormat,
params
});
if (rawText.trim()) {
await MongoRwaTextBuffer.create({
MongoRwaTextBuffer.create({
sourceId: fileId,
rawText,
metadata: {

View File

@@ -2,6 +2,15 @@ import { markdownProcess } 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';
export const initMarkdownText = ({
teamId,
@@ -23,3 +32,50 @@ export const initMarkdownText = ({
expiredTime: addHours(new Date(), 2)
})
});
export const readFileRawContent = async ({
extension,
csvFormat,
params
}: {
csvFormat?: boolean;
extension: string;
params: ReadFileByBufferParams;
}) => {
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');
}
};

View File

@@ -55,8 +55,8 @@ export const clearTmpUploadFiles = () => {
fs.stat(filePath, (err, stats) => {
if (err) return;
// 如果文件是在1小时前上传的,则认为是临时文件并删除它
if (Date.now() - stats.mtime.getTime() > 1 * 60 * 60 * 1000) {
// 如果文件是在2小时前上传的,则认为是临时文件并删除它
if (Date.now() - stats.mtime.getTime() > 2 * 60 * 60 * 1000) {
fs.unlink(filePath, (err) => {
if (err) return;
console.log(`Deleted temp file: ${filePath}`);

View File

@@ -0,0 +1,15 @@
export enum TimerIdEnum {
checkInValidDatasetFiles = 'checkInValidDatasetFiles',
checkInvalidDatasetData = 'checkInvalidDatasetData',
checkInvalidVector = 'checkInvalidVector',
clearExpiredSubPlan = 'clearExpiredSubPlan',
updateStandardPlan = 'updateStandardPlan'
}
export const timerIdMap = {
[TimerIdEnum.checkInValidDatasetFiles]: 'checkInValidDatasetFiles',
[TimerIdEnum.checkInvalidDatasetData]: 'checkInvalidDatasetData',
[TimerIdEnum.checkInvalidVector]: 'checkInvalidVector',
[TimerIdEnum.clearExpiredSubPlan]: 'clearExpiredSubPlan',
[TimerIdEnum.updateStandardPlan]: 'updateStandardPlan'
};

View File

@@ -0,0 +1,29 @@
import { connectionMongo, type Model } from '../../mongo';
import { timerIdMap } from './constants';
const { Schema, model, models } = connectionMongo;
import { TimerLockSchemaType } from './type.d';
export const collectionName = 'systemtimerlocks';
const TimerLockSchema = new Schema({
timerId: {
type: String,
required: true,
unique: true,
enum: Object.keys(timerIdMap)
},
expiredTime: {
type: Date,
required: true
}
});
try {
TimerLockSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 5 });
} catch (error) {
console.log(error);
}
export const MongoTimerLock: Model<TimerLockSchemaType> =
models[collectionName] || model(collectionName, TimerLockSchema);
MongoTimerLock.syncIndexes();

View File

@@ -0,0 +1,5 @@
export type TimerLockSchemaType = {
_id: string;
timerId: string;
expiredTime: Date;
};

View File

@@ -0,0 +1,25 @@
import { TimerIdEnum } from './constants';
import { MongoTimerLock } from './schema';
import { addMinutes } from 'date-fns';
/*
利用唯一健,使得同一时间只有一个任务在执行,后创建的锁,会因唯一健创建失败,从而无法继续执行任务
*/
export const checkTimerLock = async ({
timerId,
lockMinuted
}: {
timerId: `${TimerIdEnum}`;
lockMinuted: number;
}) => {
try {
await MongoTimerLock.create({
timerId,
expiredTime: addMinutes(new Date(), lockMinuted)
});
return true;
} catch (error) {
return false;
}
};

View File

@@ -210,7 +210,6 @@ export const runToolWithToolChoice = async (
).filter(Boolean) as ToolRunResponseType;
const flatToolsResponseData = toolsRunResponse.map((item) => item.moduleRunResponse).flat();
if (toolCalls.length > 0 && !res.closed) {
// Run the tool, combine its results, and perform another round of AI calls
const assistantToolMsgParams: ChatCompletionAssistantToolParam = {

View File

@@ -37,6 +37,7 @@ import { dispatchRunTools } from './agent/runTool/index';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import { DispatchFlowResponse } from './type';
import { dispatchStopToolCall } from './agent/runTool/stopTool';
import { dispatchLafRequest } from './tools/runLaf';
const callbackMap: Record<`${FlowNodeTypeEnum}`, Function> = {
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
@@ -56,6 +57,7 @@ const callbackMap: Record<`${FlowNodeTypeEnum}`, Function> = {
[FlowNodeTypeEnum.queryExtension]: dispatchQueryExtension,
[FlowNodeTypeEnum.tools]: dispatchRunTools,
[FlowNodeTypeEnum.stopTool]: dispatchStopToolCall,
[FlowNodeTypeEnum.lafModule]: dispatchLafRequest,
// none
[FlowNodeTypeEnum.userGuide]: () => Promise.resolve()

View File

@@ -61,7 +61,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
chatId,
responseChatItemId,
...variables,
histories: histories.slice(0, 10),
histories: histories.slice(-10),
...body
};
@@ -165,6 +165,7 @@ async function fetchData({
'Content-Type': 'application/json',
...headers
},
timeout: 120000,
params: params,
data: ['POST', 'PUT', 'PATCH'].includes(method) ? body : undefined
});

View File

@@ -0,0 +1,212 @@
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import {
DYNAMIC_INPUT_KEY,
ModuleInputKeyEnum,
ModuleOutputKeyEnum
} from '@fastgpt/global/core/module/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
import axios from 'axios';
import { valueTypeFormat } from '../utils';
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
import { addLog } from '../../../../common/system/log';
import { DispatchNodeResultType } from '@fastgpt/global/core/module/runtime/type';
type LafRequestProps = ModuleDispatchProps<{
[ModuleInputKeyEnum.httpReqUrl]: string;
[DYNAMIC_INPUT_KEY]: Record<string, any>;
[key: string]: any;
}>;
type LafResponse = DispatchNodeResultType<{
[ModuleOutputKeyEnum.failed]?: boolean;
[key: string]: any;
}>;
const UNDEFINED_SIGN = 'UNDEFINED_SIGN';
export const dispatchLafRequest = async (props: LafRequestProps): Promise<LafResponse> => {
let {
appId,
chatId,
responseChatItemId,
variables,
module: { outputs },
histories,
params: { system_httpReqUrl: httpReqUrl, [DYNAMIC_INPUT_KEY]: dynamicInput, ...body }
} = props;
if (!httpReqUrl) {
return Promise.reject('Http url is empty');
}
const concatVariables = {
appId,
chatId,
responseChatItemId,
...variables,
...body
};
httpReqUrl = replaceVariable(httpReqUrl, concatVariables);
const requestBody = {
systemParams: {
appId,
chatId,
responseChatItemId,
histories: histories.slice(0, 10)
},
variables,
...dynamicInput,
...body
};
try {
const { formatResponse, rawResponse } = await fetchData({
method: 'POST',
url: httpReqUrl,
body: requestBody
});
// format output value type
const results: Record<string, any> = {};
for (const key in formatResponse) {
const output = outputs.find((item) => item.key === key);
if (!output) continue;
results[key] = valueTypeFormat(formatResponse[key], output.valueType);
}
return {
assistantResponses: [],
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
httpResult: rawResponse
},
[DispatchNodeResponseKeyEnum.toolResponses]: rawResponse,
[ModuleOutputKeyEnum.httpRawResponse]: rawResponse,
...results
};
} catch (error) {
addLog.error('Http request error', error);
return {
[ModuleOutputKeyEnum.failed]: true,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
httpResult: { error: formatHttpError(error) }
}
};
}
};
async function fetchData({
method,
url,
body
}: {
method: string;
url: string;
body: Record<string, any>;
}): Promise<Record<string, any>> {
const { data: response } = await axios({
method,
baseURL: `http://${SERVICE_LOCAL_HOST}`,
url,
headers: {
'Content-Type': 'application/json'
},
data: body
});
const parseJson = (obj: Record<string, any>, prefix = '') => {
let result: Record<string, any> = {};
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
result[`${prefix}[${i}]`] = obj[i];
if (Array.isArray(obj[i])) {
result = {
...result,
...parseJson(obj[i], `${prefix}[${i}]`)
};
} else if (typeof obj[i] === 'object') {
result = {
...result,
...parseJson(obj[i], `${prefix}[${i}].`)
};
}
}
} else if (typeof obj == 'object') {
for (const key in obj) {
result[`${prefix}${key}`] = obj[key];
if (Array.isArray(obj[key])) {
result = {
...result,
...parseJson(obj[key], `${prefix}${key}`)
};
} else if (typeof obj[key] === 'object') {
result = {
...result,
...parseJson(obj[key], `${prefix}${key}.`)
};
}
}
}
return result;
};
return {
formatResponse:
typeof response === 'object' && !Array.isArray(response) ? parseJson(response) : {},
rawResponse: response
};
}
function replaceVariable(text: string, obj: Record<string, any>) {
for (const [key, value] of Object.entries(obj)) {
if (value === undefined) {
text = text.replace(new RegExp(`{{${key}}}`, 'g'), UNDEFINED_SIGN);
} else {
const replacement = JSON.stringify(value);
const unquotedReplacement =
replacement.startsWith('"') && replacement.endsWith('"')
? replacement.slice(1, -1)
: replacement;
text = text.replace(new RegExp(`{{${key}}}`, 'g'), unquotedReplacement);
}
}
return text || '';
}
function removeUndefinedSign(obj: Record<string, any>) {
for (const key in obj) {
if (obj[key] === UNDEFINED_SIGN) {
obj[key] = undefined;
} else if (Array.isArray(obj[key])) {
obj[key] = obj[key].map((item: any) => {
if (item === UNDEFINED_SIGN) {
return undefined;
} else if (typeof item === 'object') {
removeUndefinedSign(item);
}
return item;
});
} else if (typeof obj[key] === 'object') {
removeUndefinedSign(obj[key]);
}
}
return obj;
}
function formatHttpError(error: any) {
return {
message: error?.message,
name: error?.name,
method: error?.config?.method,
baseURL: error?.config?.baseURL,
url: error?.config?.url,
code: error?.code,
status: error?.status
};
}

View File

@@ -10,7 +10,6 @@ import { MongoTeam } from './teamSchema';
async function getTeamMember(match: Record<string, any>): Promise<TeamItemType> {
const tmb = (await MongoTeamMember.findOne(match).populate('teamId')) as TeamMemberWithTeamSchema;
if (!tmb) {
return Promise.reject('member not exist');
}
@@ -27,7 +26,8 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamItemType>
role: tmb.role,
status: tmb.status,
defaultTeam: tmb.defaultTeam,
canWrite: tmb.role !== TeamMemberRoleEnum.visitor
canWrite: tmb.role !== TeamMemberRoleEnum.visitor,
lafAccount: tmb.teamId.lafAccount
};
}

View File

@@ -35,6 +35,14 @@ const TeamSchema = new Schema({
lastWebsiteSyncTime: {
type: Date
}
},
lafAccount: {
token: {
type: String
},
appid: {
type: String
}
}
});

View File

@@ -125,7 +125,9 @@ export const iconPaths = {
'file/fill/manual': () => import('./icons/file/fill/manual.svg'),
'file/fill/markdown': () => import('./icons/file/fill/markdown.svg'),
'file/fill/pdf': () => import('./icons/file/fill/pdf.svg'),
'file/fill/ppt': () => import('./icons/file/fill/ppt.svg'),
'file/fill/txt': () => import('./icons/file/fill/txt.svg'),
'file/fill/xlsx': () => import('./icons/file/fill/xlsx.svg'),
'file/html': () => import('./icons/file/html.svg'),
'file/indexImport': () => import('./icons/file/indexImport.svg'),
'file/manualImport': () => import('./icons/file/manualImport.svg'),

View File

@@ -0,0 +1,11 @@
<svg t="1712108366314" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1576"
width="128" height="128">
<path
d="M145.6 0C100.8 0 64 36.8 64 81.6v860.8C64 987.2 100.8 1024 145.6 1024h732.8c44.8 0 81.6-36.8 81.6-81.6V324.8L657.6 0h-512z"
fill="#E34221" p-id="1577"></path>
<path d="M960 326.4v16H755.2s-100.8-20.8-99.2-108.8c0 0 4.8 92.8 97.6 92.8H960z" fill="#DC3119" p-id="1578"></path>
<path d="M657.6 0v233.6c0 25.6 17.6 92.8 97.6 92.8H960L657.6 0z" fill="#FFFFFF" p-id="1579"></path>
<path
d="M304 784h-54.4v67.2c0 6.4-4.8 11.2-11.2 11.2-6.4 0-12.8-4.8-12.8-11.2V686.4c0-9.6 8-17.6 17.6-17.6H304c38.4 0 59.2 25.6 59.2 57.6S340.8 784 304 784z m-3.2-94.4h-51.2v73.6h51.2c22.4 0 38.4-16 38.4-36.8 0-22.4-16-36.8-38.4-36.8zM480 784h-54.4v67.2c0 6.4-4.8 11.2-11.2 11.2-6.4 0-11.2-4.8-11.2-11.2V686.4c0-9.6 6.4-17.6 16-17.6H480c38.4 0 59.2 25.6 59.2 57.6S518.4 784 480 784z m-3.2-94.4h-49.6v73.6h49.6c22.4 0 38.4-16 38.4-36.8 0-22.4-16-36.8-38.4-36.8z m225.6 0h-52.8v161.6c0 6.4-4.8 11.2-11.2 11.2-6.4 0-12.8-4.8-12.8-11.2V689.6h-51.2c-6.4 0-11.2-4.8-11.2-11.2 0-4.8 4.8-9.6 11.2-9.6h128c6.4 0 11.2 4.8 11.2 11.2 0 4.8-4.8 9.6-11.2 9.6z"
fill="#FFFFFF" p-id="1580"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,11 @@
<svg t="1712108380525" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2654"
width="128" height="128">
<path
d="M145.6 0C100.8 0 64 36.8 64 81.6v860.8C64 987.2 100.8 1024 145.6 1024h732.8c44.8 0 81.6-36.8 81.6-81.6V324.8L657.6 0h-512z"
fill="#45B058" p-id="2655"></path>
<path
d="M374.4 862.4c-3.2 0-6.4-1.6-8-3.2l-59.2-80-60.8 80c-1.6 1.6-4.8 3.2-8 3.2-6.4 0-11.2-4.8-11.2-11.2 0-1.6 0-4.8 1.6-6.4l62.4-81.6-57.6-78.4c-1.6-1.6-3.2-3.2-3.2-6.4 0-4.8 4.8-11.2 11.2-11.2 4.8 0 8 1.6 9.6 4.8l56 73.6 54.4-73.6c1.6-3.2 4.8-4.8 8-4.8 6.4 0 12.8 4.8 12.8 11.2 0 3.2-1.6 4.8-1.6 6.4l-59.2 76.8 62.4 83.2c1.6 1.6 3.2 4.8 3.2 6.4 0 6.4-6.4 11.2-12.8 11.2z m160-1.6H448c-9.6 0-17.6-8-17.6-17.6V678.4c0-6.4 4.8-11.2 12.8-11.2 6.4 0 11.2 4.8 11.2 11.2v161.6h80c6.4 0 11.2 4.8 11.2 9.6 0 6.4-4.8 11.2-11.2 11.2z m112 3.2c-28.8 0-51.2-9.6-67.2-24-3.2-1.6-3.2-4.8-3.2-8 0-6.4 3.2-12.8 11.2-12.8 1.6 0 4.8 1.6 6.4 3.2 12.8 11.2 32 20.8 54.4 20.8 33.6 0 44.8-19.2 44.8-33.6 0-49.6-113.6-22.4-113.6-89.6 0-32 27.2-54.4 65.6-54.4 24 0 46.4 8 60.8 20.8 3.2 1.6 4.8 4.8 4.8 8 0 6.4-4.8 12.8-11.2 12.8-1.6 0-4.8-1.6-6.4-3.2-14.4-11.2-32-16-49.6-16-24 0-40 11.2-40 30.4 0 43.2 113.6 17.6 113.6 89.6 0 27.2-19.2 56-70.4 56z"
fill="#FFFFFF" p-id="2656"></path>
<path d="M960 326.4v16H755.2s-102.4-20.8-99.2-108.8c0 0 3.2 92.8 96 92.8h208z" fill="#349C42" p-id="2657"></path>
<path d="M656 0v233.6c0 25.6 19.2 92.8 99.2 92.8H960L656 0z" fill="#FFFFFF" p-id="2658"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -36,7 +36,7 @@ export default function Editor({
hasVariablePlugin?: boolean;
hasDropDownPlugin?: boolean;
variables: EditorVariablePickerType[];
onChange?: (editorState: EditorState) => void;
onChange?: (editorState: EditorState, editor: LexicalEditor) => void;
onBlur?: (editor: LexicalEditor) => void;
value?: string;
currentValue?: string;
@@ -119,9 +119,9 @@ export default function Editor({
<HistoryPlugin />
<FocusPlugin focus={focus} setFocus={setFocus} />
<OnChangePlugin
onChange={(e) => {
onChange={(editorState: EditorState, editor: LexicalEditor) => {
startSts(() => {
onChange?.(e);
onChange?.(editorState, editor);
});
}}
/>

View File

@@ -32,15 +32,13 @@ const HttpInput = ({
const [, startSts] = useTransition();
const onChangeInput = useCallback((editorState: EditorState) => {
const text = editorState.read(() => $getRoot().getTextContent());
const formatValue = text.replaceAll('\n\n', '\n').replaceAll('}}{{', '}} {{');
setCurrentValue(formatValue);
onChange?.(formatValue);
const onChangeInput = useCallback((editorState: EditorState, editor: LexicalEditor) => {
const text = editorStateToText(editor).replaceAll('}}{{', '}} {{');
onChange?.(text);
}, []);
const onBlurInput = useCallback((editor: LexicalEditor) => {
startSts(() => {
const text = editorStateToText(editor).replaceAll('\n\n', '\n').replaceAll('}}{{', '}} {{');
const text = editorStateToText(editor).replaceAll('}}{{', '}} {{');
onBlur?.(text);
});
}, []);

View File

@@ -7,10 +7,13 @@ import {
useDisclosure,
MenuButton,
Box,
css
css,
Flex
} from '@chakra-ui/react';
import type { ButtonProps, MenuItemProps } from '@chakra-ui/react';
import { ChevronDownIcon } from '@chakra-ui/icons';
import { useLoading } from '../../../hooks/useLoading';
import MyIcon from '../Icon';
export type SelectProps = ButtonProps & {
value?: string;
@@ -20,14 +23,16 @@ export type SelectProps = ButtonProps & {
label: string | React.ReactNode;
value: string;
}[];
isLoading?: boolean;
onchange?: (val: any) => void;
};
const MySelect = (
{ placeholder, value, width = '100%', list, onchange, ...props }: SelectProps,
{ placeholder, value, width = '100%', list, onchange, isLoading = false, ...props }: SelectProps,
selectRef: any
) => {
const ref = useRef<HTMLButtonElement>(null);
const { Loading } = useLoading();
const menuItemStyles: MenuItemProps = {
borderRadius: 'sm',
py: 2,
@@ -78,7 +83,10 @@ const MySelect = (
: {})}
{...props}
>
{selectItem?.alias || selectItem?.label || placeholder}
<Flex alignItems={'center'}>
{isLoading && <MyIcon mr={2} name={'common/loading'} w={'16px'} />}
{selectItem?.alias || selectItem?.label || placeholder}
</Flex>
</MenuButton>
<MenuList

View File

@@ -1,10 +1,10 @@
import { Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import React from 'react';
import { editorStateToText } from './utils';
import Editor from './Editor';
import MyModal from '../../MyModal';
import { useTranslation } from 'next-i18next';
import { $getRoot, EditorState, type LexicalEditor } from 'lexical';
import { EditorState, type LexicalEditor } from 'lexical';
import { EditorVariablePickerType } from './type.d';
import { useCallback, useTransition } from 'react';
@@ -34,16 +34,12 @@ const PromptEditor = ({
const { t } = useTranslation();
const onChangeInput = useCallback((editorState: EditorState, editor: LexicalEditor) => {
const stringifiedEditorState = JSON.stringify(editorState.toJSON());
const parsedEditorState = editor.parseEditorState(stringifiedEditorState);
const editorStateTextString = parsedEditorState.read(() => $getRoot().getTextContent());
const formatValue = editorStateTextString.replaceAll('\n\n', '\n').replaceAll('}}{{', '}} {{');
onChange?.(formatValue);
const text = editorStateToText(editor).replaceAll('}}{{', '}} {{');
onChange?.(text);
}, []);
const onBlurInput = useCallback((editor: LexicalEditor) => {
startSts(() => {
const text = editorStateToText(editor).replaceAll('\n\n', '\n').replaceAll('}}{{', '}} {{');
const text = editorStateToText(editor).replaceAll('}}{{', '}} {{');
onBlur?.(text);
});
}, []);

View File

@@ -1,6 +1,5 @@
import { useEffect } from 'react';
import { BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, LexicalEditor } from 'lexical';
import { mergeRegister } from '@lexical/utils';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
export default function OnBlurPlugin({ onBlur }: { onBlur?: (editor: LexicalEditor) => void }) {

View File

@@ -209,9 +209,8 @@ export function editorStateToText(editor: LexicalEditor) {
const stringifiedEditorState = JSON.stringify(editor.getEditorState().toJSON());
const parsedEditorState = editor.parseEditorState(stringifiedEditorState);
const editorStateTextString = parsedEditorState.read(() => $getRoot().getTextContent());
const compressedText = editorStateTextString.replace(/\n+/g, '\n\n');
return compressedText;
return editorStateTextString;
}
const varRegex = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;