Commit 44e87e30 by Archer Committed by GitHub

fix: var render (#5857)

* fix: timeselector ui error

* var update node

* fix: var render

* fix: prompt editor

* perf: init

* fix: retry input

* fix: prompt editor

* fix: editor
parent a499d05a
...@@ -52,11 +52,12 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4140' \ ...@@ -52,11 +52,12 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4140' \
## 🐛 修复 ## 🐛 修复
1. Claude 工具调用,如果下标从 1 开始会导致参数异常。 1. Prompt 编辑器存在特殊语法时候,无法解析正确内容。
2. S3 删除头像,如果 key 为空时,会抛错,导致流程阻塞。 2. Claude 工具调用,如果下标从 1 开始会导致参数异常。
3. 工作流前置IO 变更时,依赖未及时刷新。 3. S3 删除头像,如果 key 为空时,会抛错,导致流程阻塞。
4. 导出对话日志,缺少反馈记录。 4. 工作流前置IO 变更时,依赖未及时刷新。
5. 工作流欢迎语输入框输入时,光标会偏移到最后一位。 5. 导出对话日志,缺少反馈记录。
6. 存在交互节点和连续批量执行时,会导致工作流运行逻辑错误。 6. 工作流欢迎语输入框输入时,光标会偏移到最后一位。
7. 工作流 Redo 操作后,编辑记录无法再继续推送快照。 7. 存在交互节点和连续批量执行时,会导致工作流运行逻辑错误。
8. 工作流 Redo 操作后,编辑记录无法再继续推送快照。
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
"document/content/docs/introduction/development/custom-models/ollama.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/introduction/development/custom-models/ollama.mdx": "2025-08-05T23:20:39+08:00",
"document/content/docs/introduction/development/custom-models/xinference.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/introduction/development/custom-models/xinference.mdx": "2025-08-05T23:20:39+08:00",
"document/content/docs/introduction/development/design/dataset.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/development/design/dataset.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/development/design/design_plugin.mdx": "2025-10-30T22:14:07+08:00", "document/content/docs/introduction/development/design/design_plugin.mdx": "2025-11-04T16:58:12+08:00",
"document/content/docs/introduction/development/docker.mdx": "2025-09-29T11:34:11+08:00", "document/content/docs/introduction/development/docker.mdx": "2025-09-29T11:34:11+08:00",
"document/content/docs/introduction/development/faq.mdx": "2025-08-12T22:22:18+08:00", "document/content/docs/introduction/development/faq.mdx": "2025-08-12T22:22:18+08:00",
"document/content/docs/introduction/development/intro.mdx": "2025-09-29T11:34:11+08:00", "document/content/docs/introduction/development/intro.mdx": "2025-09-29T11:34:11+08:00",
...@@ -84,11 +84,11 @@ ...@@ -84,11 +84,11 @@
"document/content/docs/introduction/guide/knowledge_base/websync.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/introduction/guide/knowledge_base/websync.mdx": "2025-08-05T23:20:39+08:00",
"document/content/docs/introduction/guide/knowledge_base/yuque_dataset.mdx": "2025-09-17T22:29:56+08:00", "document/content/docs/introduction/guide/knowledge_base/yuque_dataset.mdx": "2025-09-17T22:29:56+08:00",
"document/content/docs/introduction/guide/plugins/bing_search_plugin.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/plugins/bing_search_plugin.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/plugins/dev_system_tool.mdx": "2025-10-30T22:14:07+08:00", "document/content/docs/introduction/guide/plugins/dev_system_tool.mdx": "2025-11-04T16:58:12+08:00",
"document/content/docs/introduction/guide/plugins/doc2x_plugin_guide.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/plugins/doc2x_plugin_guide.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/plugins/google_search_plugin_guide.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/plugins/google_search_plugin_guide.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/plugins/searxng_plugin_guide.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/plugins/searxng_plugin_guide.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/plugins/upload_system_tool.mdx": "2025-10-30T22:14:07+08:00", "document/content/docs/introduction/guide/plugins/upload_system_tool.mdx": "2025-11-04T16:58:12+08:00",
"document/content/docs/introduction/guide/team_permissions/invitation_link.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/team_permissions/invitation_link.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/team_permissions/team_roles_permissions.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/team_permissions/team_roles_permissions.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/index.en.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/index.en.mdx": "2025-07-23T21:35:03+08:00",
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
"document/content/docs/protocol/terms.en.mdx": "2025-08-03T22:37:45+08:00", "document/content/docs/protocol/terms.en.mdx": "2025-08-03T22:37:45+08:00",
"document/content/docs/protocol/terms.mdx": "2025-08-03T22:37:45+08:00", "document/content/docs/protocol/terms.mdx": "2025-08-03T22:37:45+08:00",
"document/content/docs/toc.en.mdx": "2025-08-04T13:42:36+08:00", "document/content/docs/toc.en.mdx": "2025-08-04T13:42:36+08:00",
"document/content/docs/toc.mdx": "2025-10-23T19:11:11+08:00", "document/content/docs/toc.mdx": "2025-11-04T16:58:12+08:00",
"document/content/docs/upgrading/4-10/4100.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-10/4100.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-10/4101.mdx": "2025-09-08T20:07:20+08:00", "document/content/docs/upgrading/4-10/4101.mdx": "2025-09-08T20:07:20+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",
...@@ -111,10 +111,10 @@ ...@@ -111,10 +111,10 @@
"document/content/docs/upgrading/4-12/4122.mdx": "2025-09-07T14:41:48+08:00", "document/content/docs/upgrading/4-12/4122.mdx": "2025-09-07T14:41:48+08:00",
"document/content/docs/upgrading/4-12/4123.mdx": "2025-09-07T20:55:14+08:00", "document/content/docs/upgrading/4-12/4123.mdx": "2025-09-07T20:55:14+08:00",
"document/content/docs/upgrading/4-12/4124.mdx": "2025-09-17T22:29:56+08:00", "document/content/docs/upgrading/4-12/4124.mdx": "2025-09-17T22:29:56+08:00",
"document/content/docs/upgrading/4-13/4130.mdx": "2025-09-30T16:00:10+08:00", "document/content/docs/upgrading/4-13/4130.mdx": "2025-11-04T15:06:39+08:00",
"document/content/docs/upgrading/4-13/4131.mdx": "2025-09-30T15:47:06+08:00", "document/content/docs/upgrading/4-13/4131.mdx": "2025-09-30T15:47:06+08:00",
"document/content/docs/upgrading/4-13/4132.mdx": "2025-10-21T11:46:53+08:00", "document/content/docs/upgrading/4-13/4132.mdx": "2025-10-21T11:46:53+08:00",
"document/content/docs/upgrading/4-14/4140.mdx": "2025-11-03T12:13:10+08:00", "document/content/docs/upgrading/4-14/4140.mdx": "2025-11-04T16:58:12+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",
......
...@@ -46,6 +46,54 @@ const { LOG_LEVEL, STORE_LOG_LEVEL, SIGNOZ_STORE_LEVEL } = (() => { ...@@ -46,6 +46,54 @@ const { LOG_LEVEL, STORE_LOG_LEVEL, SIGNOZ_STORE_LEVEL } = (() => {
}; };
})(); })();
/**
* Sanitize object to prevent circular references for BSON serialization
* Remove properties that may contain circular references
*/
const sanitizeObjectForBSON = (obj: Record<string, any>): Record<string, any> => {
try {
// Use JSON stringify with replacer to handle circular references
const seen = new WeakSet();
const sanitized = JSON.parse(
JSON.stringify(obj, (key, value) => {
// Handle circular references
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular Reference]';
}
seen.add(value);
}
// Remove known problematic properties from axios config
if (key === 'config' && value && typeof value === 'object') {
return {
method: value.method,
url: value.url,
baseURL: value.baseURL,
headers: value.headers,
timeout: value.timeout,
responseType: value.responseType
};
}
// Remove functions and other non-serializable values
if (typeof value === 'function' || typeof value === 'symbol') {
return undefined;
}
return value;
})
);
return sanitized;
} catch (error) {
// If sanitization fails, return a safe fallback
return {
error: 'Failed to sanitize object',
originalKeys: Object.keys(obj)
};
}
};
/* add logger */ /* add logger */
export const addLog = { export const addLog = {
log(level: LogLevelEnum, msg: string, obj: Record<string, any> = {}) { log(level: LogLevelEnum, msg: string, obj: Record<string, any> = {}) {
...@@ -77,10 +125,13 @@ export const addLog = { ...@@ -77,10 +125,13 @@ export const addLog = {
if (level >= STORE_LOG_LEVEL && connectionMongo.connection.readyState === 1) { if (level >= STORE_LOG_LEVEL && connectionMongo.connection.readyState === 1) {
(async () => { (async () => {
try { try {
// Sanitize metadata to prevent circular reference errors
const safeMetadata = sanitizeObjectForBSON(obj);
await getMongoLog().create({ await getMongoLog().create({
text: msg, text: msg,
level, level,
metadata: obj metadata: safeMetadata
}); });
} catch (error) { } catch (error) {
console.error('store log error', error); console.error('store log error', error);
......
...@@ -163,7 +163,7 @@ export const loadRequestMessages = async ({ ...@@ -163,7 +163,7 @@ export const loadRequestMessages = async ({
// If imgUrl is a local path, load image from local, and set url to base64 // If imgUrl is a local path, load image from local, and set url to base64
if ( if (
imgUrl.startsWith('/') || imgUrl.startsWith('/') ||
process.env.MULTIPLE_DATA_TO_BASE64 === 'true' || process.env.MULTIPLE_DATA_TO_BASE64 !== 'false' ||
isInternalAddress(imgUrl) isInternalAddress(imgUrl)
) { ) {
const url = await (async () => { const url = await (async () => {
......
...@@ -42,28 +42,11 @@ export type TextEditorNode = BaseEditorNode & { ...@@ -42,28 +42,11 @@ export type TextEditorNode = BaseEditorNode & {
export type LineBreakEditorNode = BaseEditorNode & { export type LineBreakEditorNode = BaseEditorNode & {
type: 'linebreak'; type: 'linebreak';
}; };
export type VariableLabelEditorNode = BaseEditorNode & {
type: 'variableLabel';
variableKey: string;
};
export type VariableEditorNode = BaseEditorNode & {
type: 'Variable';
variableKey: string;
};
export type TabEditorNode = BaseEditorNode & { export type TabEditorNode = BaseEditorNode & {
type: 'tab'; type: 'tab';
}; };
export type ChildEditorNode = // Rich text
| TextEditorNode
| LineBreakEditorNode
| VariableLabelEditorNode
| VariableEditorNode
| TabEditorNode;
export type ParagraphEditorNode = BaseEditorNode & { export type ParagraphEditorNode = BaseEditorNode & {
type: 'paragraph'; type: 'paragraph';
children: ChildEditorNode[]; children: ChildEditorNode[];
...@@ -72,15 +55,33 @@ export type ParagraphEditorNode = BaseEditorNode & { ...@@ -72,15 +55,33 @@ export type ParagraphEditorNode = BaseEditorNode & {
indent: number; indent: number;
}; };
// ListItem 节点的 children 可以包含嵌套的 list 节点
export type ListItemChildEditorNode =
| TextEditorNode
| LineBreakEditorNode
| TabEditorNode
| VariableLabelEditorNode
| VariableEditorNode;
export type ListItemEditorNode = BaseEditorNode & { export type ListItemEditorNode = BaseEditorNode & {
type: 'listitem'; type: 'listitem';
children: Array<ChildEditorNode | ListEditorNode>; children: (ListItemChildEditorNode | ListEditorNode)[];
direction: string | null; direction: string | null;
format: string; format: string;
indent: number; indent: number;
value: number; value: number;
}; };
// Custom variable node types
export type VariableLabelEditorNode = BaseEditorNode & {
type: 'variableLabel';
variableKey: string;
};
export type VariableEditorNode = BaseEditorNode & {
type: 'Variable';
variableKey: string;
};
export type ListEditorNode = BaseEditorNode & { export type ListEditorNode = BaseEditorNode & {
type: 'list'; type: 'list';
children: ListItemEditorNode[]; children: ListItemEditorNode[];
...@@ -92,10 +93,20 @@ export type ListEditorNode = BaseEditorNode & { ...@@ -92,10 +93,20 @@ export type ListEditorNode = BaseEditorNode & {
tag: 'ul' | 'ol'; tag: 'ul' | 'ol';
}; };
export type ChildEditorNode =
| TextEditorNode
| LineBreakEditorNode
| TabEditorNode
| ParagraphEditorNode
| ListEditorNode
| ListItemEditorNode
| VariableLabelEditorNode
| VariableEditorNode;
export type EditorState = { export type EditorState = {
root: { root: {
type: 'root'; type: 'root';
children: Array<ParagraphEditorNode | ListEditorNode>; children: ChildEditorNode[];
direction: string; direction: string;
format: string; format: string;
indent: number; indent: number;
......
...@@ -17,7 +17,8 @@ import type { ...@@ -17,7 +17,8 @@ import type {
ListEditorNode, ListEditorNode,
ParagraphEditorNode, ParagraphEditorNode,
EditorState, EditorState,
ListItemInfo ListItemInfo,
ChildEditorNode
} from './type'; } from './type';
export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode | VariableNode>( export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode | VariableNode>(
...@@ -472,6 +473,62 @@ export const editorStateToText = (editor: LexicalEditor) => { ...@@ -472,6 +473,62 @@ export const editorStateToText = (editor: LexicalEditor) => {
const editorState = editor.getEditorState().toJSON() as EditorState; const editorState = editor.getEditorState().toJSON() as EditorState;
const paragraphs = editorState.root.children; const paragraphs = editorState.root.children;
const extractText = (node: ChildEditorNode): string => {
if (!node) return '';
// Handle line break nodes
if (node.type === 'linebreak') {
return '\n';
}
// Handle tab nodes
if (node.type === 'tab') {
return ' ';
}
// Handle text nodes
if (node.type === 'text') {
return node.text || '';
}
// Handle custom variable nodes
if (node.type === 'variableLabel' || node.type === 'Variable') {
return node.variableKey || '';
}
// Handle paragraph nodes - recursively process children
if (node.type === 'paragraph') {
if (!node.children || node.children.length === 0) {
return '';
}
return node.children.map(extractText).join('');
}
// Handle list item nodes - recursively process children (excluding nested lists)
if (node.type === 'listitem') {
if (!node.children || node.children.length === 0) {
return '';
}
// Filter out nested list nodes as they are handled separately
return node.children
.filter((child) => child.type !== 'list')
.map(extractText)
.join('');
}
// Handle list nodes - recursively process children
if (node.type === 'list') {
if (!node.children || node.children.length === 0) {
return '';
}
return node.children.map(extractText).join('');
}
// Unknown node type
console.warn('Unknown node type in extractText:', (node as any).type, node);
return '';
};
paragraphs.forEach((paragraph) => { paragraphs.forEach((paragraph) => {
if (paragraph.type === 'list') { if (paragraph.type === 'list') {
const listResults = processList({ list: paragraph }); const listResults = processList({ list: paragraph });
...@@ -483,19 +540,15 @@ export const editorStateToText = (editor: LexicalEditor) => { ...@@ -483,19 +540,15 @@ export const editorStateToText = (editor: LexicalEditor) => {
const indentSpaces = ' '.repeat(paragraph.indent || 0); const indentSpaces = ' '.repeat(paragraph.indent || 0);
children.forEach((child) => { children.forEach((child) => {
if (child.type === 'linebreak') { const val = extractText(child);
paragraphText.push('\n'); paragraphText.push(val);
} else if (child.type === 'text') {
paragraphText.push(child.text);
} else if (child.type === 'tab') {
paragraphText.push(' ');
} else if (child.type === 'variableLabel' || child.type === 'Variable') {
paragraphText.push(child.variableKey);
}
}); });
const finalText = paragraphText.join(''); const finalText = paragraphText.join('');
editorStateTextString.push(indentSpaces + finalText); editorStateTextString.push(indentSpaces + finalText);
} else {
const text = extractText(paragraph);
editorStateTextString.push(text);
} }
}); });
return editorStateTextString.join('\n'); return editorStateTextString.join('\n');
......
...@@ -651,11 +651,12 @@ const ChatBox = ({ ...@@ -651,11 +651,12 @@ const ChatBox = ({
// retry input // retry input
const onDelMessage = useCallback( const onDelMessage = useCallback(
(contentId: string) => { (contentId: string, delFile = true) => {
return delChatRecordById({ return delChatRecordById({
appId, appId,
chatId, chatId,
contentId, contentId,
delFile,
...outLinkAuthData ...outLinkAuthData
}); });
}, },
...@@ -672,7 +673,7 @@ const ChatBox = ({ ...@@ -672,7 +673,7 @@ const ChatBox = ({
await Promise.all( await Promise.all(
delHistory.map((item) => { delHistory.map((item) => {
if (item.dataId) { if (item.dataId) {
return onDelMessage(item.dataId); return onDelMessage(item.dataId, false);
} }
}) })
); );
......
...@@ -31,7 +31,8 @@ export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): Chat ...@@ -31,7 +31,8 @@ export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): Chat
type: item.file.type, type: item.file.type,
name: item.file.name, name: item.file.name,
icon: getFileIcon(item.file.name), icon: getFileIcon(item.file.name),
url: item.file.url url: item.file.url,
key: item.file.key
} }
: undefined : undefined
) )
......
...@@ -88,6 +88,7 @@ export type DeleteChatItemProps = OutLinkChatAuthProps & { ...@@ -88,6 +88,7 @@ export type DeleteChatItemProps = OutLinkChatAuthProps & {
appId: string; appId: string;
chatId: string; chatId: string;
contentId?: string; contentId?: string;
delFile?: boolean;
}; };
export type AdminUpdateFeedbackParams = AdminFbkType & { export type AdminUpdateFeedbackParams = AdminFbkType & {
......
...@@ -34,6 +34,7 @@ import { ...@@ -34,6 +34,7 @@ import {
import { InputTypeEnum } from '@/components/core/app/formRender/constant'; import { InputTypeEnum } from '@/components/core/app/formRender/constant';
import { WorkflowActionsContext } from '../../context/workflowActionsContext'; import { WorkflowActionsContext } from '../../context/workflowActionsContext';
import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance'; import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance';
import { useMemoizedFn } from 'ahooks';
const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { inputs = [], nodeId } = data; const { inputs = [], nodeId } = data;
...@@ -105,7 +106,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => ...@@ -105,7 +106,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
[inputs, nodeId, onChangeNode] [inputs, nodeId, onChangeNode]
); );
const ValueRender = useCallback( const ValueRender = useMemoizedFn(
({ updateItem, index }: { updateItem: TUpdateListItem; index: number }) => { ({ updateItem, index }: { updateItem: TUpdateListItem; index: number }) => {
const { inputType, formParams = {} } = (() => { const { inputType, formParams = {} } = (() => {
const value = updateItem.variable; const value = updateItem.variable;
...@@ -279,7 +280,6 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => ...@@ -279,7 +280,6 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
return ( return (
<Box minW={'250px'} maxW={'400px'} borderRadius={'sm'}> <Box minW={'250px'} maxW={'400px'} borderRadius={'sm'}>
<InputRender <InputRender
// @ts-ignore
inputType={inputType} inputType={inputType}
{...formParams} {...formParams}
isRichText={false} isRichText={false}
...@@ -294,18 +294,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => ...@@ -294,18 +294,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
</Flex> </Flex>
</Container> </Container>
); );
}, }
[
appDetail.chatConfig,
externalProviderWorkflowVariables,
getNodeById,
nodeId,
onUpdateList,
systemConfigNode,
t,
updateList,
variables
]
); );
const Render = useMemo(() => { const Render = useMemo(() => {
......
...@@ -174,7 +174,7 @@ async function migrateSystemPluginsToTools(typeToGroupMap: Map<string, string>): ...@@ -174,7 +174,7 @@ async function migrateSystemPluginsToTools(typeToGroupMap: Map<string, string>):
pluginId: plugin.pluginId?.startsWith(AppToolSourceEnum.community) pluginId: plugin.pluginId?.startsWith(AppToolSourceEnum.community)
? plugin.pluginId.replace(AppToolSourceEnum.community, AppToolSourceEnum.systemTool) ? plugin.pluginId.replace(AppToolSourceEnum.community, AppToolSourceEnum.systemTool)
: plugin.pluginId, : plugin.pluginId,
status: !plugin.isActive ? PluginStatusEnum.Offline : PluginStatusEnum.Normal, status: plugin.isActive === false ? PluginStatusEnum.Offline : PluginStatusEnum.Normal,
defaultInstalled: false, defaultInstalled: false,
originCost: plugin.originCost || 0, originCost: plugin.originCost || 0,
currentCost: plugin.currentCost || 0, currentCost: plugin.currentCost || 0,
...@@ -182,7 +182,7 @@ async function migrateSystemPluginsToTools(typeToGroupMap: Map<string, string>): ...@@ -182,7 +182,7 @@ async function migrateSystemPluginsToTools(typeToGroupMap: Map<string, string>):
pluginOrder: plugin.pluginOrder, pluginOrder: plugin.pluginOrder,
systemKeyCost: plugin.systemKeyCost || 0, systemKeyCost: plugin.systemKeyCost || 0,
customConfig: plugin.customConfig ? { ...plugin.customConfig } : {}, customConfig: plugin.customConfig ? { ...plugin.customConfig } : {},
inputListVal: plugin.inputListVal || {} inputListVal: plugin.inputListVal
}; };
// 迁移 templateType → tags // 迁移 templateType → tags
......
...@@ -9,8 +9,8 @@ import { MongoChatItemResponse } from '@fastgpt/service/core/chat/chatItemRespon ...@@ -9,8 +9,8 @@ import { MongoChatItemResponse } from '@fastgpt/service/core/chat/chatItemRespon
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat'; import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat';
async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextApiResponse) { async function handler(req: ApiRequestProps<DeleteChatItemProps>, res: NextApiResponse) {
const { appId, chatId, contentId } = req.query; const { appId, chatId, contentId, delFile = true } = req.body;
if (!contentId || !chatId) { if (!contentId || !chatId) {
return Promise.reject('contentId or chatId is empty'); return Promise.reject('contentId or chatId is empty');
...@@ -20,7 +20,7 @@ async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextA ...@@ -20,7 +20,7 @@ async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextA
req, req,
authToken: true, authToken: true,
authApiKey: true, authApiKey: true,
...req.query ...req.body
}); });
await mongoSessionRun(async (session) => { await mongoSessionRun(async (session) => {
...@@ -36,7 +36,7 @@ async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextA ...@@ -36,7 +36,7 @@ async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextA
dataId: contentId dataId: contentId
}).session(session); }).session(session);
if (item?.obj === ChatRoleEnum.Human) { if (item?.obj === ChatRoleEnum.Human && delFile) {
const s3ChatSource = getS3ChatSource(); const s3ChatSource = getS3ChatSource();
for (const value of item.value) { for (const value of item.value) {
if (value.type === ChatItemValueTypeEnum.file && value.file?.key) { if (value.type === ChatItemValueTypeEnum.file && value.file?.key) {
......
...@@ -2,7 +2,7 @@ import { NextAPI } from '@/service/middleware/entry'; ...@@ -2,7 +2,7 @@ import { NextAPI } from '@/service/middleware/entry';
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { MongoTeamInstalledPlugin } from '@fastgpt/service/core/plugin/schema/teamInstalledPluginSchema'; import { MongoTeamInstalledPlugin } from '@fastgpt/service/core/plugin/schema/teamInstalledPluginSchema';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import type { ToggleInstallPluginBodyType } from '@fastgpt/global/openapi/core/plugin/team/api'; import type { ToggleInstallPluginBodyType } from '@fastgpt/global/openapi/core/plugin/team/api';
export type ToggleInstallPluginBody = ToggleInstallPluginBodyType; export type ToggleInstallPluginBody = ToggleInstallPluginBodyType;
...@@ -18,7 +18,7 @@ async function handler( ...@@ -18,7 +18,7 @@ async function handler(
const { teamId } = await authUserPer({ const { teamId } = await authUserPer({
req, req,
authToken: true, authToken: true,
per: WritePermissionVal per: ReadPermissionVal
}); });
await MongoTeamInstalledPlugin.findOneAndUpdate( await MongoTeamInstalledPlugin.findOneAndUpdate(
......
...@@ -75,7 +75,7 @@ export const delClearChatHistories = (data: ClearHistoriesProps) => ...@@ -75,7 +75,7 @@ export const delClearChatHistories = (data: ClearHistoriesProps) =>
* delete one chat record * delete one chat record
*/ */
export const delChatRecordById = (data: DeleteChatItemProps) => export const delChatRecordById = (data: DeleteChatItemProps) =>
DELETE(`/core/chat/item/delete`, data); POST(`/core/chat/item/delete`, data);
/** /**
* 修改历史记录: 标题/置顶 * 修改历史记录: 标题/置顶
......
...@@ -411,8 +411,11 @@ describe('loadRequestMessages function tests', () => { ...@@ -411,8 +411,11 @@ describe('loadRequestMessages function tests', () => {
const result = await loadRequestMessages({ messages, useVision: true }); const result = await loadRequestMessages({ messages, useVision: true });
expect(result).toHaveLength(1); expect(result).toHaveLength(1);
expect(typeof result[0].content).toBe('string'); // When useVision is true and text contains image URL, it returns array format
expect(result[0].content).toBe('https://example.com/image.png'); expect(Array.isArray(result[0].content)).toBe(true);
const content = result[0].content as any[];
expect(content.some((item: any) => item.type === 'image_url')).toBe(true);
expect(content.some((item: any) => item.type === 'text')).toBe(true);
}); });
it('should not extract images when useVision is false', async () => { it('should not extract images when useVision is false', async () => {
...@@ -474,9 +477,10 @@ describe('loadRequestMessages function tests', () => { ...@@ -474,9 +477,10 @@ describe('loadRequestMessages function tests', () => {
const result = await loadRequestMessages({ messages, useVision: true }); const result = await loadRequestMessages({ messages, useVision: true });
expect(result).toHaveLength(1); expect(result).toHaveLength(1);
// When array content has only text items and filtered images, it becomes a string // When array content has text and image_url, remains as array
expect(typeof result[0].content).toBe('string'); expect(Array.isArray(result[0].content)).toBe(true);
expect(result[0].content).toBe('Hello'); const content = result[0].content as any[];
expect(content.some((item: any) => item.type === 'text')).toBe(true);
}); });
it('should filter out empty text items from array content', async () => { it('should filter out empty text items from array content', async () => {
...@@ -539,6 +543,9 @@ describe('loadRequestMessages function tests', () => { ...@@ -539,6 +543,9 @@ describe('loadRequestMessages function tests', () => {
}); });
it('should handle invalid remote images gracefully', async () => { it('should handle invalid remote images gracefully', async () => {
const originalEnv = process.env.MULTIPLE_DATA_TO_BASE64;
process.env.MULTIPLE_DATA_TO_BASE64 = 'false'; // Disable base64 conversion
const messages: ChatCompletionMessageParam[] = [ const messages: ChatCompletionMessageParam[] = [
{ {
role: ChatCompletionRequestMessageRoleEnum.User, role: ChatCompletionRequestMessageRoleEnum.User,
...@@ -554,9 +561,17 @@ describe('loadRequestMessages function tests', () => { ...@@ -554,9 +561,17 @@ describe('loadRequestMessages function tests', () => {
const result = await loadRequestMessages({ messages, useVision: true }); const result = await loadRequestMessages({ messages, useVision: true });
expect(result).toHaveLength(1); expect(result).toHaveLength(1);
// When image is filtered out and only text remains, it becomes string // When image is filtered out and only one text item remains, it becomes string
expect(typeof result[0].content).toBe('string'); expect(typeof result[0].content).toBe('string');
expect(result[0].content).toBe('Text'); expect(result[0].content).toBe('Text');
// Restore original environment
if (originalEnv !== undefined) {
process.env.MULTIPLE_DATA_TO_BASE64 = originalEnv;
} else {
// @ts-ignore
delete process.env.MULTIPLE_DATA_TO_BASE64;
}
}); });
it('should handle 405 status as valid image', async () => { it('should handle 405 status as valid image', async () => {
...@@ -577,9 +592,11 @@ describe('loadRequestMessages function tests', () => { ...@@ -577,9 +592,11 @@ describe('loadRequestMessages function tests', () => {
const result = await loadRequestMessages({ messages, useVision: true }); const result = await loadRequestMessages({ messages, useVision: true });
expect(result).toHaveLength(1); expect(result).toHaveLength(1);
// The function processes images from array content differently, expects text to remain // 405 status is treated as valid, so image is kept and content is array
expect(typeof result[0].content).toBe('string'); expect(Array.isArray(result[0].content)).toBe(true);
expect(result[0].content).toBe('Check this image:'); const content = result[0].content as any[];
expect(content.some((item: any) => item.type === 'text')).toBe(true);
expect(content.some((item: any) => item.type === 'image_url')).toBe(true);
}); });
it('should remove origin from image URLs when provided', async () => { it('should remove origin from image URLs when provided', async () => {
......
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