mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(i18n): complete backend i18n — guard rules, tool errors, response codes
This commit is contained in:
parent
e662cce91a
commit
81ff1bf491
@ -431,7 +431,7 @@ public class NodeStreamingChatHelper {
|
||||
// Client error (400): 不重试(参数/格式错误重试也不会变)
|
||||
if (errorType == ErrorType.CLIENT_ERROR) {
|
||||
log.error("[{}] Client error (400), not retrying: {}", phase, error.getMessage());
|
||||
return buildErrorResultWithType("请求参数错误: " + extractUserFriendlyError(error),
|
||||
return buildErrorResultWithType("Bad request: " + extractUserFriendlyError(error),
|
||||
conversationId, phase, errorType);
|
||||
}
|
||||
|
||||
@ -625,12 +625,12 @@ public class NodeStreamingChatHelper {
|
||||
String msg = error.getMessage();
|
||||
if (msg == null) return error.getClass().getSimpleName();
|
||||
// 对 Jackson 反序列化错误,提取关键信息
|
||||
if (msg.contains("engine_overloaded")) return "模型服务过载,请稍后重试";
|
||||
if (msg.contains("unsupported image format") || msg.contains("unsupported")) return "不支持的文件格式(如 SVG),请使用 PNG/JPG 等光栅图片";
|
||||
if (msg.contains("invalid_request_error") || msg.contains("400 Bad Request")) return "请求参数错误,请检查输入";
|
||||
if (msg.contains("rate_limit") || msg.contains("429")) return "请求频率过高,请稍后重试";
|
||||
if (msg.contains("timeout") || msg.contains("Timeout")) return "请求超时,请重试";
|
||||
if (msg.contains("502") || msg.contains("503") || msg.contains("504")) return "模型服务暂时不可用";
|
||||
if (msg.contains("engine_overloaded")) return "Model service overloaded, please retry later";
|
||||
if (msg.contains("unsupported image format") || msg.contains("unsupported")) return "Unsupported file format (e.g. SVG), use PNG/JPG instead";
|
||||
if (msg.contains("invalid_request_error") || msg.contains("400 Bad Request")) return "Bad request, please check input";
|
||||
if (msg.contains("rate_limit") || msg.contains("429")) return "Rate limit exceeded, please retry later";
|
||||
if (msg.contains("timeout") || msg.contains("Timeout")) return "Request timeout, please retry";
|
||||
if (msg.contains("502") || msg.contains("503") || msg.contains("504")) return "Model service temporarily unavailable";
|
||||
// 截断过长的原始消息
|
||||
return msg.length() > 100 ? msg.substring(0, 100) + "..." : msg;
|
||||
}
|
||||
|
||||
@ -230,9 +230,9 @@ public class ToolExecutionExecutor {
|
||||
ToolCallback callback = toolCallbackMap.get(toolName);
|
||||
if (callback == null) {
|
||||
log.warn("[ToolExecutor] Tool not found: {}", toolName);
|
||||
events.add(GraphEventPublisher.toolComplete(toolName, "工具不存在: " + toolName, false));
|
||||
events.add(GraphEventPublisher.toolComplete(toolName, "Tool not found: " + toolName, false));
|
||||
allResponses.add(new ToolResponseMessage.ToolResponse(
|
||||
toolCall.id(), toolName, "工具不存在: " + toolName));
|
||||
toolCall.id(), toolName, "Tool not found: " + toolName));
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -270,8 +270,8 @@ public class ToolExecutionExecutor {
|
||||
ToolCallback callback = toolCallbackMap.get(toolName);
|
||||
if (callback == null) {
|
||||
log.warn("[ToolExecutor] Pre-approved tool not found: {}", toolName);
|
||||
events.add(GraphEventPublisher.toolComplete(toolName, "工具不存在: " + toolName, false));
|
||||
return new ToolResponseMessage.ToolResponse(toolCall.id(), toolName, "工具不存在: " + toolName);
|
||||
events.add(GraphEventPublisher.toolComplete(toolName, "Tool not found: " + toolName, false));
|
||||
return new ToolResponseMessage.ToolResponse(toolCall.id(), toolName, "Tool not found: " + toolName);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -288,7 +288,7 @@ public class ToolExecutionExecutor {
|
||||
log.error("[ToolExecutor] Pre-approved tool {} failed: {}", toolName, e.getMessage());
|
||||
events.add(GraphEventPublisher.toolComplete(toolName, e.getMessage(), false));
|
||||
return new ToolResponseMessage.ToolResponse(
|
||||
toolCall.id(), toolName, "工具执行失败: " + e.getMessage());
|
||||
toolCall.id(), toolName, "Tool execution failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -505,7 +505,7 @@ public class ToolExecutionExecutor {
|
||||
}
|
||||
|
||||
private String normalizeToolExecutionError(Exception e) {
|
||||
String message = e != null && e.getMessage() != null ? e.getMessage() : "未知错误";
|
||||
String message = e != null && e.getMessage() != null ? e.getMessage() : "Unknown error";
|
||||
String lower = message.toLowerCase(Locale.ROOT);
|
||||
|
||||
if (lower.contains("conversion from json")
|
||||
@ -513,16 +513,16 @@ public class ToolExecutionExecutor {
|
||||
|| lower.contains("unexpected character escape sequence")
|
||||
|| lower.contains("json parse error")
|
||||
|| lower.contains("malformed json")) {
|
||||
return "工具执行失败:模型生成的工具参数不是合法 JSON,通常表示单次 tool call 内容过长,"
|
||||
return "Tool execution failed: model generated invalid JSON for tool arguments. "
|
||||
+ "或在字符串转义位置被截断。请改为分步骤写入,拆成多个文件,或缩小单次 write_file/edit_file 的内容后重试。";
|
||||
}
|
||||
|
||||
if (lower.contains("access denied") && lower.contains("path outside allowed directories")) {
|
||||
// 提取目标路径和允许路径
|
||||
return "工具执行失败:目标路径不在允许的工作目录范围内。请将文件操作改为用户主目录下的路径(如 ~/Documents/ 或 ~/Desktop/)。";
|
||||
return "Tool execution failed: target path is outside the allowed workspace directory.";
|
||||
}
|
||||
|
||||
return "工具执行失败: " + message;
|
||||
return "Tool execution failed: " + message;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -95,7 +95,7 @@ public class FinalAnswerNode implements NodeAction {
|
||||
finishReason = FinishReason.SUMMARIZED;
|
||||
log.warn("[FinalAnswerNode] No finalAnswer or draft found, falling back to summarizedContext");
|
||||
} else {
|
||||
finalAnswer = "未能生成回答,请重试。";
|
||||
finalAnswer = "Failed to generate a response, please retry.";
|
||||
finalThinking = "";
|
||||
finishReason = FinishReason.ERROR_FALLBACK;
|
||||
log.error("[FinalAnswerNode] No answer source available, returning fallback");
|
||||
|
||||
@ -85,13 +85,13 @@ public class DefaultToolGuard implements ToolGuard {
|
||||
// Shell 工具即使未命中任何模式,也需要审批(任何本地命令执行都是敏感操作)
|
||||
if (isShellTool) {
|
||||
log.info("[ToolGuard] NEEDS_APPROVAL (shell tool default): tool={}", toolName);
|
||||
return ToolGuardResult.needsApproval("本地命令执行需要用户确认", "shell_tool_default");
|
||||
return ToolGuardResult.needsApproval("Shell command execution requires user approval", "shell_tool_default");
|
||||
}
|
||||
|
||||
// 文件写入/编辑工具需要审批
|
||||
if (toolName != null && FILE_WRITE_TOOL_NAMES.contains(toolName)) {
|
||||
log.info("[ToolGuard] NEEDS_APPROVAL (file write tool): tool={}", toolName);
|
||||
return ToolGuardResult.needsApproval("文件写入/编辑操作需要用户确认", "file_write_tool_default");
|
||||
return ToolGuardResult.needsApproval("File write/edit operation requires user approval", "file_write_tool_default");
|
||||
}
|
||||
|
||||
return ToolGuardResult.allow();
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ArrowDown } from '@element-plus/icons-vue'
|
||||
import type { Message } from '@/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
message: Message
|
||||
}>()
|
||||
@ -28,9 +31,9 @@ const compressedCount = computed(() => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="14" y1="10" x2="21" y2="3"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
||||
</span>
|
||||
<span v-if="compressedCount > 0" class="seg-compression__label">
|
||||
{{ `之前的 ${compressedCount} 条对话已整理为摘要` }}
|
||||
{{ t('chat.compressionWithCount', { count: compressedCount }) }}
|
||||
</span>
|
||||
<span v-else class="seg-compression__label">之前的对话已整理为摘要</span>
|
||||
<span v-else class="seg-compression__label">{{ t('chat.compressionSummary') }}</span>
|
||||
<el-icon class="seg-compression__arrow" :class="{ 'is-open': expanded }" :size="12"><ArrowDown /></el-icon>
|
||||
</div>
|
||||
<div v-if="expanded" class="seg-compression__body">
|
||||
|
||||
@ -42,10 +42,10 @@
|
||||
<div v-if="hasMore" ref="loadMoreRef" class="load-more-trigger text-center py-3">
|
||||
<div v-if="loadingOlder" class="text-gray-400 dark:text-gray-500 text-sm flex items-center justify-center gap-2">
|
||||
<span class="animate-spin inline-block w-4 h-4 border-2 border-gray-300 border-t-gray-500 rounded-full"></span>
|
||||
加载更早的消息...
|
||||
{{ t('chat.loadingOlder') }}
|
||||
</div>
|
||||
<button v-else class="text-gray-400 dark:text-gray-500 text-sm hover:text-gray-600 dark:hover:text-gray-300 transition-colors" @click="$emit('load-more')">
|
||||
点击加载更早的消息
|
||||
{{ t('chat.loadOlderMessages') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -85,7 +85,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, watch, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ChatDotRound, DataLine, EditPen, Monitor, Right } from '@element-plus/icons-vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
import MessageBubble from './MessageBubble.vue'
|
||||
import CompressionSummary from './CompressionSummary.vue'
|
||||
import { useStickToBottom } from '@/composables/chat/useStickToBottom'
|
||||
|
||||
@ -44,6 +44,10 @@ export default {
|
||||
stopped: 'Generation stopped',
|
||||
interrupted: 'Interrupted',
|
||||
failed: 'Generation failed',
|
||||
compressionSummary: 'Previous conversations summarized',
|
||||
compressionWithCount: '{count} previous messages summarized',
|
||||
loadingOlder: 'Loading older messages...',
|
||||
loadOlderMessages: 'Click to load older messages',
|
||||
retry: 'Retry',
|
||||
errorCode: 'Error code',
|
||||
error: {
|
||||
|
||||
@ -44,6 +44,10 @@ export default {
|
||||
stopped: '已停止生成',
|
||||
interrupted: '已中断',
|
||||
failed: '生成失败',
|
||||
compressionSummary: '之前的对话已整理为摘要',
|
||||
compressionWithCount: '之前的 {count} 条对话已整理为摘要',
|
||||
loadingOlder: '加载更早的消息...',
|
||||
loadOlderMessages: '点击加载更早的消息',
|
||||
retry: '重试',
|
||||
errorCode: '错误码',
|
||||
error: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user