|
13 | 13 | # ========= Copyright 2023-2025 @ CAMEL-AI.org. All Rights Reserved. ========= |
14 | 14 | # ========= |
15 | 15 |
|
| 16 | +import asyncio |
16 | 17 | import contextlib |
17 | 18 | import time |
18 | 19 | from typing import ( |
@@ -1368,85 +1369,91 @@ async def _sheet_input_batch_js( |
1368 | 1369 | ws_wrapper: Any, |
1369 | 1370 | system: str, |
1370 | 1371 | ) -> Dict[str, Any]: |
1371 | | - r"""Input to sheet using batch keyboard input with relative |
1372 | | - positioning. |
| 1372 | + r"""Input to sheet using batch keyboard input with absolute positioning |
| 1373 | + via Name Box (Cmd+J). |
1373 | 1374 |
|
1374 | | - Builds all operations and sends them in ONE command to TypeScript, |
1375 | | - which executes them and only waits for stability once at the end. |
| 1375 | + This is more robust than relative navigation (arrow keys) because it |
| 1376 | + handles hidden rows/columns and merged cells correctly. |
1376 | 1377 | """ |
1377 | 1378 | operations: List[Dict[str, Any]] = [] |
1378 | 1379 |
|
1379 | | - # Go to A1 to ensure we start from a known position |
1380 | | - if system == "Darwin": |
1381 | | - operations.append({"type": "press", "keys": ["Meta", "Home"]}) |
1382 | | - else: |
1383 | | - operations.append({"type": "press", "keys": ["Control", "Home"]}) |
1384 | | - operations.append({"type": "wait", "delay": 310}) |
1385 | | - |
1386 | | - # Start at (0, 0) |
1387 | | - current_row = 0 |
1388 | | - current_col = 0 |
| 1380 | + def col_to_letter(col_idx: int) -> str: |
| 1381 | + """Convert 0-based column index to letter (0->A, 25->Z, 26->AA).""" |
| 1382 | + result = "" |
| 1383 | + col_idx += 1 # Convert to 1-based for calculation |
| 1384 | + while col_idx > 0: |
| 1385 | + col_idx, remainder = divmod(col_idx - 1, 26) |
| 1386 | + result = chr(65 + remainder) + result |
| 1387 | + return result |
1389 | 1388 |
|
1390 | 1389 | for cell in cells: |
1391 | 1390 | target_row = cell.get("row", 0) |
1392 | 1391 | target_col = cell.get("col", 0) |
1393 | 1392 | text = cell.get("text", "") |
1394 | 1393 |
|
1395 | | - # Calculate relative movement needed |
1396 | | - row_diff = target_row - current_row |
1397 | | - col_diff = target_col - current_col |
1398 | | - |
1399 | | - # Navigate vertically |
1400 | | - if row_diff > 0: |
1401 | | - for _ in range(row_diff): |
1402 | | - operations.append({"type": "press", "keys": ["ArrowDown"]}) |
1403 | | - operations.append({"type": "wait", "delay": 50}) |
1404 | | - elif row_diff < 0: |
1405 | | - for _ in range(abs(row_diff)): |
1406 | | - operations.append({"type": "press", "keys": ["ArrowUp"]}) |
1407 | | - operations.append({"type": "wait", "delay": 50}) |
1408 | | - |
1409 | | - # Navigate horizontally |
1410 | | - if col_diff > 0: |
1411 | | - for _ in range(col_diff): |
1412 | | - operations.append( |
1413 | | - {"type": "press", "keys": ["ArrowRight"]} |
1414 | | - ) |
1415 | | - operations.append({"type": "wait", "delay": 50}) |
1416 | | - elif col_diff < 0: |
1417 | | - for _ in range(abs(col_diff)): |
1418 | | - operations.append({"type": "press", "keys": ["ArrowLeft"]}) |
1419 | | - operations.append({"type": "wait", "delay": 50}) |
| 1394 | + # Convert to A1 notation |
| 1395 | + col_letter = col_to_letter(target_col) |
| 1396 | + row_number = target_row + 1 |
| 1397 | + cell_address = f"{col_letter}{row_number}" |
| 1398 | + |
| 1399 | + # 1. Focus Name Box |
| 1400 | + if system == "Darwin": |
| 1401 | + operations.append({"type": "press", "keys": ["Meta", "j"]}) |
| 1402 | + else: |
| 1403 | + # On Windows/Linux, it's usually Ctrl+J or Alt+D |
| 1404 | + # The snapshot showed Cmd+J for Mac. |
| 1405 | + # Standard Google Sheets shortcut for |
| 1406 | + # "Go to range" is F5 or Ctrl+J |
| 1407 | + operations.append({"type": "press", "keys": ["Control", "j"]}) |
| 1408 | + |
| 1409 | + operations.append({"type": "wait", "delay": 500}) |
1420 | 1410 |
|
1421 | | - # Wait after navigation if moved |
1422 | | - if row_diff != 0 or col_diff != 0: |
1423 | | - operations.append({"type": "wait", "delay": 100}) |
| 1411 | + # 2. Type Address |
| 1412 | + operations.append( |
| 1413 | + {"type": "type", "text": cell_address, "delay": 0} |
| 1414 | + ) |
| 1415 | + operations.append({"type": "wait", "delay": 200}) |
| 1416 | + operations.append({"type": "press", "keys": ["Enter"]}) |
| 1417 | + operations.append({"type": "wait", "delay": 500}) |
1424 | 1418 |
|
1425 | | - # Clear and input |
| 1419 | + # 3. Clear content (Delete/Backspace) |
| 1420 | + # Just in case, press Delete to clear existing content |
1426 | 1421 | operations.append({"type": "press", "keys": ["Delete"]}) |
1427 | | - operations.append({"type": "wait", "delay": 120}) |
| 1422 | + operations.append({"type": "wait", "delay": 100}) |
1428 | 1423 |
|
| 1424 | + # 4. Type Text |
1429 | 1425 | if text: |
1430 | 1426 | operations.append({"type": "type", "text": text, "delay": 0}) |
1431 | | - operations.append({"type": "wait", "delay": 120}) |
| 1427 | + operations.append({"type": "wait", "delay": 200}) |
| 1428 | + # Press Enter to confirm input |
| 1429 | + operations.append({"type": "press", "keys": ["Enter"]}) |
| 1430 | + operations.append({"type": "wait", "delay": 300}) |
1432 | 1431 |
|
1433 | | - # Press Enter to confirm |
1434 | | - operations.append({"type": "press", "keys": ["Enter"]}) |
1435 | | - operations.append({"type": "wait", "delay": 130}) |
1436 | | - |
1437 | | - # Update current position (after Enter, cursor moves to next row) |
1438 | | - current_row = target_row + 1 |
1439 | | - current_col = target_col |
| 1432 | + # Chunk operations to avoid 100-op limit in TypeScript backend |
| 1433 | + # Each cell update takes ~10 ops, so 100 ops is only ~10 cells. |
| 1434 | + # We split into chunks of 50 ops to be safe. |
| 1435 | + CHUNK_SIZE = 50 |
1440 | 1436 |
|
1441 | 1437 | try: |
1442 | | - await ws_wrapper._send_command( |
1443 | | - 'batch_keyboard_input', |
1444 | | - {'operations': operations, 'skipStabilityWait': True}, |
1445 | | - ) |
| 1438 | + for i in range(0, len(operations), CHUNK_SIZE): |
| 1439 | + chunk = operations[i : i + CHUNK_SIZE] |
| 1440 | + await ws_wrapper._send_command( |
| 1441 | + 'batch_keyboard_input', |
| 1442 | + {'operations': chunk, 'skipStabilityWait': True}, |
| 1443 | + ) |
| 1444 | + # Small delay between chunks |
| 1445 | + await asyncio.sleep(0.2) |
| 1446 | + |
| 1447 | + # Wait a bit for the last input to settle |
| 1448 | + await asyncio.sleep(1.0) |
| 1449 | + |
1446 | 1450 | tab_info = await ws_wrapper.get_tab_info() |
1447 | 1451 |
|
1448 | 1452 | return { |
1449 | | - "result": f"Successfully input to {len(cells)} cells", |
| 1453 | + "result": ( |
| 1454 | + f"Successfully input to {len(cells)} cells " |
| 1455 | + "using absolute navigation" |
| 1456 | + ), |
1450 | 1457 | "snapshot": "", |
1451 | 1458 | "tabs": tab_info, |
1452 | 1459 | "current_tab": next( |
|
0 commit comments