Commit 3534a7fe authored by ligaowei's avatar ligaowei

refactor(react): 简化并优化ReAct执行器及回调实现

- 修改ReActAgentProcessor调用接口,简化执行逻辑,去除带Agent参数的方法调用
- 重构DefaultReactCallback,完善工作面板记录逻辑,增加日志记录,区分思考、动作、观察、最终答案步骤
- 优化异常处理,确保工作面板异常时仍记录错误工具调用信息
- 简化DefaultReactExecutor实现,去除冗余注释和过时代码,增强流式执行的日志和错误处理
- 改进流处理过程,基于完整响应解析Thought、Action、Observation和Final Answer段落,避免重复触发步骤
- 增加辅助方法提取工具名称和参数,支持多格式解析
- 优化智能体工具管理,确保DateTimeTools始终存在,移除多余日志
- 提升响应内容存储逻辑,确保最终答案正确触发并保存到历史记忆
- 总体提升代码可读性和维护性,增强日志信息输出便于调试和监控
parent 90b3874a
This diff is collapsed.
package pangea.hiagent.web.repository; package pangea.hiagent;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
......
...@@ -88,7 +88,7 @@ public class ReActAgentProcessor extends BaseAgentProcessor { ...@@ -88,7 +88,7 @@ public class ReActAgentProcessor extends BaseAgentProcessor {
} }
// 使用ReAct执行器执行流程,传递Agent对象以支持记忆功能 // 使用ReAct执行器执行流程,传递Agent对象以支持记忆功能
String finalAnswer = defaultReactExecutor.executeWithAgent(client, userMessage, tools, agent); String finalAnswer = defaultReactExecutor.execute(client, userMessage, tools, agent);
// 将助理回复添加到ChatMemory // 将助理回复添加到ChatMemory
String sessionId = generateSessionId(agent, userId); String sessionId = generateSessionId(agent, userId);
...@@ -139,7 +139,7 @@ public class ReActAgentProcessor extends BaseAgentProcessor { ...@@ -139,7 +139,7 @@ public class ReActAgentProcessor extends BaseAgentProcessor {
} }
// 使用ReAct执行器流式执行流程,传递Agent对象以支持记忆功能 // 使用ReAct执行器流式执行流程,传递Agent对象以支持记忆功能
defaultReactExecutor.executeStreamWithAgent(client, userMessage, tools, tokenConsumer, agent); defaultReactExecutor.executeStream(client, userMessage, tools, tokenConsumer, agent);
} catch (Exception e) { } catch (Exception e) {
agentErrorHandler.handleStreamError(e, tokenConsumer, "流式处理ReAct请求时发生错误"); agentErrorHandler.handleStreamError(e, tokenConsumer, "流式处理ReAct请求时发生错误");
agentErrorHandler.ensureCompletionCallback(tokenConsumer, "处理请求时发生错误: " + e.getMessage()); agentErrorHandler.ensureCompletionCallback(tokenConsumer, "处理请求时发生错误: " + e.getMessage());
......
...@@ -6,78 +6,96 @@ import lombok.extern.slf4j.Slf4j; ...@@ -6,78 +6,96 @@ import lombok.extern.slf4j.Slf4j;
import pangea.hiagent.workpanel.IWorkPanelDataCollector; import pangea.hiagent.workpanel.IWorkPanelDataCollector;
/** /**
* 自定义 ReAct 回调类,用于捕获并处理 ReAct 的每一步思维过程 * 简化的ReAct回调类
* 适配项目现有的 ReAct 实现方式
*/ */
@Slf4j @Slf4j
@Component // 注册为 Spring 组件,方便注入 @Component
public class DefaultReactCallback implements ReactCallback { public class DefaultReactCallback implements ReactCallback {
@Autowired @Autowired
private IWorkPanelDataCollector workPanelCollector; private IWorkPanelDataCollector workPanelCollector;
/**
* ReAct 每执行一个步骤,该方法会被触发
* @param reactStep ReAct 步骤对象,包含步骤的所有核心信息
*/
@Override @Override
public void onStep(ReactStep reactStep) { public void onStep(ReactStep reactStep) {
// 将信息记录到工作面板 log.info("ReAct步骤触发: 类型={}, 内容摘要={}",
reactStep.getStepType(),
reactStep.getContent() != null ?
reactStep.getContent().substring(0, Math.min(50, reactStep.getContent().length())) : "null");
recordReactStepToWorkPanel(reactStep); recordReactStepToWorkPanel(reactStep);
} }
/**
* 处理 ReAct 最终答案步骤
* @param finalAnswer 最终答案
*/
@Override @Override
public void onFinalAnswer(String finalAnswer) { public void onFinalAnswer(String finalAnswer) {
// 创建一个FINAL_ANSWER类型的ReactStep并处理
ReactStep finalStep = new ReactStep(0, ReactStepType.FINAL_ANSWER, finalAnswer); ReactStep finalStep = new ReactStep(0, ReactStepType.FINAL_ANSWER, finalAnswer);
recordReactStepToWorkPanel(finalStep); recordReactStepToWorkPanel(finalStep);
} }
/**
* 将ReAct步骤记录到工作面板
* @param reactStep ReAct步骤
*/
private void recordReactStepToWorkPanel(ReactStep reactStep) { private void recordReactStepToWorkPanel(ReactStep reactStep) {
if (workPanelCollector == null) { if (workPanelCollector == null) {
log.debug("无法记录到工作面板:collector为null");
return; return;
} }
try { try {
switch (reactStep.getStepType()) { switch (reactStep.getStepType()) {
case THOUGHT: case THOUGHT:
workPanelCollector.recordThinking(reactStep.getContent(), "reasoning"); workPanelCollector.recordThinking(reactStep.getContent(), "thought");
log.info("[WorkPanel] 记录思考步骤: {}",
reactStep.getContent().substring(0, Math.min(100, reactStep.getContent().length())));
break; break;
case ACTION: case ACTION:
if (reactStep.getAction() != null) { if (reactStep.getAction() != null) {
// 使用recordToolCallAction记录工具调用开始,状态为pending // 记录工具调用动作
String toolName = reactStep.getAction().getToolName();
Object parameters = reactStep.getAction().getParameters();
// 记录工具调用,初始状态为pending
workPanelCollector.recordToolCallAction( workPanelCollector.recordToolCallAction(
reactStep.getAction().getToolName(), toolName,
reactStep.getAction().getParameters(), parameters,
null, null, // 结果为空
"pending", "pending", // 状态为pending
null null // 错误信息为空
); );
// 同时记录工具调用信息到日志
log.info("[WorkPanel] 记录工具调用: 工具={} 参数={}", toolName, parameters);
} else {
// 如果没有具体的工具信息,记录为一般动作
workPanelCollector.recordThinking(reactStep.getContent(), "action");
log.info("[WorkPanel] 记录动作步骤: {}",
reactStep.getContent().substring(0, Math.min(100, reactStep.getContent().length())));
} }
break; break;
case OBSERVATION: case OBSERVATION:
if (reactStep.getObservation() != null && reactStep.getAction() != null) { if (reactStep.getObservation() != null) {
// 使用recordToolCallAction记录工具调用完成,状态为success // 检查是否有对应的动作信息
workPanelCollector.recordToolCallAction( if (reactStep.getAction() != null) {
reactStep.getAction().getToolName(), // 使用动作信息更新工具调用结果
reactStep.getAction().getParameters(), workPanelCollector.recordToolCallAction(
reactStep.getObservation().getContent(), reactStep.getAction().getToolName(),
"success", reactStep.getAction().getParameters(),
null reactStep.getObservation().getContent(),
); "success", // 状态为success
null // 无错误信息
);
log.info("[WorkPanel] 更新工具调用结果: 工具={} 结果摘要={}",
reactStep.getAction().getToolName(),
reactStep.getObservation().getContent().substring(0, Math.min(50, reactStep.getObservation().getContent().length())));
} else {
// 如果没有动作信息,记录为观察结果
workPanelCollector.recordThinking(reactStep.getContent(), "observation");
log.info("[WorkPanel] 记录观察步骤: {}",
reactStep.getContent().substring(0, Math.min(100, reactStep.getContent().length())));
}
} }
break; break;
case FINAL_ANSWER: case FINAL_ANSWER:
workPanelCollector.recordFinalAnswer(reactStep.getContent()); workPanelCollector.recordFinalAnswer(reactStep.getContent());
// 记录最终答案到日志
log.info("[WorkPanel] 记录最终答案: {}",
reactStep.getContent().substring(0, Math.min(100, reactStep.getContent().length())));
break; break;
default: default:
log.warn("未知的ReAct步骤类型: {}", reactStep.getStepType()); log.warn("未知的ReAct步骤类型: {}", reactStep.getStepType());
...@@ -85,6 +103,20 @@ public class DefaultReactCallback implements ReactCallback { ...@@ -85,6 +103,20 @@ public class DefaultReactCallback implements ReactCallback {
} }
} catch (Exception e) { } catch (Exception e) {
log.error("记录ReAct步骤到工作面板失败", e); log.error("记录ReAct步骤到工作面板失败", e);
// 即使发生异常,也尝试记录错误信息到工作面板
try {
if (reactStep != null && reactStep.getAction() != null) {
workPanelCollector.recordToolCallAction(
reactStep.getAction().getToolName(),
reactStep.getAction().getParameters(),
"记录失败: " + e.getMessage(),
"error",
System.currentTimeMillis() // 使用当前时间戳作为执行时间
);
}
} catch (Exception ex) {
log.error("记录错误信息到工作面板也失败", ex);
}
} }
} }
} }
\ No newline at end of file
...@@ -17,19 +17,7 @@ public interface ReactExecutor { ...@@ -17,19 +17,7 @@ public interface ReactExecutor {
* @param tools 工具列表 * @param tools 工具列表
* @return 最终答案 * @return 最终答案
*/ */
String execute(ChatClient chatClient, String userInput, List<Object> tools); String execute(ChatClient chatClient, String userInput, List<Object> tools, Agent agent);
/**
* 执行ReAct流程(同步方式)- 支持Agent配置
* @param chatClient ChatClient实例
* @param userInput 用户输入
* @param tools 工具列表
* @param agent Agent对象(可选)
* @return 最终答案
*/
default String executeWithAgent(ChatClient chatClient, String userInput, List<Object> tools, Agent agent) {
return execute(chatClient, userInput, tools);
}
/** /**
* 流式执行ReAct流程 * 流式执行ReAct流程
...@@ -38,19 +26,7 @@ public interface ReactExecutor { ...@@ -38,19 +26,7 @@ public interface ReactExecutor {
* @param tools 工具列表 * @param tools 工具列表
* @param tokenConsumer token处理回调函数 * @param tokenConsumer token处理回调函数
*/ */
void executeStream(ChatClient chatClient, String userInput, List<Object> tools, Consumer<String> tokenConsumer); void executeStream(ChatClient chatClient, String userInput, List<Object> tools, Consumer<String> tokenConsumer, Agent agent);
/**
* 流式执行ReAct流程 - 支持Agent配置
* @param chatClient ChatClient实例
* @param userInput 用户输入
* @param tools 工具列表
* @param tokenConsumer token处理回调函数
* @param agent Agent对象(可选)
*/
default void executeStreamWithAgent(ChatClient chatClient, String userInput, List<Object> tools, Consumer<String> tokenConsumer, Agent agent) {
executeStream(chatClient, userInput, tools, tokenConsumer);
}
/** /**
* 添加ReAct回调 * 添加ReAct回调
......
package pangea.hiagent.agent.react;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import pangea.hiagent.workpanel.IWorkPanelDataCollector;
@Slf4j
@Component
public class TokenTextSegmenter {
@Autowired
private IWorkPanelDataCollector workPanelCollector;
// 定义分段标识(按出现优先级排序)
private static final String[] SEGMENT_MARKERS = {
"Thought:",
"Action:",
"Observation:",
"Final_Answer:"
};
// 当前缓存的输入字符
private StringBuilder currentInputBuffer;
// 已匹配到的分段标识
private String matchedMarker;
// 分段内容起始索引
private int segmentContentStartIndex;
public TokenTextSegmenter() {
currentInputBuffer = new StringBuilder();
matchedMarker = null;
segmentContentStartIndex = 0;
}
/**
* 逐字输入字符并处理分段
*
* @param inputChar 单个输入字符
* @return 当分割出有效文本段时返回该段内容,否则返回null
*/
public void inputChar(String inputChar) {
// 将字符加入缓存
currentInputBuffer.append(inputChar);
String currentBufferStr = currentInputBuffer.toString();
// 1. 未匹配到标识时,检测是否出现分段标识
if (matchedMarker == null) {
for (String marker : SEGMENT_MARKERS) {
if (currentBufferStr.endsWith(marker)) {
// 匹配到标识,记录信息
matchedMarker = marker;
segmentContentStartIndex = currentBufferStr.length();
// 输出标识本身(可选,根据需求决定是否包含标识)
log.info("【识别到分段标识】: {}", matchedMarker);
}
}
}
// 2. 已匹配到标识,检测是否出现下一个标识(或文本结束)
else {
for (String marker : SEGMENT_MARKERS) {
if (!marker.equals(matchedMarker) && currentBufferStr.contains(marker)) {
// 找到下一个标识,截取当前分段内容
int nextMarkerStartIndex = currentBufferStr.indexOf(marker);
String segmentContent = currentBufferStr.substring(segmentContentStartIndex, nextMarkerStartIndex)
.trim();
// 重置状态,准备处理下一个分段
resetSegmentState(nextMarkerStartIndex);
// 输出当前分段内容
outputSegment(matchedMarker, segmentContent);
}
}
}
}
/**
* 文本输入结束时,处理最后一个分段
*
* @return 最后一个分段的内容,无则返回null
*/
public void finishInput() {
if (matchedMarker != null && segmentContentStartIndex < currentInputBuffer.length()) {
String lastSegmentContent = currentInputBuffer.substring(segmentContentStartIndex).trim();
resetSegmentState(currentInputBuffer.length());
outputSegment(matchedMarker, lastSegmentContent);
}
}
/**
* 重置分段状态
*
* @param newStartIndex 新的起始索引
*/
private void resetSegmentState(int newStartIndex) {
// 保留未处理的部分,用于下一个分段
String remainingStr = currentInputBuffer.substring(newStartIndex);
currentInputBuffer = new StringBuilder(remainingStr);
matchedMarker = null;
segmentContentStartIndex = 0;
}
/**
* 输出分段内容(封装输出逻辑)
*
* @param marker 分段标识
* @param content 分段内容
* @return 格式化后的分段内容
*/
private void outputSegment(String marker, String content) {
log.info("【分段内容】{}: {}", marker, content);
workPanelCollector.addEvent(null);
}
}
...@@ -21,19 +21,18 @@ MERGE INTO agent (id, name, description, status, default_model, owner, system_pr ...@@ -21,19 +21,18 @@ MERGE INTO agent (id, name, description, status, default_model, owner, system_pr
-- 插入Agent和Tool的关联关系 -- 插入Agent和Tool的关联关系
MERGE INTO agent_tool_relation (id, agent_id, tool_id) VALUES MERGE INTO agent_tool_relation (id, agent_id, tool_id) VALUES
('relation-4', 'agent-2', 'tool-1'),
('relation-5', 'agent-2', 'tool-2'), ('relation-5', 'agent-2', 'tool-2'),
('relation-6', 'agent-2', 'tool-5'), ('relation-6', 'agent-2', 'tool-5'),
('relation-7', 'agent-2', 'tool-6'), ('relation-7', 'agent-2', 'tool-6'),
('relation-8', 'agent-3', 'tool-2'), ('relation-8', 'agent-3', 'tool-2'),
('relation-9', 'agent-3', 'tool-7'), ('relation-9', 'agent-3', 'tool-7'),
('relation-10', 'agent-3', 'tool-8'), ('relation-10', 'agent-3', 'tool-8'),
('relation-11', 'agent-4', 'tool-1'),
('relation-12', 'agent-4', 'tool-9'), ('relation-12', 'agent-4', 'tool-9'),
('relation-13', 'agent-4', 'tool-10'), ('relation-13', 'agent-4', 'tool-10'),
('relation-14', 'agent-5', 'tool-1'),
('relation-15', 'agent-5', 'tool-11'), ('relation-15', 'agent-5', 'tool-11'),
('relation-16', 'agent-5', 'tool-12'); ('relation-17', 'agent-2', 'tool-3'),
('relation-18', 'agent-4', 'tool-3'),
('relation-19', 'agent-5', 'tool-3');
-- 插入默认工具数据 -- 插入默认工具数据
MERGE INTO tool (id, name, display_name, description, category, status, bean_name, owner, timeout, http_method, parameters, return_type, return_schema, implementation, api_endpoint, headers, auth_type, auth_config) VALUES MERGE INTO tool (id, name, display_name, description, category, status, bean_name, owner, timeout, http_method, parameters, return_type, return_schema, implementation, api_endpoint, headers, auth_type, auth_config) VALUES
...@@ -48,9 +47,26 @@ MERGE INTO tool (id, name, display_name, description, category, status, bean_nam ...@@ -48,9 +47,26 @@ MERGE INTO tool (id, name, display_name, description, category, status, bean_nam
('tool-9', 'writingStyleReference', '创作风格参考', '提供各种写作风格的参考和指导', 'FUNCTION', 'active', 'writingStyleReferenceTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'), ('tool-9', 'writingStyleReference', '创作风格参考', '提供各种写作风格的参考和指导', 'FUNCTION', 'active', 'writingStyleReferenceTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-10', 'documentTemplate', '文档模板', '提供各种类型的文档模板', 'FUNCTION', 'active', 'documentTemplateTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'), ('tool-10', 'documentTemplate', '文档模板', '提供各种类型的文档模板', 'FUNCTION', 'active', 'documentTemplateTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-11', 'studyPlanGeneration', '学习计划制定', '根据学习目标和时间安排制定个性化的学习计划', 'FUNCTION', 'active', 'studyPlanGenerationTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'), ('tool-11', 'studyPlanGeneration', '学习计划制定', '根据学习目标和时间安排制定个性化的学习计划', 'FUNCTION', 'active', 'studyPlanGenerationTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-12', 'courseMaterialRetrieval', '课程资料检索', '检索和查询相关课程资料', 'FUNCTION', 'active', 'courseMaterialRetrievalTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'); ('tool-12', 'courseMaterialRetrieval', '课程资料检索', '检索和查询相关课程资料', 'FUNCTION', 'active', 'courseMaterialRetrievalTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-13', 'emailTools', '邮件工具', '提供基于POP3协议的邮件访问功能', 'FUNCTION', 'active', 'emailTools', 'user-001', 30000, 'POST', '{"defaultPop3Port": 995, "defaultAttachmentPath": "attachments", "pop3SslEnable": true, "pop3SocketFactoryClass": "javax.net.ssl.SSLSocketFactory"}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-14', 'fileProcessing', '文件处理工具', '提供文件读写和管理功能,支持多种文本格式文件', 'FUNCTION', 'active', 'fileProcessingTools', 'user-001', 10000, 'POST', '{"textFileExtensions": ".txt,.md,.java,.html,.htm,.css,.js,.json,.xml,.yaml,.yml,.properties,.sql,.py,.cpp,.c,.h,.cs,.php,.rb,.go,.rs,.swift,.kt,.scala,.sh,.bat,.cmd,.ps1,.log,.csv,.ts,.jsx,.tsx,.vue,.scss,.sass,.less", "imageFileExtensions": ".jpg,.jpeg,.png,.gif,.bmp,.svg,.webp,.ico", "defaultStorageDir": "storage"}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-15', 'hisenseSsoAuth', '海信SSO认证工具', '用于访问需要SSO认证的海信业务系统,自动完成登录并提取页面内容', 'FUNCTION', 'active', 'hisenseSsoAuthTool', 'user-001', 60000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-16', 'oauth2Authorization', 'OAuth2.0授权工具', '支持标准OAuth2.0认证流程,包括密码凭证流、令牌刷新和受保护资源访问', 'FUNCTION', 'active', 'oauth2AuthorizationTool', 'user-001', 30000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-17', 'orderQuery', '订单查询工具', '用于查询客户订单信息', 'FUNCTION', 'active', 'orderQueryTool', 'user-001', 10000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-18', 'playwrightWeb', 'Playwright网页自动化工具', '提供基于Playwright的网页内容抓取、交互操作、截图等功能', 'FUNCTION', 'active', 'playwrightWebTools', 'user-001', 30000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-19', 'refundProcessing', '退款处理工具', '用于处理客户退款申请', 'FUNCTION', 'active', 'refundProcessingTool', 'user-001', 10000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-20', 'storageFileAccess', '存储文件访问工具', '提供访问服务器后端 storage 目录下文件的功能', 'FUNCTION', 'active', 'storageFileAccessTool', 'user-001', 10000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-21', 'stringProcessing', '字符串处理工具', '提供字符串处理和转换功能', 'FUNCTION', 'active', 'stringProcessingTools', 'user-001', 5000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-22', 'webPageAccess', '网页访问工具', '提供根据网站名称或URL地址访问网页并在工作面板中预览的功能', 'FUNCTION', 'active', 'webPageAccessTools', 'user-001', 30000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}');
-- 插入默认工具配置数据 -- 插入默认工具配置数据
MERGE INTO tool_configs (id, tool_name, param_name, param_value, description, default_value, type, required, group_name) VALUES MERGE INTO tool_configs (id, tool_name, param_name, param_value, description, default_value, type, required, group_name) VALUES
('config-1', 'search', 'apiKey', 'test-key-123', '搜索引擎API密钥', '', 'string', 1, 'auth'), ('config-1', 'search', 'apiKey', 'test-key-123', '搜索引擎API密钥', '', 'string', 1, 'auth'),
('config-2', 'search', 'endpoint', 'https://api.search.com/v1/search', '搜索引擎API端点', 'https://api.search.com/v1/search', 'string', 1, 'connection'); ('config-2', 'search', 'endpoint', 'https://api.search.com/v1/search', '搜索引擎API端点', 'https://api.search.com/v1/search', 'string', 1, 'connection'),
\ No newline at end of file ('config-3', 'emailTools', 'defaultPop3Port', '995', '默认POP3服务器端口', '995', 'integer', 1, 'email'),
('config-4', 'emailTools', 'defaultAttachmentPath', 'attachments', '默认附件保存路径', 'attachments', 'string', 1, 'email'),
('config-5', 'emailTools', 'pop3SslEnable', 'true', '是否启用POP3 SSL', 'true', 'boolean', 1, 'email'),
('config-6', 'emailTools', 'pop3SocketFactoryClass', 'javax.net.ssl.SSLSocketFactory', 'POP3 SSL套接字工厂类', 'javax.net.ssl.SSLSocketFactory', 'string', 1, 'email'),
('config-7', 'fileProcessing', 'textFileExtensions', '.txt,.md,.java,.html,.htm,.css,.js,.json,.xml,.yaml,.yml,.properties,.sql,.py,.cpp,.c,.h,.cs,.php,.rb,.go,.rs,.swift,.kt,.scala,.sh,.bat,.cmd,.ps1,.log,.csv,.ts,.jsx,.tsx,.vue,.scss,.sass,.less', '支持的文本文件扩展名,逗号分隔', '.txt,.md,.java,.html,.htm,.css,.js,.json,.xml,.yaml,.yml,.properties,.sql,.py,.cpp,.c,.h,.cs,.php,.rb,.go,.rs,.swift,.kt,.scala,.sh,.bat,.cmd,.ps1,.log,.csv,.ts,.jsx,.tsx,.vue,.scss,.sass,.less', 'string', 1, 'file'),
('config-8', 'fileProcessing', 'imageFileExtensions', '.jpg,.jpeg,.png,.gif,.bmp,.svg,.webp,.ico', '支持的图片文件扩展名,逗号分隔', '.jpg,.jpeg,.png,.gif,.bmp,.svg,.webp,.ico', 'string', 1, 'file'),
('config-9', 'fileProcessing', 'defaultStorageDir', 'storage', '默认文件存储目录', 'storage', 'string', 1, 'file');
\ No newline at end of file
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 to comment