Commit 3f9b0fa1 by Archer Committed by GitHub

V4.12.3 features (#5595)

* refactor: remove ModelProviderIdType and update related types (#5549)

* perf: model provider

* fix eval create split (#5570)

* git rebase --continuedoc

* add more variable types (#5540)

* variable types

* password

* time picker

* internal var

* file

* fix-test

* time select default value & range

* password & type render

* fix

* fix build

* fix

* move method

* split date select

* icon

* perf: variable code

* prompt editor add markdown plugin (#5556)

* editor markdown

* fix build

* pnpm lock

* add props

* update code

* fix list

* editor ui

* fix variable reset (#5586)

* perf: variables type code

* customize lexical indent (#5588)

* perf: multiple selector

* perf: tab plugin

* doc

* refactor: update workflow constants to use ToolTypeEnum (#5491)

* refactor: replace FlowNodeTemplateTypeEnum with string literals in workflow templates

* perf: tool type

---------

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

* update doc

* fix: make table's row more natural while dragging it (#5596)

* feat: add APIGetTemplate function and refactor template fetching logic (#5498)

* feat: add APIGetTemplate function and refactor template fetching logic

* chore: adjust the code

* chore: update sdk

---------

Co-authored-by: FinleyGe <m13203533462@163.com>

* perf init system

* doc

* remove log

* remove i18n

* perf: variables render

---------

Co-authored-by: Ctrlz <143257420+ctrlz526@users.noreply.github.com>
Co-authored-by: heheer <heheer@sealos.io>
Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com>
Co-authored-by: FinleyGe <m13203533462@163.com>
parent c747fc03
--- ---
title: 'V4.12.0(包含升级脚本)' title: 'V4.12.0(包含升级脚本)'
description: 'FastGPT V4.12.0 更新说明, 发布于 2025-8-11' description: 'FastGPT V4.12.0 更新说明, 发布于 2025-8-11'
--- ---
## 更新指南 ## 更新指南
......
--- ---
title: 'V4.12.1(包含升级脚本)' title: 'V4.12.1(包含升级脚本)'
description: 'FastGPT V4.12.1 更新说明, 发布于 2025-8-18' description: 'FastGPT V4.12.1 更新说明, 发布于 2025-8-18'
--- ---
## 更新指南 ## 更新指南
......
--- ---
title: 'V4.12.2' title: 'V4.12.2'
description: 'FastGPT V4.12.2 更新说明, 发布于 2025-8-26' description: 'FastGPT V4.12.2 更新说明, 发布于 2025-8-26'
--- ---
## 更新指南 ## 更新指南
......
...@@ -3,13 +3,16 @@ title: 'V4.12.3(进行中)' ...@@ -3,13 +3,16 @@ title: 'V4.12.3(进行中)'
description: 'FastGPT V4.12.3 更新说明' description: 'FastGPT V4.12.3 更新说明'
--- ---
## 🚀 新增内容 ## 🚀 新增内容
1. 提示词编辑器支持列表、tab 渲染等部分富文本交互。
2. 应用增加更多全局变量:密码、多选、内部变量(在站内对话不会显示)。
## ⚙️ 优化 ## ⚙️ 优化
1. 纠正 RRF 权重合并算法,使用标准 RRF 权重公式。 1. 纠正 RRF 权重合并算法,使用标准 RRF 权重公式。
2. 多选组件支持动态宽度计算,适配可见 tag。
3. 变量更新组件渲染优化,与全局变量渲染保持一致性。
## 🐛 修复 ## 🐛 修复
...@@ -17,5 +20,10 @@ description: 'FastGPT V4.12.3 更新说明' ...@@ -17,5 +20,10 @@ description: 'FastGPT V4.12.3 更新说明'
2. 工作流文件上传默认打开,但输入侧未添加文件输出。 2. 工作流文件上传默认打开,但输入侧未添加文件输出。
3. 连续用户选择,分支无法正常运行。 3. 连续用户选择,分支无法正常运行。
4. 工作流,变量更新,数组选择器异常。 4. 工作流,变量更新,数组选择器异常。
5. 应用评测,评测内容仅获取了首个输出文本,未获取所有输出文本。
## 🔨 工具更新 ## 🔨 插件更新
1. 系统工具类型迁移至 plugin。
2. 将模型提供商配置移动到 plugin,实现热更新。
3. 将应用模板移动至 plugin。
\ No newline at end of file
...@@ -102,10 +102,10 @@ ...@@ -102,10 +102,10 @@
"document/content/docs/upgrading/4-10/4101.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-10/4101.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-11/4110.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/upgrading/4-11/4110.mdx": "2025-08-05T23:20:39+08:00",
"document/content/docs/upgrading/4-11/4111.mdx": "2025-08-07T22:49:09+08:00", "document/content/docs/upgrading/4-11/4111.mdx": "2025-08-07T22:49:09+08:00",
"document/content/docs/upgrading/4-12/4120.mdx": "2025-08-12T22:45:19+08:00", "document/content/docs/upgrading/4-12/4120.mdx": "2025-09-04T22:59:41+08:00",
"document/content/docs/upgrading/4-12/4121.mdx": "2025-08-15T22:53:06+08:00", "document/content/docs/upgrading/4-12/4121.mdx": "2025-09-04T22:59:41+08:00",
"document/content/docs/upgrading/4-12/4122.mdx": "2025-09-04T21:37:57+08:00", "document/content/docs/upgrading/4-12/4122.mdx": "2025-09-04T22:59:41+08:00",
"document/content/docs/upgrading/4-12/4123.mdx": "2025-09-04T21:37:57+08:00", "document/content/docs/upgrading/4-12/4123.mdx": "2025-09-05T17:31:22+08:00",
"document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00",
......
...@@ -19,4 +19,4 @@ export default defineConfig({ ...@@ -19,4 +19,4 @@ export default defineConfig({
mdxOptions: { mdxOptions: {
// MDX options // MDX options
} }
}); });
\ No newline at end of file
import type { I18nStringType, localeType } from './type'; import type { I18nStringType, localeType } from './type';
export const parseI18nString = (str: I18nStringType | string = '', lang: localeType = 'en') => { export const parseI18nString = (str: I18nStringType | string = '', lang = 'en') => {
if (!str || typeof str === 'string') return str; if (!str || typeof str === 'string') return str;
return str[lang] ?? str['en']; return str[lang as localeType] ?? str['en'];
}; };
export const isSecretValue = (val: any) => {
return typeof val === 'object' && val !== null && !!val.secret;
};
import type { ModelTypeEnum } from './model'; import type { ModelTypeEnum } from './model';
import type { ModelProviderIdType } from './provider';
type PriceType = { type PriceType = {
charsPointsPrice?: number; // 1k chars=n points; 60s=n points; charsPointsPrice?: number; // 1k chars=n points; 60s=n points;
...@@ -9,7 +8,7 @@ type PriceType = { ...@@ -9,7 +8,7 @@ type PriceType = {
outputPrice?: number; // 1k tokens=n points outputPrice?: number; // 1k tokens=n points
}; };
type BaseModelItemType = { type BaseModelItemType = {
provider: ModelProviderIdType; provider: string;
model: string; model: string;
name: string; name: string;
avatar?: string; // model icon, from provider avatar?: string; // model icon, from provider
......
import { i18nT } from '../../../web/i18n/utils'; import { i18nT } from '../../../web/i18n/utils';
import type { LLMModelItemType, STTModelType, EmbeddingModelItemType } from './model.d'; import type { LLMModelItemType, STTModelType, EmbeddingModelItemType } from './model.d';
import { getModelProvider, type ModelProviderIdType } from './provider';
export enum ModelTypeEnum { export enum ModelTypeEnum {
llm = 'llm', llm = 'llm',
...@@ -54,29 +53,6 @@ export const defaultSTTModels: STTModelType[] = [ ...@@ -54,29 +53,6 @@ export const defaultSTTModels: STTModelType[] = [
} }
]; ];
export const getModelFromList = (
modelList: { provider: ModelProviderIdType; name: string; model: string }[],
model: string,
language: string
):
| {
avatar: string;
provider: ModelProviderIdType;
name: string;
model: string;
}
| undefined => {
const modelData = modelList.find((item) => item.model === model) ?? modelList[0];
if (!modelData) {
return;
}
const provider = getModelProvider(modelData.provider, language);
return {
...modelData,
avatar: provider.avatar
};
};
export const modelTypeList = [ export const modelTypeList = [
{ label: i18nT('common:model.type.chat'), value: ModelTypeEnum.llm }, { label: i18nT('common:model.type.chat'), value: ModelTypeEnum.llm },
{ label: i18nT('common:model.type.embedding'), value: ModelTypeEnum.embedding }, { label: i18nT('common:model.type.embedding'), value: ModelTypeEnum.embedding },
......
import { ModelProviders } from '../../sdk/fastgpt-plugin'; import type { I18nStringStrictType } from '@fastgpt-sdk/plugin';
export type ModelProviderIdType = keyof typeof ModelProviders; export type ModelProviderItemType = {
type ProviderValueTypes = (typeof ModelProviders)[ModelProviderIdType]; id: string;
type langType = 'en' | 'zh-CN' | 'zh-Hant'; name: string;
export type ModelProviderType = {
id: ModelProviderIdType;
name: any;
avatar: string; avatar: string;
order: number; order: number;
}; };
const getLocalizedName = (translations: ProviderValueTypes, language = 'en'): string => { export type ModelProviderListType = {
return translations[language as langType]; id: string;
name: I18nStringStrictType | string;
avatar: string;
provider: string;
}; };
export const formatModelProviderList = (language?: string) => { export type langType = keyof I18nStringStrictType;
return Object.entries(ModelProviders).map(([id, translations], index) => ({
id: id as ModelProviderIdType, export const defaultProvider: ModelProviderItemType = {
name: getLocalizedName(translations, language), id: 'Other',
avatar: `/api/system/plugin/models/${id}.svg`, name: 'Other',
order: index avatar: 'model/huggingface',
})); order: 999
}; };
export const formatModelProviderMap = (language?: string) => {
const provider = {} as Record<
ModelProviderIdType,
{
id: string;
name: string;
avatar: string;
order: number;
}
>;
Object.entries(ModelProviders).forEach(([id, translations], index) => { export const formatModelProviders = (data: { provider: string; value: I18nStringStrictType }[]) => {
provider[id as ModelProviderIdType] = { const getLocalizedName = (translations: I18nStringStrictType, language = 'en'): string => {
id: id as ModelProviderIdType, return translations[language as langType] || translations.en;
name: getLocalizedName(translations, language), };
avatar: `/api/system/plugin/models/${id}.svg`,
order: index
};
});
return provider; const formatModelProviderList = (language?: string): ModelProviderItemType[] => {
}; return data.map(({ provider, value }, index) => ({
id: provider,
name: getLocalizedName(value, language),
avatar: `/api/system/plugin/models/${provider}.svg`,
order: index
}));
};
const ModelProviderListCache = { const formatModelProviderMap = (language?: string) => {
en: formatModelProviderList('en'), const provider = {} as Record<string, ModelProviderItemType>;
'zh-CN': formatModelProviderList('zh-CN'),
'zh-Hant': formatModelProviderList('zh-Hant')
};
const ModelProviderMapCache = {
en: formatModelProviderMap('en'),
'zh-CN': formatModelProviderMap('zh-CN'),
'zh-Hant': formatModelProviderMap('zh-Hant')
};
const defaultProvider = { data.forEach(({ provider: id, value }, index) => {
id: 'Other' as ModelProviderIdType, provider[id] = {
name: 'Other', id,
avatar: 'model/other', name: getLocalizedName(value, language),
order: 0 avatar: `/api/system/plugin/models/${id}.svg`,
}; order: index
};
});
export const getModelProviders = (language = 'en') => { return provider;
return ModelProviderListCache[language as langType]; };
};
export const getModelProvider = (provider?: ModelProviderIdType, language = 'en') => { const ModelProviderListCache = {
if (!provider) { en: formatModelProviderList('en'),
return defaultProvider; 'zh-CN': formatModelProviderList('zh-CN'),
} 'zh-Hant': formatModelProviderList('zh-Hant')
};
const ModelProviderMapCache = {
en: formatModelProviderMap('en'),
'zh-CN': formatModelProviderMap('zh-CN'),
'zh-Hant': formatModelProviderMap('zh-Hant')
};
return ModelProviderMapCache[language as langType][provider] ?? defaultProvider; return {
ModelProviderListCache,
ModelProviderMapCache
};
}; };
...@@ -162,11 +162,23 @@ export type VariableItemType = { ...@@ -162,11 +162,23 @@ export type VariableItemType = {
// input // input
maxLength?: number; maxLength?: number;
// password
minLength?: number;
// numberInput // numberInput
max?: number; max?: number;
min?: number; min?: number;
// select // select
list?: { label: string; value: string }[]; list?: { label: string; value: string }[];
// file
canSelectFile?: boolean;
canSelectImg?: boolean;
maxFiles?: number;
// timeSelect
timeGranularity?: 'second' | 'minute' | 'hour' | 'day';
timeType?: 'point' | 'range';
timeRangeStart?: string;
timeRangeEnd?: string;
// @deprecated // @deprecated
enums?: { value: string; label: string }[]; enums?: { value: string; label: string }[];
}; };
......
import type { AppChatConfigType, AppSimpleEditFormType } from '../app/type'; import type { AppChatConfigType, AppSimpleEditFormType } from '../app/type';
import { FlowNodeTypeEnum } from '../workflow/node/constant'; import { FlowNodeTypeEnum } from '../workflow/node/constant';
import { NodeInputKeyEnum, FlowNodeTemplateTypeEnum } from '../workflow/constants'; import { FlowNodeTemplateTypeEnum, NodeInputKeyEnum } from '../workflow/constants';
import type { FlowNodeInputItemType } from '../workflow/type/io.d'; import type { FlowNodeInputItemType } from '../workflow/type/io.d';
import { getAppChatConfig } from '../workflow/utils'; import { getAppChatConfig } from '../workflow/utils';
import { type StoreNodeItemType } from '../workflow/type/node'; import { type StoreNodeItemType } from '../workflow/type/node';
......
...@@ -4,21 +4,8 @@ import type { JsonSchemaPropertiesItemType } from '../app/jsonschema'; ...@@ -4,21 +4,8 @@ import type { JsonSchemaPropertiesItemType } from '../app/jsonschema';
export enum FlowNodeTemplateTypeEnum { export enum FlowNodeTemplateTypeEnum {
systemInput = 'systemInput', systemInput = 'systemInput',
ai = 'ai', ai = 'ai',
function = 'function',
interactive = 'interactive', interactive = 'interactive',
// System tool type
tools = 'tools', tools = 'tools',
search = 'search',
multimodal = 'multimodal',
communication = 'communication',
finance = 'finance',
design = 'design',
productivity = 'productivity',
news = 'news',
entertainment = 'entertainment',
social = 'social',
scientific = 'scientific',
other = 'other', other = 'other',
// Team app type // Team app type
...@@ -332,53 +319,144 @@ export enum VariableInputEnum { ...@@ -332,53 +319,144 @@ export enum VariableInputEnum {
input = 'input', input = 'input',
textarea = 'textarea', textarea = 'textarea',
numberInput = 'numberInput', numberInput = 'numberInput',
JSONEditor = 'JSONEditor',
select = 'select', select = 'select',
custom = 'custom' multipleSelect = 'multipleSelect',
timePointSelect = 'timePointSelect',
timeRangeSelect = 'timeRangeSelect',
switch = 'switch',
password = 'password',
file = 'file',
modelSelect = 'modelSelect',
datasetSelect = 'datasetSelect',
custom = 'custom',
internal = 'internal'
} }
export const variableMap: Record<
VariableInputEnum, type VariableConfigType = {
{ icon: string;
icon: string; label: string;
label: string; value: VariableInputEnum;
value: VariableInputEnum; defaultValueType: WorkflowIOValueTypeEnum;
defaultValueType: WorkflowIOValueTypeEnum; description?: string;
description?: string; };
}
> = { export const variableConfigs: VariableConfigType[][] = [
[VariableInputEnum.input]: { [
icon: 'core/workflow/inputType/input', {
label: i18nT('common:core.workflow.inputType.textInput'), icon: 'core/workflow/inputType/input',
value: VariableInputEnum.input, label: i18nT('common:core.workflow.inputType.textInput'),
defaultValueType: WorkflowIOValueTypeEnum.string value: VariableInputEnum.input,
}, defaultValueType: WorkflowIOValueTypeEnum.string
},
{
icon: 'core/workflow/inputType/password',
label: i18nT('common:core.workflow.inputType.password'),
value: VariableInputEnum.password,
defaultValueType: WorkflowIOValueTypeEnum.string
},
{
icon: 'core/workflow/inputType/numberInput',
label: i18nT('common:core.workflow.inputType.number input'),
value: VariableInputEnum.numberInput,
defaultValueType: WorkflowIOValueTypeEnum.number
},
// {
// icon: 'core/workflow/inputType/jsonEditor',
// label: i18nT('common:core.workflow.inputType.jsonEditor'),
// value: VariableInputEnum.JSONEditor,
// defaultValueType: WorkflowIOValueTypeEnum.object
// },
{
icon: 'core/workflow/inputType/option',
label: i18nT('common:core.workflow.inputType.select'),
value: VariableInputEnum.select,
defaultValueType: WorkflowIOValueTypeEnum.string
},
{
icon: 'core/workflow/inputType/multipleSelect',
label: i18nT('common:core.workflow.inputType.multipleSelect'),
value: VariableInputEnum.multipleSelect,
defaultValueType: WorkflowIOValueTypeEnum.arrayString
},
{
icon: 'core/workflow/inputType/switch',
label: i18nT('common:core.workflow.inputType.switch'),
value: VariableInputEnum.switch,
defaultValueType: WorkflowIOValueTypeEnum.boolean
}
// {
// icon: 'core/workflow/inputType/timePointSelect',
// label: i18nT('common:core.workflow.inputType.timePointSelect'),
// value: VariableInputEnum.timePointSelect,
// defaultValueType: WorkflowIOValueTypeEnum.string
// },
// {
// icon: 'core/workflow/inputType/timeRangeSelect',
// label: i18nT('common:core.workflow.inputType.timeRangeSelect'),
// value: VariableInputEnum.timeRangeSelect,
// defaultValueType: WorkflowIOValueTypeEnum.arrayString
// }
// {
// icon: 'core/workflow/inputType/file',
// label: i18nT('common:core.workflow.inputType.file'),
// value: VariableInputEnum.file,
// defaultValueType: WorkflowIOValueTypeEnum.arrayString
// }
],
// [
// {
// icon: 'core/workflow/inputType/model',
// label: i18nT('common:core.workflow.inputType.modelSelect'),
// value: VariableInputEnum.modelSelect,
// defaultValueType: WorkflowIOValueTypeEnum.string
// },
// {
// icon: 'core/workflow/inputType/dataset',
// label: i18nT('common:core.workflow.inputType.datasetSelect'),
// value: VariableInputEnum.datasetSelect,
// defaultValueType: WorkflowIOValueTypeEnum.arrayString
// }
// ],
[
{
icon: 'core/workflow/inputType/external',
label: i18nT('common:core.workflow.inputType.custom'),
value: VariableInputEnum.custom,
defaultValueType: WorkflowIOValueTypeEnum.string,
description: i18nT('app:variable.select type_desc')
},
{
icon: 'core/workflow/inputType/internal',
label: i18nT('common:core.workflow.inputType.internal'),
value: VariableInputEnum.internal,
defaultValueType: WorkflowIOValueTypeEnum.string,
description: i18nT('app:variable.internal_type_desc')
}
]
];
export const variableMap: Record<VariableInputEnum, VariableConfigType> = {
...variableConfigs
.flat()
.reduce(
(acc, config) => ({ ...acc, [config.value]: config }),
{} as Record<VariableInputEnum, VariableConfigType>
),
[VariableInputEnum.textarea]: { [VariableInputEnum.textarea]: {
icon: 'core/workflow/inputType/textarea', icon: 'core/workflow/inputType/textarea',
label: i18nT('common:core.workflow.inputType.textarea'), label: i18nT('common:core.workflow.inputType.textarea'),
value: VariableInputEnum.textarea, value: VariableInputEnum.textarea,
defaultValueType: WorkflowIOValueTypeEnum.string, defaultValueType: WorkflowIOValueTypeEnum.string,
description: i18nT('app:variable.textarea_type_desc') description: i18nT('app:variable.textarea_type_desc')
},
[VariableInputEnum.numberInput]: {
icon: 'core/workflow/inputType/numberInput',
label: i18nT('common:core.workflow.inputType.number input'),
value: VariableInputEnum.numberInput,
defaultValueType: WorkflowIOValueTypeEnum.number
},
[VariableInputEnum.select]: {
icon: 'core/workflow/inputType/option',
label: i18nT('common:core.workflow.inputType.select'),
value: VariableInputEnum.select,
defaultValueType: WorkflowIOValueTypeEnum.string
},
[VariableInputEnum.custom]: {
icon: 'core/workflow/inputType/customVariable',
label: i18nT('common:core.workflow.inputType.custom'),
value: VariableInputEnum.custom,
defaultValueType: WorkflowIOValueTypeEnum.string,
description: i18nT('app:variable.select type_desc')
} }
}; };
// Keep backward compatibility
export const variableMapGroups = variableConfigs;
/* run time */ /* run time */
export enum RuntimeEdgeStatusEnum { export enum RuntimeEdgeStatusEnum {
'waiting' = 'waiting', 'waiting' = 'waiting',
......
...@@ -30,7 +30,10 @@ export enum FlowNodeInputTypeEnum { // render ui ...@@ -30,7 +30,10 @@ export enum FlowNodeInputTypeEnum { // render ui
hidden = 'hidden', hidden = 'hidden',
custom = 'custom', custom = 'custom',
fileSelect = 'fileSelect' fileSelect = 'fileSelect',
timePointSelect = 'timePointSelect',
timeRangeSelect = 'timeRangeSelect',
password = 'password'
} }
export const FlowNodeInputMap: Record< export const FlowNodeInputMap: Record<
FlowNodeInputTypeEnum, FlowNodeInputTypeEnum,
...@@ -94,6 +97,15 @@ export const FlowNodeInputMap: Record< ...@@ -94,6 +97,15 @@ export const FlowNodeInputMap: Record<
}, },
[FlowNodeInputTypeEnum.fileSelect]: { [FlowNodeInputTypeEnum.fileSelect]: {
icon: 'core/workflow/inputType/file' icon: 'core/workflow/inputType/file'
},
[FlowNodeInputTypeEnum.timePointSelect]: {
icon: 'core/workflow/inputType/timePointSelect'
},
[FlowNodeInputTypeEnum.timeRangeSelect]: {
icon: 'core/workflow/inputType/timeRangeSelect'
},
[FlowNodeInputTypeEnum.password]: {
icon: 'core/workflow/inputType/password'
} }
}; };
......
...@@ -19,6 +19,7 @@ import type { FlowNodeOutputItemType, ReferenceValueType } from '../type/io'; ...@@ -19,6 +19,7 @@ import type { FlowNodeOutputItemType, ReferenceValueType } from '../type/io';
import type { StoreNodeItemType } from '../type/node'; import type { StoreNodeItemType } from '../type/node';
import { isValidReferenceValueFormat } from '../utils'; import { isValidReferenceValueFormat } from '../utils';
import type { RuntimeEdgeItemType, RuntimeNodeItemType } from './type'; import type { RuntimeEdgeItemType, RuntimeNodeItemType } from './type';
import { isSecretValue } from '../../../common/secret/utils';
export const checkIsBranchNode = (node: RuntimeNodeItemType) => { export const checkIsBranchNode = (node: RuntimeNodeItemType) => {
if (node.catchError) return true; if (node.catchError) return true;
...@@ -67,7 +68,7 @@ export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number ...@@ -67,7 +68,7 @@ export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number
}; };
/* value type format */ /* value type format */
export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => { export const valueTypeFormat = (value: any, valueType?: WorkflowIOValueTypeEnum) => {
const isObjectString = (value: any) => { const isObjectString = (value: any) => {
if (typeof value === 'string' && value !== 'false' && value !== 'true') { if (typeof value === 'string' && value !== 'false' && value !== 'true') {
const trimmedValue = value.trim(); const trimmedValue = value.trim();
...@@ -81,34 +82,37 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => { ...@@ -81,34 +82,37 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => {
// 1. any值,忽略格式化 // 1. any值,忽略格式化
if (value === undefined || value === null) return value; if (value === undefined || value === null) return value;
if (!type || type === WorkflowIOValueTypeEnum.any) return value; if (!valueType || valueType === WorkflowIOValueTypeEnum.any) return value;
// Password check
if (valueType === WorkflowIOValueTypeEnum.string && isSecretValue(value)) return value;
// 2. 如果值已经符合目标类型,直接返回 // 2. 如果值已经符合目标类型,直接返回
if ( if (
(type === WorkflowIOValueTypeEnum.string && typeof value === 'string') || (valueType === WorkflowIOValueTypeEnum.string && typeof value === 'string') ||
(type === WorkflowIOValueTypeEnum.number && typeof value === 'number') || (valueType === WorkflowIOValueTypeEnum.number && typeof value === 'number') ||
(type === WorkflowIOValueTypeEnum.boolean && typeof value === 'boolean') || (valueType === WorkflowIOValueTypeEnum.boolean && typeof value === 'boolean') ||
(type.startsWith('array') && Array.isArray(value)) || (valueType.startsWith('array') && Array.isArray(value)) ||
(type === WorkflowIOValueTypeEnum.object && typeof value === 'object') || (valueType === WorkflowIOValueTypeEnum.object && typeof value === 'object') ||
(type === WorkflowIOValueTypeEnum.chatHistory && (valueType === WorkflowIOValueTypeEnum.chatHistory &&
(Array.isArray(value) || typeof value === 'number')) || (Array.isArray(value) || typeof value === 'number')) ||
(type === WorkflowIOValueTypeEnum.datasetQuote && Array.isArray(value)) || (valueType === WorkflowIOValueTypeEnum.datasetQuote && Array.isArray(value)) ||
(type === WorkflowIOValueTypeEnum.selectDataset && Array.isArray(value)) || (valueType === WorkflowIOValueTypeEnum.selectDataset && Array.isArray(value)) ||
(type === WorkflowIOValueTypeEnum.selectApp && typeof value === 'object') (valueType === WorkflowIOValueTypeEnum.selectApp && typeof value === 'object')
) { ) {
return value; return value;
} }
// 4. 按目标类型,进行格式转化 // 4. 按目标类型,进行格式转化
// 4.1 基本类型转换 // 4.1 基本类型转换
if (type === WorkflowIOValueTypeEnum.string) { if (valueType === WorkflowIOValueTypeEnum.string) {
return typeof value === 'object' ? JSON.stringify(value) : String(value); return typeof value === 'object' ? JSON.stringify(value) : String(value);
} }
if (type === WorkflowIOValueTypeEnum.number) { if (valueType === WorkflowIOValueTypeEnum.number) {
if (value === '') return undefined; if (value === '') return undefined;
return Number(value); return Number(value);
} }
if (type === WorkflowIOValueTypeEnum.boolean) { if (valueType === WorkflowIOValueTypeEnum.boolean) {
if (typeof value === 'string') { if (typeof value === 'string') {
return value.toLowerCase() === 'true'; return value.toLowerCase() === 'true';
} }
...@@ -116,7 +120,7 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => { ...@@ -116,7 +120,7 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => {
} }
// 4.3 字符串转对象 // 4.3 字符串转对象
if (type === WorkflowIOValueTypeEnum.object) { if (valueType === WorkflowIOValueTypeEnum.object) {
if (isObjectString(value)) { if (isObjectString(value)) {
const trimmedValue = value.trim(); const trimmedValue = value.trim();
try { try {
...@@ -127,7 +131,7 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => { ...@@ -127,7 +131,7 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => {
} }
// 4.4 数组类型(这里 value 不是数组类型)(TODO: 嵌套数据类型转化) // 4.4 数组类型(这里 value 不是数组类型)(TODO: 嵌套数据类型转化)
if (type.startsWith('array')) { if (valueType.startsWith('array')) {
if (isObjectString(value)) { if (isObjectString(value)) {
try { try {
return json5.parse(value); return json5.parse(value);
...@@ -142,7 +146,7 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => { ...@@ -142,7 +146,7 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => {
WorkflowIOValueTypeEnum.datasetQuote, WorkflowIOValueTypeEnum.datasetQuote,
WorkflowIOValueTypeEnum.selectDataset, WorkflowIOValueTypeEnum.selectDataset,
WorkflowIOValueTypeEnum.selectApp WorkflowIOValueTypeEnum.selectApp
].includes(type) ].includes(valueType)
) { ) {
if (isObjectString(value)) { if (isObjectString(value)) {
try { try {
...@@ -153,7 +157,7 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => { ...@@ -153,7 +157,7 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => {
} }
// Invalid history type // Invalid history type
if (type === WorkflowIOValueTypeEnum.chatHistory) { if (valueType === WorkflowIOValueTypeEnum.chatHistory) {
if (isObjectString(value)) { if (isObjectString(value)) {
try { try {
return json5.parse(value); return json5.parse(value);
......
...@@ -2,8 +2,8 @@ import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant'; ...@@ -2,8 +2,8 @@ import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
import { type FlowNodeTemplateType } from '../../type/node.d'; import { type FlowNodeTemplateType } from '../../type/node.d';
import { import {
WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum,
FlowNodeTemplateTypeEnum, NodeInputKeyEnum,
NodeInputKeyEnum FlowNodeTemplateTypeEnum
} from '../../constants'; } from '../../constants';
import { i18nT } from '../../../../../web/i18n/utils'; import { i18nT } from '../../../../../web/i18n/utils';
......
...@@ -8,8 +8,8 @@ import { ...@@ -8,8 +8,8 @@ import {
WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum,
NodeInputKeyEnum, NodeInputKeyEnum,
NodeOutputKeyEnum, NodeOutputKeyEnum,
FlowNodeTemplateTypeEnum, ContentTypes,
ContentTypes FlowNodeTemplateTypeEnum
} from '../../constants'; } from '../../constants';
import { Input_Template_DynamicInput } from '../input'; import { Input_Template_DynamicInput } from '../input';
import { Output_Template_AddOutput } from '../output'; import { Output_Template_AddOutput } from '../output';
......
...@@ -7,8 +7,8 @@ import { type FlowNodeTemplateType } from '../../type/node.d'; ...@@ -7,8 +7,8 @@ import { type FlowNodeTemplateType } from '../../type/node.d';
import { import {
WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum,
NodeOutputKeyEnum, NodeOutputKeyEnum,
FlowNodeTemplateTypeEnum, NodeInputKeyEnum,
NodeInputKeyEnum FlowNodeTemplateTypeEnum
} from '../../constants'; } from '../../constants';
import { i18nT } from '../../../../../web/i18n/utils'; import { i18nT } from '../../../../../web/i18n/utils';
......
...@@ -18,7 +18,6 @@ import { ...@@ -18,7 +18,6 @@ import {
type ReferenceArrayValueType, type ReferenceArrayValueType,
type ReferenceItemValueType type ReferenceItemValueType
} from './type/io.d'; } from './type/io.d';
import type { NodeToolConfigType } from './type/node';
import { type StoreNodeItemType } from './type/node'; import { type StoreNodeItemType } from './type/node';
import type { import type {
VariableItemType, VariableItemType,
...@@ -247,7 +246,7 @@ export const appData2FlowNodeIO = ({ ...@@ -247,7 +246,7 @@ export const appData2FlowNodeIO = ({
const variableInput = !chatConfig?.variables const variableInput = !chatConfig?.variables
? [] ? []
: chatConfig.variables.map((item) => { : chatConfig.variables.map((item) => {
const renderTypeMap = { const renderTypeMap: Record<VariableInputEnum, FlowNodeInputTypeEnum[]> = {
[VariableInputEnum.input]: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], [VariableInputEnum.input]: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference],
[VariableInputEnum.textarea]: [ [VariableInputEnum.textarea]: [
FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.textarea,
...@@ -255,16 +254,22 @@ export const appData2FlowNodeIO = ({ ...@@ -255,16 +254,22 @@ export const appData2FlowNodeIO = ({
], ],
[VariableInputEnum.numberInput]: [FlowNodeInputTypeEnum.numberInput], [VariableInputEnum.numberInput]: [FlowNodeInputTypeEnum.numberInput],
[VariableInputEnum.select]: [FlowNodeInputTypeEnum.select], [VariableInputEnum.select]: [FlowNodeInputTypeEnum.select],
[VariableInputEnum.custom]: [ [VariableInputEnum.multipleSelect]: [FlowNodeInputTypeEnum.multipleSelect],
FlowNodeInputTypeEnum.input, [VariableInputEnum.JSONEditor]: [FlowNodeInputTypeEnum.JSONEditor],
FlowNodeInputTypeEnum.reference [VariableInputEnum.timePointSelect]: [FlowNodeInputTypeEnum.timePointSelect],
], [VariableInputEnum.timeRangeSelect]: [FlowNodeInputTypeEnum.timeRangeSelect],
default: [FlowNodeInputTypeEnum.reference] [VariableInputEnum.switch]: [FlowNodeInputTypeEnum.switch],
[VariableInputEnum.password]: [FlowNodeInputTypeEnum.password],
[VariableInputEnum.file]: [FlowNodeInputTypeEnum.fileSelect],
[VariableInputEnum.modelSelect]: [FlowNodeInputTypeEnum.selectLLMModel],
[VariableInputEnum.datasetSelect]: [FlowNodeInputTypeEnum.selectDataset],
[VariableInputEnum.internal]: [FlowNodeInputTypeEnum.hidden],
[VariableInputEnum.custom]: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference]
}; };
return { return {
key: item.key, key: item.key,
renderTypeList: renderTypeMap[item.type] || renderTypeMap.default, renderTypeList: renderTypeMap[item.type] || [FlowNodeInputTypeEnum.reference],
label: item.label, label: item.label,
debugLabel: item.label, debugLabel: item.label,
description: '', description: '',
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"name": "@fastgpt/global", "name": "@fastgpt/global",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@fastgpt-sdk/plugin": "^0.1.12", "@fastgpt-sdk/plugin": "^0.1.16",
"@apidevtools/swagger-parser": "^10.1.0", "@apidevtools/swagger-parser": "^10.1.0",
"@bany/curl-to-json": "^1.2.8", "@bany/curl-to-json": "^1.2.8",
"axios": "^1.8.2", "axios": "^1.8.2",
......
...@@ -2,6 +2,7 @@ import { decryptSecret, encryptSecret } from './aes256gcm'; ...@@ -2,6 +2,7 @@ import { decryptSecret, encryptSecret } from './aes256gcm';
import type { SecretValueType } from '@fastgpt/global/common/secret/type'; import type { SecretValueType } from '@fastgpt/global/common/secret/type';
import { type StoreSecretValueType } from '@fastgpt/global/common/secret/type'; import { type StoreSecretValueType } from '@fastgpt/global/common/secret/type';
import { HeaderSecretTypeEnum } from '@fastgpt/global/common/secret/constants'; import { HeaderSecretTypeEnum } from '@fastgpt/global/common/secret/constants';
import { isSecretValue } from '../../../global/common/secret/utils';
export const encryptSecretValue = (value: SecretValueType): SecretValueType => { export const encryptSecretValue = (value: SecretValueType): SecretValueType => {
if (!value.value) { if (!value.value) {
...@@ -48,3 +49,9 @@ export const getSecretValue = ({ ...@@ -48,3 +49,9 @@ export const getSecretValue = ({
return acc; return acc;
}, {}); }, {});
}; };
export const anyValueDecrypt = (value: any) => {
if (!isSecretValue(value)) return value;
return decryptSecret(value.secret);
};
...@@ -11,7 +11,7 @@ import type { ...@@ -11,7 +11,7 @@ import type {
EmbeddingRecallResponse, EmbeddingRecallResponse,
InsertVectorControllerProps InsertVectorControllerProps
} from '../controller.d'; } from '../controller.d';
import { delay, retryFn } from '@fastgpt/global/common/system/utils'; import { retryFn } from '@fastgpt/global/common/system/utils';
import { addLog } from '../../system/log'; import { addLog } from '../../system/log';
import { customNanoid } from '@fastgpt/global/common/string/tools'; import { customNanoid } from '@fastgpt/global/common/string/tools';
......
...@@ -9,7 +9,7 @@ import { ...@@ -9,7 +9,7 @@ import {
type RerankModelItemType type RerankModelItemType
} from '@fastgpt/global/core/ai/model.d'; } from '@fastgpt/global/core/ai/model.d';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { getModelProvider } from '@fastgpt/global/core/ai/provider'; import { getModelProvider } from '../../../core/app/provider/controller';
import { findModelFromAlldata } from '../model'; import { findModelFromAlldata } from '../model';
import { import {
reloadFastGPTConfigBuffer, reloadFastGPTConfigBuffer,
...@@ -18,6 +18,7 @@ import { ...@@ -18,6 +18,7 @@ import {
import { delay } from '@fastgpt/global/common/system/utils'; import { delay } from '@fastgpt/global/common/system/utils';
import { pluginClient } from '../../../thirdProvider/fastgptPlugin'; import { pluginClient } from '../../../thirdProvider/fastgptPlugin';
import { setCron } from '../../../common/system/cron'; import { setCron } from '../../../common/system/cron';
import { preloadModelProviders } from '../../../core/app/provider/controller';
export const loadSystemModels = async (init = false, language = 'en') => { export const loadSystemModels = async (init = false, language = 'en') => {
const pushModel = (model: SystemModelItemType) => { const pushModel = (model: SystemModelItemType) => {
...@@ -77,6 +78,8 @@ export const loadSystemModels = async (init = false, language = 'en') => { ...@@ -77,6 +78,8 @@ export const loadSystemModels = async (init = false, language = 'en') => {
if (!init && global.systemModelList) return; if (!init && global.systemModelList) return;
await preloadModelProviders();
global.systemModelList = []; global.systemModelList = [];
global.systemActiveModelList = []; global.systemActiveModelList = [];
global.llmModelMap = new Map<string, LLMModelItemType>(); global.llmModelMap = new Map<string, LLMModelItemType>();
...@@ -109,14 +112,13 @@ export const loadSystemModels = async (init = false, language = 'en') => { ...@@ -109,14 +112,13 @@ export const loadSystemModels = async (init = false, language = 'en') => {
}; };
const dbModel = dbModels.find((item) => item.model === model.model); const dbModel = dbModels.find((item) => item.model === model.model);
const provider = getModelProvider(dbModel?.metadata?.provider || model.provider, language);
const modelData: any = { const modelData: any = {
...model, ...model,
...dbModel?.metadata, ...dbModel?.metadata,
provider: getModelProvider( provider: provider.id,
dbModel?.metadata?.provider || (model.provider as any), avatar: provider.avatar,
language
).id,
type: dbModel?.metadata?.type || model.type, type: dbModel?.metadata?.type || model.type,
isCustom: false, isCustom: false,
......
...@@ -6,6 +6,12 @@ import type { ...@@ -6,6 +6,12 @@ import type {
EmbeddingModelItemType, EmbeddingModelItemType,
LLMModelItemType LLMModelItemType
} from '@fastgpt/global/core/ai/model.d'; } from '@fastgpt/global/core/ai/model.d';
import type { ModelProviderListType } from '@fastgpt/global/core/app/model/type';
import type {
AiproxyMapProviderType,
I18nStringStrictType
} from '@fastgpt/global/sdk/fastgpt-plugin';
import type { langType, ModelProviderItemType } from '@fastgpt/global/core/ai/provider';
export type SystemModelSchemaType = { export type SystemModelSchemaType = {
_id: string; _id: string;
...@@ -32,6 +38,11 @@ export type SystemDefaultModelType = { ...@@ -32,6 +38,11 @@ export type SystemDefaultModelType = {
}; };
declare global { declare global {
var ModelProviderRawCache: { provider: string; value: I18nStringStrictType }[];
var ModelProviderListCache: Record<langType, ModelProviderItemType[]>;
var ModelProviderMapCache: Record<langType, Record<string, ModelProviderItemType>>;
var aiproxyIdMapCache: AiproxyMapProviderType;
var systemModelList: SystemModelItemType[]; var systemModelList: SystemModelItemType[];
// var systemModelMap: Map<string, SystemModelItemType>; // var systemModelMap: Map<string, SystemModelItemType>;
var llmModelMap: Map<string, LLMModelItemType>; var llmModelMap: Map<string, LLMModelItemType>;
......
import { evaluationFileErrors } from '@fastgpt/global/core/app/evaluation/constants';
import { getEvaluationFileHeader } from '@fastgpt/global/core/app/evaluation/utils';
import type { VariableItemType } from '@fastgpt/global/core/app/type';
import { addLog } from '../../../common/system/log';
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
import Papa from 'papaparse';
export const parseEvaluationCSV = (rawText: string) => {
const parseResult = Papa.parse(rawText.trim(), {
skipEmptyLines: true,
header: false,
transformHeader: (header: string) => header.trim()
});
if (parseResult.errors.length > 0) {
addLog.error('CSV parsing failed', parseResult.errors);
throw new Error('CSV parsing failed');
}
return parseResult.data as string[][];
};
export const validateEvaluationFile = async (
rawText: string,
appVariables?: VariableItemType[]
) => {
// Parse CSV using Papa Parse
const csvData = parseEvaluationCSV(rawText);
const dataLength = csvData.length;
// Validate file header
const expectedHeader = getEvaluationFileHeader(appVariables);
const actualHeader = csvData[0]?.join(',') || '';
if (actualHeader !== expectedHeader) {
addLog.error(`Header mismatch. Expected: ${expectedHeader}, Got: ${actualHeader}`);
return Promise.reject(evaluationFileErrors);
}
// Validate data rows count
if (dataLength <= 1) {
addLog.error('No data rows found');
return Promise.reject(evaluationFileErrors);
}
const maxRows = 1000;
if (dataLength - 1 > maxRows) {
addLog.error(`Too many rows. Max: ${maxRows}, Got: ${dataLength - 1}`);
return Promise.reject(evaluationFileErrors);
}
const headers = csvData[0];
// Get required field indices
const requiredFields = headers
.map((header, index) => ({ header: header.trim(), index }))
.filter(({ header }) => header.startsWith('*'));
const errors: string[] = [];
// Validate each data row
for (let i = 1; i < csvData.length; i++) {
const values = csvData[i];
// Check required fields
requiredFields.forEach(({ header, index }) => {
if (!values[index]?.trim()) {
errors.push(`Row ${i + 1}: required field "${header}" is empty`);
}
});
// Validate app variables
if (appVariables) {
validateRowVariables({
values,
variables: appVariables,
rowNum: i + 1,
errors
});
}
}
if (errors.length > 0) {
addLog.error(`Validation failed: ${errors.join('; ')}`);
return Promise.reject(evaluationFileErrors);
}
return { csvData, dataLength };
};
const validateRowVariables = ({
values,
variables,
rowNum,
errors
}: {
values: string[];
variables: VariableItemType[];
rowNum: number;
errors: string[];
}) => {
variables.forEach((variable, index) => {
const value = values[index]?.trim();
// Skip validation if value is empty and not required
if (!value && !variable.required) return;
switch (variable.type) {
case VariableInputEnum.input:
// Validate string length
if (variable.maxLength && value && value.length > variable.maxLength) {
errors.push(
`Row ${rowNum}: "${variable.label}" exceeds max length (${variable.maxLength})`
);
}
break;
case VariableInputEnum.numberInput:
// Validate number type and range
if (value) {
const numValue = Number(value);
if (isNaN(numValue)) {
errors.push(`Row ${rowNum}: "${variable.label}" must be a number`);
} else {
if (variable.min !== undefined && numValue < variable.min) {
errors.push(`Row ${rowNum}: "${variable.label}" below minimum (${variable.min})`);
}
if (variable.max !== undefined && numValue > variable.max) {
errors.push(`Row ${rowNum}: "${variable.label}" exceeds maximum (${variable.max})`);
}
}
}
break;
case VariableInputEnum.select:
// Validate select options
if (value && variable.enums?.length) {
const validOptions = variable.enums.map((item) => item.value);
if (!validOptions.includes(value)) {
errors.push(
`Row ${rowNum}: "${variable.label}" invalid option. Valid: [${validOptions.join(', ')}]`
);
}
}
break;
}
});
};
...@@ -504,17 +504,17 @@ const dbPluginFormat = (item: SystemPluginConfigSchemaType): SystemPluginTemplat ...@@ -504,17 +504,17 @@ const dbPluginFormat = (item: SystemPluginConfigSchemaType): SystemPluginTemplat
/* FastsGPT-Pluign api: */ /* FastsGPT-Pluign api: */
function getCachedSystemPlugins() { function getCachedSystemPlugins() {
if (!global.systemPlugins_cache) { if (!global.systemToolsCache) {
global.systemPlugins_cache = { global.systemToolsCache = {
expires: 0, expires: 0,
data: [] as SystemPluginTemplateItemType[] data: [] as SystemPluginTemplateItemType[]
}; };
} }
return global.systemPlugins_cache; return global.systemToolsCache;
} }
const cleanSystemPluginCache = () => { const cleanSystemPluginCache = () => {
global.systemPlugins_cache = undefined; global.systemToolsCache = undefined;
}; };
export const refetchSystemPlugins = () => { export const refetchSystemPlugins = () => {
...@@ -579,15 +579,20 @@ export const getSystemTools = async (): Promise<SystemPluginTemplateItemType[]> ...@@ -579,15 +579,20 @@ export const getSystemTools = async (): Promise<SystemPluginTemplateItemType[]>
.filter((item) => item.customConfig?.associatedPluginId) .filter((item) => item.customConfig?.associatedPluginId)
.map((item) => dbPluginFormat(item)); .map((item) => dbPluginFormat(item));
const plugins = [...formatTools, ...dbPlugins]; const concatTools = [...formatTools, ...dbPlugins];
plugins.sort((a, b) => (a.pluginOrder ?? 0) - (b.pluginOrder ?? 0)); concatTools.sort((a, b) => (a.pluginOrder ?? 0) - (b.pluginOrder ?? 0));
global.systemPlugins_cache = { global.systemToolsCache = {
expires: Date.now() + 30 * 60 * 1000, // 30 minutes expires: Date.now() + 30 * 60 * 1000, // 30 minutes
data: plugins data: concatTools
}; };
return plugins; global.systemToolsTypeCache = {};
concatTools.forEach((item) => {
global.systemToolsTypeCache[item.templateType] = 1;
});
return concatTools;
} }
}; };
...@@ -608,10 +613,11 @@ export const getSystemToolById = async (id: string): Promise<SystemPluginTemplat ...@@ -608,10 +613,11 @@ export const getSystemToolById = async (id: string): Promise<SystemPluginTemplat
}; };
declare global { declare global {
var systemPlugins_cache: var systemToolsCache:
| { | {
expires: number; expires: number;
data: SystemPluginTemplateItemType[]; data: SystemPluginTemplateItemType[];
} }
| undefined; | undefined;
var systemToolsTypeCache: Record<string, 1>;
} }
import { connectionMongo, getMongoModel } from '../../../common/mongo/index'; import { connectionMongo, getMongoModel } from '../../../common/mongo/index';
import { type PluginGroupSchemaType, type TGroupType } from './type'; import { type SystemToolGroupSchemaType, type TGroupType } from './type';
const { Schema } = connectionMongo; const { Schema } = connectionMongo;
export const collectionName = 'app_plugin_groups'; export const collectionName = 'app_plugin_groups';
...@@ -29,7 +29,7 @@ const PluginGroupSchema = new Schema({ ...@@ -29,7 +29,7 @@ const PluginGroupSchema = new Schema({
PluginGroupSchema.index({ groupId: 1 }, { unique: true }); PluginGroupSchema.index({ groupId: 1 }, { unique: true });
export const MongoPluginGroups = getMongoModel<PluginGroupSchemaType>( export const MongoToolGroups = getMongoModel<SystemToolGroupSchemaType>(
collectionName, collectionName,
PluginGroupSchema PluginGroupSchema
); );
...@@ -2,6 +2,7 @@ import { SystemPluginListItemType } from '@fastgpt/global/core/app/type'; ...@@ -2,6 +2,7 @@ import { SystemPluginListItemType } from '@fastgpt/global/core/app/type';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import type { WorkflowTemplateBasicType } from '@fastgpt/global/core/workflow/type'; import type { WorkflowTemplateBasicType } from '@fastgpt/global/core/workflow/type';
import type { InputConfigType } from '@fastgpt/global/core/workflow/type/io'; import type { InputConfigType } from '@fastgpt/global/core/workflow/type/io';
import type { I18nStringStrictType } from '@fastgpt/global/sdk/fastgpt-plugin';
export type SystemPluginConfigSchemaType = { export type SystemPluginConfigSchemaType = {
pluginId: string; pluginId: string;
...@@ -38,11 +39,11 @@ export type SystemPluginConfigSchemaType = { ...@@ -38,11 +39,11 @@ export type SystemPluginConfigSchemaType = {
}; };
export type TGroupType = { export type TGroupType = {
typeName: string; typeName: I18nStringStrictType | string;
typeId: string; typeId: string;
}; };
export type PluginGroupSchemaType = { export type SystemToolGroupSchemaType = {
groupId: string; groupId: string;
groupAvatar: string; groupAvatar: string;
groupName: string; groupName: string;
......
import { loadModelProviders } from '../../../thirdProvider/fastgptPlugin/model';
import {
type langType,
defaultProvider,
formatModelProviders
} from '@fastgpt/global/core/ai/provider';
// Preload model providers
export async function preloadModelProviders(): Promise<void> {
const { modelProviders, aiproxyIdMap } = await loadModelProviders();
const { ModelProviderListCache, ModelProviderMapCache } = formatModelProviders(modelProviders);
global.ModelProviderRawCache = modelProviders;
global.ModelProviderListCache = ModelProviderListCache;
global.ModelProviderMapCache = ModelProviderMapCache;
global.aiproxyIdMapCache = aiproxyIdMap;
}
export const getModelProviders = (language = 'en') => {
return global.ModelProviderListCache[language as langType] || [];
};
export const getModelProvider = (provider?: string, language = 'en') => {
if (!provider) {
return defaultProvider;
}
return ModelProviderMapCache[language as langType][provider] ?? defaultProvider;
};
import fs from 'fs';
import path from 'path';
import { isProduction } from '@fastgpt/global/common/system/constants'; import { isProduction } from '@fastgpt/global/common/system/constants';
import { PluginSourceEnum } from '@fastgpt/global/core/app/plugin/constants'; import { PluginSourceEnum } from '@fastgpt/global/core/app/plugin/constants';
import { MongoAppTemplate } from '@fastgpt/service/core/app/templates/templateSchema';
import { type AppTemplateSchemaType } from '@fastgpt/global/core/app/type'; import { type AppTemplateSchemaType } from '@fastgpt/global/core/app/type';
import { MongoAppTemplate } from './templateSchema';
const getTemplateNameList = () => { import { pluginClient } from '../../../thirdProvider/fastgptPlugin';
const currentFileUrl = new URL(import.meta.url);
const filePath = decodeURIComponent(
process.platform === 'win32'
? currentFileUrl.pathname.substring(1) // Remove leading slash on Windows
: currentFileUrl.pathname
);
const templatesPath = path.join(path.dirname(filePath), 'src');
return fs.promises.readdir(templatesPath);
};
const getFileTemplates = async (): Promise<AppTemplateSchemaType[]> => { const getFileTemplates = async (): Promise<AppTemplateSchemaType[]> => {
const templateNames = await getTemplateNameList(); const res = await pluginClient.workflow.getTemplateList();
if (res.status === 200) return res.body as AppTemplateSchemaType[];
return Promise.all( else return Promise.reject(res.body);
templateNames.map<Promise<AppTemplateSchemaType>>(async (name) => {
const fileContent = (await import(`./src/${name}/template.json`))?.default;
return {
...fileContent,
templateId: `${PluginSourceEnum.community}-${name}`,
isActive: true
};
})
);
}; };
const getAppTemplates = async () => { const getAppTemplates = async () => {
...@@ -86,3 +63,7 @@ export const getAppTemplatesAndLoadThem = async (refresh = false) => { ...@@ -86,3 +63,7 @@ export const getAppTemplatesAndLoadThem = async (refresh = false) => {
export const isCommunityTemplate = (templateId: string) => { export const isCommunityTemplate = (templateId: string) => {
return templateId.startsWith(PluginSourceEnum.community); return templateId.startsWith(PluginSourceEnum.community);
}; };
declare global {
var appTemplates: AppTemplateSchemaType[];
}
import type { I18nStringStrictType, ToolTypeEnum } from '@fastgpt/global/sdk/fastgpt-plugin';
import { RunToolWithStream } from '@fastgpt/global/sdk/fastgpt-plugin'; import { RunToolWithStream } from '@fastgpt/global/sdk/fastgpt-plugin';
import { PluginSourceEnum } from '@fastgpt/global/core/app/plugin/constants'; import { PluginSourceEnum } from '@fastgpt/global/core/app/plugin/constants';
import { pluginClient, BASE_URL, TOKEN } from '../../../thirdProvider/fastgptPlugin'; import { pluginClient, BASE_URL, TOKEN } from '../../../thirdProvider/fastgptPlugin';
import { addLog } from '../../../common/system/log';
import { retryFn } from '@fastgpt/global/common/system/utils';
export async function APIGetSystemToolList() { export async function APIGetSystemToolList() {
const res = await pluginClient.tool.list(); const res = await pluginClient.tool.list();
...@@ -27,3 +30,24 @@ const runToolInstance = new RunToolWithStream({ ...@@ -27,3 +30,24 @@ const runToolInstance = new RunToolWithStream({
token: TOKEN token: TOKEN
}); });
export const APIRunSystemTool = runToolInstance.run.bind(runToolInstance); export const APIRunSystemTool = runToolInstance.run.bind(runToolInstance);
// Tool Types Cache
type SystemToolTypeItem = {
type: ToolTypeEnum;
name: I18nStringStrictType;
};
export const getSystemToolTypes = (): Promise<SystemToolTypeItem[]> => {
return retryFn(async () => {
const res = await pluginClient.tool.getType();
if (res.status === 200) {
const toolTypes = res.body || [];
return toolTypes;
}
addLog.error('Get system tool type error', res.body);
return [];
});
};
...@@ -7,7 +7,7 @@ import type { ...@@ -7,7 +7,7 @@ import type {
ToolRunResponseItemType ToolRunResponseItemType
} from '@fastgpt/global/core/chat/type.d'; } from '@fastgpt/global/core/chat/type.d';
import type { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import type { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum, VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
import { import {
FlowNodeInputTypeEnum, FlowNodeInputTypeEnum,
FlowNodeTypeEnum FlowNodeTypeEnum
...@@ -43,9 +43,10 @@ import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type ...@@ -43,9 +43,10 @@ import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type
import { addLog } from '../../../common/system/log'; import { addLog } from '../../../common/system/log';
import { surrenderProcess } from '../../../common/system/tools'; import { surrenderProcess } from '../../../common/system/tools';
import type { DispatchFlowResponse, WorkflowDebugResponse } from './type'; import type { DispatchFlowResponse, WorkflowDebugResponse } from './type';
import { removeSystemVariable, rewriteRuntimeWorkFlow } from './utils'; import { rewriteRuntimeWorkFlow, removeSystemVariable } from './utils';
import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { callbackMap } from './constants'; import { callbackMap } from './constants';
import { anyValueDecrypt } from '../../../common/secret/utils';
type Props = Omit<ChatDispatchProps, 'workflowDispatchDeep'> & { type Props = Omit<ChatDispatchProps, 'workflowDispatchDeep'> & {
runtimeNodes: RuntimeNodeItemType[]; runtimeNodes: RuntimeNodeItemType[];
...@@ -147,7 +148,11 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -147,7 +148,11 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
[DispatchNodeResponseKeyEnum.runTimes]: 1, [DispatchNodeResponseKeyEnum.runTimes]: 1,
[DispatchNodeResponseKeyEnum.assistantResponses]: [], [DispatchNodeResponseKeyEnum.assistantResponses]: [],
[DispatchNodeResponseKeyEnum.toolResponses]: null, [DispatchNodeResponseKeyEnum.toolResponses]: null,
newVariables: removeSystemVariable(variables, externalProvider.externalWorkflowVariables), newVariables: removeSystemVariable(
variables,
externalProvider.externalWorkflowVariables,
data.chatConfig?.variables
),
durationSeconds: 0 durationSeconds: 0
}; };
} }
...@@ -903,6 +908,12 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -903,6 +908,12 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
}); });
} }
const encryptedNewVariables = removeSystemVariable(
variables,
externalProvider.externalWorkflowVariables,
data.chatConfig?.variables
);
return { return {
flowResponses: workflowQueue.chatResponses, flowResponses: workflowQueue.chatResponses,
flowUsages: workflowQueue.chatNodeUsages, flowUsages: workflowQueue.chatNodeUsages,
...@@ -913,10 +924,7 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -913,10 +924,7 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
workflowQueue.chatAssistantResponse workflowQueue.chatAssistantResponse
), ),
[DispatchNodeResponseKeyEnum.toolResponses]: workflowQueue.toolRunResponse, [DispatchNodeResponseKeyEnum.toolResponses]: workflowQueue.toolRunResponse,
[DispatchNodeResponseKeyEnum.newVariables]: removeSystemVariable( [DispatchNodeResponseKeyEnum.newVariables]: encryptedNewVariables,
variables,
externalProvider.externalWorkflowVariables
),
[DispatchNodeResponseKeyEnum.memories]: [DispatchNodeResponseKeyEnum.memories]:
Object.keys(workflowQueue.system_memories).length > 0 Object.keys(workflowQueue.system_memories).length > 0
? workflowQueue.system_memories ? workflowQueue.system_memories
...@@ -937,10 +945,18 @@ const getSystemVariables = ({ ...@@ -937,10 +945,18 @@ const getSystemVariables = ({
variables variables
}: Props): SystemVariablesType => { }: Props): SystemVariablesType => {
// Get global variables(Label -> key; Key -> key) // Get global variables(Label -> key; Key -> key)
const globalVariables = chatConfig?.variables || []; const variablesConfig = chatConfig?.variables || [];
const variablesMap = globalVariables.reduce<Record<string, any>>((acc, item) => {
const variablesMap = variablesConfig.reduce<Record<string, any>>((acc, item) => {
// For internal variables, ignore external input and use default value
if (item.type === VariableInputEnum.password) {
const val = variables[item.label] || variables[item.key] || item.defaultValue;
const actualValue = anyValueDecrypt(val);
acc[item.key] = valueTypeFormat(actualValue, item.valueType);
}
// API // API
if (variables[item.label] !== undefined) { else if (variables[item.label] !== undefined) {
acc[item.key] = valueTypeFormat(variables[item.label], item.valueType); acc[item.key] = valueTypeFormat(variables[item.label], item.valueType);
} }
// Web // Web
......
...@@ -22,6 +22,7 @@ type Response = DispatchNodeResultType<{}>; ...@@ -22,6 +22,7 @@ type Response = DispatchNodeResultType<{}>;
export const dispatchUpdateVariable = async (props: Props): Promise<Response> => { export const dispatchUpdateVariable = async (props: Props): Promise<Response> => {
const { const {
chatConfig,
params, params,
variables, variables,
runtimeNodes, runtimeNodes,
...@@ -91,7 +92,11 @@ export const dispatchUpdateVariable = async (props: Props): Promise<Response> => ...@@ -91,7 +92,11 @@ export const dispatchUpdateVariable = async (props: Props): Promise<Response> =>
if (!runningAppInfo.isChildApp) { if (!runningAppInfo.isChildApp) {
workflowStreamResponse?.({ workflowStreamResponse?.({
event: SseResponseEventEnum.updateVariables, event: SseResponseEventEnum.updateVariables,
data: removeSystemVariable(variables, externalProvider.externalWorkflowVariables) data: removeSystemVariable(
variables,
externalProvider.externalWorkflowVariables,
chatConfig?.variables
)
}); });
} }
......
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeOutputKeyEnum, VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
import type { VariableItemType } from '@fastgpt/global/core/app/type';
import { encryptSecret } from '../../../common/secret/aes256gcm';
import { import {
type RuntimeEdgeItemType, type RuntimeEdgeItemType,
type RuntimeNodeItemType, type RuntimeNodeItemType,
...@@ -115,9 +117,12 @@ export const checkQuoteQAValue = (quoteQA?: SearchDataResponseItemType[]) => { ...@@ -115,9 +117,12 @@ export const checkQuoteQAValue = (quoteQA?: SearchDataResponseItemType[]) => {
/* remove system variable */ /* remove system variable */
export const removeSystemVariable = ( export const removeSystemVariable = (
variables: Record<string, any>, variables: Record<string, any>,
removeObj: Record<string, string> = {} removeObj: Record<string, string> = {},
userVariablesConfigs: VariableItemType[] = []
) => { ) => {
const copyVariables = { ...variables }; const copyVariables = { ...variables };
// Delete system variables
delete copyVariables.userId; delete copyVariables.userId;
delete copyVariables.appId; delete copyVariables.appId;
delete copyVariables.chatId; delete copyVariables.chatId;
...@@ -125,13 +130,25 @@ export const removeSystemVariable = ( ...@@ -125,13 +130,25 @@ export const removeSystemVariable = (
delete copyVariables.histories; delete copyVariables.histories;
delete copyVariables.cTime; delete copyVariables.cTime;
// delete external provider workflow variables // Delete special variables
Object.keys(removeObj).forEach((key) => { Object.keys(removeObj).forEach((key) => {
delete copyVariables[key]; delete copyVariables[key];
}); });
// Encrypt password variables
userVariablesConfigs.forEach((item) => {
const val = copyVariables[item.key];
if (item.type === VariableInputEnum.password && typeof val === 'string') {
copyVariables[item.key] = {
value: '',
secret: encryptSecret(val)
};
}
});
return copyVariables; return copyVariables;
}; };
export const filterSystemVariables = (variables: Record<string, any>): SystemVariablesType => { export const filterSystemVariables = (variables: Record<string, any>): SystemVariablesType => {
return { return {
userId: variables.userId, userId: variables.userId,
......
...@@ -3,7 +3,6 @@ import { countPromptTokens } from '../../common/string/tiktoken/index'; ...@@ -3,7 +3,6 @@ import { countPromptTokens } from '../../common/string/tiktoken/index';
import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
import { getSystemPluginByIdAndVersionId, getSystemTools } from '../app/plugin/controller'; import { getSystemPluginByIdAndVersionId, getSystemTools } from '../app/plugin/controller';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { parseI18nString } from '@fastgpt/global/common/i18n/utils'; import { parseI18nString } from '@fastgpt/global/common/i18n/utils';
import type { localeType } from '@fastgpt/global/common/i18n/type'; import type { localeType } from '@fastgpt/global/common/i18n/type';
......
import { pluginClient } from '.';
export const loadModelProviders = async () => {
const res = await pluginClient.model.getProviders();
if (res.status === 200) {
return res.body;
}
return Promise.reject(res.body);
};
import { getWorkerController, WorkerNameEnum } from './utils'; import { getWorkerController, WorkerNameEnum } from './utils';
export const preLoadWorker = async () => { export const preLoadWorker = async () => {
const max = Math.min(Number(global.systemEnv?.tokenWorkers || 30), 100); const start = Date.now();
const max = Math.min(Number(global.systemEnv?.tokenWorkers || 30), 500);
const workerController = getWorkerController({ const workerController = getWorkerController({
name: WorkerNameEnum.countGptMessagesTokens, name: WorkerNameEnum.countGptMessagesTokens,
maxReservedThreads: max maxReservedThreads: max
}); });
for await (const item of new Array(max).fill(0)) { // Batch size for concurrent loading
const worker = workerController.createWorker(); const batchSize = 5;
await workerController.run({
workerId: worker.id, for (let i = 0; i < max; i += batchSize) {
messages: [ const currentBatchSize = Math.min(batchSize, max - i);
{ const promises = [];
role: 'user',
content: '1' // Create batch of promises for concurrent loading
} for (let j = 0; j < currentBatchSize; j++) {
] const promise = (async () => {
}); const worker = workerController.createWorker();
console.log(`Preload worker ${workerController.workerQueue.length}`); await workerController.run({
workerId: worker.id,
messages: [
{
role: 'user',
content: '1'
}
]
});
})();
promises.push(promise);
}
// Wait for current batch to complete
await Promise.all(promises);
console.log('Preload worker', workerController.workerQueue.length);
} }
console.log('Preload worker success');
console.log('Preload worker success', workerController.workerQueue.length, Date.now() - start);
}; };
# Package 说明
该 package 存放应用模板。
\ No newline at end of file
{
"name": "@fastgpt/templates",
"version": "1.0.0",
"type": "module",
"dependencies": {},
"devDependencies": {
"@fastgpt/global": "workspace:*",
"@fastgpt/service": "workspace:*"
}
}
{
"name": "对话引导 + 变量",
"intro": "可以在对话开始发送一段提示,或者让用户填写一些内容,作为本次对话的变量",
"author": "",
"avatar": "core/workflow/template/systemConfig",
"tags": ["office-services"],
"type": "simple",
"weight": 1,
"workflow": {
"nodes": [
{
"nodeId": "userGuide",
"name": "系统配置",
"intro": "可以配置应用的系统参数",
"avatar": "/imgs/workflow/userGuide.png",
"flowNodeType": "userGuide",
"position": {
"x": 496.57560693988853,
"y": -490.7611729549753
},
"version": "481",
"inputs": [],
"outputs": []
},
{
"nodeId": "448745",
"name": "流程开始",
"intro": "",
"avatar": "/imgs/workflow/userChatInput.svg",
"flowNodeType": "workflowStart",
"position": {
"x": 558.4082376415505,
"y": 123.72387429194112
},
"version": "481",
"inputs": [
{
"key": "userChatInput",
"renderTypeList": ["FlowNodeInputTypeEnum.reference", "FlowNodeInputTypeEnum.textarea"],
"valueType": "string",
"label": "用户问题",
"required": true,
"toolDescription": "用户问题"
}
],
"outputs": [
{
"id": "userChatInput",
"key": "userChatInput",
"label": "core.module.input.label.user question",
"valueType": "string",
"type": "FlowNodeOutputTypeEnum.static"
}
]
},
{
"nodeId": "loOvhld2ZTKa",
"name": "AI 对话",
"intro": "AI 大模型对话",
"avatar": "/imgs/workflow/AI.png",
"flowNodeType": "chatNode",
"showStatus": true,
"position": {
"x": 1097.7317280958762,
"y": -244.16014496351386
},
"version": "481",
"inputs": [
{
"key": "model",
"renderTypeList": [
"FlowNodeInputTypeEnum.settingLLMModel",
"FlowNodeInputTypeEnum.reference"
],
"label": "core.module.input.label.aiModel",
"valueType": "string",
"value": "gpt-3.5-turbo"
},
{
"key": "temperature",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"value": 0,
"valueType": "number",
"min": 0,
"max": 10,
"step": 1
},
{
"key": "maxToken",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"value": 2000,
"valueType": "number",
"min": 100,
"max": 4000,
"step": 50
},
{
"key": "isResponseAnswerText",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"value": true,
"valueType": "boolean"
},
{
"key": "quoteTemplate",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"valueType": "string"
},
{
"key": "quotePrompt",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"valueType": "string"
},
{
"key": "systemPrompt",
"renderTypeList": ["FlowNodeInputTypeEnum.textarea", "FlowNodeInputTypeEnum.reference"],
"max": 3000,
"valueType": "string",
"label": "core.ai.Prompt",
"description": "core.app.tip.systemPromptTip",
"placeholder": "core.app.tip.chatNodeSystemPromptTip",
"value": "请直接将我的问题翻译成{{language}},不需要回答问题。"
},
{
"key": "history",
"renderTypeList": [
"FlowNodeInputTypeEnum.numberInput",
"FlowNodeInputTypeEnum.reference"
],
"valueType": "chatHistory",
"label": "core.module.input.label.chat history",
"required": true,
"min": 0,
"max": 30,
"value": 6
},
{
"key": "userChatInput",
"renderTypeList": ["FlowNodeInputTypeEnum.reference", "FlowNodeInputTypeEnum.textarea"],
"valueType": "string",
"label": "用户问题",
"required": true,
"toolDescription": "用户问题",
"value": ["448745", "userChatInput"]
},
{
"key": "quoteQA",
"renderTypeList": ["FlowNodeInputTypeEnum.settingDatasetQuotePrompt"],
"label": "",
"debugLabel": "知识库引用",
"description": "",
"valueType": "datasetQuote"
}
],
"outputs": [
{
"id": "history",
"key": "history",
"label": "core.module.output.label.New context",
"description": "core.module.output.description.New context",
"valueType": "chatHistory",
"type": "FlowNodeOutputTypeEnum.static"
},
{
"id": "answerText",
"key": "answerText",
"label": "core.module.output.label.Ai response content",
"description": "core.module.output.description.Ai response content",
"valueType": "string",
"type": "FlowNodeOutputTypeEnum.static"
}
]
}
],
"edges": [
{
"source": "448745",
"target": "loOvhld2ZTKa",
"sourceHandle": "448745-source-right",
"targetHandle": "loOvhld2ZTKa-target-left"
}
],
"chatConfig": {
"welcomeText": "你好,我可以为你翻译各种语言,请告诉我你需要翻译成什么语言?",
"scheduledTriggerConfig": {
"cronString": "",
"timezone": "Asia/Shanghai",
"defaultPrompt": ""
}
}
}
}
{
"name": "知识库+对话引导",
"intro": "每次提问时进行一次知识库搜索,将搜索结果注入 LLM 模型进行参考回答",
"author": "",
"avatar": "core/workflow/template/datasetSearch",
"type": "simple",
"tags": ["office-services"],
"workflow": {
"nodes": [
{
"nodeId": "userGuide",
"name": "系统配置",
"intro": "可以配置应用的系统参数",
"avatar": "core/workflow/template/systemConfig",
"flowNodeType": "userGuide",
"position": {
"x": 531.2422736065552,
"y": -486.7611729549753
},
"version": "481",
"inputs": [],
"outputs": []
},
{
"nodeId": "workflowStartNodeId",
"name": "流程开始",
"intro": "",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "workflowStart",
"position": {
"x": 558.4082376415505,
"y": 123.72387429194112
},
"version": "481",
"inputs": [
{
"key": "userChatInput",
"renderTypeList": ["FlowNodeInputTypeEnum.reference", "FlowNodeInputTypeEnum.textarea"],
"valueType": "string",
"label": "用户问题",
"required": true,
"toolDescription": "用户问题"
}
],
"outputs": [
{
"id": "userChatInput",
"key": "userChatInput",
"label": "core.module.input.label.user question",
"valueType": "string",
"type": "FlowNodeOutputTypeEnum.static"
}
]
},
{
"nodeId": "7BdojPlukIQw",
"name": "AI 对话",
"intro": "AI 大模型对话",
"avatar": "core/workflow/template/aiChat",
"flowNodeType": "chatNode",
"showStatus": true,
"position": {
"x": 1638.509551404687,
"y": -341.0428450861567
},
"version": "481",
"inputs": [
{
"key": "model",
"renderTypeList": [
"FlowNodeInputTypeEnum.settingLLMModel",
"FlowNodeInputTypeEnum.reference"
],
"label": "core.module.input.label.aiModel",
"valueType": "string",
"value": "gpt-5"
},
{
"key": "temperature",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"value": 3,
"valueType": "number",
"min": 0,
"max": 10,
"step": 1
},
{
"key": "maxToken",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"value": 1950,
"valueType": "number",
"min": 100,
"max": 4000,
"step": 50
},
{
"key": "isResponseAnswerText",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"value": true,
"valueType": "boolean"
},
{
"key": "quoteTemplate",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"valueType": "string"
},
{
"key": "quotePrompt",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"valueType": "string"
},
{
"key": "systemPrompt",
"renderTypeList": ["FlowNodeInputTypeEnum.textarea", "FlowNodeInputTypeEnum.reference"],
"max": 3000,
"valueType": "string",
"label": "core.ai.Prompt",
"description": "core.app.tip.systemPromptTip",
"placeholder": "core.app.tip.chatNodeSystemPromptTip",
"value": ""
},
{
"key": "history",
"renderTypeList": [
"FlowNodeInputTypeEnum.numberInput",
"FlowNodeInputTypeEnum.reference"
],
"valueType": "chatHistory",
"label": "core.module.input.label.chat history",
"required": true,
"min": 0,
"max": 30,
"value": 6
},
{
"key": "userChatInput",
"renderTypeList": ["FlowNodeInputTypeEnum.reference", "FlowNodeInputTypeEnum.textarea"],
"valueType": "string",
"label": "用户问题",
"required": true,
"toolDescription": "用户问题",
"value": ["workflowStartNodeId", "userChatInput"]
},
{
"key": "quoteQA",
"renderTypeList": ["FlowNodeInputTypeEnum.settingDatasetQuotePrompt"],
"label": "",
"debugLabel": "知识库引用",
"description": "",
"valueType": "datasetQuote",
"value": ["iKBoX2vIzETU", "quoteQA"]
}
],
"outputs": [
{
"id": "history",
"key": "history",
"label": "core.module.output.label.New context",
"description": "core.module.output.description.New context",
"valueType": "chatHistory",
"type": "FlowNodeOutputTypeEnum.static"
},
{
"id": "answerText",
"key": "answerText",
"label": "core.module.output.label.Ai response content",
"description": "core.module.output.description.Ai response content",
"valueType": "string",
"type": "FlowNodeOutputTypeEnum.static"
}
]
},
{
"nodeId": "iKBoX2vIzETU",
"name": "知识库搜索",
"intro": "调用\"语义检索\"\"全文检索\"能力,从\"知识库\"中查找可能与问题相关的参考内容",
"avatar": "core/workflow/template/datasetSearch",
"flowNodeType": "datasetSearchNode",
"showStatus": true,
"position": {
"x": 918.5901682164496,
"y": -227.11542247619582
},
"version": "481",
"inputs": [
{
"key": "datasets",
"renderTypeList": [
"FlowNodeInputTypeEnum.selectDataset",
"FlowNodeInputTypeEnum.reference"
],
"label": "core.module.input.label.Select dataset",
"value": [],
"valueType": "selectDataset",
"list": [],
"required": true
},
{
"key": "similarity",
"renderTypeList": ["FlowNodeInputTypeEnum.selectDatasetParamsModal"],
"label": "",
"value": 0.4,
"valueType": "number"
},
{
"key": "limit",
"renderTypeList": ["hidden"],
"label": "",
"value": 5000,
"valueType": "number"
},
{
"key": "searchMode",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"valueType": "string",
"value": "embedding"
},
{
"key": "usingReRank",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"valueType": "boolean",
"value": false
},
{
"key": "datasetSearchUsingExtensionQuery",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"valueType": "boolean",
"value": true
},
{
"key": "datasetSearchExtensionModel",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"valueType": "string"
},
{
"key": "datasetSearchExtensionBg",
"renderTypeList": ["FlowNodeInputTypeEnum.hidden"],
"label": "",
"valueType": "string",
"value": ""
},
{
"key": "userChatInput",
"renderTypeList": ["FlowNodeInputTypeEnum.reference", "FlowNodeInputTypeEnum.textarea"],
"valueType": "string",
"label": "用户问题",
"required": true,
"toolDescription": "需要检索的内容",
"value": ["workflowStartNodeId", "userChatInput"]
}
],
"outputs": [
{
"id": "quoteQA",
"key": "quoteQA",
"label": "core.module.Dataset quote.label",
"type": "FlowNodeOutputTypeEnum.static",
"valueType": "datasetQuote",
"description": "特殊数组格式,搜索结果为空时,返回空数组。"
}
]
}
],
"edges": [
{
"source": "workflowStartNodeId",
"target": "iKBoX2vIzETU",
"sourceHandle": "workflowStartNodeId-source-right",
"targetHandle": "iKBoX2vIzETU-target-left"
},
{
"source": "iKBoX2vIzETU",
"target": "7BdojPlukIQw",
"sourceHandle": "iKBoX2vIzETU-source-right",
"targetHandle": "7BdojPlukIQw-target-left"
}
]
}
}
{
"name": "知道时间的机器人",
"intro": "通过挂载时间插件,让模型获取当前最新时间",
"author": "",
"avatar": "core/workflow/template/getTime",
"tags": ["recommendation", "roleplay"],
"type": "simple",
"weight": 1,
"workflow": {
"nodes": [
{
"nodeId": "userGuide",
"name": "系统配置",
"intro": "可以配置应用的系统参数",
"avatar": "/imgs/workflow/userGuide.png",
"flowNodeType": "userGuide",
"position": {
"x": 531.2422736065552,
"y": -486.7611729549753
},
"version": "481",
"inputs": [],
"outputs": []
},
{
"nodeId": "workflowStartNodeId",
"name": "流程开始",
"intro": "",
"avatar": "/imgs/workflow/userChatInput.svg",
"flowNodeType": "workflowStart",
"position": {
"x": 558.4082376415505,
"y": 123.72387429194112
},
"version": "481",
"inputs": [
{
"key": "userChatInput",
"renderTypeList": ["reference", "textarea"],
"valueType": "string",
"label": "用户问题",
"required": true,
"toolDescription": "用户问题"
}
],
"outputs": [
{
"id": "userChatInput",
"key": "userChatInput",
"label": "core.module.input.label.user question",
"valueType": "string",
"type": "static"
}
]
},
{
"nodeId": "jrWPV9",
"name": "工具调用",
"intro": "通过AI模型自动选择一个或多个功能块进行调用,也可以对插件进行调用。",
"avatar": "/imgs/workflow/tool.svg",
"flowNodeType": "tools",
"showStatus": true,
"position": {
"x": 1062.1738942532802,
"y": -223.65033022650476
},
"version": "481",
"inputs": [
{
"key": "model",
"renderTypeList": ["settingLLMModel", "reference"],
"label": "core.module.input.label.aiModel",
"valueType": "string",
"llmModelType": "all",
"value": "gpt-3.5-turbo"
},
{
"key": "temperature",
"renderTypeList": ["hidden"],
"label": "",
"value": 0,
"valueType": "number",
"min": 0,
"max": 10,
"step": 1
},
{
"key": "maxToken",
"renderTypeList": ["hidden"],
"label": "",
"value": 2000,
"valueType": "number",
"min": 100,
"max": 4000,
"step": 50
},
{
"key": "systemPrompt",
"renderTypeList": ["textarea", "reference"],
"max": 3000,
"valueType": "string",
"label": "core.ai.Prompt",
"description": "core.app.tip.systemPromptTip",
"placeholder": "core.app.tip.chatNodeSystemPromptTip",
"value": ""
},
{
"key": "history",
"renderTypeList": ["numberInput", "reference"],
"valueType": "chatHistory",
"label": "core.module.input.label.chat history",
"description": "最多携带多少轮对话记录",
"required": true,
"min": 0,
"max": 30,
"value": 6
},
{
"key": "userChatInput",
"renderTypeList": ["reference", "textarea"],
"valueType": "string",
"label": "用户问题",
"required": true,
"value": ["workflowStartNodeId", "userChatInput"]
}
],
"outputs": [
{
"id": "answerText",
"key": "answerText",
"label": "core.module.output.label.Ai response content",
"description": "core.module.output.description.Ai response content",
"valueType": "string",
"type": "static"
}
]
},
{
"nodeId": "zBxjo5",
"name": "获取当前时间",
"intro": "获取用户当前时区的时间。",
"avatar": "/imgs/workflow/getCurrentTime.svg",
"flowNodeType": "pluginModule",
"showStatus": false,
"position": {
"x": 1000,
"y": 545
},
"version": "481",
"inputs": [],
"outputs": [
{
"id": "time",
"type": "static",
"key": "time",
"valueType": "string",
"label": "time",
"description": ""
}
],
"pluginId": "community-getTime"
}
],
"edges": [
{
"source": "workflowStartNodeId",
"target": "jrWPV9",
"sourceHandle": "workflowStartNodeId-source-right",
"targetHandle": "jrWPV9-target-left"
},
{
"source": "jrWPV9",
"target": "zBxjo5",
"sourceHandle": "selectedTools",
"targetHandle": "selectedTools"
}
],
"chatConfig": {
"scheduledTriggerConfig": {
"cronString": "",
"timezone": "Asia/Shanghai",
"defaultPrompt": ""
}
}
}
}
{
"compilerOptions": {
"target": "es2015",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": "."
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts", "../**/*.d.ts"],
"exclude": ["node_modules"]
}
import type { AppTemplateSchemaType } from '@fastgpt/global/core/app/type';
declare global {
var appTemplates: AppTemplateSchemaType[];
}
import React, { useState, useMemo, useRef, useEffect } from 'react';
import type { BoxProps } from '@chakra-ui/react';
import { Box, Card, Flex, useOutsideClick } from '@chakra-ui/react';
import { format } from 'date-fns';
import type { Matcher } from 'react-day-picker';
import { DayPicker } from 'react-day-picker';
import 'react-day-picker/dist/style.css';
import zhCN from 'date-fns/locale/zh-CN';
import MyIcon from '../Icon';
const DateTimePicker = ({
onChange,
popPosition = 'bottom',
defaultDate = new Date(),
selectedDateTime,
disabled,
...props
}: {
onChange?: (dateTime: Date) => void;
popPosition?: 'bottom' | 'top';
defaultDate?: Date;
selectedDateTime?: Date;
disabled?: Matcher[];
} & Omit<BoxProps, 'onChange'>) => {
const OutRangeRef = useRef(null);
const [selectedDate, setSelectedDate] = useState<Date | undefined>(
selectedDateTime || defaultDate
);
const [showSelected, setShowSelected] = useState(false);
useEffect(() => {
if (selectedDateTime) {
setSelectedDate(selectedDateTime);
}
}, [selectedDateTime]);
const formatSelected = useMemo(() => {
if (selectedDate) {
const dateStr = format(selectedDate, 'y/MM/dd');
return dateStr;
}
return format(new Date(), 'y/MM/dd');
}, [selectedDate]);
useOutsideClick({
ref: OutRangeRef,
handler: () => {
setShowSelected(false);
}
});
return (
<Box position={'relative'} ref={OutRangeRef}>
<Flex
border={'base'}
px={3}
pr={3}
py={1}
borderRadius={'sm'}
cursor={'pointer'}
bg={'myGray.50'}
fontSize={'sm'}
onClick={() => setShowSelected(true)}
alignItems={'center'}
{...props}
>
<Box color={'myGray.600'} fontWeight={'400'} flex={1}>
{formatSelected}
</Box>
<MyIcon ml={2} name={'date'} w={'16px'} color={'myGray.600'} />
</Flex>
{showSelected && (
<Card
position={'absolute'}
zIndex={1}
css={{
'--rdp-background-color': '#d6e8ff',
'--rdp-accent-color': '#0000ff'
}}
{...(popPosition === 'top'
? {
bottom: '40px'
}
: {})}
>
<DayPicker
locale={zhCN}
mode="single"
defaultMonth={selectedDate}
selected={selectedDate}
disabled={disabled}
onSelect={(date) => {
if (date) {
setSelectedDate(date);
onChange?.(date);
setShowSelected(false);
}
}}
/>
</Card>
)}
</Box>
);
};
export default DateTimePicker;
...@@ -271,14 +271,25 @@ export const iconPaths = { ...@@ -271,14 +271,25 @@ export const iconPaths = {
'core/workflow/inputType/array': () => import('./icons/core/workflow/inputType/array.svg'), 'core/workflow/inputType/array': () => import('./icons/core/workflow/inputType/array.svg'),
'core/workflow/inputType/customVariable': () => 'core/workflow/inputType/customVariable': () =>
import('./icons/core/workflow/inputType/customVariable.svg'), import('./icons/core/workflow/inputType/customVariable.svg'),
'core/workflow/inputType/dataset': () => import('./icons/core/workflow/inputType/dataset.svg'),
'core/workflow/inputType/timePointSelect': () =>
import('./icons/core/workflow/inputType/timePointSelect.svg'),
'core/workflow/inputType/timeRangeSelect': () =>
import('./icons/core/workflow/inputType/timeRangeSelect.svg'),
'core/workflow/inputType/dynamic': () => import('./icons/core/workflow/inputType/dynamic.svg'), 'core/workflow/inputType/dynamic': () => import('./icons/core/workflow/inputType/dynamic.svg'),
'core/workflow/inputType/external': () => import('./icons/core/workflow/inputType/external.svg'),
'core/workflow/inputType/file': () => import('./icons/core/workflow/inputType/file.svg'), 'core/workflow/inputType/file': () => import('./icons/core/workflow/inputType/file.svg'),
'core/workflow/inputType/input': () => import('./icons/core/workflow/inputType/input.svg'), 'core/workflow/inputType/input': () => import('./icons/core/workflow/inputType/input.svg'),
'core/workflow/inputType/internal': () => import('./icons/core/workflow/inputType/internal.svg'),
'core/workflow/inputType/jsonEditor': () => 'core/workflow/inputType/jsonEditor': () =>
import('./icons/core/workflow/inputType/jsonEditor.svg'), import('./icons/core/workflow/inputType/jsonEditor.svg'),
'core/workflow/inputType/model': () => import('./icons/core/workflow/inputType/model.svg'),
'core/workflow/inputType/multipleSelect': () =>
import('./icons/core/workflow/inputType/multipleSelect.svg'),
'core/workflow/inputType/numberInput': () => 'core/workflow/inputType/numberInput': () =>
import('./icons/core/workflow/inputType/numberInput.svg'), import('./icons/core/workflow/inputType/numberInput.svg'),
'core/workflow/inputType/option': () => import('./icons/core/workflow/inputType/option.svg'), 'core/workflow/inputType/option': () => import('./icons/core/workflow/inputType/option.svg'),
'core/workflow/inputType/password': () => import('./icons/core/workflow/inputType/password.svg'),
'core/workflow/inputType/reference': () => 'core/workflow/inputType/reference': () =>
import('./icons/core/workflow/inputType/reference.svg'), import('./icons/core/workflow/inputType/reference.svg'),
'core/workflow/inputType/select': () => import('./icons/core/workflow/inputType/select.svg'), 'core/workflow/inputType/select': () => import('./icons/core/workflow/inputType/select.svg'),
...@@ -419,6 +430,7 @@ export const iconPaths = { ...@@ -419,6 +430,7 @@ export const iconPaths = {
'model/cloudflare': () => import('./icons/model/cloudflare.svg'), 'model/cloudflare': () => import('./icons/model/cloudflare.svg'),
'model/cohere': () => import('./icons/model/cohere.svg'), 'model/cohere': () => import('./icons/model/cohere.svg'),
'model/coze': () => import('./icons/model/coze.svg'), 'model/coze': () => import('./icons/model/coze.svg'),
'model/huggingface': () => import('./icons/model/huggingface.svg'),
more: () => import('./icons/more.svg'), more: () => import('./icons/more.svg'),
moreLine: () => import('./icons/moreLine.svg'), moreLine: () => import('./icons/moreLine.svg'),
optimizer: () => import('./icons/optimizer.svg'), optimizer: () => import('./icons/optimizer.svg'),
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path d="M17.596 5.17271V4.75465L17.5955 4.71549C17.5505 2.77985 14.1671 1.66669 10.0001 1.66669C5.83276 1.66669 2.44882 2.76409 2.40417 4.71549V5.17271H2.40791C2.40542 5.20487 2.40417 5.23731 2.40417 5.27002C2.40417 6.73999 5.10674 7.93164 10.0001 7.93164C14.8934 7.93164 17.596 6.73999 17.596 5.27002C17.596 5.23732 17.5947 5.20488 17.5922 5.17271H17.596Z"/>
<path d="M2.40417 7.46899V10.4345C2.404 10.4415 2.40405 10.4485 2.40411 10.4554L2.40417 10.466C2.40417 11.9342 5.10674 13.1244 10.0001 13.1244C14.8934 13.1244 17.596 11.9342 17.596 10.466C17.596 10.4555 17.5958 10.445 17.5956 10.4345L17.596 7.46899H17.5437C17.1466 8.80744 14.4994 9.84077 10.0001 9.84077C5.50068 9.84077 2.85352 8.80744 2.45645 7.46899H2.40417Z"/>
<path d="M2.40417 12.3715V15.3524H2.40774C2.51867 17.0083 5.20952 18.3334 10.0001 18.3334C14.7906 18.3334 17.4815 17.0083 17.5924 15.3524H17.596V12.3715C17.596 13.9683 14.8934 15.2627 10.0001 15.2627C5.10674 15.2627 2.40417 13.9683 2.40417 12.3715Z"/>
</svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path d="M16.6723 15.5599C17.2705 15.56 17.7555 16.0448 17.7555 16.6431C17.7554 17.2413 17.2705 17.7262 16.6723 17.7262H3.32756C2.72934 17.7262 2.24445 17.2413 2.24438 16.6431C2.24438 16.0448 2.7293 15.56 3.32756 15.5599H16.6723ZM7.31681 3.30322C8.8057 3.30329 10.1139 4.27969 10.5427 5.69987L11.5819 4.48405C12.2224 3.7344 13.1594 3.30333 14.1454 3.30322H14.6833C15.3487 3.30323 15.8877 3.8423 15.8878 4.50765C15.8878 5.17304 15.3487 5.71207 14.6833 5.71208H14.1454C13.8642 5.71218 13.5973 5.83601 13.4146 6.0498L11.3272 8.49202L11.6755 9.73633C11.7919 10.1515 12.1705 10.4385 12.6016 10.4386H13.5058L13.6295 10.4451C14.2366 10.5071 14.7108 11.0197 14.711 11.6431C14.711 12.2667 14.2367 12.7798 13.6295 12.8418L13.5058 12.8483H12.6016C11.1954 12.8482 9.9499 11.9769 9.45467 10.6836L8.61564 11.6667C7.97512 12.4164 7.03824 12.8483 6.05216 12.8483H5.31567L5.19279 12.8418C4.58539 12.78 4.11125 12.2668 4.11125 11.6431C4.11148 11.0196 4.58554 10.507 5.19279 10.4451L5.31567 10.4386H6.05216C6.33331 10.4386 6.60025 10.3154 6.78296 10.1017L8.6604 7.90365L8.24373 6.4152C8.12742 5.99992 7.74807 5.71214 7.31681 5.71208H6.26945C5.60406 5.71208 5.06502 5.17304 5.06502 4.50765C5.06508 3.8423 5.60409 3.30322 6.26945 3.30322H7.31681Z" />
</svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 13" fill="none"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 16" >
<path fill-rule="evenodd" clip-rule="evenodd" <path fill-rule="evenodd" clip-rule="evenodd" d="M0.242676 2.98988C0.242676 1.51712 1.43658 0.323212 2.90934 0.323212H15.0908C16.5636 0.323212 17.7575 1.51712 17.7575 2.98988V13.0101C17.7575 14.4829 16.5636 15.6768 15.0908 15.6768H2.90934C1.43659 15.6768 0.242676 14.4828 0.242676 13.0101V2.98988ZM3.95217 4.0327V4.63745C3.95217 4.77104 3.84386 4.87935 3.71027 4.87935H2.98457C2.85098 4.87935 2.74268 4.77104 2.74268 4.63745V3.06511C2.74268 2.93151 2.85098 2.82321 2.98457 2.82321H8.54823C8.68183 2.82321 8.79013 2.93151 8.79013 3.06511V4.63745C8.79013 4.77104 8.68183 4.87935 8.54823 4.87935H7.82254C7.68894 4.87935 7.58064 4.77104 7.58064 4.63745V4.0327H6.37115V7.68837H7.07026C7.20385 7.68837 7.31215 7.79667 7.31215 7.93027V8.65596C7.31215 8.78956 7.20385 8.89786 7.07026 8.89786H5.7678L5.7664 8.89786L5.765 8.89786H4.46256C4.32896 8.89786 4.22066 8.78956 4.22066 8.65596V7.93027C4.22066 7.79667 4.32896 7.68837 4.46256 7.68837H5.16166V4.0327H3.95217ZM13.4238 13.8287C13.4238 13.7819 13.4418 13.7369 13.4742 13.7031L15.7042 11.3773C15.7466 11.3332 15.8051 11.3082 15.8663 11.3082C15.9903 11.3082 16.0908 11.4087 16.0908 11.5327V13.2101C16.0908 13.4901 16.0908 13.6301 16.0363 13.7371C15.9884 13.8312 15.9119 13.9077 15.8178 13.9556C15.7109 14.0101 15.5708 14.0101 15.2908 14.0101H13.6052C13.505 14.0101 13.4238 13.9289 13.4238 13.8287Z" />
d="M2.1 1.93982C1.21634 1.93982 0.5 2.65616 0.5 3.53982V9.46026C0.5 10.3439 1.21635 11.0603 2.1 11.0603H9.89076C10.7744 11.0603 11.4908 10.3439 11.4908 9.46026V3.53982C11.4908 2.65616 10.7744 1.93982 9.89076 1.93982H2.1ZM2.51667 4.54432C2.51667 4.26817 2.74053 4.04432 3.01667 4.04432H4.51156C4.7877 4.04432 5.01156 4.26817 5.01156 4.54432C5.01156 4.82046 4.7877 5.04432 4.51156 5.04432H4.21412V7.96631H4.51156C4.7877 7.96631 5.01156 8.19017 5.01156 8.46631C5.01156 8.74245 4.7877 8.96631 4.51156 8.96631H3.01667C2.74053 8.96631 2.51667 8.74245 2.51667 8.46631C2.51667 8.19017 2.74053 7.96631 3.01667 7.96631H3.31412L3.31412 5.04432H3.01667C2.74053 5.04432 2.51667 4.82046 2.51667 4.54432ZM6.63225 8.11713C6.43699 7.92186 6.1204 7.92186 5.92514 8.11713C5.72988 8.31239 5.72988 8.62897 5.92514 8.82423L5.92868 8.82777C6.12394 9.02303 6.44052 9.02303 6.63578 8.82777C6.83105 8.63251 6.83105 8.31592 6.63578 8.12066L6.63225 8.11713ZM8.39262 8.11713C8.19736 7.92186 7.88078 7.92186 7.68552 8.11713C7.49025 8.31239 7.49025 8.62897 7.68552 8.82423L7.68905 8.82777C7.88431 9.02303 8.2009 9.02303 8.39616 8.82777C8.59142 8.63251 8.59142 8.31592 8.39616 8.12066L8.39262 8.11713Z"
/>
</svg> </svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 16" fill="none">
<path d="M1.74992 0.499573C2.34817 0.499578 2.833 0.984513 2.83309 1.58274V14.4172C2.833 15.0154 2.34817 15.5004 1.74992 15.5004C1.15166 15.5004 0.666836 15.0154 0.666748 14.4172V1.58274C0.666835 0.984509 1.15166 0.499573 1.74992 0.499573Z" />
<path d="M16.5839 0.499573C17.182 0.499743 17.667 0.984626 17.6671 1.58274V14.4172C17.667 15.0154 17.182 15.5002 16.5839 15.5004C15.9856 15.5004 15.5 15.0155 15.4999 14.4172V1.58274C15.5 0.984521 15.9857 0.499573 16.5839 0.499573Z" />
<path d="M7.19181 3.65143C8.28863 3.65161 9.2518 4.38273 9.54614 5.43935L9.65682 5.83893L10.7856 4.51243C11.2498 3.96627 11.9307 3.65148 12.6475 3.65143H13.4109C14.0091 3.65149 14.494 4.13638 14.4941 4.7346C14.4939 5.33277 14.0091 5.81771 13.4109 5.81777H12.6475C12.5665 5.81782 12.4893 5.8537 12.4368 5.91543L10.3591 8.35927L10.8018 9.94863C10.8353 10.0682 10.9446 10.1512 11.0688 10.1513H12.344L12.4547 10.157C13.0007 10.2126 13.427 10.6738 13.4272 11.2344C13.427 11.7951 13.0007 12.2571 12.4547 12.3127L12.344 12.3176H11.0688C9.97195 12.3176 9.00896 11.587 8.71444 10.5305L8.66479 10.3531L7.72729 11.4566C7.26306 12.0027 6.5821 12.3176 5.86532 12.3176H4.92212L4.81144 12.3127C4.26532 12.2572 3.83906 11.7952 3.83895 11.2344C3.8391 10.6737 4.26538 10.2125 4.81144 10.157L4.92212 10.1513H5.86532C5.94641 10.1512 6.02352 10.1154 6.07609 10.0536L7.9633 7.83274L7.45874 6.02041C7.4254 5.90083 7.31592 5.81795 7.19181 5.81777H5.78638C5.18817 5.81774 4.70333 5.33278 4.70321 4.7346C4.70327 4.13637 5.18813 3.65146 5.78638 3.65143H7.19181Z" />
</svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
<path d="M13.2734 4.04181H14.2084C15.9343 4.04181 17.3334 5.44092 17.3334 7.16678V11.3334C17.3334 13.0593 15.9343 14.4584 14.2084 14.4584H3.79175C2.06587 14.4584 0.666748 13.0593 0.666748 11.3334V7.16678C0.666748 5.44092 2.06587 4.04181 3.79175 4.04181H4.72675L4.34616 1.75845C4.26104 1.24775 4.60608 0.764714 5.11679 0.679589C5.6275 0.594465 6.11054 0.939504 6.19566 1.45021L6.61233 3.95018C6.61746 3.98085 6.621 4.01143 6.62308 4.04181H11.3771C11.3792 4.01139 11.3827 3.98085 11.3878 3.95018L11.8045 1.45021C11.8896 0.939504 12.3727 0.594465 12.8834 0.679589C13.3941 0.764714 13.7391 1.24775 13.654 1.75845L13.2734 4.04181ZM4.75008 8.41677V9.25009C4.75008 9.76784 5.16983 10.1876 5.68758 10.1876C6.20533 10.1876 6.62508 9.76784 6.62508 9.25009V8.41677C6.62508 7.89902 6.20533 7.47928 5.68758 7.47928C5.16983 7.47928 4.75008 7.89902 4.75008 8.41677ZM11.3751 8.41677V9.25009C11.3751 9.76784 11.7948 10.1876 12.3126 10.1876C12.8303 10.1876 13.2501 9.76784 13.2501 9.25009V8.41677C13.2501 7.89902 12.8303 7.47928 12.3126 7.47928C11.7948 7.47928 11.3751 7.89902 11.3751 8.41677ZM4.83341 17.3334C4.31566 17.3334 3.89591 16.9136 3.89591 16.3959C3.89591 15.8781 4.31566 15.4584 4.83341 15.4584H13.1667C13.6845 15.4584 14.1042 15.8781 14.1042 16.3959C14.1042 16.9136 13.6845 17.3334 13.1667 17.3334H4.83341Z" />
</svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9169 1.66666C16.7577 1.66688 18.2502 3.15918 18.2502 4.99999V15C18.2502 16.8409 16.757 18.3333 14.9161 18.3333H4.9161C3.07533 18.3331 1.58276 16.8408 1.58276 15V4.99999C1.58276 3.15904 3.07596 1.66666 4.91691 1.66666H14.9169ZM14.9324 6.42821C14.6135 6.10946 14.0964 6.1094 13.7776 6.42821L8.36662 11.8392L6.22225 9.69481C5.90338 9.37594 5.38637 9.37601 5.06746 9.69481C4.74856 10.0137 4.74856 10.5307 5.06746 10.8496L7.78963 13.5718C8.10856 13.8904 8.6256 13.8906 8.94442 13.5718L14.9324 7.583C15.2512 7.26409 15.2513 6.7471 14.9324 6.42821Z" />
</svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 21"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.00033 2.22217C3.15938 2.22217 1.66699 3.71455 1.66699 5.5555V15.5555C1.66699 17.3965 3.15938 18.8888 5.00033 18.8888H15.0003C16.8413 18.8888 18.3337 17.3965 18.3337 15.5555V5.5555C18.3337 3.71455 16.8413 2.22217 15.0003 2.22217H5.00033ZM8.52689 7.35494C8.81978 7.06204 8.81978 6.58717 8.52689 6.29428C8.23399 6.00138 7.75912 6.00138 7.46623 6.29427L5.8429 7.9176L5.24853 7.32322C4.95563 7.03033 4.48076 7.03033 4.18787 7.32322C3.89497 7.61612 3.89497 8.09099 4.18786 8.38388L5.31032 9.50633C5.4569 9.65292 5.64907 9.72614 5.84119 9.726C6.03445 9.72702 6.22803 9.6538 6.37548 9.50634L8.52689 7.35494ZM16.0325 7.9003C16.0325 8.31452 15.6967 8.6503 15.2825 8.6503H10.7764C10.3622 8.6503 10.0264 8.31452 10.0264 7.9003C10.0264 7.48609 10.3622 7.1503 10.7764 7.1503L15.2825 7.1503C15.6967 7.1503 16.0325 7.48609 16.0325 7.9003ZM6.12591 15.3471C6.95434 15.3471 7.62591 14.6755 7.62591 13.8471C7.62591 13.0186 6.95434 12.3471 6.12591 12.3471C5.29748 12.3471 4.62591 13.0186 4.62591 13.8471C4.62591 14.6755 5.29748 15.3471 6.12591 15.3471ZM16.0325 13.8471C16.0325 14.2613 15.6967 14.5971 15.2825 14.5971H10.7764C10.3622 14.5971 10.0264 14.2613 10.0264 13.8471C10.0264 13.4329 10.3622 13.0971 10.7764 13.0971H15.2825C15.6967 13.0971 16.0325 13.4329 16.0325 13.8471Z" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M9.9161 1.7814C14.5184 1.7814 18.2502 5.51242 18.2502 10.1147C18.2502 14.7171 14.5185 18.4481 9.9161 18.4481C5.31389 18.4479 1.58276 14.717 1.58276 10.1147C1.58285 5.51255 5.31394 1.78161 9.9161 1.7814ZM14.9324 6.42821C14.6135 6.10946 14.0964 6.1094 13.7776 6.42821L8.36662 11.8392L6.22225 9.69481C5.90338 9.37594 5.38637 9.37601 5.06746 9.69481C4.74856 10.0137 4.74856 10.5307 5.06746 10.8496L7.78963 13.5718C8.10856 13.8904 8.6256 13.8906 8.94442 13.5718L14.9324 7.583C15.2512 7.26409 15.2513 6.7471 14.9324 6.42821Z" />
</svg> </svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 20" fill="none">
<path d="M4.57143 5.625C4.57143 3.76087 6.10629 2.25 8 2.25C9.89371 2.25 11.4286 3.76087 11.4286 5.625C11.4286 6.02212 11.3463 6.39675 11.2183 6.75H13.7143V5.625C13.7143 2.51888 11.1554 0 8 0C4.84343 0 2.28571 2.51775 2.28571 5.625V6.75C2.28571 6.75 3.32457 6.75 4.78171 6.75C4.65371 6.39562 4.57143 6.021 4.57143 5.625ZM13.7143 6.75H2.28571C1.02286 6.75 0 7.758 0 9V15.75C0 16.9931 1.02286 18 2.28571 18H13.7143C14.9771 18 16 16.992 16 15.75V9C16 7.758 14.9771 6.75 13.7143 6.75ZM9.14286 13.1873V14.625C9.14286 15.246 8.63086 15.75 8 15.75C7.36914 15.75 6.85714 15.246 6.85714 14.625V13.1884C6.17714 12.798 5.71429 12.0802 5.71429 11.25C5.71429 10.0069 6.73714 9 8 9C9.26286 9 10.2857 10.0069 10.2857 11.25C10.2857 12.0802 9.82286 12.798 9.14286 13.1873Z" />
</svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" >
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99992 0.41684C13.7404 0.41684 17.5831 4.25957 17.5831 9.00001C17.5831 13.7405 13.7404 17.5832 8.99992 17.5832C4.25948 17.5832 0.416748 13.7404 0.416748 9.00001C0.416754 4.25957 4.25948 0.416845 8.99992 0.41684ZM8.99992 3.51824C8.53977 3.51824 8.16674 3.89146 8.16658 4.35157V9.00001C8.16659 9.31565 8.34488 9.60429 8.6272 9.74545L11.7262 11.2949C12.1378 11.5006 12.6385 11.3338 12.8443 10.9222C13.0501 10.5106 12.8831 10.0099 12.4716 9.80405L9.83325 8.48487V4.35157C9.8331 3.89146 9.46006 3.51824 8.99992 3.51824Z" />
</svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
<path d="M9 0.419922C13.7387 0.41993 17.5801 4.26129 17.5801 9C17.5801 13.7387 13.7387 17.5801 9 17.5801C4.26126 17.5801 0.419922 13.7387 0.419922 9C0.419947 4.26128 4.26128 0.419922 9 0.419922ZM9 2.08691C5.18175 2.08691 2.08596 5.18176 2.08594 9C2.08594 12.8183 5.18174 15.9141 9 15.9141C12.8183 15.9141 15.9131 12.8183 15.9131 9C15.9131 5.18176 12.8182 2.08692 9 2.08691ZM9.39941 4.01562C10.1815 4.07832 10.9401 4.32451 11.6123 4.73633C12.3985 5.21813 13.0364 5.90886 13.4551 6.73047C13.8736 7.55199 14.0567 8.47343 13.9844 9.39258C13.9225 10.1785 13.6756 10.9369 13.2666 11.6064C13.1514 11.7946 12.9003 11.8337 12.7217 11.7041L9.16504 9.12012C9.06145 9.04486 9 8.92394 9 8.7959V4.40039C9 4.17956 9.17932 3.99813 9.39941 4.01562Z" />
</svg>
\ No newline at end of file
<svg t="1710841272884" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2684"
width="128" height="128">
<path
d="M511.968 959.936c298.688 0 447.968-200.576 447.968-448 0-247.36-149.28-447.936-448-447.936C213.28 64 64 264.576 64 511.968c0 247.392 149.248 447.968 447.968 447.968z"
fill="#FFB02E" p-id="2685"></path>
<path
d="M103.936 586.912a31.936 31.936 0 0 0-7.584 25.568 32 32 0 0 0-37.152 51.84l9.344 8a32 32 0 0 0-24.992 56.256l63.52 52.928-4.032-1.984a35.712 35.712 0 0 0-36.672 60.896C107.712 869.76 163.008 908.64 192 928c48 32 102.72 42.944 160 0 32-24 72.48-97.984 29.92-171.712-8.064-13.952-15.296-28.64-18.304-44.48-13.152-69.76-32.8-141.216-75.616-119.808-23.2 11.584-21.184 31.584-18.304 60 1.088 10.784 2.304 22.784 2.304 36l-2.56 1.28-120.384-105.376a32 32 0 0 0-45.12 3.04zM920.096 586.912c6.368 7.296 8.832 16.64 7.584 25.568a32 32 0 0 1 37.12 51.84l-9.344 8a32 32 0 0 1 25.024 56.256l-63.52 52.928 4.032-1.984a35.712 35.712 0 0 1 36.672 60.896C916.32 869.76 861.024 908.64 832 928c-48 32-102.752 42.944-160 0-32-24-72.48-97.984-29.92-171.712 8.064-13.952 15.296-28.64 18.304-44.48 13.152-69.76 32.8-141.216 75.616-119.808 23.2 11.584 21.184 31.584 18.304 60-1.088 10.784-2.304 22.784-2.304 36l2.56 1.28 120.384-105.376a32 32 0 0 1 45.12 3.04z"
fill="#FF822D" p-id="2686"></path>
<path
d="M224 464c0 44.16-28.64 80-64 80s-64-35.84-64-80 28.64-80 64-80 64 35.84 64 80zM928 464c0 44.16-28.64 80-64 80s-64-35.84-64-80 28.64-80 64-80 64 35.84 64 80z"
fill="#FF6723" p-id="2687"></path>
<path
d="M299.168 333.184c-6.72 7.296-10.24 17.024-11.744 24.928a32 32 0 0 1-62.848-12.224c2.848-14.592 9.92-36.896 27.456-55.968C270.496 269.792 298.112 256 336 256c38.24 0 65.984 14.464 84.352 34.624 17.408 19.104 24.64 41.344 27.2 55.904a32 32 0 0 1-63.072 10.944 49.472 49.472 0 0 0-11.456-23.744C367.04 327.104 356.544 320 336 320c-20.896 0-31.104 6.944-36.832 13.184zM651.2 333.184c-6.72 7.296-10.24 17.024-11.776 24.928a32 32 0 0 1-62.816-12.224c2.816-14.592 9.92-36.896 27.424-55.968C622.496 269.792 650.112 256 688 256c38.272 0 65.984 14.464 84.352 34.624 17.408 19.104 24.64 41.344 27.2 55.904a32 32 0 0 1-63.072 10.944 49.44 49.44 0 0 0-11.456-23.744C719.04 327.104 708.544 320 688 320c-20.896 0-31.072 6.944-36.8 13.184zM313.6 492.8a32 32 0 1 0-51.2 38.4c22.464 29.952 96.256 92.8 249.6 92.8s227.136-62.848 249.6-92.8a32 32 0 0 0-51.2-38.4c-9.536 12.704-63.744 67.2-198.4 67.2s-188.864-54.496-198.4-67.2z"
fill="#402A32" p-id="2688"></path>
</svg>
\ No newline at end of file
...@@ -26,7 +26,6 @@ import { textToEditorState } from '../../Textarea/PromptEditor/utils'; ...@@ -26,7 +26,6 @@ import { textToEditorState } from '../../Textarea/PromptEditor/utils';
import { SingleLinePlugin } from '../../Textarea/PromptEditor/plugins/SingleLinePlugin'; import { SingleLinePlugin } from '../../Textarea/PromptEditor/plugins/SingleLinePlugin';
import OnBlurPlugin from '../../Textarea/PromptEditor/plugins/OnBlurPlugin'; import OnBlurPlugin from '../../Textarea/PromptEditor/plugins/OnBlurPlugin';
import VariablePlugin from '../../Textarea/PromptEditor/plugins/VariablePlugin'; import VariablePlugin from '../../Textarea/PromptEditor/plugins/VariablePlugin';
import VariablePickerPlugin from '../../Textarea/PromptEditor/plugins/VariablePickerPlugin';
import FocusPlugin from '../../Textarea/PromptEditor/plugins/FocusPlugin'; import FocusPlugin from '../../Textarea/PromptEditor/plugins/FocusPlugin';
import VariableLabelPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin'; import VariableLabelPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin';
import { VariableLabelNode } from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin/node'; import { VariableLabelNode } from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin/node';
......
import React from 'react'; import React from 'react';
import type { EditorState, LexicalEditor } from 'lexical'; import type { EditorState, LexicalEditor } from 'lexical';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { editorStateToText } from '../../Textarea/PromptEditor/utils';
import { import {
type EditorVariableLabelPickerType, type EditorVariableLabelPickerType,
type EditorVariablePickerType type EditorVariablePickerType
} from '../../Textarea/PromptEditor/type'; } from '../../Textarea/PromptEditor/type';
import Editor from './Editor'; import Editor from './Editor';
import { editorStateToText } from '../../Textarea/PromptEditor/utils';
const HttpInput = ({ const HttpInput = ({
variables = [], variables = [],
......
...@@ -18,10 +18,20 @@ type Props = Omit<NumberInputProps, 'onChange' | 'onBlur'> & { ...@@ -18,10 +18,20 @@ type Props = Omit<NumberInputProps, 'onChange' | 'onBlur'> & {
register?: UseFormRegister<any>; register?: UseFormRegister<any>;
name?: string; name?: string;
inputFieldProps?: NumberInputFieldProps; inputFieldProps?: NumberInputFieldProps;
hideStepper?: boolean;
}; };
const MyNumberInput = (props: Props) => { const MyNumberInput = (props: Props) => {
const { register, name, onChange, onBlur, placeholder, inputFieldProps, ...restProps } = props; const {
register,
name,
onChange,
onBlur,
placeholder,
inputFieldProps,
hideStepper = false,
...restProps
} = props;
return ( return (
<NumberInput <NumberInput
...@@ -91,14 +101,16 @@ const MyNumberInput = (props: Props) => { ...@@ -91,14 +101,16 @@ const MyNumberInput = (props: Props) => {
: {})} : {})}
{...inputFieldProps} {...inputFieldProps}
/> />
<NumberInputStepper> {!hideStepper && (
<NumberIncrementStepper> <NumberInputStepper>
<MyIcon name={'core/chat/chevronUp'} width={'12px'} /> <NumberIncrementStepper>
</NumberIncrementStepper> <MyIcon name={'core/chat/chevronUp'} width={'12px'} />
<NumberDecrementStepper> </NumberIncrementStepper>
<MyIcon name={'core/chat/chevronDown'} width={'12px'} /> <NumberDecrementStepper>
</NumberDecrementStepper> <MyIcon name={'core/chat/chevronDown'} width={'12px'} />
</NumberInputStepper> </NumberDecrementStepper>
</NumberInputStepper>
)}
</NumberInput> </NumberInput>
); );
}; };
......
...@@ -22,6 +22,7 @@ import type { useScrollPagination } from '../../../hooks/useScrollPagination'; ...@@ -22,6 +22,7 @@ import type { useScrollPagination } from '../../../hooks/useScrollPagination';
import MyDivider from '../MyDivider'; import MyDivider from '../MyDivider';
import { shadowLight } from '../../../styles/theme'; import { shadowLight } from '../../../styles/theme';
import { isArray } from 'lodash'; import { isArray } from 'lodash';
import { useMount } from 'ahooks';
const menuItemStyles: MenuItemProps = { const menuItemStyles: MenuItemProps = {
borderRadius: 'sm', borderRadius: 'sm',
...@@ -83,7 +84,6 @@ const MultipleSelect = <T = any,>({ ...@@ -83,7 +84,6 @@ const MultipleSelect = <T = any,>({
tagStyle, tagStyle,
...props ...props
}: SelectProps<T>) => { }: SelectProps<T>) => {
const ref = useRef<HTMLButtonElement>(null);
const SearchInputRef = useRef<HTMLInputElement>(null); const SearchInputRef = useRef<HTMLInputElement>(null);
const tagsContainerRef = useRef<HTMLDivElement>(null); const tagsContainerRef = useRef<HTMLDivElement>(null);
...@@ -116,7 +116,7 @@ const MultipleSelect = <T = any,>({ ...@@ -116,7 +116,7 @@ const MultipleSelect = <T = any,>({
onSelect(newValue); onSelect(newValue);
} }
}, },
[inputValue, value, isSelectAll, onSelect] [inputValue, value, onSelect]
); );
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {
...@@ -124,40 +124,6 @@ const MultipleSelect = <T = any,>({ ...@@ -124,40 +124,6 @@ const MultipleSelect = <T = any,>({
} }
}, [isOpen]); }, [isOpen]);
useEffect(() => {
const getWidth = (w: any) =>
typeof w === 'number' ? w : typeof w === 'string' ? parseInt(w) : 0;
const totalWidth = getWidth(props.w) || 200;
const tagWidth = getWidth(tagStyle?.w) || 60;
const formLabelWidth = formLabel ? formLabel.length * 8 + 20 : 0;
const availableWidth = totalWidth - formLabelWidth - 40;
const overflowWidth = 30;
if (availableWidth <= 0) {
setVisibleItems(selectedItems.length > 0 ? [selectedItems[0]] : []);
setOverflowItems(selectedItems.slice(1));
return;
}
const { count } = selectedItems.reduce(
(acc, item, i) => {
const remain = selectedItems.length - i - 1;
const needOverflow = remain > 0 ? overflowWidth : 0;
if (acc.used + tagWidth + needOverflow <= availableWidth) {
return {
used: acc.used + tagWidth,
count: i + 1
};
}
return acc;
},
{ used: 0, count: 0 }
);
setVisibleItems(selectedItems.slice(0, count));
setOverflowItems(selectedItems.slice(count));
}, [selectedItems, isOpen, props.w, tagStyle, formLabel]);
const onclickItem = useCallback( const onclickItem = useCallback(
(val: T) => { (val: T) => {
if (isSelectAll) { if (isSelectAll) {
...@@ -172,15 +138,128 @@ const MultipleSelect = <T = any,>({ ...@@ -172,15 +138,128 @@ const MultipleSelect = <T = any,>({
onSelect([...value, val]); onSelect([...value, val]);
} }
}, },
[value, isSelectAll, onSelect, setIsSelectAll] [isSelectAll, value, onSelect, list, setIsSelectAll]
); );
const onSelectAll = useCallback(() => { const onSelectAll = useCallback(() => {
const hasSelected = isSelectAll || value.length > 0; onSelect(isSelectAll ? [] : list.map((item) => item.value));
onSelect(hasSelected ? [] : list.map((item) => item.value));
setIsSelectAll?.((state) => !state); setIsSelectAll?.((state) => !state);
}, [value, list, setIsSelectAll, onSelect]); }, [isSelectAll, onSelect, list, setIsSelectAll]);
// 动态长度计算器 - 计算一行能展示多少个tag,剩余用+n表示
const calculateLayout = useCallback(() => {
if (!tagsContainerRef.current || selectedItems.length === 0) {
setVisibleItems(selectedItems);
setOverflowItems([]);
return;
}
const containerWidth = tagsContainerRef.current.offsetWidth;
const tagGap = 4; // tag之间的gap
const overflowIndicatorWidth = 30; // "+n" 宽度
const formLabelWidth = formLabel ? formLabel.length * 8 + 20 : 0;
// 实际可用宽度
const availableWidth = containerWidth - formLabelWidth - 10;
// 如果只有一个项目,直接显示
if (selectedItems.length === 1) {
setVisibleItems(selectedItems);
setOverflowItems([]);
return;
}
// 创建临时元素来测量每个tag的实际宽度
const measureTagWidth = (item: any): number => {
// 如果有tagStyle.w,优先使用
if (tagStyle?.w) {
return typeof tagStyle.w === 'number' ? tagStyle.w : parseInt(String(tagStyle.w)) || 60;
}
// 否则根据文本长度估算(更精确)
const text = String(item.label || item.value);
const baseWidth = 16; // 基础padding
const charWidth = 8; // 每个字符约8px
const closeIconWidth = closeable ? 20 : 0; // 关闭按钮宽度
return baseWidth + text.length * charWidth + closeIconWidth;
};
// 确保至少显示1个tag
const firstTagWidth = measureTagWidth(selectedItems[0]);
// 如果连第一个tag都放不下,也要强制显示
if (availableWidth < firstTagWidth) {
setVisibleItems([selectedItems[0]]);
setOverflowItems(selectedItems.slice(1));
return;
}
// 精确计算每个tag的宽度
let usedWidth = 0;
let visibleCount = 0;
for (let i = 0; i < selectedItems.length; i++) {
const currentTagWidth = measureTagWidth(selectedItems[i]);
const currentGap = i > 0 ? tagGap : 0;
const remainingItems = selectedItems.length - i - 1;
const needsOverflow = remainingItems > 0;
const overflowSpace = needsOverflow ? overflowIndicatorWidth + tagGap : 0;
const totalNeeded = usedWidth + currentTagWidth + currentGap + overflowSpace;
if (totalNeeded <= availableWidth) {
usedWidth += currentTagWidth + currentGap;
visibleCount = i + 1;
} else {
break;
}
}
// 保证至少显示1个tag
if (visibleCount === 0) {
visibleCount = 1;
}
setVisibleItems(selectedItems.slice(0, visibleCount));
setOverflowItems(selectedItems.slice(visibleCount));
}, [closeable, formLabel, selectedItems, tagStyle?.w]);
// 动态监听容器宽度变化并重新计算布局
useEffect(() => {
if (!tagsContainerRef.current) return;
// 创建 ResizeObserver 监听容器宽度变化
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
// 当容器宽度发生变化时,触发重新计算
requestAnimationFrame(() => {
calculateLayout();
});
}
});
// 开始监听容器
resizeObserver.observe(tagsContainerRef.current);
// 初始计算
requestAnimationFrame(() => {
calculateLayout();
});
// 清理监听器
return () => {
resizeObserver.disconnect();
};
}, [calculateLayout]);
// 当选中项目、样式等发生变化时重新计算
useEffect(() => {
requestAnimationFrame(() => {
calculateLayout();
});
}, [calculateLayout]);
const ListRender = useMemo(() => { const ListRender = useMemo(() => {
return ( return (
...@@ -215,7 +294,7 @@ const MultipleSelect = <T = any,>({ ...@@ -215,7 +294,7 @@ const MultipleSelect = <T = any,>({
})} })}
</> </>
); );
}, [value, list, isSelectAll]); }, [list, isSelectAll, value, onclickItem]);
return ( return (
<Box h={'100%'} w={'100%'}> <Box h={'100%'} w={'100%'}>
...@@ -230,7 +309,6 @@ const MultipleSelect = <T = any,>({ ...@@ -230,7 +309,6 @@ const MultipleSelect = <T = any,>({
> >
<MenuButton <MenuButton
as={Flex} as={Flex}
ref={ref}
px={3} px={3}
alignItems={'center'} alignItems={'center'}
borderRadius={'md'} borderRadius={'md'}
......
...@@ -9,20 +9,26 @@ ...@@ -9,20 +9,26 @@
import { useMemo, useState, useTransition } from 'react'; import { useMemo, useState, useTransition } from 'react';
import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin'; import { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
import { ListPlugin } from '@lexical/react/LexicalListPlugin';
import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
import { ListItemNode, ListNode } from '@lexical/list';
import { CodeHighlightNode, CodeNode } from '@lexical/code';
import VariableLabelPickerPlugin from './plugins/VariableLabelPickerPlugin'; import VariableLabelPickerPlugin from './plugins/VariableLabelPickerPlugin';
import ListDisplayFixPlugin from './plugins/ListDisplayFixPlugin';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import styles from './index.module.scss'; import styles from './index.module.scss';
import VariablePlugin from './plugins/VariablePlugin'; import VariablePlugin from './plugins/VariablePlugin';
import { VariableNode } from './plugins/VariablePlugin/node'; import { VariableNode } from './plugins/VariablePlugin/node';
import type { EditorState, LexicalEditor } from 'lexical'; import type { EditorState, LexicalEditor } from 'lexical';
import OnBlurPlugin from './plugins/OnBlurPlugin'; import OnBlurPlugin from './plugins/OnBlurPlugin';
import MyIcon from '../../Icon'; import type { FormPropsType } from './type';
import type { FormPropsType } from './type.d'; import { type EditorVariableLabelPickerType, type EditorVariablePickerType } from './type';
import { type EditorVariableLabelPickerType, type EditorVariablePickerType } from './type.d';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import FocusPlugin from './plugins/FocusPlugin'; import FocusPlugin from './plugins/FocusPlugin';
import { textToEditorState } from './utils'; import { textToEditorState } from './utils';
...@@ -31,8 +37,38 @@ import { VariableLabelNode } from './plugins/VariableLabelPlugin/node'; ...@@ -31,8 +37,38 @@ import { VariableLabelNode } from './plugins/VariableLabelPlugin/node';
import VariableLabelPlugin from './plugins/VariableLabelPlugin'; import VariableLabelPlugin from './plugins/VariableLabelPlugin';
import { useDeepCompareEffect } from 'ahooks'; import { useDeepCompareEffect } from 'ahooks';
import VariablePickerPlugin from './plugins/VariablePickerPlugin'; import VariablePickerPlugin from './plugins/VariablePickerPlugin';
import MarkdownPlugin from './plugins/MarkdownPlugin';
import MyIcon from '../../Icon';
import TabToSpacesPlugin from './plugins/TabToSpacesPlugin';
import ListExitPlugin from './plugins/ListExitPlugin';
const Placeholder = ({ children }: { children: React.ReactNode }) => (
<Box
position={'absolute'}
top={0}
left={0}
right={0}
bottom={0}
py={3}
px={3.5}
pointerEvents={'none'}
overflow={'hidden'}
>
<Box
color={'myGray.400'}
fontSize={'mini'}
userSelect={'none'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
h={'100%'}
>
{children}
</Box>
</Box>
);
export type EditorProps = { export type EditorProps = {
isRichText?: boolean;
variables?: EditorVariablePickerType[]; variables?: EditorVariablePickerType[];
variableLabels?: EditorVariableLabelPickerType[]; variableLabels?: EditorVariableLabelPickerType[];
value?: string; value?: string;
...@@ -50,6 +86,7 @@ export type EditorProps = { ...@@ -50,6 +86,7 @@ export type EditorProps = {
}; };
export default function Editor({ export default function Editor({
isRichText = false,
minH = 200, minH = 200,
maxH = 400, maxH = 400,
maxLength, maxLength,
...@@ -71,7 +108,7 @@ export default function Editor({ ...@@ -71,7 +108,7 @@ export default function Editor({
onOpenModal?: () => void; onOpenModal?: () => void;
onChange: (editorState: EditorState, editor: LexicalEditor) => void; onChange: (editorState: EditorState, editor: LexicalEditor) => void;
onChangeText?: ((text: string) => void) | undefined; onChangeText?: ((text: string) => void) | undefined;
onBlur: (editor: LexicalEditor) => void; onBlur?: (editor: LexicalEditor) => void;
}) { }) {
const [key, setKey] = useState(getNanoid(6)); const [key, setKey] = useState(getNanoid(6));
const [_, startSts] = useTransition(); const [_, startSts] = useTransition();
...@@ -79,8 +116,17 @@ export default function Editor({ ...@@ -79,8 +116,17 @@ export default function Editor({
const [scrollHeight, setScrollHeight] = useState(0); const [scrollHeight, setScrollHeight] = useState(0);
const initialConfig = { const initialConfig = {
namespace: 'promptEditor', namespace: isRichText ? 'richPromptEditor' : 'promptEditor',
nodes: [VariableNode, VariableLabelNode], nodes: [
VariableNode,
VariableLabelNode,
HeadingNode,
ListNode,
ListItemNode,
QuoteNode,
CodeNode,
CodeHighlightNode
],
editorState: textToEditorState(value), editorState: textToEditorState(value),
onError: (error: Error) => { onError: (error: Error) => {
throw error; throw error;
...@@ -125,59 +171,75 @@ export default function Editor({ ...@@ -125,59 +171,75 @@ export default function Editor({
borderRadius={'md'} borderRadius={'md'}
> >
<LexicalComposer initialConfig={initialConfig} key={key}> <LexicalComposer initialConfig={initialConfig} key={key}>
<PlainTextPlugin {/* Text type */}
contentEditable={ {isRichText ? (
<ContentEditable <RichTextPlugin
className={isInvalid ? styles.contentEditable_invalid : styles.contentEditable} contentEditable={
style={{ <ContentEditable
minHeight: `${minH}px`, className={`${isInvalid ? styles.contentEditable_invalid : styles.contentEditable} ${styles.richText}`}
maxHeight: `${maxH}px` style={{
}} minHeight: `${minH}px`,
/> maxHeight: `${maxH}px`
} }}
placeholder={ onFocus={() => setFocus(true)}
<Box onBlur={() => setFocus(false)}
position={'absolute'} />
top={0} }
left={0} placeholder={<Placeholder>{placeholder}</Placeholder>}
right={0} ErrorBoundary={LexicalErrorBoundary}
bottom={0} />
py={3} ) : (
px={3.5} <PlainTextPlugin
pointerEvents={'none'} contentEditable={
overflow={'hidden'} <ContentEditable
> className={isInvalid ? styles.contentEditable_invalid : styles.contentEditable}
<Box style={{
color={'myGray.400'} minHeight: `${minH}px`,
fontSize={'mini'} maxHeight: `${maxH}px`
userSelect={'none'} }}
whiteSpace={'pre-wrap'} />
wordBreak={'break-all'} }
h={'100%'} placeholder={<Placeholder>{placeholder}</Placeholder>}
> ErrorBoundary={LexicalErrorBoundary}
{placeholder} />
</Box> )}
</Box>
} {/* Basic Plugin */}
ErrorBoundary={LexicalErrorBoundary} <>
/> <HistoryPlugin />
<HistoryPlugin /> <MaxLengthPlugin maxLength={maxLength || 999999} />
<MaxLengthPlugin maxLength={maxLength || 999999} /> <FocusPlugin focus={focus} setFocus={setFocus} />
<FocusPlugin focus={focus} setFocus={setFocus} />
<OnChangePlugin <VariablePlugin variables={variables} />
onChange={(editorState, editor) => { {variableLabels.length > 0 && (
const rootElement = editor.getRootElement(); <>
setScrollHeight(rootElement?.scrollHeight || 0); <VariableLabelPlugin variables={variableLabels} />
startSts(() => { <VariableLabelPickerPlugin variables={variableLabels} isFocus={focus} />
onChange?.(editorState, editor); </>
}); )}
}} {variableLabels.length > 0 && <VariablePickerPlugin variables={variables} />}
/> <OnBlurPlugin onBlur={onBlur} />
<VariableLabelPlugin variables={variableLabels} /> <ListDisplayFixPlugin />
<VariablePlugin variables={variables} /> <OnChangePlugin
<VariableLabelPickerPlugin variables={variableLabels} isFocus={focus} /> onChange={(editorState, editor) => {
<VariablePickerPlugin variables={variableLabels.length > 0 ? [] : variables} /> const rootElement = editor.getRootElement();
<OnBlurPlugin onBlur={onBlur} /> setScrollHeight(rootElement?.scrollHeight || 0);
startSts(() => {
onChange?.(editorState, editor);
});
}}
/>
{isRichText && (
<>
{/* <ListPlugin />
<CheckListPlugin />
<ListExitPlugin /> */}
<TabToSpacesPlugin />
{/* <MarkdownPlugin /> */}
</>
)}
</>
</LexicalComposer> </LexicalComposer>
{onChangeText && {onChangeText &&
......
...@@ -76,3 +76,14 @@ ...@@ -76,3 +76,14 @@
color: var(--chakra-colors-primary-600); color: var(--chakra-colors-primary-600);
padding: 0 2px; padding: 0 2px;
} }
.richText {
ul,
ol {
padding-left: 16px;
li::marker {
color: var(--chakra-colors-primary-600);
}
}
}
import { Box, Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react'; import { Box, Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react';
import React, { useMemo } from 'react'; import React, { useMemo, useCallback } from 'react';
import { editorStateToText } from './utils'; import { editorStateToText } from './utils';
import type { EditorProps } from './Editor'; import type { EditorProps } from './Editor';
import Editor from './Editor'; import Editor from './Editor';
import MyModal from '../../MyModal'; import MyModal from '../../MyModal';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import type { EditorState, LexicalEditor } from 'lexical'; import type { EditorState, LexicalEditor } from 'lexical';
import type { FormPropsType } from './type.d'; import type { FormPropsType } from './type';
import { useCallback } from 'react';
const PromptEditor = ({ const PromptEditor = ({
showOpenModal = true, showOpenModal = true,
...@@ -34,18 +33,27 @@ const PromptEditor = ({ ...@@ -34,18 +33,27 @@ const PromptEditor = ({
}, },
[onChange] [onChange]
); );
const onBlurInput = useCallback( const onBlurInput = useCallback(
(editor: LexicalEditor) => { (editor: LexicalEditor) => {
const text = editorStateToText(editor); if (onBlur) {
onBlur?.(text); const text = editorStateToText(editor);
onBlur(text);
}
}, },
[onBlur] [onBlur]
); );
const formattedValue = useMemo(() => { const formattedValue = useMemo(() => {
if (typeof value === 'object') { if (typeof value === 'object') {
return JSON.stringify(value); return JSON.stringify(value);
} }
return value;
if (value === undefined || value === null) {
return '';
}
return String(value || '');
}, [value]); }, [value]);
return ( return (
...@@ -74,6 +82,7 @@ const PromptEditor = ({ ...@@ -74,6 +82,7 @@ const PromptEditor = ({
/> />
)} )}
</Box> </Box>
<MyModal <MyModal
isOpen={isOpen} isOpen={isOpen}
onClose={onClose} onClose={onClose}
...@@ -102,4 +111,5 @@ const PromptEditor = ({ ...@@ -102,4 +111,5 @@ const PromptEditor = ({
</> </>
); );
}; };
export default React.memo(PromptEditor); export default React.memo(PromptEditor);
import { Box, Flex } from '@chakra-ui/react';
import { type EditorVariablePickerType } from '../../type';
import MyIcon from '../../../../Icon';
import React, { useCallback, useEffect } from 'react';
export default function DropDownMenu({
variables,
setDropdownValue
}: {
variables: EditorVariablePickerType[];
setDropdownValue?: (value: string) => void;
}) {
const [highlightedIndex, setHighlightedIndex] = React.useState(0);
const handleKeyDown = useCallback(
(event: any) => {
if (event.keyCode === 38) {
setHighlightedIndex((prevIndex) => Math.max(prevIndex - 1, 0));
} else if (event.keyCode === 40) {
setHighlightedIndex((prevIndex) => Math.min(prevIndex + 1, variables.length - 1));
} else if (event.keyCode === 13 && variables[highlightedIndex]?.key) {
setDropdownValue?.(variables[highlightedIndex].key);
}
},
[highlightedIndex, variables]
);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [handleKeyDown]);
return variables.length ? (
<Box
bg={'white'}
boxShadow={'lg'}
borderWidth={'1px'}
borderColor={'borderColor.base'}
p={2}
borderRadius={'md'}
position={'absolute'}
top={'100%'}
w={'auto'}
zIndex={99999}
maxH={'300px'}
overflow={'auto'}
className="nowheel"
>
{variables.map((item, index) => (
<Flex
alignItems={'center'}
as={'li'}
key={item.key}
px={4}
py={2}
borderRadius={'sm'}
cursor={'pointer'}
maxH={'300px'}
overflow={'auto'}
_notLast={{
mb: 2
}}
{...(highlightedIndex === index
? {
bg: 'primary.50',
color: 'primary.600'
}
: {
bg: 'white',
color: 'myGray.600'
})}
onMouseDown={(e) => {
e.preventDefault();
setDropdownValue?.(item.key);
}}
onMouseEnter={() => {
setHighlightedIndex(index);
}}
>
<MyIcon name={(item.icon as any) || 'core/modules/variable'} w={'14px'} />
<Box ml={2} fontSize={'sm'}>
{item.key}
{item.key !== item.label && `(${item.label})`}
</Box>
</Flex>
))}
</Box>
) : null;
}
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect } from 'react';
export default function ListDisplayFixPlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext();
useEffect(() => {
const fixListDisplay = () => {
const rootElement = editor.getRootElement();
if (!rootElement) return;
const allListItems = rootElement.querySelectorAll('li');
allListItems.forEach((li) => {
const htmlLi = li as HTMLLIElement;
// Check if this li only contains a sublist without direct text content
const hasDirectText = Array.from(htmlLi.childNodes).some((node) => {
return node.nodeType === Node.TEXT_NODE && node.textContent?.trim();
});
const hasSpan = htmlLi.querySelector(':scope > span');
const hasOnlySublist =
htmlLi.children.length === 1 &&
(htmlLi.children[0].tagName === 'UL' || htmlLi.children[0].tagName === 'OL');
// If this li only contains a sublist without text content, hide its marker
if (!hasDirectText && !hasSpan && hasOnlySublist) {
// Only hide the marker, don't adjust position, let CSS handle indentation
htmlLi.style.listStyle = 'none';
htmlLi.style.paddingLeft = '0';
htmlLi.style.marginLeft = '0';
// Keep normal indentation for sublists
const sublist = htmlLi.children[0] as HTMLElement;
sublist.style.marginTop = '0';
sublist.style.marginBottom = '0';
// Don't modify marginLeft and paddingLeft, let CSS handle it
} else {
htmlLi.style.listStyle = '';
htmlLi.style.paddingLeft = '';
htmlLi.style.marginLeft = '';
if (
htmlLi.children[0] &&
(htmlLi.children[0].tagName === 'UL' || htmlLi.children[0].tagName === 'OL')
) {
const sublist = htmlLi.children[0] as HTMLElement;
sublist.style.marginTop = '';
sublist.style.marginBottom = '';
}
}
});
};
const removeListener = editor.registerUpdateListener(() => {
setTimeout(fixListDisplay, 10);
});
setTimeout(fixListDisplay, 10);
return removeListener;
}, [editor]);
return null;
}
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect } from 'react';
import {
$getSelection,
$isRangeSelection,
COMMAND_PRIORITY_HIGH,
KEY_ENTER_COMMAND,
KEY_BACKSPACE_COMMAND,
$createParagraphNode
} from 'lexical';
import { $isListItemNode, $isListNode } from '@lexical/list';
export default function ListExitPlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext();
useEffect(() => {
const handleEnterKey = () => {
let handled = false;
editor.update(() => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return;
}
const anchorNode = selection.anchor.getNode();
const listItemNode = anchorNode.getParent();
if ($isListItemNode(listItemNode)) {
// Check if the list item is empty
const textContent = listItemNode.getTextContent().trim();
if (textContent === '') {
// Remove the empty list item and exit list mode
const listNode = listItemNode.getParent();
if ($isListNode(listNode)) {
// If this is the only item in the list, remove the entire list
if (listNode.getChildrenSize() === 1) {
listNode.remove();
} else {
// Remove just this list item
listItemNode.remove();
}
// Insert a paragraph after the list to exit list mode
const paragraph = $createParagraphNode();
if (listNode && !listNode.isAttached()) {
// If we removed the entire list, replace it with a paragraph
listNode.getParent()?.append(paragraph);
} else {
// Insert paragraph after the list
listNode?.insertAfter(paragraph);
}
paragraph.select();
handled = true;
}
}
}
});
return handled;
};
const handleBackspaceKey = (event: KeyboardEvent) => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
const anchorNode = selection.anchor.getNode();
const listItemNode = anchorNode.getParent();
if ($isListItemNode(listItemNode)) {
// Check if cursor is at the beginning of an empty list item
const textContent = listItemNode.getTextContent().trim();
const cursorOffset = selection.anchor.offset;
// Only handle empty list items with cursor at the beginning
if (textContent === '' && cursorOffset === 0) {
// Prevent default backspace behavior
event.preventDefault();
event.stopPropagation();
editor.update(() => {
const listNode = listItemNode.getParent();
if ($isListNode(listNode)) {
// Create a new paragraph
const paragraph = $createParagraphNode();
// Always insert after the current list item and remove it
// This ensures the paragraph appears at the current position
listItemNode.insertAfter(paragraph);
listItemNode.remove();
// If the list is now empty, remove it
if (listNode.getChildrenSize() === 0) {
listNode.remove();
}
// Focus the new paragraph
paragraph.select();
}
});
return true;
}
}
return false;
};
// Register the keyboard event handlers
const removeEnterListener = editor.registerCommand(
KEY_ENTER_COMMAND,
handleEnterKey,
COMMAND_PRIORITY_HIGH
);
const removeBackspaceListener = editor.registerCommand(
KEY_BACKSPACE_COMMAND,
handleBackspaceKey,
COMMAND_PRIORITY_HIGH
);
return () => {
removeEnterListener();
removeBackspaceListener();
};
}, [editor]);
return null;
}
import type { JSX } from 'react';
import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin';
import * as React from 'react';
import { RICH_PROMPT_TRANSFORMERS } from '../MarkdownTransformers';
export default function MarkdownPlugin(): JSX.Element {
return <MarkdownShortcutPlugin transformers={RICH_PROMPT_TRANSFORMERS} />;
}
import {
CHECK_LIST,
ELEMENT_TRANSFORMERS,
TEXT_FORMAT_TRANSFORMERS,
type Transformer
} from '@lexical/markdown';
export const RICH_PROMPT_TRANSFORMERS: Array<Transformer> = [
CHECK_LIST,
...ELEMENT_TRANSFORMERS,
...TEXT_FORMAT_TRANSFORMERS
];
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import {
KEY_TAB_COMMAND,
COMMAND_PRIORITY_EDITOR,
$getSelection,
$isRangeSelection,
$isTextNode
} from 'lexical';
import { $createTextNode } from 'lexical';
import { $isListNode, $isListItemNode } from '@lexical/list';
import { useEffect } from 'react';
export default function TabToSpacesPlugin(): null {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerCommand(
KEY_TAB_COMMAND,
(event) => {
try {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
// Check if we're in a list context
let isInList = false;
try {
const nodes = selection.getNodes();
isInList = nodes.some((node) => {
// Check if current node or any of its ancestors is a list or list item
let currentNode = node;
while (currentNode) {
try {
if ($isListNode(currentNode) || $isListItemNode(currentNode)) {
return true;
}
// @ts-ignore
currentNode = currentNode.getParent();
} catch (e) {
// If node is no longer valid, break the loop
break;
}
}
return false;
});
} catch (e) {
// If we can't get nodes safely, assume we're not in a list
isInList = false;
}
// If we're in a list, let the built-in list indentation handle it
if (isInList) {
return false;
}
// Only handle tab for non-list contexts
event.preventDefault();
const isShiftTab = event.shiftKey;
// Handle Shift+Tab (outdent)
if (isShiftTab) {
if (!selection.isCollapsed()) {
// For selected text, remove 4 spaces from the beginning of each line
try {
const selectedText = selection.getTextContent();
const lines = selectedText.split('\n');
const outdentedText = lines
.map((line) => {
// Remove up to 4 spaces from the beginning of the line
if (line.startsWith(' ')) {
return line.slice(4);
} else if (line.startsWith(' ')) {
return line.slice(3);
} else if (line.startsWith(' ')) {
return line.slice(2);
} else if (line.startsWith(' ')) {
return line.slice(1);
}
return line;
})
.join('\n');
// Insert the outdented text and let Lexical handle cursor positioning
selection.insertText(outdentedText);
// Schedule selection restoration in the next update cycle
setTimeout(() => {
editor.update(() => {
const currentSelection = $getSelection();
if ($isRangeSelection(currentSelection) && !currentSelection.isCollapsed()) {
// Selection is already maintained, do nothing
return;
}
// If selection was lost, try to select the inserted text
if ($isRangeSelection(currentSelection)) {
const currentOffset = currentSelection.anchor.offset;
const selectionStart = Math.max(0, currentOffset - outdentedText.length);
currentSelection.anchor.set(
currentSelection.anchor.key,
selectionStart,
'text'
);
currentSelection.focus.set(currentSelection.focus.key, currentOffset, 'text');
}
});
}, 0);
return true;
} catch (e) {
// If operation fails, do nothing
return true;
}
} else {
// For cursor position, try to remove spaces before cursor
try {
const anchorNode = selection.anchor.getNode();
const anchorOffset = selection.anchor.offset;
if ($isTextNode(anchorNode)) {
const textContent = anchorNode.getTextContent();
const beforeCursor = textContent.slice(0, anchorOffset);
const afterCursor = textContent.slice(anchorOffset);
// Check if there are spaces before cursor to remove
let spacesToRemove = 0;
for (let i = beforeCursor.length - 1; i >= 0 && spacesToRemove < 4; i--) {
if (beforeCursor[i] === ' ') {
spacesToRemove++;
} else {
break;
}
}
if (spacesToRemove > 0) {
const newTextContent =
beforeCursor.slice(0, beforeCursor.length - spacesToRemove) + afterCursor;
anchorNode.setTextContent(newTextContent);
selection.anchor.set(
anchorNode.getKey(),
anchorOffset - spacesToRemove,
'text'
);
selection.focus.set(anchorNode.getKey(), anchorOffset - spacesToRemove, 'text');
}
}
return true;
} catch (e) {
return true;
}
}
} else {
// Handle regular Tab (indent)
if (!selection.isCollapsed()) {
try {
const selectedText = selection.getTextContent();
const lines = selectedText.split('\n');
const indentedText = lines.map((line) => ' ' + line).join('\n');
// Insert the indented text and let Lexical handle cursor positioning
selection.insertText(indentedText);
// Schedule selection restoration in the next update cycle
setTimeout(() => {
editor.update(() => {
const currentSelection = $getSelection();
if ($isRangeSelection(currentSelection) && !currentSelection.isCollapsed()) {
// Selection is already maintained, do nothing
return;
}
// If selection was lost, try to select the inserted text
if ($isRangeSelection(currentSelection)) {
const currentOffset = currentSelection.anchor.offset;
const selectionStart = Math.max(0, currentOffset - indentedText.length);
currentSelection.anchor.set(
currentSelection.anchor.key,
selectionStart,
'text'
);
currentSelection.focus.set(currentSelection.focus.key, currentOffset, 'text');
}
});
}, 0);
return true;
} catch (e) {
// If selection operation fails, fall back to simple space insertion
const textNode = $createTextNode(' ');
selection.insertNodes([textNode]);
return true;
}
} else {
// For cursor position (no selection), insert 4 spaces
const textNode = $createTextNode(' '); // 4 spaces
selection.insertNodes([textNode]);
return true;
}
}
} catch (e) {
// If anything fails, just let the default behavior handle it
console.warn('TabToSpacesPlugin error:', e);
return false;
}
},
COMMAND_PRIORITY_EDITOR
);
}, [editor]);
return null;
}
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
import type { DecoratorNode, Klass, LexicalEditor, LexicalNode } from 'lexical'; import type { DecoratorNode, Klass, LexicalEditor, LexicalNode } from 'lexical';
import type { EntityMatch } from '@lexical/text'; import type { EntityMatch } from '@lexical/text';
import { $createTextNode, $getRoot, $isTextNode, TextNode } from 'lexical'; import { $createTextNode, $isTextNode, TextNode } from 'lexical';
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { VariableLabelNode } from './plugins/VariableLabelPlugin/node'; import type { VariableLabelNode } from './plugins/VariableLabelPlugin/node';
import type { VariableNode } from './plugins/VariablePlugin/node'; import type { VariableNode } from './plugins/VariablePlugin/node';
...@@ -209,36 +209,9 @@ export function textToEditorState(text = '') { ...@@ -209,36 +209,9 @@ export function textToEditorState(text = '') {
}); });
} }
export function editorStateToText(editor: LexicalEditor) {
const editorStateTextString: string[] = [];
const paragraphs = editor.getEditorState().toJSON().root.children;
paragraphs.forEach((paragraph: any) => {
const children = paragraph.children;
const paragraphText: string[] = [];
children.forEach((child: any) => {
if (child.type === 'linebreak') {
paragraphText.push(`
`);
} else if (child.text) {
paragraphText.push(child.text);
} else if (child.type === 'variableLabel') {
paragraphText.push(child.variableKey);
} else if (child.type === 'Variable') {
paragraphText.push(child.variableKey);
}
});
editorStateTextString.push(paragraphText.join(''));
});
return editorStateTextString.join(`
`);
}
const varRegex = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g; const varRegex = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
export const getVars = (value: string) => { export const getVars = (value: string) => {
if (!value) return []; if (!value) return [];
// .filter((item) => {
// return ![CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT].includes(item)
// })
const keys = const keys =
value value
.match(varRegex) .match(varRegex)
...@@ -292,3 +265,23 @@ export function useBasicTypeaheadTriggerMatch( ...@@ -292,3 +265,23 @@ export function useBasicTypeaheadTriggerMatch(
[maxLength, minLength, trigger] [maxLength, minLength, trigger]
); );
} }
export function editorStateToText(editor: LexicalEditor) {
const editorStateTextString: string[] = [];
const paragraphs = editor.getEditorState().toJSON().root.children;
paragraphs.forEach((paragraph: any) => {
const children = paragraph.children || [];
const paragraphText: string[] = [];
children.forEach((child: any) => {
if (child.type === 'linebreak') {
paragraphText.push('\n');
} else if (child.text) {
paragraphText.push(child.text);
} else if (child.type === 'variableLabel' || child.type === 'Variable') {
paragraphText.push(child.variableKey);
}
});
editorStateTextString.push(paragraphText.join(''));
});
return editorStateTextString.join('\n');
}
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { i18nT } from '../../i18n/utils'; import { i18nT } from '../../i18n/utils';
import type { PluginGroupSchemaType, TGroupType } from '../../../service/core/app/plugin/type'; import type { SystemToolGroupSchemaType } from '../../../service/core/app/plugin/type';
import { AppTemplateTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTemplateTypeEnum } from '@fastgpt/global/core/app/constants';
import { type TemplateTypeSchemaType } from '@fastgpt/global/core/app/type'; import { type TemplateTypeSchemaType } from '@fastgpt/global/core/app/type';
export const systemPluginTemplateList: TGroupType[] = [ export const workflowSystemNodeTemplateList: {
{
typeId: FlowNodeTemplateTypeEnum.tools,
typeName: i18nT('app:tool_type_tools')
},
{
typeId: FlowNodeTemplateTypeEnum.search,
typeName: i18nT('app:tool_type_search')
},
{
typeId: FlowNodeTemplateTypeEnum.multimodal,
typeName: i18nT('app:tool_type_multimodal')
},
{
typeId: FlowNodeTemplateTypeEnum.productivity,
typeName: i18nT('app:tool_type_productivity')
},
{
typeId: FlowNodeTemplateTypeEnum.scientific,
typeName: i18nT('app:tool_type_scientific')
},
{
typeId: FlowNodeTemplateTypeEnum.finance,
typeName: i18nT('app:tool_type_finance')
},
{
typeId: FlowNodeTemplateTypeEnum.design,
typeName: i18nT('app:tool_type_design')
},
{
typeId: FlowNodeTemplateTypeEnum.news,
typeName: i18nT('app:tool_type_news')
},
{
typeId: FlowNodeTemplateTypeEnum.entertainment,
typeName: i18nT('app:tool_type_entertainment')
},
{
typeId: FlowNodeTemplateTypeEnum.communication,
typeName: i18nT('app:tool_type_communication')
},
{
typeId: FlowNodeTemplateTypeEnum.social,
typeName: i18nT('app:tool_type_social')
},
{
typeId: FlowNodeTemplateTypeEnum.other,
typeName: i18nT('common:Other')
}
];
export const workflowNodeTemplateList: {
type: string; type: string;
label: string; label: string;
}[] = [ }[] = [
...@@ -67,28 +16,27 @@ export const workflowNodeTemplateList: { ...@@ -67,28 +16,27 @@ export const workflowNodeTemplateList: {
type: FlowNodeTemplateTypeEnum.ai, type: FlowNodeTemplateTypeEnum.ai,
label: i18nT('common:core.module.template.AI function') label: i18nT('common:core.module.template.AI function')
}, },
{ {
type: FlowNodeTemplateTypeEnum.interactive, type: FlowNodeTemplateTypeEnum.interactive,
label: i18nT('common:core.workflow.template.Interactive') label: i18nT('common:core.workflow.template.Interactive')
}, },
...systemPluginTemplateList.map((item) => ({
type: item.typeId,
label: item.typeName
})),
{ {
type: FlowNodeTemplateTypeEnum.teamApp, type: FlowNodeTemplateTypeEnum.tools,
label: '' label: i18nT('app:tool_type_tools')
},
{
type: FlowNodeTemplateTypeEnum.other,
label: i18nT('common:Other')
} }
]; ];
export const defaultGroup: PluginGroupSchemaType = { export const defaultGroup: SystemToolGroupSchemaType = {
groupId: 'systemPlugin', groupId: 'systemPlugin',
groupAvatar: 'core/app/type/pluginLight', groupAvatar: 'core/app/type/pluginLight',
groupName: i18nT('common:core.module.template.System Plugin'), groupName: i18nT('common:core.module.template.System Plugin'),
groupOrder: 0, groupOrder: 0,
groupTypes: systemPluginTemplateList groupTypes: [] // from getPluginGroups
}; };
export const defaultTemplateTypes: TemplateTypeSchemaType[] = [ export const defaultTemplateTypes: TemplateTypeSchemaType[] = [
......
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
"chat_debug": "Chat Preview", "chat_debug": "Chat Preview",
"chat_logs": "Logs", "chat_logs": "Logs",
"chat_logs_tips": "Logs will record the online, shared, and API (requires chatId) conversation records of this app.", "chat_logs_tips": "Logs will record the online, shared, and API (requires chatId) conversation records of this app.",
"common.day": "day",
"config_ai_model_params": "Click to configure AI model related properties", "config_ai_model_params": "Click to configure AI model related properties",
"config_file_upload": "Click to Configure File Upload Rules", "config_file_upload": "Click to Configure File Upload Rules",
"config_question_guide": "Configuration guess you want to ask", "config_question_guide": "Configuration guess you want to ask",
...@@ -246,16 +247,14 @@ ...@@ -246,16 +247,14 @@
"templateMarket.Search_template": "Search Template", "templateMarket.Search_template": "Search Template",
"templateMarket.Use": "Use", "templateMarket.Use": "Use",
"templateMarket.no_intro": "No introduction yet~", "templateMarket.no_intro": "No introduction yet~",
"templateMarket.templateTags.Image_generation": "Image Generation",
"templateMarket.templateTags.Office_services": "Office Services",
"templateMarket.templateTags.Recommendation": "Recommendation", "templateMarket.templateTags.Recommendation": "Recommendation",
"templateMarket.templateTags.Roleplay": "Roleplay",
"templateMarket.templateTags.Web_search": "Web Search",
"templateMarket.templateTags.Writing": "Writing",
"templateMarket.template_guide": "Guide", "templateMarket.template_guide": "Guide",
"template_market": "Templates", "template_market": "Templates",
"template_market_description": "Explore more features in the template market, with configuration tutorials and usage guides to help you understand and get started with various applications.", "template_market_description": "Explore more features in the template market, with configuration tutorials and usage guides to help you understand and get started with various applications.",
"template_market_empty_data": "No suitable templates found", "template_market_empty_data": "No suitable templates found",
"time_granularity": "Time granularity",
"time_range_limit": "Time range limit",
"time_type": "Time Type",
"time_zone": "Time Zone", "time_zone": "Time Zone",
"too_to_active": "Active", "too_to_active": "Active",
"tool_active_manual_config_desc": "The temporary key is saved in this application and is only for use by this application.", "tool_active_manual_config_desc": "The temporary key is saved in this application and is only for use by this application.",
...@@ -266,16 +265,6 @@ ...@@ -266,16 +265,6 @@
"tool_input_param_tip": "This plugin requires configuration of related information to run properly.", "tool_input_param_tip": "This plugin requires configuration of related information to run properly.",
"tool_not_active": "This tool has not been activated yet", "tool_not_active": "This tool has not been activated yet",
"tool_run_free": "This tool runs without points consumption", "tool_run_free": "This tool runs without points consumption",
"tool_type_communication": "Communication",
"tool_type_design": "design",
"tool_type_entertainment": "Business",
"tool_type_finance": "finance",
"tool_type_multimodal": "Multimodal",
"tool_type_news": "news",
"tool_type_productivity": "productive forces",
"tool_type_scientific": "research",
"tool_type_search": "Search",
"tool_type_social": "Social",
"tool_type_tools": "tool", "tool_type_tools": "tool",
"tools_no_description": "This tool has not been introduced ~", "tools_no_description": "This tool has not been introduced ~",
"transition_to_workflow": "Convert to Workflow", "transition_to_workflow": "Convert to Workflow",
...@@ -313,7 +302,8 @@ ...@@ -313,7 +302,8 @@
"un_auth": "No permission", "un_auth": "No permission",
"upload_file_max_amount": "Maximum File Quantity", "upload_file_max_amount": "Maximum File Quantity",
"upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation", "upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation",
"variable.select type_desc": "You can define a global variable that does not need to be filled in by the user.\n\nThe value of this variable can come from the API interface, the Query of the shared link, or assigned through the [Variable Update] module.", "variable.internal_type_desc": "Use only inside the workflow and will not appear in the dialog box",
"variable.select type_desc": "The input box will be displayed in the site conversation and run preview, and this variable will not be displayed in the sharing link.",
"variable.textarea_type_desc": "Allows users to input up to 4000 characters in the dialogue box.", "variable.textarea_type_desc": "Allows users to input up to 4000 characters in the dialogue box.",
"variable_name_required": "Required variable name", "variable_name_required": "Required variable name",
"variable_repeat": "This variable name has been occupied and cannot be used", "variable_repeat": "This variable name has been occupied and cannot be used",
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
"input_guide_tip": "You can set up some preset questions. When the user inputs a question, related questions from these presets will be suggested.", "input_guide_tip": "You can set up some preset questions. When the user inputs a question, related questions from these presets will be suggested.",
"input_placeholder_phone": "Please enter your question", "input_placeholder_phone": "Please enter your question",
"insert_input_guide,_some_data_already_exists": "Duplicate data detected, automatically filtered, {{len}} items inserted", "insert_input_guide,_some_data_already_exists": "Duplicate data detected, automatically filtered, {{len}} items inserted",
"internal_variables_tip": "Internal variables are visible only when debugging",
"invalid_share_url": "Invalid sharing link", "invalid_share_url": "Invalid sharing link",
"is_chatting": "Chatting in progress... please wait until it finishes", "is_chatting": "Chatting in progress... please wait until it finishes",
"items": "Items", "items": "Items",
...@@ -159,7 +160,7 @@ ...@@ -159,7 +160,7 @@
"stream_output": "Stream Output", "stream_output": "Stream Output",
"unsupported_file_type": "Unsupported file types", "unsupported_file_type": "Unsupported file types",
"upload": "Upload", "upload": "Upload",
"variable_invisable_in_share": "Custom variables are not visible in login-free links", "variable_invisable_in_share": "External variables are not visible in login-free links",
"view_citations": "View References", "view_citations": "View References",
"web_site_sync": "Web Site Sync" "web_site_sync": "Web Site Sync"
} }
...@@ -601,6 +601,7 @@ ...@@ -601,6 +601,7 @@
"core.module.Max Length": "Max Length", "core.module.Max Length": "Max Length",
"core.module.Max Length placeholder": "Maximum length of input text", "core.module.Max Length placeholder": "Maximum length of input text",
"core.module.Max Value": "Max Value", "core.module.Max Value": "Max Value",
"core.module.Min Length": "Minimum number of digits",
"core.module.Min Value": "Min Value", "core.module.Min Value": "Min Value",
"core.module.QueryExtension.placeholder": "For example:\nQuestions about the introduction and use of Python.\nThe current conversation is related to the game 'GTA5'.", "core.module.QueryExtension.placeholder": "For example:\nQuestions about the introduction and use of Python.\nThe current conversation is related to the game 'GTA5'.",
"core.module.Select app": "Select App", "core.module.Select app": "Select App",
...@@ -727,10 +728,16 @@ ...@@ -727,10 +728,16 @@
"core.workflow.inputType.Manual input": "Manual Input", "core.workflow.inputType.Manual input": "Manual Input",
"core.workflow.inputType.Manual select": "Manual Select", "core.workflow.inputType.Manual select": "Manual Select",
"core.workflow.inputType.Reference": "Variable Reference", "core.workflow.inputType.Reference": "Variable Reference",
"core.workflow.inputType.custom": "Custom Variable", "core.workflow.inputType.custom": "External Variable",
"core.workflow.inputType.dynamicTargetInput": "Dynamic External Data", "core.workflow.inputType.dynamicTargetInput": "Dynamic External Data",
"core.workflow.inputType.file": "File upload",
"core.workflow.inputType.input": "Single Line Input Box", "core.workflow.inputType.input": "Single Line Input Box",
"core.workflow.inputType.internal": "Internal variables",
"core.workflow.inputType.jsonEditor": "JSON Editor",
"core.workflow.inputType.modelSelect": "Dialogue Model Selection",
"core.workflow.inputType.multipleSelect": "Multiple selection boxes",
"core.workflow.inputType.number input": "Number Input Box", "core.workflow.inputType.number input": "Number Input Box",
"core.workflow.inputType.password": "password",
"core.workflow.inputType.select": "Single Select Box", "core.workflow.inputType.select": "Single Select Box",
"core.workflow.inputType.selectApp": "App Select", "core.workflow.inputType.selectApp": "App Select",
"core.workflow.inputType.selectDataset": "Dataset Select", "core.workflow.inputType.selectDataset": "Dataset Select",
...@@ -738,6 +745,8 @@ ...@@ -738,6 +745,8 @@
"core.workflow.inputType.switch": "Switch", "core.workflow.inputType.switch": "Switch",
"core.workflow.inputType.textInput": "Text Input box", "core.workflow.inputType.textInput": "Text Input box",
"core.workflow.inputType.textarea": "Multi-line Input Box", "core.workflow.inputType.textarea": "Multi-line Input Box",
"core.workflow.inputType.timePointSelect": "Time point",
"core.workflow.inputType.timeRangeSelect": "Time range",
"core.workflow.publish.OnRevert version": "Click to Revert to This Version", "core.workflow.publish.OnRevert version": "Click to Revert to This Version",
"core.workflow.publish.OnRevert version confirm": "Confirm to Revert to This Version? The configuration of the editing version will be saved, and a new release version will be created for the reverted version.", "core.workflow.publish.OnRevert version confirm": "Confirm to Revert to This Version? The configuration of the editing version will be saved, and a new release version will be created for the reverted version.",
"core.workflow.publish.histories": "Release Records", "core.workflow.publish.histories": "Release Records",
...@@ -802,6 +811,7 @@ ...@@ -802,6 +811,7 @@
"date_1_month": "1 Month", "date_1_month": "1 Month",
"date_3_months": "3 Months", "date_3_months": "3 Months",
"date_6_months": "6 Months", "date_6_months": "6 Months",
"day": "Day",
"deep_rag_search": "In-depth search", "deep_rag_search": "In-depth search",
"delete_api": "Are you sure you want to delete this API key? \nAfter deletion, the key will become invalid immediately and the corresponding conversation log will not be deleted. Please confirm!", "delete_api": "Are you sure you want to delete this API key? \nAfter deletion, the key will become invalid immediately and the corresponding conversation log will not be deleted. Please confirm!",
"delete_failed": "Deletion Failed", "delete_failed": "Deletion Failed",
...@@ -844,6 +854,8 @@ ...@@ -844,6 +854,8 @@
"had_auth_value": "Filled in", "had_auth_value": "Filled in",
"has_verification": "Verified, Click to Unbind", "has_verification": "Verified, Click to Unbind",
"have_done": "Completed", "have_done": "Completed",
"hour": "Hour",
"hour_unit": "h",
"import_failed": "Import Failed", "import_failed": "Import Failed",
"import_success": "Imported Successfully", "import_success": "Imported Successfully",
"info.buy_extra": "Buy Extra Package", "info.buy_extra": "Buy Extra Package",
...@@ -881,8 +893,11 @@ ...@@ -881,8 +893,11 @@
"max_quote_tokens_tips": "The maximum number of tokens in a single search, about 1 character in Chinese = 1.7 tokens, and about 1 character in English = 1 token", "max_quote_tokens_tips": "The maximum number of tokens in a single search, about 1 character in Chinese = 1.7 tokens, and about 1 character in English = 1 token",
"mcp_server": "MCP Services", "mcp_server": "MCP Services",
"member": "member", "member": "member",
"min_length": "At least {{minLenth}} characters are required",
"min_similarity": "lowest correlation", "min_similarity": "lowest correlation",
"min_similarity_tip": "The relevance of different index models is different. Please select the appropriate value through search testing. \nWhen using Result Rearrange , use the rearranged results for filtering.", "min_similarity_tip": "The relevance of different index models is different. Please select the appropriate value through search testing. \nWhen using Result Rearrange , use the rearranged results for filtering.",
"minute": "Minute",
"minute_unit": "m",
"model.billing": "Billing", "model.billing": "Billing",
"model.model_type": "Model type", "model.model_type": "Model type",
"model.name": "Model name", "model.name": "Model name",
...@@ -1007,6 +1022,8 @@ ...@@ -1007,6 +1022,8 @@
"save_success": "Saved Successfully", "save_success": "Saved Successfully",
"scan_code": "Scan the QR code to pay", "scan_code": "Scan the QR code to pay",
"search_tool": "Search Tools", "search_tool": "Search Tools",
"second": "Second",
"second_unit": "s",
"secret_key": "Secret", "secret_key": "Secret",
"secret_tips": "The value will not return plaintext again after saving", "secret_tips": "The value will not return plaintext again after saving",
"select_count_num": "{{num}} item selected", "select_count_num": "{{num}} item selected",
...@@ -1223,6 +1240,8 @@ ...@@ -1223,6 +1240,8 @@
"templateTags.Writing": "Writing", "templateTags.Writing": "Writing",
"template_market": "Template Market", "template_market": "Template Market",
"textarea_variable_picker_tip": "Enter \"/\" to select a variable", "textarea_variable_picker_tip": "Enter \"/\" to select a variable",
"time_point": "Time point",
"time_range": "Time period",
"to_dataset": "To dataset", "to_dataset": "To dataset",
"total_num": "Total: {{num}}", "total_num": "Total: {{num}}",
"ui.textarea.Magnifying": "Magnifying", "ui.textarea.Magnifying": "Magnifying",
......
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
"execute_a_simple_script_code_usually_for_complex_data_processing": "Execute a simple script code, usually for complex data processing.", "execute_a_simple_script_code_usually_for_complex_data_processing": "Execute a simple script code, usually for complex data processing.",
"execute_different_branches_based_on_conditions": "Execute different branches based on conditions.", "execute_different_branches_based_on_conditions": "Execute different branches based on conditions.",
"execution_error": "Execution Error", "execution_error": "Execution Error",
"external_variables": "External variables",
"extraction_requirements_description": "Extraction Requirements Description", "extraction_requirements_description": "Extraction Requirements Description",
"extraction_requirements_description_detail": "Provide AI with some background knowledge or requirements to guide it in completing the task better.\\nThis input box can use global variables.", "extraction_requirements_description_detail": "Provide AI with some background knowledge or requirements to guide it in completing the task better.\\nThis input box can use global variables.",
"extraction_requirements_placeholder": "For example: 1. The current time is: {{cTime}}. \nYou are a laboratory reservation assistant. Your task is to help users make laboratory reservations and obtain the corresponding reservation information from the text.\n\n2. You are the Google Search Assistant and need to extract appropriate search terms from text.", "extraction_requirements_placeholder": "For example: 1. The current time is: {{cTime}}. \nYou are a laboratory reservation assistant. Your task is to help users make laboratory reservations and obtain the corresponding reservation information from the text.\n\n2. You are the Google Search Assistant and need to extract appropriate search terms from text.",
...@@ -87,6 +88,7 @@ ...@@ -87,6 +88,7 @@
"input_description": "Field Description", "input_description": "Field Description",
"input_type_multiple_select": "Multiple selection boxes", "input_type_multiple_select": "Multiple selection boxes",
"input_variable_list": "Type / to invoke variable list", "input_variable_list": "Type / to invoke variable list",
"internal_variables": "Internal variables",
"intro_assigned_reply": "This module can directly reply with a specified content. Commonly used for guidance or prompts. Non-string content will be converted to string for output.", "intro_assigned_reply": "This module can directly reply with a specified content. Commonly used for guidance or prompts. Non-string content will be converted to string for output.",
"intro_custom_feedback": "When this module is triggered, a feedback will be added to the current conversation record. It can be used to automatically record conversation effects, etc.", "intro_custom_feedback": "When this module is triggered, a feedback will be added to the current conversation record. It can be used to automatically record conversation effects, etc.",
"intro_custom_plugin_output": "Custom configuration of external output. When using plugins, only the custom configured output is exposed.", "intro_custom_plugin_output": "Custom configuration of external output. When using plugins, only the custom configured output is exposed.",
......
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
"chat_debug": "调试预览", "chat_debug": "调试预览",
"chat_logs": "对话日志", "chat_logs": "对话日志",
"chat_logs_tips": "日志会记录该应用的在线、分享和 API(需填写 chatId)对话记录", "chat_logs_tips": "日志会记录该应用的在线、分享和 API(需填写 chatId)对话记录",
"common.day": "日",
"config_ai_model_params": "点击配置 AI 模型相关属性", "config_ai_model_params": "点击配置 AI 模型相关属性",
"config_file_upload": "点击配置文件上传规则", "config_file_upload": "点击配置文件上传规则",
"config_question_guide": "配置猜你想问", "config_question_guide": "配置猜你想问",
...@@ -255,16 +256,16 @@ ...@@ -255,16 +256,16 @@
"templateMarket.Search_template": "搜索模板", "templateMarket.Search_template": "搜索模板",
"templateMarket.Use": "使用", "templateMarket.Use": "使用",
"templateMarket.no_intro": "还没有介绍~", "templateMarket.no_intro": "还没有介绍~",
"templateMarket.templateTags.Image_generation": "图片生成",
"templateMarket.templateTags.Office_services": "办公服务",
"templateMarket.templateTags.Recommendation": "推荐", "templateMarket.templateTags.Recommendation": "推荐",
"templateMarket.templateTags.Roleplay": "角色扮演",
"templateMarket.templateTags.Web_search": "联网搜索",
"templateMarket.templateTags.Writing": "文本创作",
"templateMarket.template_guide": "模板说明", "templateMarket.template_guide": "模板说明",
"template_market": "模板市场", "template_market": "模板市场",
"template_market_description": "在模板市场探索更多玩法,配置教程与使用引导,带你理解并上手各种应用", "template_market_description": "在模板市场探索更多玩法,配置教程与使用引导,带你理解并上手各种应用",
"template_market_empty_data": "找不到合适的模板", "template_market_empty_data": "找不到合适的模板",
"time_granularity": "时间粒度",
"time_range_end": "结束时间",
"time_range_limit": "时间范围限制",
"time_range_start": "开始时间",
"time_type": "时间类型",
"time_zone": "时区", "time_zone": "时区",
"too_to_active": "去激活", "too_to_active": "去激活",
"tool_active_manual_config_desc": "临时密钥保存在本应用中,仅供该应用使用", "tool_active_manual_config_desc": "临时密钥保存在本应用中,仅供该应用使用",
...@@ -275,16 +276,6 @@ ...@@ -275,16 +276,6 @@
"tool_input_param_tip": "该插件正常运行需要配置相关信息", "tool_input_param_tip": "该插件正常运行需要配置相关信息",
"tool_not_active": "该工具尚未激活", "tool_not_active": "该工具尚未激活",
"tool_run_free": "该工具运行无积分消耗", "tool_run_free": "该工具运行无积分消耗",
"tool_type_communication": "通讯",
"tool_type_design": "设计",
"tool_type_entertainment": "商业",
"tool_type_finance": "金融",
"tool_type_multimodal": "多模态",
"tool_type_news": "新闻",
"tool_type_productivity": "生产力",
"tool_type_scientific": "科研",
"tool_type_search": "搜索",
"tool_type_social": "社交",
"tool_type_tools": "工具", "tool_type_tools": "工具",
"tools_no_description": "这个工具没有介绍~", "tools_no_description": "这个工具没有介绍~",
"transition_to_workflow": "转成工作流", "transition_to_workflow": "转成工作流",
...@@ -322,7 +313,8 @@ ...@@ -322,7 +313,8 @@
"un_auth": "无权限", "un_auth": "无权限",
"upload_file_max_amount": "最大文件数量", "upload_file_max_amount": "最大文件数量",
"upload_file_max_amount_tip": "单轮对话中最大上传文件数量", "upload_file_max_amount_tip": "单轮对话中最大上传文件数量",
"variable.select type_desc": "可以为工作流定义全局变量,常用临时缓存。赋值的方式包括:\n1. 从对话页面的 query 参数获取。\n2. 通过 API 的 variables 对象传递。\n3. 通过【变量更新】节点进行赋值。", "variable.internal_type_desc": "仅在工作流内部使用,不会出现在对话框中",
"variable.select type_desc": "会在站内对话和运行预览中显示输入框,在分享链接中不会显示此变量",
"variable.textarea_type_desc": "允许用户最多输入4000字的对话框。", "variable.textarea_type_desc": "允许用户最多输入4000字的对话框。",
"variable_name_required": "变量名必填", "variable_name_required": "变量名必填",
"variable_repeat": "该变量名已被占用,无法使用", "variable_repeat": "该变量名已被占用,无法使用",
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
"input_guide_tip": "可以配置一些预设的问题。在用户输入问题时,会从这些预设问题中获取相关问题进行提示。", "input_guide_tip": "可以配置一些预设的问题。在用户输入问题时,会从这些预设问题中获取相关问题进行提示。",
"input_placeholder_phone": "输入问题", "input_placeholder_phone": "输入问题",
"insert_input_guide,_some_data_already_exists": "有重复数据,已自动过滤,共插入 {{len}} 条数据", "insert_input_guide,_some_data_already_exists": "有重复数据,已自动过滤,共插入 {{len}} 条数据",
"internal_variables_tip": "内部变量仅调试时可见",
"invalid_share_url": "无效的分享链接", "invalid_share_url": "无效的分享链接",
"is_chatting": "正在聊天中...请等待结束", "is_chatting": "正在聊天中...请等待结束",
"items": "条", "items": "条",
...@@ -108,16 +109,16 @@ ...@@ -108,16 +109,16 @@
"setting.favourite.categories_modal.delete_confirm_button": "删除", "setting.favourite.categories_modal.delete_confirm_button": "删除",
"setting.favourite.categories_modal.delete_confirm_title": "确认删除", "setting.favourite.categories_modal.delete_confirm_title": "确认删除",
"setting.favourite.categories_modal.title": "共 {{num}} 个分类", "setting.favourite.categories_modal.title": "共 {{num}} 个分类",
"setting.favourite.category_all": "全部分类",
"setting.favourite.category_tab.all": "全部",
"setting.favourite.category.no_data": "暂无可用精选应用", "setting.favourite.category.no_data": "暂无可用精选应用",
"setting.favourite.tag.no_data": "暂无分类", "setting.favourite.category_all": "全部分类",
"setting.favourite.category_placeholder": "选择分类", "setting.favourite.category_placeholder": "选择分类",
"setting.favourite.category_tab.all": "全部",
"setting.favourite.confirm_button": "确定", "setting.favourite.confirm_button": "确定",
"setting.favourite.delete_app_cancel_button": "取消", "setting.favourite.delete_app_cancel_button": "取消",
"setting.favourite.delete_app_confirm": "确定要移除该精选应用吗?", "setting.favourite.delete_app_confirm": "确定要移除该精选应用吗?",
"setting.favourite.delete_app_confirm_button": "确定", "setting.favourite.delete_app_confirm_button": "确定",
"setting.favourite.delete_app_title": "删除应用", "setting.favourite.delete_app_title": "删除应用",
"setting.favourite.goto_add": "去配置",
"setting.favourite.manage_categories_button": "分类管理", "setting.favourite.manage_categories_button": "分类管理",
"setting.favourite.save_category_for_app_button": "保存", "setting.favourite.save_category_for_app_button": "保存",
"setting.favourite.search_placeholder": "搜索应用", "setting.favourite.search_placeholder": "搜索应用",
...@@ -126,17 +127,21 @@ ...@@ -126,17 +127,21 @@
"setting.favourite.table_column_category": "分类", "setting.favourite.table_column_category": "分类",
"setting.favourite.table_column_intro": "介绍", "setting.favourite.table_column_intro": "介绍",
"setting.favourite.table_column_name": "名称", "setting.favourite.table_column_name": "名称",
"setting.favourite.tag.no_data": "暂无分类",
"setting.favourite.title": "精选应用", "setting.favourite.title": "精选应用",
"setting.favourite.goto_add": "去配置",
"setting.home.available_tools": "可用工具", "setting.home.available_tools": "可用工具",
"setting.home.available_tools.add": "添加", "setting.home.available_tools.add": "添加",
"setting.home.cancel_button": "取消",
"setting.home.commercial_version": "商业版", "setting.home.commercial_version": "商业版",
"setting.home.confirm_button": "确定",
"setting.home.diagram": "示意图", "setting.home.diagram": "示意图",
"setting.home.dialogue_tips": "对话框提示文字", "setting.home.dialogue_tips": "对话框提示文字",
"setting.home.dialogue_tips.default": "你可以问我任何问题", "setting.home.dialogue_tips.default": "你可以问我任何问题",
"setting.home.dialogue_tips_placeholder": "请输入对话框提示文字", "setting.home.dialogue_tips_placeholder": "请输入对话框提示文字",
"setting.home.enable": "启用首页",
"setting.home.home_tab_title": "首页标题", "setting.home.home_tab_title": "首页标题",
"setting.home.home_tab_title_placeholder": "请输入首页标题", "setting.home.home_tab_title_placeholder": "请输入首页标题",
"setting.home.no_selected_app": "未选择应用",
"setting.home.quick_apps": "快捷应用", "setting.home.quick_apps": "快捷应用",
"setting.home.quick_apps.add": "配置快捷应用", "setting.home.quick_apps.add": "配置快捷应用",
"setting.home.quick_apps.placeholder": "请选择应用", "setting.home.quick_apps.placeholder": "请选择应用",
...@@ -144,10 +149,6 @@ ...@@ -144,10 +149,6 @@
"setting.home.slogan.default": "你好👋,我是 FastGPT ! 请问有什么可以帮你?", "setting.home.slogan.default": "你好👋,我是 FastGPT ! 请问有什么可以帮你?",
"setting.home.slogan_placeholder": "请输入 Slogan", "setting.home.slogan_placeholder": "请输入 Slogan",
"setting.home.title": "首页配置", "setting.home.title": "首页配置",
"setting.home.cancel_button": "取消",
"setting.home.confirm_button": "确定",
"setting.home.no_selected_app": "未选择应用",
"setting.home.enable": "启用首页",
"setting.incorrect_plan": "当前套餐不支持该功能,请升级订阅套餐", "setting.incorrect_plan": "当前套餐不支持该功能,请升级订阅套餐",
"setting.incorrect_version": "当前版本不支持该功能", "setting.incorrect_version": "当前版本不支持该功能",
"setting.log_details.title": "首页日志", "setting.log_details.title": "首页日志",
...@@ -162,7 +163,7 @@ ...@@ -162,7 +163,7 @@
"stream_output": "流输出", "stream_output": "流输出",
"unsupported_file_type": "不支持的文件类型", "unsupported_file_type": "不支持的文件类型",
"upload": "上传", "upload": "上传",
"variable_invisable_in_share": "自定义变量在免登录链接中不可见", "variable_invisable_in_share": "外部变量在免登录链接中不可见",
"view_citations": "查看引用", "view_citations": "查看引用",
"web_site_sync": "Web站点同步" "web_site_sync": "Web站点同步"
} }
...@@ -601,6 +601,7 @@ ...@@ -601,6 +601,7 @@
"core.module.Max Length": "最大长度", "core.module.Max Length": "最大长度",
"core.module.Max Length placeholder": "输入文本的最大长度", "core.module.Max Length placeholder": "输入文本的最大长度",
"core.module.Max Value": "最大值", "core.module.Max Value": "最大值",
"core.module.Min Length": "最小位数",
"core.module.Min Value": "最小值", "core.module.Min Value": "最小值",
"core.module.QueryExtension.placeholder": "例如:\n关于 Python 的介绍和使用等问题。\n当前对话与游戏《GTA5》有关。", "core.module.QueryExtension.placeholder": "例如:\n关于 Python 的介绍和使用等问题。\n当前对话与游戏《GTA5》有关。",
"core.module.Select app": "选择应用", "core.module.Select app": "选择应用",
...@@ -727,10 +728,17 @@ ...@@ -727,10 +728,17 @@
"core.workflow.inputType.Manual input": "手动输入", "core.workflow.inputType.Manual input": "手动输入",
"core.workflow.inputType.Manual select": "手动选择", "core.workflow.inputType.Manual select": "手动选择",
"core.workflow.inputType.Reference": "变量引用", "core.workflow.inputType.Reference": "变量引用",
"core.workflow.inputType.custom": "自定义变量", "core.workflow.inputType.custom": "外部变量",
"core.workflow.inputType.datasetSelect": "知识库选择",
"core.workflow.inputType.dynamicTargetInput": "动态外部数据", "core.workflow.inputType.dynamicTargetInput": "动态外部数据",
"core.workflow.inputType.file": "文件上传",
"core.workflow.inputType.input": "单行输入框", "core.workflow.inputType.input": "单行输入框",
"core.workflow.inputType.internal": "内部变量",
"core.workflow.inputType.jsonEditor": "JSON 输入框",
"core.workflow.inputType.modelSelect": "对话模型选择",
"core.workflow.inputType.multipleSelect": "多选框",
"core.workflow.inputType.number input": "数字输入框", "core.workflow.inputType.number input": "数字输入框",
"core.workflow.inputType.password": "密码",
"core.workflow.inputType.select": "单选框", "core.workflow.inputType.select": "单选框",
"core.workflow.inputType.selectApp": "应用选择", "core.workflow.inputType.selectApp": "应用选择",
"core.workflow.inputType.selectDataset": "知识库选择", "core.workflow.inputType.selectDataset": "知识库选择",
...@@ -738,6 +746,8 @@ ...@@ -738,6 +746,8 @@
"core.workflow.inputType.switch": "开关", "core.workflow.inputType.switch": "开关",
"core.workflow.inputType.textInput": "文本输入框", "core.workflow.inputType.textInput": "文本输入框",
"core.workflow.inputType.textarea": "多行输入框", "core.workflow.inputType.textarea": "多行输入框",
"core.workflow.inputType.timePointSelect": "时间点",
"core.workflow.inputType.timeRangeSelect": "时间范围",
"core.workflow.publish.OnRevert version": "点击回退到该版本", "core.workflow.publish.OnRevert version": "点击回退到该版本",
"core.workflow.publish.OnRevert version confirm": "确认回退至该版本?会为您保存编辑中版本的配置,并为回退版本创建一个新的发布版本。", "core.workflow.publish.OnRevert version confirm": "确认回退至该版本?会为您保存编辑中版本的配置,并为回退版本创建一个新的发布版本。",
"core.workflow.publish.histories": "发布记录", "core.workflow.publish.histories": "发布记录",
...@@ -802,6 +812,7 @@ ...@@ -802,6 +812,7 @@
"date_1_month": "1个月", "date_1_month": "1个月",
"date_3_months": "3个月", "date_3_months": "3个月",
"date_6_months": "6个月", "date_6_months": "6个月",
"day": "日",
"deep_rag_search": "深度搜索", "deep_rag_search": "深度搜索",
"delete_api": "确认删除该API密钥?删除后该密钥立即失效,对应的对话日志不会删除,请确认!", "delete_api": "确认删除该API密钥?删除后该密钥立即失效,对应的对话日志不会删除,请确认!",
"delete_failed": "删除失败", "delete_failed": "删除失败",
...@@ -844,6 +855,8 @@ ...@@ -844,6 +855,8 @@
"had_auth_value": "已填写", "had_auth_value": "已填写",
"has_verification": "已验证,点击取消绑定", "has_verification": "已验证,点击取消绑定",
"have_done": "已完成", "have_done": "已完成",
"hour": "小时",
"hour_unit": "时",
"import_failed": "导入失败", "import_failed": "导入失败",
"import_success": "导入成功", "import_success": "导入成功",
"info.buy_extra": "购买额外套餐", "info.buy_extra": "购买额外套餐",
...@@ -881,8 +894,11 @@ ...@@ -881,8 +894,11 @@
"max_quote_tokens_tips": "单次搜索最大的 token 数量,中文约 1 字=1.7 tokens,英文约 1 字=1 token", "max_quote_tokens_tips": "单次搜索最大的 token 数量,中文约 1 字=1.7 tokens,英文约 1 字=1 token",
"mcp_server": "MCP 服务", "mcp_server": "MCP 服务",
"member": "成员", "member": "成员",
"min_length": "最少需要 {{minLenth}} 个字符",
"min_similarity": "最低相关度", "min_similarity": "最低相关度",
"min_similarity_tip": "不同索引模型的相关度有区别,请通过搜索测试来选择合适的数值。使用 结果重排 时,使用重排结果进行过滤。", "min_similarity_tip": "不同索引模型的相关度有区别,请通过搜索测试来选择合适的数值。使用 结果重排 时,使用重排结果进行过滤。",
"minute": "分钟",
"minute_unit": "分",
"model.billing": "模型计费", "model.billing": "模型计费",
"model.model_type": "模型类型", "model.model_type": "模型类型",
"model.name": "模型名", "model.name": "模型名",
...@@ -1007,6 +1023,8 @@ ...@@ -1007,6 +1023,8 @@
"save_success": "保存成功", "save_success": "保存成功",
"scan_code": "扫码支付", "scan_code": "扫码支付",
"search_tool": "搜索工具", "search_tool": "搜索工具",
"second": "秒",
"second_unit": "秒",
"secret_key": "密钥", "secret_key": "密钥",
"secret_tips": "值保存后不会再次明文返回", "secret_tips": "值保存后不会再次明文返回",
"select_count_num": "已选 {{num}} 项", "select_count_num": "已选 {{num}} 项",
...@@ -1224,6 +1242,8 @@ ...@@ -1224,6 +1242,8 @@
"templateTags.Writing": "文本创作", "templateTags.Writing": "文本创作",
"template_market": "模板市场", "template_market": "模板市场",
"textarea_variable_picker_tip": "输入\"/\"可选择变量", "textarea_variable_picker_tip": "输入\"/\"可选择变量",
"time_point": "时间点",
"time_range": "时间段",
"to_dataset": "前往知识库", "to_dataset": "前往知识库",
"total_num": "总数: {{num}}", "total_num": "总数: {{num}}",
"ui.textarea.Magnifying": "放大", "ui.textarea.Magnifying": "放大",
......
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
"execute_a_simple_script_code_usually_for_complex_data_processing": "执行一段简单的脚本代码,通常用于进行复杂的数据处理。", "execute_a_simple_script_code_usually_for_complex_data_processing": "执行一段简单的脚本代码,通常用于进行复杂的数据处理。",
"execute_different_branches_based_on_conditions": "根据一定的条件,执行不同的分支。", "execute_different_branches_based_on_conditions": "根据一定的条件,执行不同的分支。",
"execution_error": "运行错误", "execution_error": "运行错误",
"external_variables": "外部变量",
"extraction_requirements_description": "提取要求描述", "extraction_requirements_description": "提取要求描述",
"extraction_requirements_description_detail": "给AI一些对应的背景知识或要求描述,引导AI更好的完成任务。\\n该输入框可使用全局变量。", "extraction_requirements_description_detail": "给AI一些对应的背景知识或要求描述,引导AI更好的完成任务。\\n该输入框可使用全局变量。",
"extraction_requirements_placeholder": "例如: 1. 当前时间为: {{cTime}}。你是一个实验室预约助手,你的任务是帮助用户预约实验室,从文本中获取对应的预约信息。\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。", "extraction_requirements_placeholder": "例如: 1. 当前时间为: {{cTime}}。你是一个实验室预约助手,你的任务是帮助用户预约实验室,从文本中获取对应的预约信息。\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。",
...@@ -87,6 +88,7 @@ ...@@ -87,6 +88,7 @@
"input_description": "字段描述", "input_description": "字段描述",
"input_type_multiple_select": "多选框", "input_type_multiple_select": "多选框",
"input_variable_list": "可输入 / 唤起变量列表", "input_variable_list": "可输入 / 唤起变量列表",
"internal_variables": "内部变量",
"intro_assigned_reply": "该模块可以直接回复一段指定的内容。常用于引导、提示。非字符串内容传入时,会转成字符串进行输出。", "intro_assigned_reply": "该模块可以直接回复一段指定的内容。常用于引导、提示。非字符串内容传入时,会转成字符串进行输出。",
"intro_custom_feedback": "该模块被触发时,会给当前的对话记录增加一条反馈。可用于自动记录对话效果等。", "intro_custom_feedback": "该模块被触发时,会给当前的对话记录增加一条反馈。可用于自动记录对话效果等。",
"intro_custom_plugin_output": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出", "intro_custom_plugin_output": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
......
...@@ -246,16 +246,14 @@ ...@@ -246,16 +246,14 @@
"templateMarket.Search_template": "搜尋範本", "templateMarket.Search_template": "搜尋範本",
"templateMarket.Use": "使用", "templateMarket.Use": "使用",
"templateMarket.no_intro": "還沒有介紹~", "templateMarket.no_intro": "還沒有介紹~",
"templateMarket.templateTags.Image_generation": "影像生成",
"templateMarket.templateTags.Office_services": "辦公服務",
"templateMarket.templateTags.Recommendation": "推薦", "templateMarket.templateTags.Recommendation": "推薦",
"templateMarket.templateTags.Roleplay": "角色扮演",
"templateMarket.templateTags.Web_search": "網路搜尋",
"templateMarket.templateTags.Writing": "文字創作",
"templateMarket.template_guide": "範本說明", "templateMarket.template_guide": "範本說明",
"template_market": "範本市集", "template_market": "範本市集",
"template_market_description": "在範本市集探索更多玩法,設定教學與使用指引,帶您理解並上手各種應用程式", "template_market_description": "在範本市集探索更多玩法,設定教學與使用指引,帶您理解並上手各種應用程式",
"template_market_empty_data": "找不到合適的範本", "template_market_empty_data": "找不到合適的範本",
"time_granularity": "時間粒度",
"time_range_limit": "時間範圍限制",
"time_type": "時間類型",
"time_zone": "時區", "time_zone": "時區",
"too_to_active": "去激活", "too_to_active": "去激活",
"tool_active_manual_config_desc": "臨時密鑰保存在本應用中,僅供該應用使用", "tool_active_manual_config_desc": "臨時密鑰保存在本應用中,僅供該應用使用",
...@@ -266,16 +264,6 @@ ...@@ -266,16 +264,6 @@
"tool_input_param_tip": "這個外掛正常執行需要設定相關資訊", "tool_input_param_tip": "這個外掛正常執行需要設定相關資訊",
"tool_not_active": "該工具尚未激活", "tool_not_active": "該工具尚未激活",
"tool_run_free": "該工具運行無積分消耗", "tool_run_free": "該工具運行無積分消耗",
"tool_type_communication": "通訊",
"tool_type_design": "設計",
"tool_type_entertainment": "商業",
"tool_type_finance": "金融",
"tool_type_multimodal": "多模態",
"tool_type_news": "新聞",
"tool_type_productivity": "生產力",
"tool_type_scientific": "科研",
"tool_type_search": "搜尋",
"tool_type_social": "社交",
"tool_type_tools": "工具", "tool_type_tools": "工具",
"tools_no_description": "這個工具沒有介紹~", "tools_no_description": "這個工具沒有介紹~",
"transition_to_workflow": "轉換成工作流程", "transition_to_workflow": "轉換成工作流程",
...@@ -313,7 +301,8 @@ ...@@ -313,7 +301,8 @@
"un_auth": "無權限", "un_auth": "無權限",
"upload_file_max_amount": "最大檔案數量", "upload_file_max_amount": "最大檔案數量",
"upload_file_max_amount_tip": "單輪對話中最大上傳檔案數量", "upload_file_max_amount_tip": "單輪對話中最大上傳檔案數量",
"variable.select type_desc": "可以為工作流程定義全域變數,常用於暫存。賦值的方式包括:\n1. 從對話頁面的 query 參數取得。\n2. 透過 API 的 variables 物件傳遞。\n3. 透過【變數更新】節點進行賦值。", "variable.internal_type_desc": "僅在工作流內部使用,不會出現在對話框中",
"variable.select type_desc": "會在站內對話和運行預覽中顯示輸入框,在分享鏈接中不會顯示此變量",
"variable.textarea_type_desc": "允許使用者最多輸入 4000 字的對話框。", "variable.textarea_type_desc": "允許使用者最多輸入 4000 字的對話框。",
"variable_name_required": "變量名必填", "variable_name_required": "變量名必填",
"variable_repeat": "該變量名已被佔用,無法使用", "variable_repeat": "該變量名已被佔用,無法使用",
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
"input_guide_tip": "您可以設定一些預設問題。當使用者輸入問題時,系統會從這些預設問題中提供相關問題作為提示。", "input_guide_tip": "您可以設定一些預設問題。當使用者輸入問題時,系統會從這些預設問題中提供相關問題作為提示。",
"input_placeholder_phone": "請輸入問題", "input_placeholder_phone": "請輸入問題",
"insert_input_guide,_some_data_already_exists": "偵測到重複資料,已自動過濾,共插入 {{len}} 筆資料", "insert_input_guide,_some_data_already_exists": "偵測到重複資料,已自動過濾,共插入 {{len}} 筆資料",
"internal_variables_tip": "內部變量僅調試時可見",
"invalid_share_url": "無效的分享鏈接", "invalid_share_url": "無效的分享鏈接",
"is_chatting": "對話進行中...請稍候", "is_chatting": "對話進行中...請稍候",
"items": "筆", "items": "筆",
...@@ -159,7 +160,7 @@ ...@@ -159,7 +160,7 @@
"stream_output": "串流輸出", "stream_output": "串流輸出",
"unsupported_file_type": "不支援的檔案類型", "unsupported_file_type": "不支援的檔案類型",
"upload": "上傳", "upload": "上傳",
"variable_invisable_in_share": "自定義變數在免登入連結中不可見", "variable_invisable_in_share": "外部變量在免登錄鏈接中不可見",
"view_citations": "檢視引用", "view_citations": "檢視引用",
"web_site_sync": "網站同步" "web_site_sync": "網站同步"
} }
...@@ -600,6 +600,7 @@ ...@@ -600,6 +600,7 @@
"core.module.Max Length": "最大長度", "core.module.Max Length": "最大長度",
"core.module.Max Length placeholder": "輸入文字的最大長度", "core.module.Max Length placeholder": "輸入文字的最大長度",
"core.module.Max Value": "最大值", "core.module.Max Value": "最大值",
"core.module.Min Length": "最小位數",
"core.module.Min Value": "最小值", "core.module.Min Value": "最小值",
"core.module.QueryExtension.placeholder": "例如:\n關於 Python 的介紹和使用等問題。\n目前對話與遊戲《GTA5》有關。", "core.module.QueryExtension.placeholder": "例如:\n關於 Python 的介紹和使用等問題。\n目前對話與遊戲《GTA5》有關。",
"core.module.Select app": "選擇應用程式", "core.module.Select app": "選擇應用程式",
...@@ -726,10 +727,16 @@ ...@@ -726,10 +727,16 @@
"core.workflow.inputType.Manual input": "手動輸入", "core.workflow.inputType.Manual input": "手動輸入",
"core.workflow.inputType.Manual select": "手動選擇", "core.workflow.inputType.Manual select": "手動選擇",
"core.workflow.inputType.Reference": "變數引用", "core.workflow.inputType.Reference": "變數引用",
"core.workflow.inputType.custom": "自訂變數", "core.workflow.inputType.custom": "外部變數",
"core.workflow.inputType.dynamicTargetInput": "動態外部資料", "core.workflow.inputType.dynamicTargetInput": "動態外部資料",
"core.workflow.inputType.file": "文件上傳",
"core.workflow.inputType.input": "單行輸入框", "core.workflow.inputType.input": "單行輸入框",
"core.workflow.inputType.internal": "內部變量",
"core.workflow.inputType.jsonEditor": "JSON 輸入框",
"core.workflow.inputType.modelSelect": "對話模型選擇",
"core.workflow.inputType.multipleSelect": "多選框",
"core.workflow.inputType.number input": "數字輸入框", "core.workflow.inputType.number input": "數字輸入框",
"core.workflow.inputType.password": "密碼",
"core.workflow.inputType.select": "單選框", "core.workflow.inputType.select": "單選框",
"core.workflow.inputType.selectApp": "應用程式選擇", "core.workflow.inputType.selectApp": "應用程式選擇",
"core.workflow.inputType.selectDataset": "知識庫選擇", "core.workflow.inputType.selectDataset": "知識庫選擇",
...@@ -737,6 +744,8 @@ ...@@ -737,6 +744,8 @@
"core.workflow.inputType.switch": "開關", "core.workflow.inputType.switch": "開關",
"core.workflow.inputType.textInput": "文字輸入框", "core.workflow.inputType.textInput": "文字輸入框",
"core.workflow.inputType.textarea": "多行輸入框", "core.workflow.inputType.textarea": "多行輸入框",
"core.workflow.inputType.timePointSelect": "時間點",
"core.workflow.inputType.timeRangeSelect": "時間範圍",
"core.workflow.publish.OnRevert version": "點選回復至此版本", "core.workflow.publish.OnRevert version": "點選回復至此版本",
"core.workflow.publish.OnRevert version confirm": "確認回復至此版本?將為您儲存編輯中版本的設定,並為回復版本建立一個新的發布版本。", "core.workflow.publish.OnRevert version confirm": "確認回復至此版本?將為您儲存編輯中版本的設定,並為回復版本建立一個新的發布版本。",
"core.workflow.publish.histories": "發布記錄", "core.workflow.publish.histories": "發布記錄",
...@@ -801,6 +810,7 @@ ...@@ -801,6 +810,7 @@
"date_1_month": "1個月", "date_1_month": "1個月",
"date_3 months": "3個月", "date_3 months": "3個月",
"date_6_months": "6個月", "date_6_months": "6個月",
"day": "日",
"deep_rag_search": "深度搜尋", "deep_rag_search": "深度搜尋",
"delete_api": "確認刪除此 API 金鑰?\n刪除後該金鑰將立即失效,對應的對話記錄不會被刪除,請確認!", "delete_api": "確認刪除此 API 金鑰?\n刪除後該金鑰將立即失效,對應的對話記錄不會被刪除,請確認!",
"delete_failed": "刪除失敗", "delete_failed": "刪除失敗",
...@@ -843,6 +853,8 @@ ...@@ -843,6 +853,8 @@
"had_auth_value": "已填寫", "had_auth_value": "已填寫",
"has_verification": "已驗證,點選解除綁定", "has_verification": "已驗證,點選解除綁定",
"have_done": "已完成", "have_done": "已完成",
"hour": "小時",
"hour_unit": "時",
"import_failed": "匯入失敗", "import_failed": "匯入失敗",
"import_success": "匯入成功", "import_success": "匯入成功",
"info.buy_extra": "購買額外方案", "info.buy_extra": "購買額外方案",
...@@ -880,8 +892,11 @@ ...@@ -880,8 +892,11 @@
"max_quote_tokens_tips": "單次搜尋最大的 token 數量,中文約 1 字=1.7 tokens,英文約 1 字=1 token", "max_quote_tokens_tips": "單次搜尋最大的 token 數量,中文約 1 字=1.7 tokens,英文約 1 字=1 token",
"mcp_server": "MCP 服務", "mcp_server": "MCP 服務",
"member": "成員", "member": "成員",
"min_length": "最少需要 {{minLenth}} 個字符",
"min_similarity": "最低相關度", "min_similarity": "最低相關度",
"min_similarity_tip": "不同索引模型的相關度有區別,請透過搜尋測試來選擇合適的數值。\n使用 結果重排 時,使用重排結果過濾。", "min_similarity_tip": "不同索引模型的相關度有區別,請透過搜尋測試來選擇合適的數值。\n使用 結果重排 時,使用重排結果過濾。",
"minute": "分鐘",
"minute_unit": "分",
"model.billing": "模型計費", "model.billing": "模型計費",
"model.model_type": "模型類型", "model.model_type": "模型類型",
"model.name": "模型名", "model.name": "模型名",
...@@ -1006,6 +1021,8 @@ ...@@ -1006,6 +1021,8 @@
"save_success": "儲存成功", "save_success": "儲存成功",
"scan_code": "掃碼支付", "scan_code": "掃碼支付",
"search_tool": "搜索工具", "search_tool": "搜索工具",
"second": "秒",
"second_unit": "秒",
"secret_key": "密鑰", "secret_key": "密鑰",
"secret_tips": "值保存後不會再次明文返回", "secret_tips": "值保存後不會再次明文返回",
"select_count_num": "已選 {{num}} 項", "select_count_num": "已選 {{num}} 項",
...@@ -1222,6 +1239,8 @@ ...@@ -1222,6 +1239,8 @@
"templateTags.Writing": "文字創作", "templateTags.Writing": "文字創作",
"template_market": "模板市場", "template_market": "模板市場",
"textarea_variable_picker_tip": "輸入「/」以選擇變數", "textarea_variable_picker_tip": "輸入「/」以選擇變數",
"time_point": "時間點",
"time_range": "時間段",
"to_dataset": "前往知識庫", "to_dataset": "前往知識庫",
"total_num": "總數: {{num}}", "total_num": "總數: {{num}}",
"ui.textarea.Magnifying": "放大", "ui.textarea.Magnifying": "放大",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment