Spaces:
Running
Running
File size: 6,628 Bytes
b2beaf0 | 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 | """V12: Auto-continue feature - final fix.
Key fixes for V11 bugs:
1. Button import: check exact MUI import format
2. useAgentChat: match two-line `return {\n messages: chat.messages,` instead of bare `return {`
3. Use \u unicode escapes for Vietnamese text
"""
import os
ROOT = "/source/frontend/src"
EVENTS = f"{ROOT}/types/events.ts"
SSE = f"{ROOT}/lib/sse-chat-transport.ts"
HOOK = f"{ROOT}/hooks/useAgentChat.ts"
INPUT = f"{ROOT}/components/Chat/ChatInput.tsx"
SESS = f"{ROOT}/components/SessionChat.tsx"
AGENT = "/app/agent/core/agent_loop.py"
def ok(f):
print(f"OK: {f}")
# --- 1. events.ts: add task_incomplete ---
with open(EVENTS) as f:
c = f.read()
if "'task_incomplete'" not in c:
c = c.replace(" | 'interrupted'", " | 'interrupted'\n | 'task_incomplete'")
with open(EVENTS, 'w') as f:
f.write(c)
ok("events.ts")
# --- 2. sse-chat-transport.ts: add case handler ---
with open(SSE) as f:
c = f.read()
if "case 'task_incomplete'" not in c:
block = """ case 'task_incomplete':
(sideChannel as any).onTaskIncomplete(
(event.data?.incomplete_plan as Array<{ id: string; content: string; status: string }>) || [],
);
break;
default:"""
c = c.replace("\n default:", "\n" + block)
with open(SSE, 'w') as f:
f.write(c)
ok("sse.ts")
# --- 3. useAgentChat.ts: add state, handler, effect, return ---
with open(HOOK) as f:
content = f.read()
# Add useState to import
if "useState" not in content:
content = content.replace(
"import { useCallback, useEffect, useMemo, useRef } from 'react';",
"import { useCallback, useEffect, useMemo, useRef, useState } from 'react';"
)
# Add state after callbacksRef line
state_block = """\
// Auto-continue state
const [_showAc, _setShowAc] = useState(false);
const _acRef = useRef<Array<{ id: string; content: string; status: string }>>([]);
"""
if "_showAc" not in content:
content = content.replace(
"callbacksRef.current = { onReady, onError, onSessionDead };",
"callbacksRef.current = { onReady, onError, onSessionDead };\n" + state_block
)
# Add handler inside onSessionUpdate
handler_code = """\
// Auto-continue check
if ((data as any).ac_plan) {
_acRef.current = (data as any).ac_plan;
_setShowAc(true);
return;
}
"""
if "ac_plan" not in content:
content = content.replace(
"onSessionUpdate: (data) => {",
"onSessionUpdate: (data) => {\n" + handler_code
)
# Add cancel + effect BEFORE the two-line return marker
cancel_effect = """\
const _acCancel = useCallback(() => { _setShowAc(false); }, []);
useEffect(() => {
if (!_showAc) return;
const plan = _acRef.current;
const timer = setTimeout(() => {
const s = chatActionsRef.current.setMessages;
const m = chatActionsRef.current.messages;
if (s && plan.length > 0) {
const t = plan.map((i: { content: string }) => `- ${i.content}`).join('\\n');
const nu = { id: 'ac-'+Date.now(), role: 'user' as const, parts: [{ type: 'text' as const, text: `[TIEP TUC] Task chua hoan thanh:\\n${t}` }], content: '' };
s([...m, nu]);
}
_setShowAc(false);
}, 10000);
return () => clearTimeout(timer);
}, [_showAc]);
"""
if "_acCancel" not in content:
# Match the EXACT two-line return pattern
content = content.replace(
" return {\n messages: chat.messages,",
cancel_effect + " return {\n messages: chat.messages,"
)
# Add return values
if "_showAc," not in content:
content = content.replace(
"refreshMessages,\n };",
"refreshMessages,\n _showAc,\n _acCancel,\n };"
)
with open(HOOK, 'w') as f:
f.write(content)
ok("useAgentChat.ts")
# --- 4. ChatInput.tsx: add Button + props + pause button ---
with open(INPUT) as f:
content = f.read()
# Add Button to MUI imports - match the actual format
if "Button" not in content:
content = content.replace(
" Tooltip,",
" Tooltip,\n Button,"
)
# Add props to interface
if "_showAc" not in content:
content = content.replace(
"placeholder?: string;",
"placeholder?: string;\n _showAc?: boolean;\n _acCancel?: () => void;"
)
# Add to destructured function params
if "_showAc" not in content or "_acCancel" not in content:
content = content.replace(
"placeholder = 'Ask anything...' }: ChatInputProps) {",
"placeholder = 'Ask anything...', _showAc = false, _acCancel }: ChatInputProps) {"
)
# Add pause button before JobsUpgradeDialog
if "Tam dung" not in content:
btn = """
{_showAc && _acCancel && (
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 1 }}>
<Button variant="outlined" size="small" onClick={_acCancel}
sx={{ textTransform: 'none', fontSize: '0.7rem', opacity: 0.7, '&:hover': { opacity: 1 } }}>
T\u1ea1m d\u1eebng t\u1ef1 \u0111\u1ed9ng ti\u1ebfp t\u1ee5c (10s)
</Button>
</Box>
)}
"""
content = content.replace("<JobsUpgradeDialog", btn + "\n <JobsUpgradeDialog")
with open(INPUT, 'w') as f:
f.write(content)
ok("ChatInput.tsx")
# --- 5. SessionChat.tsx: pass props ---
if os.path.exists(SESS):
with open(SESS) as f:
c = f.read()
if "_showAc" not in c:
c = c.replace("<ChatInput ", "<ChatInput _showAc={_showAc} _acCancel={_acCancel} ")
with open(SESS, 'w') as f:
f.write(c)
ok("SessionChat.tsx")
# --- 6. agent_loop.py: detection ---
if os.path.exists(AGENT):
with open(AGENT) as f:
c = f.read()
if "_unfinished_plan" not in c:
fn = """
def _unfinished_plan(s):
p = getattr(s, "current_plan", None) or []
return [it for it in p if it.get("status") in ("pending", "in_progress")]
"""
c = c.replace("class Handlers:", fn + "\n\nclass Handlers:")
if "ac_plan" not in c:
check = """
if not llm_result.tool_calls_acc and llm_result.content:
unfinished = _unfinished_plan(session)
if unfinished:
await session.send_event(Event(event_type="session_update", data={
"ac_plan": unfinished,
}))
"""
marker = " # -- End of turn --"
if marker in c:
c = c.replace(marker, check + marker)
with open(AGENT, 'w') as f:
f.write(c)
ok("agent_loop.py")
else:
print("SKIP: agent_loop.py (backend)")
print("\nDONE: V12") |