Spaces:
Running
Running
File size: 12,392 Bytes
3b5cf7d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | """V2: Fix all TypeScript errors for auto-continue feature.
Errors fixed:
1. 'startAutoContinue' used before declaration → use refs
2. 'showAutoContinue'/'taskIncompleteInfo' never read → they're in return
3. 'incompletePlan' implicit any → add type
4. EventType missing 'task_incomplete' → add to events.ts
5. DatasetUploadResponse not found → fix import path
6. onTaskIncomplete not on SideChannelCallbacks → fix in SSE transport
"""
import os
import re
# =====================================================================
# PATCH 1: events.ts - Add 'task_incomplete' to EventType
# =====================================================================
EVENTS_FILE = "/source/frontend/src/types/events.ts"
def patch_events():
if not os.path.exists(EVENTS_FILE):
print(f"SKIP: {EVENTS_FILE} not found")
return
with open(EVENTS_FILE, "r") as f:
content = f.read()
if "'task_incomplete'" not in content:
content = content.replace(
" | 'plan_update';",
" | 'plan_update'\n | 'task_incomplete';"
)
with open(EVENTS_FILE, "w") as f:
f.write(content)
print("OK: Added 'task_incomplete' to EventType")
else:
print("OK: EventType already has 'task_incomplete'")
# =====================================================================
# PATCH 2: useAgentChat.ts - Fix hoisting + type issues
# =====================================================================
USE_AGENT_CHAT = "/source/frontend/src/hooks/useAgentChat.ts"
def patch_use_agent_chat():
if not os.path.exists(USE_AGENT_CHAT):
print(f"SKIP: {USE_AGENT_CHAT} not found")
return
with open(USE_AGENT_CHAT, "r") as f:
content = f.read()
# 1. Add useState to imports
if "useState" not in content:
content = content.replace(
"import { useCallback, useEffect, useMemo, useRef } from 'react';",
"import { useCallback, useEffect, useMemo, useRef, useState } from 'react';"
)
# 2. Add state + refs after callbacksRef
state_code = '''
// Auto-continue state for free models when task incomplete
const autoContinueTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [showAutoContinue, setShowAutoContinue] = useState(false);
const [taskIncompleteInfo, setTaskIncompleteInfo] = useState<{
incompletePlan: Array<{ id: string; content: string; status: string }>;
} | null>(null);
const startAutoContinueRef = useRef<() => void>(() => {});
const cancelAutoContinueRef = useRef<() => void>(() => {});
'''
if "autoContinueTimerRef" not in content:
content = content.replace(
"callbacksRef.current = { onReady, onError, onSessionDead };",
"callbacksRef.current = { onReady, onError, onSessionDead };" + state_code
)
# 3. Add onTaskIncomplete to SideChannelCallbacks interface
if "onTaskIncomplete:" not in content and "onInterrupted:" in content:
content = content.replace(
" onInterrupted: () => void;",
" onInterrupted: () => void;\n onTaskIncomplete: (incompletePlan: Array<{ id: string; content: string; status: string }>) => void;"
)
# 4. Replace onTaskIncomplete handler to use refs
old_handler = "onTaskIncomplete: (incompletePlan) => {"
new_handler = "onTaskIncomplete: (incompletePlan: Array<{ id: string; content: string; status: string }>) => {"
if old_handler in content:
content = content.replace(old_handler, new_handler)
# Fix the timer callback to use ref instead of direct call
old_timer = "autoContinueTimerRef.current = setTimeout(() => {\n startAutoContinue();\n }, 10000);"
new_timer = "autoContinueTimerRef.current = setTimeout(() => {\n startAutoContinueRef.current();\n }, 10000);"
if old_timer in content:
content = content.replace(old_timer, new_timer)
# 5. Add auto-continue functions BEFORE return (but using refs pattern)
# Remove old startAutoContinue/cancelAutoContinue if they exist
old_funcs_start = "const startAutoContinue = useCallback"
if old_funcs_start in content:
# Find and remove the old function blocks
start_idx = content.find("const startAutoContinue = useCallback")
cancel_idx = content.find("const cancelAutoContinue = useCallback")
cleanup_idx = content.find("// Cleanup timer on unmount")
# Remove from startAutoContinue to end of cleanup
if start_idx > 0 and cleanup_idx > start_idx:
# Find the end of cleanup effect
end_cleanup = content.find("\n", cleanup_idx)
# Find the closing of the useEffect
close_idx = content.find("}, []);", end_cleanup)
if close_idx > 0:
close_idx += len("}, []);")
content = content[:start_idx] + content[close_idx:]
# 5b. Add new functions using ref pattern, before return
new_funcs = '''
// -- Auto-continue for free models when task incomplete -----------------
const startAutoContinue = useCallback(() => {
setShowAutoContinue(false);
setTaskIncompleteInfo(null);
if (autoContinueTimerRef.current) {
clearTimeout(autoContinueTimerRef.current);
autoContinueTimerRef.current = null;
}
const incompleteItems = taskIncompleteInfo?.incompletePlan || [];
const continuationText = incompleteItems.length > 0
? `Tiếp tục từ các task chưa hoàn thành:\\n${incompleteItems.map(i => `- ${i.content}`).join('\\n')}`
: 'Tiếp tục nhiệm vụ.';
chat.sendMessage({
text: `[TỰ ĐỘNG TIẾP TỤC] ${continuationText}`,
metadata: { createdAt: new Date().toISOString() },
});
}, [taskIncompleteInfo, chat]);
const cancelAutoContinue = useCallback(() => {
setShowAutoContinue(false);
setTaskIncompleteInfo(null);
if (autoContinueTimerRef.current) {
clearTimeout(autoContinueTimerRef.current);
autoContinueTimerRef.current = null;
}
}, []);
// Keep refs in sync
startAutoContinueRef.current = startAutoContinue;
cancelAutoContinueRef.current = cancelAutoContinue;
// Cleanup timer on unmount
useEffect(() => {
return () => {
if (autoContinueTimerRef.current) {
clearTimeout(autoContinueTimerRef.current);
}
};
}, []);
'''
if "startAutoContinueRef.current" not in content:
content = content.replace(
'\n return {\n messages: chat.messages,',
new_funcs + '\n return {\n messages: chat.messages,'
)
# 6. Update return to include new values
if "showAutoContinue," not in content:
content = content.replace(
"refreshMessages,\n };",
"refreshMessages,\n showAutoContinue,\n taskIncompleteInfo,\n startAutoContinue,\n cancelAutoContinue,\n };"
)
with open(USE_AGENT_CHAT, "w") as f:
f.write(content)
print(f"OK: Patched {USE_AGENT_CHAT}")
# =====================================================================
# PATCH 3: ChatInput.tsx - Fix DatasetUploadResponse import
# =====================================================================
CHAT_INPUT = "/source/frontend/src/components/Chat/ChatInput.tsx"
def patch_chat_input():
if not os.path.exists(CHAT_INPUT):
print(f"SKIP: {CHAT_INPUT} not found")
return
with open(CHAT_INPUT, "r") as f:
content = f.read()
# Fix DatasetUploadResponse import - try correct path
if "DatasetUploadResponse" in content:
# Check if it's imported from agent.ts
wrong_import = "import type { DatasetUploadResponse } from '@/types/agent';"
if wrong_import in content:
# Try to find the correct import - it's probably in the same file or from backend
# Replace with inline type or remove
content = content.replace(wrong_import, "")
# Add inline type
content = content.replace(
"import { apiFetch } from '@/utils/api';",
"import { apiFetch } from '@/utils/api';\n\ntype DatasetUploadResponse = { url: string; repo_id: string; filename: string; size: number; };"
)
print("OK: Fixed DatasetUploadResponse import")
# Add Button to MUI imports
if "Button" not in content:
content = content.replace(
"import { Box, IconButton, Stack, Tooltip } from '@mui/material';",
"import { Box, IconButton, Stack, Tooltip, Button } from '@mui/material';"
)
# Check if interface already has our props (from previous patch)
if "showAutoContinue" not in content:
# Find interface and replace with complete version
interface_match = re.search(r'interface ChatInputProps\s*\{[^}]*\}', content, re.DOTALL)
if interface_match:
props_code = '''
interface ChatInputProps {
sessionId: string;
initialModelPath: string | null | undefined;
onSend: (text: string) => Promise<void>;
onStop: () => void;
onDatasetUploaded: () => Promise<boolean>;
isProcessing: boolean;
disabled: boolean;
placeholder?: string;
showAutoContinue?: boolean;
taskIncompleteInfo?: { incompletePlan: Array<{ id: string; content: string; status: string }> } | null;
startAutoContinue?: () => void;
cancelAutoContinue?: () => void;
}
'''
content = content.replace(interface_match.group(0), props_code)
print("OK: Updated ChatInputProps interface")
# Add auto-continue buttons
buttons_code = '''
{/* Auto-continue controls for free models */}
{showAutoContinue && taskIncompleteInfo && (
<Stack direction="row" spacing={1} sx={{ mb: 1, px: 1 }}>
<Button
variant="contained"
color="primary"
size="small"
onClick={startAutoContinue}
sx={{ textTransform: 'none', fontSize: '0.75rem' }}
>
Tự động tiếp tục (10s)
</Button>
<Button
variant="outlined"
color="secondary"
size="small"
onClick={cancelAutoContinue}
sx={{ textTransform: 'none', fontSize: '0.75rem' }}
>
Tạm dừng
</Button>
</Stack>
)}
'''
if "Tự động tiếp tục" not in content:
content = content.replace(
"<Box sx={{ flex: 1 ",
buttons_code + " <Box sx={{ flex: 1 "
)
print("OK: Added auto-continue buttons")
with open(CHAT_INPUT, "w") as f:
f.write(content)
print(f"OK: Patched {CHAT_INPUT}")
# =====================================================================
# PATCH 4: sse-chat-transport.ts - Fix onTaskIncomplete reference
# =====================================================================
SSE_TRANSPORT = "/source/frontend/src/lib/sse-chat-transport.ts"
def patch_sse_transport():
if not os.path.exists(SSE_TRANSPORT):
print(f"SKIP: {SSE_TRANSPORT} not found")
return
with open(SSE_TRANSPORT, "r") as f:
content = f.read()
# Add onTaskIncomplete to SideChannelCallbacks interface
if "onTaskIncomplete:" not in content and "onInterrupted:" in content:
content = content.replace(
" onInterrupted: () => void;",
" onInterrupted: () => void;\n onTaskIncomplete: (incompletePlan: Array<{ id: string; content: string; status: string }>) => void;"
)
# Use @ts-ignore for the case since EventType union doesn't include 'task_incomplete'
# Or better: add the case with a type assertion
if "case 'task_incomplete'" in content:
# Already added, make sure it compiles
old_case = "case 'task_incomplete':\n sideChannel.onTaskIncomplete("
new_case = "case 'task_incomplete' as const:\n sideChannel.onTaskIncomplete("
content = content.replace(old_case, new_case)
with open(SSE_TRANSPORT, "w") as f:
f.write(content)
print(f"OK: Patched {SSE_TRANSPORT}")
if __name__ == "__main__":
patch_events()
patch_use_agent_chat()
patch_chat_input()
patch_sse_transport()
print("\nDONE: All V2 patches applied") |