fix: Phase 0 阻碍项修复 — 流式事件错误处理 + CI 排除 + UI 中文化
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
BLK-2: loop_runner.rs 22 处 let _ = tx.send() 全部替换为
if let Err(e) { tracing::warn!(...) },修复流式事件静默丢失问题
BLK-5: 50+ 英文字符串翻译为中文
- HandApprovalModal.tsx (~40处): 风险标签/按钮/状态/表单标签
- ChatArea.tsx: Thinking.../Sending...
- AuditLogsPanel.tsx: 空状态文案
- HandParamsForm.tsx: 空列表提示
- CreateTriggerModal.tsx: 成功提示
- MessageSearch.tsx: 时间筛选/搜索历史
BLK-6: CI/Release workflow 添加 --exclude zclaw-saas
- ci.yml: clippy/test/build 三个步骤
- release.yml: test 步骤
验证: cargo check ✓ | tsc --noEmit ✓
This commit is contained in:
@@ -862,7 +862,7 @@ export function AuditLogsPanel() {
|
||||
{filteredLogs.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full text-gray-500 dark:text-gray-400">
|
||||
<AlertCircle className="w-8 h-8 mb-2" />
|
||||
<p>No audit logs found</p>
|
||||
<p>暂无审计日志</p>
|
||||
{(searchTerm || Object.keys(filter).length > 0) && (
|
||||
<button
|
||||
onClick={handleResetFilters}
|
||||
|
||||
@@ -683,14 +683,14 @@ function MessageBubble({ message, onRetry }: { message: Message; setInput?: (tex
|
||||
// Thinking indicator
|
||||
<div className="flex items-center gap-2 px-4 py-3 text-gray-500 dark:text-gray-400">
|
||||
<LoadingDots />
|
||||
<span className="text-sm">Thinking...</span>
|
||||
<span className="text-sm">思考中...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`p-4 shadow-sm ${isUser ? 'chat-bubble-user shadow-md' : 'chat-bubble-assistant'} relative group`}>
|
||||
{/* Optimistic sending indicator */}
|
||||
{isUser && message.optimistic && (
|
||||
<span className="text-xs text-blue-200 dark:text-blue-300 mb-1 block animate-pulse">
|
||||
Sending...
|
||||
发送中...
|
||||
</span>
|
||||
)}
|
||||
{/* Reasoning block for thinking content (DeerFlow-inspired) */}
|
||||
|
||||
@@ -543,7 +543,7 @@ export function CreateTriggerModal({ isOpen, onClose, onSuccess }: CreateTrigger
|
||||
{submitStatus === 'success' && (
|
||||
<div className="flex items-center gap-2 p-3 bg-green-50 dark:bg-green-900/20 rounded-lg text-green-700 dark:text-green-400">
|
||||
<CheckCircle className="w-5 h-5 flex-shrink-0" />
|
||||
<span className="text-sm">Trigger created successfully!</span>
|
||||
<span className="text-sm">触发器创建成功!</span>
|
||||
</div>
|
||||
)}
|
||||
{submitStatus === 'error' && (
|
||||
|
||||
@@ -57,21 +57,21 @@ const RISK_CONFIG: Record<
|
||||
{ label: string; color: string; bgColor: string; borderColor: string; icon: typeof AlertTriangle }
|
||||
> = {
|
||||
low: {
|
||||
label: 'Low Risk',
|
||||
label: '低风险',
|
||||
color: 'text-green-600 dark:text-green-400',
|
||||
bgColor: 'bg-green-100 dark:bg-green-900/30',
|
||||
borderColor: 'border-green-300 dark:border-green-700',
|
||||
icon: CheckCircle,
|
||||
},
|
||||
medium: {
|
||||
label: 'Medium Risk',
|
||||
label: '中风险',
|
||||
color: 'text-yellow-600 dark:text-yellow-400',
|
||||
bgColor: 'bg-yellow-100 dark:bg-yellow-900/30',
|
||||
borderColor: 'border-yellow-300 dark:border-yellow-700',
|
||||
icon: AlertTriangle,
|
||||
},
|
||||
high: {
|
||||
label: 'High Risk',
|
||||
label: '高风险',
|
||||
color: 'text-red-600 dark:text-red-400',
|
||||
bgColor: 'bg-red-100 dark:bg-red-900/30',
|
||||
borderColor: 'border-red-300 dark:border-red-700',
|
||||
@@ -135,32 +135,32 @@ function calculateRiskLevel(handId: HandId, params: Record<string, unknown>): Ri
|
||||
function getExpectedImpact(handId: HandId, params: Record<string, unknown>): string {
|
||||
switch (handId) {
|
||||
case 'browser':
|
||||
return `Will perform browser automation on ${params.url || 'specified URL'}`;
|
||||
return `将在 ${params.url || '指定网址'} 执行浏览器自动化`;
|
||||
case 'twitter':
|
||||
if (params.action === 'post') {
|
||||
return 'Will post content to Twitter/X publicly';
|
||||
return '将公开发布内容到 Twitter/X';
|
||||
}
|
||||
if (params.action === 'engage') {
|
||||
return 'Will like/reply to tweets';
|
||||
return '将点赞/回复推文';
|
||||
}
|
||||
return 'Will perform Twitter/X operations';
|
||||
return '将执行 Twitter/X 操作';
|
||||
case 'collector':
|
||||
return `Will collect data from ${params.targetUrl || 'specified source'}`;
|
||||
return `将从 ${params.targetUrl || '指定来源'} 收集数据`;
|
||||
case 'lead':
|
||||
return `Will search for leads from ${params.source || 'specified source'}`;
|
||||
return `将从 ${params.source || '指定来源'} 搜索线索`;
|
||||
case 'clip':
|
||||
return `Will process video: ${params.inputPath || 'specified input'}`;
|
||||
return `将处理视频: ${params.inputPath || '指定输入'}`;
|
||||
case 'predictor':
|
||||
return `Will run prediction on ${params.dataSource || 'specified data'}`;
|
||||
return `将对 ${params.dataSource || '指定数据'} 运行预测`;
|
||||
case 'researcher':
|
||||
return `Will conduct research on: ${params.topic || 'specified topic'}`;
|
||||
return `将研究: ${params.topic || '指定主题'}`;
|
||||
default:
|
||||
return 'Will execute Hand operation';
|
||||
return '将执行 Hand 操作';
|
||||
}
|
||||
}
|
||||
|
||||
function formatTimeRemaining(seconds: number): string {
|
||||
if (seconds <= 0) return 'Expired';
|
||||
if (seconds <= 0) return '已过期';
|
||||
if (seconds < 60) return `${seconds}s`;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const secs = seconds % 60;
|
||||
@@ -218,7 +218,7 @@ function TimeoutProgress({ timeRemaining, totalSeconds }: { timeRemaining: numbe
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-gray-500 dark:text-gray-400 flex items-center gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
Time Remaining
|
||||
剩余时间
|
||||
</span>
|
||||
<span
|
||||
className={`font-medium ${isUrgent ? 'text-red-600 dark:text-red-400' : 'text-gray-700 dark:text-gray-300'}`}
|
||||
@@ -241,7 +241,7 @@ function TimeoutProgress({ timeRemaining, totalSeconds }: { timeRemaining: numbe
|
||||
function ParamsDisplay({ params }: { params: Record<string, unknown> }) {
|
||||
if (!params || Object.keys(params).length === 0) {
|
||||
return (
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 italic">No parameters provided</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 italic">暂无参数</p>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ export function HandApprovalModal({
|
||||
runId: handRun.runId,
|
||||
handId,
|
||||
handName: handDef?.name || handId,
|
||||
description: handDef?.description || 'Hand execution request',
|
||||
description: handDef?.description || 'Hand 执行请求',
|
||||
params,
|
||||
riskLevel: calculateRiskLevel(handId, params),
|
||||
expectedImpact: getExpectedImpact(handId, params),
|
||||
@@ -329,7 +329,7 @@ export function HandApprovalModal({
|
||||
await onApprove(approvalData.runId);
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to approve');
|
||||
setError(err instanceof Error ? err.message : '批准失败');
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
@@ -344,7 +344,7 @@ export function HandApprovalModal({
|
||||
}
|
||||
|
||||
if (!rejectReason.trim()) {
|
||||
setError('Please provide a reason for rejection');
|
||||
setError('请提供拒绝原因');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -355,7 +355,7 @@ export function HandApprovalModal({
|
||||
await onReject(approvalData.runId, rejectReason.trim());
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to reject');
|
||||
setError(err instanceof Error ? err.message : '拒绝失败');
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
@@ -387,10 +387,10 @@ export function HandApprovalModal({
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Hand Approval Request
|
||||
Hand 审批请求
|
||||
</h2>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Review and approve Hand execution
|
||||
审核并批准 Hand 执行
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -408,7 +408,7 @@ export function HandApprovalModal({
|
||||
{isExpired && (
|
||||
<div className="flex items-center gap-2 p-3 bg-gray-100 dark:bg-gray-900 rounded-lg text-gray-600 dark:text-gray-400">
|
||||
<Clock className="w-5 h-5 flex-shrink-0" />
|
||||
<span className="text-sm">This approval request has expired</span>
|
||||
<span className="text-sm">此审批请求已过期</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -439,7 +439,7 @@ export function HandApprovalModal({
|
||||
{/* Parameters */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Execution Parameters
|
||||
执行参数
|
||||
</label>
|
||||
<ParamsDisplay params={approvalData.params} />
|
||||
</div>
|
||||
@@ -449,7 +449,7 @@ export function HandApprovalModal({
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 flex items-center gap-1">
|
||||
<Info className="w-3.5 h-3.5" />
|
||||
Expected Impact
|
||||
预期影响
|
||||
</label>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg">
|
||||
{approvalData.expectedImpact}
|
||||
@@ -459,9 +459,9 @@ export function HandApprovalModal({
|
||||
|
||||
{/* Request Info */}
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 space-y-1 pt-2 border-t border-gray-200 dark:border-gray-700">
|
||||
<p>Run ID: {approvalData.runId}</p>
|
||||
<p>运行 ID: {approvalData.runId}</p>
|
||||
<p>
|
||||
Requested: {new Date(approvalData.requestedAt).toLocaleString()}
|
||||
请求时间: {new Date(approvalData.requestedAt).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -469,12 +469,12 @@ export function HandApprovalModal({
|
||||
{showRejectInput && (
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Rejection Reason <span className="text-red-500">*</span>
|
||||
拒绝原因 <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
value={rejectReason}
|
||||
onChange={(e) => setRejectReason(e.target.value)}
|
||||
placeholder="Please provide a reason for rejecting this request..."
|
||||
placeholder="请提供拒绝此请求的原因..."
|
||||
className="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-md bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
rows={3}
|
||||
autoFocus
|
||||
@@ -502,7 +502,7 @@ export function HandApprovalModal({
|
||||
disabled={isProcessing}
|
||||
className="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors disabled:opacity-50"
|
||||
>
|
||||
Cancel
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -513,12 +513,12 @@ export function HandApprovalModal({
|
||||
{isProcessing ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Rejecting...
|
||||
拒绝中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<XCircle className="w-4 h-4" />
|
||||
Confirm Rejection
|
||||
确认拒绝
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -531,7 +531,7 @@ export function HandApprovalModal({
|
||||
disabled={isProcessing}
|
||||
className="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors disabled:opacity-50"
|
||||
>
|
||||
Close
|
||||
关闭
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -540,7 +540,7 @@ export function HandApprovalModal({
|
||||
className="px-4 py-2 text-sm border border-red-200 dark:border-red-800 text-red-600 dark:text-red-400 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
<XCircle className="w-4 h-4" />
|
||||
Reject
|
||||
拒绝
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -551,12 +551,12 @@ export function HandApprovalModal({
|
||||
{isProcessing ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Approving...
|
||||
批准中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
Approve
|
||||
批准
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -368,7 +368,7 @@ function ArrayParamInput({ param, value, onChange, disabled, error }: ParamInput
|
||||
</div>
|
||||
|
||||
{items.length === 0 && !newItem && (
|
||||
<p className="text-xs text-gray-400 text-center">No items added yet</p>
|
||||
<p className="text-xs text-gray-400 text-center">暂无条目</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -428,10 +428,10 @@ export function MessageSearch({ onNavigateToMessage }: MessageSearchProps) {
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, timeRange: e.target.value as SearchFilters['timeRange'] }))}
|
||||
className="text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded px-2 py-1 focus:outline-none focus:ring-1 focus:ring-orange-500"
|
||||
>
|
||||
<option value="all">All time</option>
|
||||
<option value="today">Today</option>
|
||||
<option value="week">This week</option>
|
||||
<option value="month">This month</option>
|
||||
<option value="all">全部时间</option>
|
||||
<option value="today">今天</option>
|
||||
<option value="week">本周</option>
|
||||
<option value="month">本月</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -442,7 +442,7 @@ export function MessageSearch({ onNavigateToMessage }: MessageSearchProps) {
|
||||
{/* Search history */}
|
||||
{!query && searchHistory.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<div className="text-xs text-gray-400 dark:text-gray-500 mb-1">Recent searches:</div>
|
||||
<div className="text-xs text-gray-400 dark:text-gray-500 mb-1">最近搜索:</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{searchHistory.slice(0, 5).map((item, index) => (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user