myra-ai-assistant 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. myra_ai_assistant-0.1.0/Aria_office_manage.py +1985 -0
  2. myra_ai_assistant-0.1.0/LICENSE +21 -0
  3. myra_ai_assistant-0.1.0/MANIFEST.in +13 -0
  4. myra_ai_assistant-0.1.0/Main_normal.py +175 -0
  5. myra_ai_assistant-0.1.0/PKG-INFO +113 -0
  6. myra_ai_assistant-0.1.0/README.md +44 -0
  7. myra_ai_assistant-0.1.0/aria.py +1844 -0
  8. myra_ai_assistant-0.1.0/aria_daemon.py +302 -0
  9. myra_ai_assistant-0.1.0/aria_modules/__init__.py +0 -0
  10. myra_ai_assistant-0.1.0/aria_modules/aria_activation.py +1341 -0
  11. myra_ai_assistant-0.1.0/aria_modules/aria_advanced_features.py +1256 -0
  12. myra_ai_assistant-0.1.0/aria_modules/aria_advanced_tools.py +1097 -0
  13. myra_ai_assistant-0.1.0/aria_modules/aria_login.py +674 -0
  14. myra_ai_assistant-0.1.0/aria_modules/cloud_database.py +10 -0
  15. myra_ai_assistant-0.1.0/aria_modules/face_auth.py +1161 -0
  16. myra_ai_assistant-0.1.0/aria_modules/firebase_connection.py +43 -0
  17. myra_ai_assistant-0.1.0/aria_modules/game_install.py +1048 -0
  18. myra_ai_assistant-0.1.0/aria_modules/minibar_aria.py +354 -0
  19. myra_ai_assistant-0.1.0/aria_modules/tools.py +2198 -0
  20. myra_ai_assistant-0.1.0/aria_startup.py +216 -0
  21. myra_ai_assistant-0.1.0/camera_vision_module.py +503 -0
  22. myra_ai_assistant-0.1.0/main_hotkey.py +530 -0
  23. myra_ai_assistant-0.1.0/myra/__init__.py +3 -0
  24. myra_ai_assistant-0.1.0/myra/__main__.py +6 -0
  25. myra_ai_assistant-0.1.0/myra/cli.py +33 -0
  26. myra_ai_assistant-0.1.0/myra/config.py +22 -0
  27. myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/PKG-INFO +113 -0
  28. myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/SOURCES.txt +49 -0
  29. myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/dependency_links.txt +1 -0
  30. myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/entry_points.txt +2 -0
  31. myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/requires.txt +46 -0
  32. myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/top_level.txt +14 -0
  33. myra_ai_assistant-0.1.0/normal_prompt.py +41 -0
  34. myra_ai_assistant-0.1.0/prompt.py +243 -0
  35. myra_ai_assistant-0.1.0/pyproject.toml +99 -0
  36. myra_ai_assistant-0.1.0/screen_share_module.py +452 -0
  37. myra_ai_assistant-0.1.0/setup.cfg +4 -0
  38. myra_ai_assistant-0.1.0/website_builder/Config.py +36 -0
  39. myra_ai_assistant-0.1.0/website_builder/Content_agent.py +152 -0
  40. myra_ai_assistant-0.1.0/website_builder/Generator_agent.py +243 -0
  41. myra_ai_assistant-0.1.0/website_builder/Github_service.py +111 -0
  42. myra_ai_assistant-0.1.0/website_builder/Planner_agent.py +162 -0
  43. myra_ai_assistant-0.1.0/website_builder/Preview_server.py +62 -0
  44. myra_ai_assistant-0.1.0/website_builder/Project_manager.py +49 -0
  45. myra_ai_assistant-0.1.0/website_builder/Projects_db.py +50 -0
  46. myra_ai_assistant-0.1.0/website_builder/Qr_display.py +214 -0
  47. myra_ai_assistant-0.1.0/website_builder/Vercel_service.py +116 -0
  48. myra_ai_assistant-0.1.0/website_builder/Webforge_integration.py +220 -0
  49. myra_ai_assistant-0.1.0/website_builder/Webforge_manager.py +267 -0
  50. myra_ai_assistant-0.1.0/website_builder/__init__.py +1 -0
  51. myra_ai_assistant-0.1.0/whatsapp_file_sender.py +661 -0
@@ -0,0 +1,1985 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ TITAN Office & File Manager v1.0
4
+ ================================
5
+ Features:
6
+ ✅ Excel — create, edit, navigate rows/cols, salary calc, SUM/AVG
7
+ ✅ MS Word — create, type text, click buttons, format
8
+ ✅ PowerPoint — auto 7-8 slide deck with full content
9
+ ✅ File Management — copy, paste, move, delete, extract, merge, read folder
10
+ ✅ PDF Reader — read PDF content (any open PDF)
11
+ ✅ Real-time Location Awareness — current folder, file, active window auto-detect
12
+ ✅ Windows Management — list, focus, move, resize, snap windows
13
+ """
14
+
15
+ import os, sys, asyncio, subprocess, time, shutil, json, ctypes, threading
16
+ import datetime, math, re, glob, tempfile
17
+ from pathlib import Path
18
+
19
+ # ── Optional imports ──────────────────────────────────────────
20
+ try:
21
+ import win32com.client
22
+ import win32gui, win32con, win32api, win32process
23
+ import pythoncom
24
+ HAS_WIN32 = True
25
+ except ImportError:
26
+ HAS_WIN32 = False
27
+
28
+ try:
29
+ import pyautogui
30
+ import pyperclip
31
+ HAS_GUI = True
32
+ except ImportError:
33
+ HAS_GUI = False
34
+
35
+ try:
36
+ import openpyxl
37
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
38
+ from openpyxl.utils import get_column_letter, column_index_from_string
39
+ HAS_OPENPYXL = True
40
+ except ImportError:
41
+ HAS_OPENPYXL = False
42
+
43
+ try:
44
+ from docx import Document
45
+ from docx.shared import Pt, RGBColor, Inches, Cm
46
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
47
+ HAS_DOCX = True
48
+ except ImportError:
49
+ HAS_DOCX = False
50
+
51
+ try:
52
+ from pptx import Presentation
53
+ from pptx.util import Inches as PInches, Pt as PPt, Emu
54
+ from pptx.dml.color import RGBColor as PRGBColor
55
+ from pptx.enum.text import PP_ALIGN
56
+ HAS_PPTX = True
57
+ except ImportError:
58
+ HAS_PPTX = False
59
+
60
+ try:
61
+ import pypdf
62
+ HAS_PYPDF = True
63
+ except ImportError:
64
+ HAS_PYPDF = False
65
+
66
+ try:
67
+ import psutil
68
+ HAS_PSUTIL = True
69
+ except ImportError:
70
+ HAS_PSUTIL = False
71
+
72
+ try:
73
+ import pygetwindow as gw
74
+ HAS_GW = True
75
+ except ImportError:
76
+ HAS_GW = False
77
+
78
+ # ── Directories ───────────────────────────────────────────────
79
+ HOME = Path.home()
80
+ EXCEL_DIR = HOME / "Documents" / "TITAN_Excel"
81
+ WORD_DIR = HOME / "Documents" / "TITAN_Word"
82
+ PPT_DIR = HOME / "Documents" / "TITAN_PPT"
83
+ for d in [EXCEL_DIR, WORD_DIR, PPT_DIR]:
84
+ d.mkdir(parents=True, exist_ok=True)
85
+
86
+ # ── Location State ─────────────────────────────────────────────
87
+ _location_state = {
88
+ "active_app": "",
89
+ "active_file": "",
90
+ "active_folder": "",
91
+ "active_window": "",
92
+ "excel_cell": "A1",
93
+ "excel_sheet": "Sheet1",
94
+ "word_page": 1,
95
+ "last_update": 0,
96
+ }
97
+
98
+ def _set_clipboard(text: str):
99
+ """Direct Win32 clipboard — IME bypass"""
100
+ if not HAS_WIN32:
101
+ try: pyperclip.copy(text)
102
+ except: pass
103
+ return
104
+ try:
105
+ import ctypes
106
+ CF_UNICODETEXT = 13
107
+ GMEM_MOVEABLE = 0x0002
108
+ u32 = ctypes.windll.user32
109
+ k32 = ctypes.windll.kernel32
110
+ encoded = (text + '\0').encode('utf-16-le')
111
+ h = k32.GlobalAlloc(GMEM_MOVEABLE, len(encoded))
112
+ p = k32.GlobalLock(h)
113
+ ctypes.memmove(p, encoded, len(encoded))
114
+ k32.GlobalUnlock(h)
115
+ u32.OpenClipboard(None)
116
+ u32.EmptyClipboard()
117
+ u32.SetClipboardData(CF_UNICODETEXT, h)
118
+ u32.CloseClipboard()
119
+ except:
120
+ try: pyperclip.copy(text)
121
+ except: pass
122
+
123
+
124
+ # ══════════════════════════════════════════════════════════════
125
+ # REAL-TIME LOCATION AWARENESS
126
+ # ══════════════════════════════════════════════════════════════
127
+
128
+ def _get_active_window_info() -> dict:
129
+ """Active window ka title, file path, folder detect karo"""
130
+ info = {"title": "", "file": "", "folder": "", "app": ""}
131
+ if not HAS_WIN32:
132
+ return info
133
+ try:
134
+ hwnd = win32gui.GetForegroundWindow()
135
+ title = win32gui.GetWindowText(hwnd)
136
+ info["title"] = title
137
+
138
+ # Process name nikalo
139
+ try:
140
+ _, pid = win32process.GetWindowThreadProcessId(hwnd)
141
+ proc = psutil.Process(pid) if HAS_PSUTIL else None
142
+ if proc:
143
+ info["app"] = proc.name().lower()
144
+ # Agar Excel/Word/PPT open files hain toh unka path bhi mil sakta hai
145
+ for f in proc.open_files():
146
+ ext = Path(f.path).suffix.lower()
147
+ if ext in ('.xlsx','.xls','.xlsm','.docx','.doc','.pptx','.ppt','.pdf','.txt'):
148
+ info["file"] = f.path
149
+ info["folder"] = str(Path(f.path).parent)
150
+ break
151
+ except: pass
152
+
153
+ # Title se file name extract karo (e.g. "Budget.xlsx - Excel")
154
+ if not info["file"]:
155
+ for sep in [" - Microsoft Excel", " - Excel", " - Microsoft Word", " - Word",
156
+ " - PowerPoint", " - Microsoft PowerPoint", " - Adobe Acrobat",
157
+ " - Notepad", " - Notepad++"]:
158
+ if sep in title:
159
+ fname = title.split(sep)[0].strip()
160
+ if fname and not fname.startswith("Microsoft"):
161
+ info["file"] = fname
162
+ break
163
+
164
+ # Active Explorer window ka folder detect karo
165
+ if not info["folder"] and HAS_WIN32:
166
+ try:
167
+ pythoncom.CoInitialize()
168
+ shell = win32com.client.Dispatch("Shell.Application")
169
+ for w in shell.Windows():
170
+ try:
171
+ loc = w.LocationURL
172
+ if loc and loc.startswith("file:///"):
173
+ folder = loc.replace("file:///","").replace("/","\\")
174
+ info["folder"] = folder
175
+ break
176
+ except: pass
177
+ except: pass
178
+
179
+ except: pass
180
+ return info
181
+
182
+
183
+ async def get_current_location() -> str:
184
+ """
185
+ User abhi kahan hai — automatically detect karo.
186
+ Active window, file, folder, app sab batao.
187
+ Kuch poochne ki zaroorat nahi.
188
+ """
189
+ def _detect():
190
+ wi = _get_active_window_info()
191
+ _location_state["active_window"] = wi.get("title", "")
192
+ _location_state["active_app"] = wi.get("app", "")
193
+ _location_state["active_file"] = wi.get("file", "")
194
+ _location_state["active_folder"] = wi.get("folder", "")
195
+ _location_state["last_update"] = time.time()
196
+
197
+ lines = [f"📍 Current Location:"]
198
+ if wi["title"]: lines.append(f" 🪟 Active Window: {wi['title']}")
199
+ if wi["app"]: lines.append(f" 💻 Application: {wi['app']}")
200
+ if wi["file"]: lines.append(f" 📄 File: {wi['file']}")
201
+ if wi["folder"]: lines.append(f" 📁 Folder: {wi['folder']}")
202
+
203
+ # Excel cell position
204
+ if "excel" in wi.get("app","") or "excel" in wi.get("title","").lower():
205
+ cell = _get_excel_active_cell()
206
+ if cell:
207
+ _location_state["excel_cell"] = cell
208
+ lines.append(f" 📊 Excel Cell: {cell}")
209
+
210
+ if len(lines) == 1:
211
+ lines.append(" ℹ️ Koi active file/folder nahi mila")
212
+ return "\n".join(lines)
213
+
214
+ return await asyncio.to_thread(_detect)
215
+
216
+
217
+ def _get_excel_active_cell() -> str:
218
+ """Excel mein currently selected cell ka address (e.g. B5)"""
219
+ if not HAS_WIN32:
220
+ return _location_state.get("excel_cell", "A1")
221
+ try:
222
+ pythoncom.CoInitialize()
223
+ xl = win32com.client.GetActiveObject("Excel.Application")
224
+ cell = xl.ActiveCell
225
+ addr = cell.Address.replace("$", "")
226
+ sheet = xl.ActiveSheet.Name
227
+ _location_state["excel_cell"] = addr
228
+ _location_state["excel_sheet"] = sheet
229
+ return f"{sheet}!{addr}"
230
+ except:
231
+ return _location_state.get("excel_cell", "A1")
232
+
233
+
234
+ async def watch_location_realtime(interval_seconds: int = 5) -> str:
235
+ """Background mein continuously location track karo"""
236
+ async def _loop():
237
+ while True:
238
+ wi = _get_active_window_info()
239
+ _location_state["active_window"] = wi.get("title","")
240
+ _location_state["active_app"] = wi.get("app","")
241
+ _location_state["active_file"] = wi.get("file","")
242
+ _location_state["active_folder"] = wi.get("folder","")
243
+ _location_state["last_update"] = time.time()
244
+ if "excel" in wi.get("app","").lower():
245
+ cell = await asyncio.to_thread(_get_excel_active_cell)
246
+ await asyncio.sleep(interval_seconds)
247
+ asyncio.create_task(_loop())
248
+ return f"✅ Location tracking shuru hua — har {interval_seconds}s mein update"
249
+
250
+
251
+ # ══════════════════════════════════════════════════════════════
252
+ # EXCEL TOOLS
253
+ # ══════════════════════════════════════════════════════════════
254
+
255
+ def _excel_app():
256
+ """Running Excel instance ya naya open karo"""
257
+ if not HAS_WIN32:
258
+ return None
259
+ try:
260
+ pythoncom.CoInitialize()
261
+ try:
262
+ return win32com.client.GetActiveObject("Excel.Application")
263
+ except:
264
+ xl = win32com.client.Dispatch("Excel.Application")
265
+ xl.Visible = True
266
+ return xl
267
+ except:
268
+ return None
269
+
270
+
271
+ async def excel_create_file(filename: str = "", sheet_name: str = "Sheet1") -> str:
272
+ """Naya Excel file banao"""
273
+ if not HAS_OPENPYXL:
274
+ return "❌ pip install openpyxl"
275
+ def _create():
276
+ ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
277
+ fname = filename if filename else f"TITAN_Excel_{ts}.xlsx"
278
+ if not fname.endswith(".xlsx"): fname += ".xlsx"
279
+ path = EXCEL_DIR / fname
280
+ wb = openpyxl.Workbook()
281
+ ws = wb.active
282
+ ws.title = sheet_name
283
+ wb.save(str(path))
284
+ os.startfile(str(path))
285
+ _location_state["active_file"] = str(path)
286
+ return f"✅ Excel file bani: {path}"
287
+ return await asyncio.to_thread(_create)
288
+
289
+
290
+ async def excel_navigate(cell: str = "", row: int = 0, col: int = 0,
291
+ direction: str = "") -> str:
292
+ """
293
+ Excel mein navigate karo.
294
+ cell: 'B5' format
295
+ direction: up/down/left/right/home/end
296
+ """
297
+ def _nav():
298
+ xl = _excel_app()
299
+ if not xl:
300
+ return "❌ Excel open nahi hai"
301
+ try:
302
+ wb = xl.ActiveWorkbook
303
+ ws = xl.ActiveSheet
304
+
305
+ if cell:
306
+ ws.Range(cell).Select()
307
+ _location_state["excel_cell"] = cell
308
+ return f"✅ Cell {cell} pe gaye"
309
+
310
+ if row > 0 and col > 0:
311
+ ws.Cells(row, col).Select()
312
+ addr = ws.Cells(row, col).Address.replace("$","")
313
+ _location_state["excel_cell"] = addr
314
+ return f"✅ Row {row}, Col {col} ({addr}) pe gaye"
315
+
316
+ if direction:
317
+ d_map = {
318
+ "up": win32con.VK_UP,
319
+ "down": win32con.VK_DOWN,
320
+ "left": win32con.VK_LEFT,
321
+ "right": win32con.VK_RIGHT,
322
+ "home": win32con.VK_HOME,
323
+ "end": win32con.VK_END,
324
+ }
325
+ key = d_map.get(direction.lower())
326
+ if key:
327
+ pyautogui.press(direction.lower())
328
+ # Update cell
329
+ new_cell = xl.ActiveCell.Address.replace("$","")
330
+ _location_state["excel_cell"] = new_cell
331
+ return f"✅ {direction} → {new_cell}"
332
+ return "❌ cell/row+col/direction mein se koi ek do"
333
+ except Exception as e:
334
+ return f"❌ Error: {e}"
335
+ return await asyncio.to_thread(_nav)
336
+
337
+
338
+ async def excel_get_position() -> str:
339
+ """Excel mein abhi kahan hain — row, col, cell address"""
340
+ def _pos():
341
+ xl = _excel_app()
342
+ if not xl:
343
+ return f"📊 Last known: {_location_state.get('excel_cell','?')}"
344
+ try:
345
+ c = xl.ActiveCell
346
+ addr = c.Address.replace("$","")
347
+ row = c.Row
348
+ col = c.Column
349
+ sheet = xl.ActiveSheet.Name
350
+ total_rows = xl.ActiveSheet.UsedRange.Rows.Count
351
+ total_cols = xl.ActiveSheet.UsedRange.Columns.Count
352
+ _location_state["excel_cell"] = addr
353
+ _location_state["excel_sheet"] = sheet
354
+ return (f"📊 Excel Position:\n"
355
+ f" Sheet: {sheet}\n"
356
+ f" Cell: {addr} (Row {row}, Col {col})\n"
357
+ f" Used Range: {total_rows} rows × {total_cols} cols")
358
+ except Exception as e:
359
+ return f"❌ Error: {e}"
360
+ return await asyncio.to_thread(_pos)
361
+
362
+
363
+ async def excel_write_cell(value: str, cell: str = "", row: int = 0,
364
+ col: int = 0, next_row: bool = False) -> str:
365
+ """Excel cell mein value likhо"""
366
+ def _write():
367
+ xl = _excel_app()
368
+ if not xl:
369
+ return "❌ Excel open nahi hai"
370
+ try:
371
+ ws = xl.ActiveSheet
372
+ if cell:
373
+ ws.Range(cell).Value = value
374
+ addr = cell
375
+ elif row > 0 and col > 0:
376
+ ws.Cells(row, col).Value = value
377
+ addr = ws.Cells(row, col).Address.replace("$","")
378
+ else:
379
+ c = xl.ActiveCell
380
+ c.Value = value
381
+ addr = c.Address.replace("$","")
382
+ if next_row:
383
+ xl.ActiveCell.Offset(1, 0).Select()
384
+ else:
385
+ xl.ActiveCell.Offset(0, 1).Select()
386
+ _location_state["excel_cell"] = addr
387
+ xl.ActiveWorkbook.Save()
388
+ return f"✅ {addr} = '{value}'"
389
+ except Exception as e:
390
+ return f"❌ Error: {e}"
391
+ return await asyncio.to_thread(_write)
392
+
393
+
394
+ async def excel_add_headers(headers: list, row: int = 1, start_col: int = 1,
395
+ bold: bool = True, bg_color: str = "4472C4") -> str:
396
+ """Excel mein headers row banao"""
397
+ def _headers():
398
+ xl = _excel_app()
399
+ if not xl:
400
+ return "❌ Excel open nahi hai"
401
+ try:
402
+ ws = xl.ActiveSheet
403
+ for i, h in enumerate(headers, start_col):
404
+ c = ws.Cells(row, i)
405
+ c.Value = h
406
+ if bold:
407
+ c.Font.Bold = True
408
+ c.Font.Color = 0xFFFFFF # White text
409
+ c.Interior.Color = int(bg_color, 16) # Blue bg
410
+ c.ColumnWidth = max(15, len(str(h)) + 4)
411
+ c.HorizontalAlignment = -4108 # Center
412
+ xl.ActiveWorkbook.Save()
413
+ return f"✅ {len(headers)} headers add ho gaye: {', '.join(headers)}"
414
+ except Exception as e:
415
+ return f"❌ Error: {e}"
416
+ return await asyncio.to_thread(_headers)
417
+
418
+
419
+ async def excel_add_data_row(data: list, row: int = 0) -> str:
420
+ """Excel mein data row add karo. row=0 means next empty row"""
421
+ def _add():
422
+ xl = _excel_app()
423
+ if not xl:
424
+ return "❌ Excel open nahi hai"
425
+ try:
426
+ ws = xl.ActiveSheet
427
+ if row == 0:
428
+ # Next empty row find karo
429
+ r = ws.UsedRange.Rows.Count + 1
430
+ if r < 2: r = 2
431
+ else:
432
+ r = row
433
+ for i, val in enumerate(data, 1):
434
+ ws.Cells(r, i).Value = val
435
+ xl.ActiveWorkbook.Save()
436
+ return f"✅ Row {r} mein data add ho gaya: {data}"
437
+ except Exception as e:
438
+ return f"❌ Error: {e}"
439
+ return await asyncio.to_thread(_add)
440
+
441
+
442
+ async def excel_formula(cell: str, formula: str) -> str:
443
+ """Excel cell mein formula daalo (SUM, AVERAGE, etc.)"""
444
+ def _formula():
445
+ xl = _excel_app()
446
+ if not xl:
447
+ return "❌ Excel open nahi hai"
448
+ try:
449
+ ws = xl.ActiveSheet
450
+ ws.Range(cell).Formula = formula
451
+ result = ws.Range(cell).Value
452
+ xl.ActiveWorkbook.Save()
453
+ return f"✅ {cell} = {formula} → {result}"
454
+ except Exception as e:
455
+ return f"❌ Error: {e}"
456
+ return await asyncio.to_thread(_formula)
457
+
458
+
459
+ async def excel_calculate(operation: str, range_addr: str, result_cell: str = "") -> str:
460
+ """
461
+ SUM, AVERAGE, COUNT, MAX, MIN calculate karo
462
+ operation: sum/average/count/max/min
463
+ range_addr: e.g. 'B2:B20'
464
+ """
465
+ op_map = {
466
+ "sum": "SUM",
467
+ "average": "AVERAGE",
468
+ "avg": "AVERAGE",
469
+ "count": "COUNT",
470
+ "max": "MAX",
471
+ "min": "MIN",
472
+ }
473
+ op = op_map.get(operation.lower(), operation.upper())
474
+ formula = f"={op}({range_addr})"
475
+
476
+ def _calc():
477
+ xl = _excel_app()
478
+ if not xl:
479
+ return "❌ Excel open nahi hai"
480
+ try:
481
+ ws = xl.ActiveSheet
482
+ if result_cell:
483
+ ws.Range(result_cell).Formula = formula
484
+ val = ws.Range(result_cell).Value
485
+ xl.ActiveWorkbook.Save()
486
+ return f"✅ {result_cell} = {formula} → {val}"
487
+ else:
488
+ # Next empty cell mein daalo
489
+ last_row = ws.UsedRange.Rows.Count
490
+ last_col = ws.UsedRange.Columns.Count
491
+ r_cell = ws.Cells(last_row + 2, 1)
492
+ r_cell.Value = f"{op}:"
493
+ ws.Cells(last_row + 2, 2).Formula = formula
494
+ val = ws.Cells(last_row + 2, 2).Value
495
+ xl.ActiveWorkbook.Save()
496
+ return f"✅ {op}({range_addr}) = {val}"
497
+ except Exception as e:
498
+ return f"❌ Error: {e}"
499
+ return await asyncio.to_thread(_calc)
500
+
501
+
502
+ async def excel_create_salary_sheet(
503
+ workers: list,
504
+ month: str = "",
505
+ company_name: str = "Company"
506
+ ) -> str:
507
+ """
508
+ Employee salary sheet banao with complete details.
509
+ workers: [{"name":"Rahul","join_date":"2023-01-15","leaves":2,"basic":25000}, ...]
510
+ month: "January 2025"
511
+ Shows: earnings, months worked, attendance, absents
512
+ """
513
+ if not HAS_OPENPYXL:
514
+ return "❌ pip install openpyxl"
515
+
516
+ def _salary():
517
+ month_str = month or datetime.datetime.now().strftime("%B %Y")
518
+ ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
519
+ fname = f"Salary_{company_name}_{month_str.replace(' ','_')}_{ts}.xlsx"
520
+ path = EXCEL_DIR / fname
521
+
522
+ wb = openpyxl.Workbook()
523
+ ws = wb.active
524
+ ws.title = f"Salary {month_str}"
525
+
526
+ # ── Styles ─────────────────────────────────────
527
+ header_fill = PatternFill("solid", fgColor="1F4E79")
528
+ header_font = Font(bold=True, color="FFFFFF", size=11)
529
+ title_font = Font(bold=True, size=14, color="1F4E79")
530
+ sub_font = Font(bold=True, size=11, color="2E75B6")
531
+ center_align = Alignment(horizontal="center", vertical="center")
532
+ thin_border = Border(
533
+ left=Side(style='thin'), right=Side(style='thin'),
534
+ top=Side(style='thin'), bottom=Side(style='thin')
535
+ )
536
+
537
+ # ── Title ──────────────────────────────────────
538
+ ws.merge_cells("A1:L1")
539
+ ws["A1"] = f"{company_name} — Monthly Salary Sheet"
540
+ ws["A1"].font = title_font
541
+ ws["A1"].alignment = center_align
542
+
543
+ ws.merge_cells("A2:L2")
544
+ ws["A2"] = f"Month: {month_str}"
545
+ ws["A2"].font = sub_font
546
+ ws["A2"].alignment = center_align
547
+ ws.row_dimensions[1].height = 30
548
+ ws.row_dimensions[2].height = 20
549
+
550
+ # ── Headers ────────────────────────────────────
551
+ headers = [
552
+ "S.No", "Employee Name", "Joining Date", "Months Worked",
553
+ "Working Days", "Leaves Taken", "Present Days", "Absent Days",
554
+ "Basic Salary (Rs)", "Per Day Salary (Rs)", "Leave Deduction (Rs)",
555
+ "HRA (Rs)", "Bonus (Rs)", "Net Salary (Rs)"
556
+ ]
557
+ col_widths = [6, 20, 14, 14, 13, 13, 13, 13, 17, 17, 18, 12, 12, 15]
558
+
559
+ for i, (h, w) in enumerate(zip(headers, col_widths), 1):
560
+ c = ws.cell(row=3, column=i, value=h)
561
+ c.font = header_font
562
+ c.fill = header_fill
563
+ c.alignment = center_align
564
+ c.border = thin_border
565
+ ws.column_dimensions[get_column_letter(i)].width = w
566
+ ws.row_dimensions[3].height = 25
567
+
568
+ # ── Working days in month ──────────────────────
569
+ try:
570
+ mo = datetime.datetime.strptime(month_str, "%B %Y")
571
+ if mo.month == 12:
572
+ nxt = datetime.datetime(mo.year+1,1,1)
573
+ else:
574
+ nxt = datetime.datetime(mo.year, mo.month+1, 1)
575
+ total_days = (nxt - datetime.datetime(mo.year, mo.month, 1)).days
576
+ working_days = total_days - 8 # ~8 Sundays
577
+ except:
578
+ working_days = 26
579
+
580
+ # ── Data rows ─────────────────────────────────
581
+ alt_fill = PatternFill("solid", fgColor="EBF3FB")
582
+ for idx, w in enumerate(workers, 1):
583
+ r = idx + 3
584
+ fill = alt_fill if idx % 2 == 0 else PatternFill("solid", fgColor="FFFFFF")
585
+
586
+ name = w.get("name", f"Employee {idx}")
587
+ join_date = w.get("join_date", "")
588
+ leaves = int(w.get("leaves", 0))
589
+ basic = float(w.get("basic", 0))
590
+ hra_pct = float(w.get("hra_pct", 0.20)) # 20% default
591
+ bonus = float(w.get("bonus", 0))
592
+
593
+ # Calculate months worked
594
+ months_worked = 1 # Default current month
595
+ if join_date:
596
+ try:
597
+ jd = datetime.datetime.strptime(join_date, "%Y-%m-%d")
598
+ current_date = datetime.datetime.now()
599
+ months = (current_date.year - jd.year) * 12 + (current_date.month - jd.month) + 1
600
+ months_worked = max(1, months)
601
+ except:
602
+ months_worked = 1
603
+
604
+ present = max(0, working_days - leaves)
605
+ absent = leaves # Absent days = leaves taken
606
+ per_day = basic / working_days if working_days else 0
607
+ deduction = per_day * leaves
608
+ hra = basic * hra_pct
609
+ net = basic - deduction + hra + bonus
610
+
611
+ row_data = [
612
+ idx, name, join_date, months_worked, working_days, leaves, present, absent,
613
+ round(basic, 2), round(per_day, 2), round(deduction, 2),
614
+ round(hra, 2), round(bonus, 2), round(net, 2)
615
+ ]
616
+ for ci, val in enumerate(row_data, 1):
617
+ c = ws.cell(row=r, column=ci, value=val)
618
+ c.border = thin_border
619
+ c.alignment = center_align
620
+ c.fill = fill
621
+ if ci in (9, 10, 11, 12, 13, 14):
622
+ c.number_format = '#,##0.00'
623
+
624
+ # ── Totals row ────────────────────────────────
625
+ total_row = len(workers) + 4
626
+ ws.cell(total_row, 1, "TOTAL").font = Font(bold=True)
627
+ ws.cell(total_row, 2, "").font = Font(bold=True)
628
+ total_fill = PatternFill("solid", fgColor="BDD7EE")
629
+ for ci in range(1, len(headers)+1):
630
+ c = ws.cell(row=total_row, column=ci)
631
+ c.fill = total_fill
632
+ c.border = thin_border
633
+ c.alignment = center_align
634
+ if ci >= 9:
635
+ col_letter = get_column_letter(ci)
636
+ start = 4; end = total_row - 1
637
+ c.value = f"=SUM({col_letter}{start}:{col_letter}{end})"
638
+ c.number_format = '#,##0.00'
639
+ c.font = Font(bold=True)
640
+
641
+ # ── Summary box ───────────────────────────────
642
+ sr = total_row + 2
643
+ ws.cell(sr, 1, "Summary").font = Font(bold=True, size=12, color="1F4E79")
644
+ ws.cell(sr+1, 1, f"Total Employees:").font = Font(bold=True)
645
+ ws.cell(sr+1, 2, len(workers))
646
+ ws.cell(sr+2, 1, f"Month:").font = Font(bold=True)
647
+ ws.cell(sr+2, 2, month_str)
648
+ ws.cell(sr+3, 1, f"Working Days:").font = Font(bold=True)
649
+ ws.cell(sr+3, 2, working_days)
650
+ ws.cell(sr+4, 1, "Generated by TITAN").font = Font(italic=True, color="888888")
651
+
652
+ # ── Freeze top rows ───────────────────────────
653
+ ws.freeze_panes = "A4"
654
+
655
+ wb.save(str(path))
656
+ os.startfile(str(path))
657
+ _location_state["active_file"] = str(path)
658
+ return f"✅ Salary sheet bani: {path}\n📊 {len(workers)} employees | {working_days} working days"
659
+
660
+ return await asyncio.to_thread(_salary)
661
+
662
+
663
+ async def excel_read_cell(cell: str = "") -> str:
664
+ """Excel cell ki value padho"""
665
+ def _read():
666
+ xl = _excel_app()
667
+ if not xl:
668
+ return "❌ Excel open nahi hai"
669
+ try:
670
+ ws = xl.ActiveSheet
671
+ if cell:
672
+ val = ws.Range(cell).Value
673
+ return f"📊 {cell} = {val}"
674
+ else:
675
+ c = xl.ActiveCell
676
+ addr = c.Address.replace("$","")
677
+ val = c.Value
678
+ return f"📊 Current ({addr}) = {val}"
679
+ except Exception as e:
680
+ return f"❌ Error: {e}"
681
+ return await asyncio.to_thread(_read)
682
+
683
+
684
+ async def excel_select_range(start_cell: str, end_cell: str) -> str:
685
+ """Excel mein range select karo"""
686
+ def _select():
687
+ xl = _excel_app()
688
+ if not xl: return "❌ Excel open nahi hai"
689
+ try:
690
+ rng = f"{start_cell}:{end_cell}"
691
+ xl.ActiveSheet.Range(rng).Select()
692
+ return f"✅ Range {rng} selected"
693
+ except Exception as e:
694
+ return f"❌ Error: {e}"
695
+ return await asyncio.to_thread(_select)
696
+
697
+
698
+ async def excel_auto_fit() -> str:
699
+ """Excel columns aur rows ko auto-fit karo"""
700
+ def _fit():
701
+ xl = _excel_app()
702
+ if not xl: return "❌ Excel open nahi hai"
703
+ try:
704
+ xl.ActiveSheet.Columns.AutoFit()
705
+ xl.ActiveSheet.Rows.AutoFit()
706
+ return "✅ AutoFit ho gaya"
707
+ except Exception as e:
708
+ return f"❌ Error: {e}"
709
+ return await asyncio.to_thread(_fit)
710
+
711
+
712
+ async def excel_add_chart(chart_type: str = "bar", data_range: str = "",
713
+ title: str = "Chart") -> str:
714
+ """Excel mein chart add karo"""
715
+ def _chart():
716
+ xl = _excel_app()
717
+ if not xl: return "❌ Excel open nahi hai"
718
+ try:
719
+ ws = xl.ActiveSheet
720
+ wb = xl.ActiveWorkbook
721
+ rng = ws.Range(data_range) if data_range else ws.UsedRange
722
+ chart_obj = ws.ChartObjects().Add(100, 50, 375, 225)
723
+ chart = chart_obj.Chart
724
+ chart.SetSourceData(rng)
725
+ chart_types = {
726
+ "bar": -4100, "column": 51, "line": 4,
727
+ "pie": 5, "area": 1, "scatter":74
728
+ }
729
+ chart.ChartType = chart_types.get(chart_type.lower(), 51)
730
+ chart.HasTitle = True
731
+ chart.ChartTitle.Text = title
732
+ wb.Save()
733
+ return f"✅ {chart_type} chart add ho gaya: '{title}'"
734
+ except Exception as e:
735
+ return f"❌ Error: {e}"
736
+ return await asyncio.to_thread(_chart)
737
+
738
+
739
+ # ══════════════════════════════════════════════════════════════
740
+ # MS WORD TOOLS
741
+ # ══════════════════════════════════════════════════════════════
742
+
743
+ def _word_app():
744
+ if not HAS_WIN32: return None
745
+ try:
746
+ pythoncom.CoInitialize()
747
+ try:
748
+ return win32com.client.GetActiveObject("Word.Application")
749
+ except:
750
+ app = win32com.client.Dispatch("Word.Application")
751
+ app.Visible = True
752
+ return app
753
+ except:
754
+ return None
755
+
756
+
757
+ async def word_create_file(filename: str = "", template: str = "") -> str:
758
+ """Naya Word document banao"""
759
+ if not HAS_DOCX:
760
+ return "❌ pip install python-docx"
761
+ def _create():
762
+ ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
763
+ fname = filename if filename else f"TITAN_Doc_{ts}.docx"
764
+ if not fname.endswith(".docx"): fname += ".docx"
765
+ path = WORD_DIR / fname
766
+ doc = Document()
767
+ # Default styles
768
+ style = doc.styles['Normal']
769
+ style.font.name = 'Calibri'
770
+ style.font.size = Pt(11)
771
+ doc.save(str(path))
772
+ os.startfile(str(path))
773
+ _location_state["active_file"] = str(path)
774
+ return f"✅ Word document bana: {path}"
775
+ return await asyncio.to_thread(_create)
776
+
777
+
778
+ async def word_type_text(text: str, heading_level: int = 0,
779
+ bold: bool = False, italic: bool = False,
780
+ font_size: int = 11, new_line: bool = True) -> str:
781
+ """
782
+ Active Word document mein text type karo.
783
+ heading_level: 0=normal, 1=Heading1, 2=Heading2, 3=Heading3
784
+ """
785
+ def _type():
786
+ app = _word_app()
787
+ if not app:
788
+ # Fallback: active window mein type karo
789
+ if HAS_GUI:
790
+ _set_clipboard(text)
791
+ pyautogui.hotkey('ctrl','v')
792
+ if new_line:
793
+ pyautogui.press('enter')
794
+ return f"✅ Text type kiya (clipboard method)"
795
+ return "❌ Word open nahi hai"
796
+ try:
797
+ doc = app.ActiveDocument
798
+ sel = app.Selection
799
+
800
+ if heading_level > 0:
801
+ sel.Style = doc.Styles(f"Heading {heading_level}")
802
+ sel.TypeText(text)
803
+ sel.TypeParagraph()
804
+ else:
805
+ if bold: sel.Font.Bold = True
806
+ if italic: sel.Font.Italic = True
807
+ sel.Font.Size = font_size
808
+ sel.TypeText(text)
809
+ if new_line: sel.TypeParagraph()
810
+ sel.Font.Bold = False
811
+ sel.Font.Italic = False
812
+ sel.Font.Size = 11 # reset
813
+ doc.Save()
814
+ return f"✅ Text add ho gaya: '{text[:50]}...'" if len(text)>50 else f"✅ '{text}' add ho gaya"
815
+ except Exception as e:
816
+ return f"❌ Error: {e}"
817
+ return await asyncio.to_thread(_type)
818
+
819
+
820
+ async def word_click_button(button_name: str) -> str:
821
+ """
822
+ Word ka ribbon button click karo.
823
+ button_name: bold, italic, underline, align_left, align_center, align_right,
824
+ justify, bullet_list, numbered_list, insert_table, save,
825
+ undo, redo, find, replace, print, font_bigger, font_smaller,
826
+ heading1, heading2, heading3, page_break, spell_check
827
+ """
828
+ buttons = {
829
+ "bold": lambda: pyautogui.hotkey('ctrl','b'),
830
+ "italic": lambda: pyautogui.hotkey('ctrl','i'),
831
+ "underline": lambda: pyautogui.hotkey('ctrl','u'),
832
+ "align_left": lambda: pyautogui.hotkey('ctrl','l'),
833
+ "align_center": lambda: pyautogui.hotkey('ctrl','e'),
834
+ "align_right": lambda: pyautogui.hotkey('ctrl','r'),
835
+ "justify": lambda: pyautogui.hotkey('ctrl','j'),
836
+ "save": lambda: pyautogui.hotkey('ctrl','s'),
837
+ "undo": lambda: pyautogui.hotkey('ctrl','z'),
838
+ "redo": lambda: pyautogui.hotkey('ctrl','y'),
839
+ "find": lambda: pyautogui.hotkey('ctrl','f'),
840
+ "replace": lambda: pyautogui.hotkey('ctrl','h'),
841
+ "print": lambda: pyautogui.hotkey('ctrl','p'),
842
+ "select_all": lambda: pyautogui.hotkey('ctrl','a'),
843
+ "copy": lambda: pyautogui.hotkey('ctrl','c'),
844
+ "paste": lambda: pyautogui.hotkey('ctrl','v'),
845
+ "cut": lambda: pyautogui.hotkey('ctrl','x'),
846
+ "page_break": lambda: pyautogui.hotkey('ctrl','enter'),
847
+ "new_line": lambda: pyautogui.press('enter'),
848
+ "tab": lambda: pyautogui.press('tab'),
849
+ "font_bigger": lambda: pyautogui.hotkey('ctrl','shift','.'),
850
+ "font_smaller": lambda: pyautogui.hotkey('ctrl','shift',','),
851
+ "heading1": lambda: pyautogui.hotkey('ctrl','alt','1'),
852
+ "heading2": lambda: pyautogui.hotkey('ctrl','alt','2'),
853
+ "heading3": lambda: pyautogui.hotkey('ctrl','alt','3'),
854
+ "normal_style": lambda: pyautogui.hotkey('ctrl','shift','n'),
855
+ "spell_check": lambda: pyautogui.press('f7'),
856
+ "bullet_list": lambda: _word_bullet(),
857
+ "numbered_list": lambda: _word_numbered(),
858
+ "insert_table": lambda: pyautogui.hotkey('ctrl','f5'),
859
+ "zoom_in": lambda: pyautogui.hotkey('ctrl','='),
860
+ "zoom_out": lambda: pyautogui.hotkey('ctrl','-'),
861
+ }
862
+
863
+ def _word_bullet():
864
+ app = _word_app()
865
+ if app:
866
+ sel = app.Selection
867
+ sel.Range.ListFormat.ApplyBulletDefault()
868
+ def _word_numbered():
869
+ app = _word_app()
870
+ if app:
871
+ sel = app.Selection
872
+ sel.Range.ListFormat.ApplyNumberDefault()
873
+
874
+ def _click():
875
+ if not HAS_GUI:
876
+ return "❌ pyautogui install karo"
877
+ fn = buttons.get(button_name.lower())
878
+ if fn:
879
+ fn()
880
+ return f"✅ '{button_name}' button click hua"
881
+ return f"❌ Unknown button: {button_name}. Available: {', '.join(buttons.keys())}"
882
+ return await asyncio.to_thread(_click)
883
+
884
+
885
+ async def word_insert_table(rows: int, cols: int, data: list = None) -> str:
886
+ """Word mein table insert karo"""
887
+ def _table():
888
+ app = _word_app()
889
+ if not app:
890
+ return "❌ Word open nahi hai"
891
+ try:
892
+ doc = app.ActiveDocument
893
+ sel = app.Selection
894
+ tbl = sel.Tables.Add(sel.Range, rows, cols)
895
+ tbl.Borders.Enable = True
896
+ # Header row style
897
+ for c in range(1, cols+1):
898
+ cell = tbl.Cell(1, c)
899
+ cell.Range.Font.Bold = True
900
+ cell.Shading.BackgroundPatternColor = 0x4472C4
901
+ cell.Range.Font.Color = 0xFFFFFF
902
+ # Fill data if provided
903
+ if data:
904
+ for ri, row_data in enumerate(data[:rows], 1):
905
+ for ci, val in enumerate(row_data[:cols], 1):
906
+ tbl.Cell(ri, ci).Range.Text = str(val)
907
+ doc.Save()
908
+ return f"✅ {rows}×{cols} table insert ho gaya"
909
+ except Exception as e:
910
+ return f"❌ Error: {e}"
911
+ return await asyncio.to_thread(_table)
912
+
913
+
914
+ async def word_format_text(text_to_find: str, bold: bool = False,
915
+ italic: bool = False, color: str = "",
916
+ size: int = 0) -> str:
917
+ """Word mein specific text ko format karo"""
918
+ def _format():
919
+ app = _word_app()
920
+ if not app: return "❌ Word open nahi hai"
921
+ try:
922
+ doc = app.ActiveDocument
923
+ find = app.Selection.Find
924
+ find.ClearFormatting()
925
+ find.Replacement.ClearFormatting()
926
+ find.Text = text_to_find
927
+ find.Forward = True
928
+ find.MatchCase = False
929
+ if find.Execute():
930
+ sel = app.Selection
931
+ if bold: sel.Font.Bold = True
932
+ if italic: sel.Font.Italic = True
933
+ if size: sel.Font.Size = size
934
+ if color:
935
+ r = int(color[0:2],16)
936
+ g = int(color[2:4],16)
937
+ b = int(color[4:6],16)
938
+ sel.Font.Color = r + (g<<8) + (b<<16)
939
+ doc.Save()
940
+ return f"✅ '{text_to_find}' format ho gaya"
941
+ return f"❌ '{text_to_find}' nahi mila"
942
+ except Exception as e:
943
+ return f"❌ Error: {e}"
944
+ return await asyncio.to_thread(_format)
945
+
946
+
947
+ async def word_get_position() -> str:
948
+ """Word mein abhi kahan hain — page, line, column"""
949
+ def _pos():
950
+ app = _word_app()
951
+ if not app: return f"📄 Word position unknown"
952
+ try:
953
+ sel = app.Selection
954
+ page = sel.Information(3) # wdActiveEndPageNumber
955
+ line = sel.Information(10) # wdFirstCharacterLineNumber
956
+ col = sel.Information(16) # wdFirstCharacterColumnNumber
957
+ doc = app.ActiveDocument
958
+ total_pages = doc.ComputeStatistics(2) # wdStatisticPages
959
+ _location_state["word_page"] = page
960
+ return (f"📄 Word Position:\n"
961
+ f" Page: {page} / {total_pages}\n"
962
+ f" Line: {line}\n"
963
+ f" Column: {col}")
964
+ except Exception as e:
965
+ return f"❌ Error: {e}"
966
+ return await asyncio.to_thread(_pos)
967
+
968
+
969
+ # ══════════════════════════════════════════════════════════════
970
+ # POWERPOINT TOOLS
971
+ # ══════════════════════════════════════════════════════════════
972
+
973
+ async def ppt_create_presentation(
974
+ topic: str,
975
+ slides_content: list = None,
976
+ title: str = "",
977
+ theme_color: str = "1F4E79",
978
+ num_slides: int = 7
979
+ ) -> str:
980
+ """
981
+ Full PowerPoint presentation banao.
982
+ slides_content: [{"title":"...", "content":"...", "bullets":["a","b"]}]
983
+ num_slides: 7-8 slides with full content
984
+ """
985
+ if not HAS_PPTX:
986
+ return "❌ pip install python-pptx"
987
+
988
+ def _ppt():
989
+ ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
990
+ fname = f"TITAN_PPT_{topic[:30].replace(' ','_')}_{ts}.pptx"
991
+ path = PPT_DIR / fname
992
+
993
+ prs = Presentation()
994
+ prs.slide_width = PInches(13.33)
995
+ prs.slide_height = PInches(7.5)
996
+
997
+ # Color scheme
998
+ bg_color = PRGBColor(0x1F, 0x4E, 0x79)
999
+ text_color = PRGBColor(0xFF, 0xFF, 0xFF)
1000
+ accent_col = PRGBColor(0x2E, 0x75, 0xB6)
1001
+
1002
+ # Auto generate content if not provided
1003
+ if not slides_content:
1004
+ slides_content_gen = _auto_generate_slides(topic, num_slides)
1005
+ else:
1006
+ slides_content_gen = slides_content
1007
+
1008
+ layouts = prs.slide_layouts
1009
+
1010
+ for si, slide_data in enumerate(slides_content_gen):
1011
+ if si == 0:
1012
+ # Title Slide
1013
+ layout = layouts[0]
1014
+ slide = prs.slides.add_slide(layout)
1015
+ # Background
1016
+ bg = slide.background
1017
+ fill = bg.fill
1018
+ fill.solid()
1019
+ fill.fore_color.rgb = bg_color
1020
+
1021
+ # Title
1022
+ tf = slide.placeholders[0].text_frame
1023
+ tf.clear()
1024
+ p = tf.paragraphs[0]
1025
+ p.alignment = PP_ALIGN.CENTER
1026
+ run = p.add_run()
1027
+ run.text = slide_data.get("title", topic)
1028
+ run.font.size = PPt(44)
1029
+ run.font.bold = True
1030
+ run.font.color.rgb = PRGBColor(0xFF, 0xFF, 0xFF)
1031
+
1032
+ # Subtitle
1033
+ if len(slide.placeholders) > 1:
1034
+ tf2 = slide.placeholders[1].text_frame
1035
+ tf2.clear()
1036
+ p2 = tf2.paragraphs[0]
1037
+ p2.alignment = PP_ALIGN.CENTER
1038
+ r2 = p2.add_run()
1039
+ r2.text = slide_data.get("content", f"Presented by TITAN\n{datetime.date.today()}")
1040
+ r2.font.size = PPt(20)
1041
+ r2.font.color.rgb = PRGBColor(0xBD, 0xD7, 0xEE)
1042
+
1043
+ else:
1044
+ # Content Slide
1045
+ layout = layouts[1]
1046
+ slide = prs.slides.add_slide(layout)
1047
+
1048
+ # Background gradient effect using shape
1049
+ bg = slide.background
1050
+ bg.fill.solid()
1051
+ bg.fill.fore_color.rgb = PRGBColor(0x0D, 0x1B, 0x2A)
1052
+
1053
+ # Slide title
1054
+ tf = slide.placeholders[0].text_frame
1055
+ tf.clear()
1056
+ p = tf.paragraphs[0]
1057
+ run = p.add_run()
1058
+ run.text = slide_data.get("title", f"Slide {si+1}")
1059
+ run.font.size = PPt(32)
1060
+ run.font.bold = True
1061
+ run.font.color.rgb = PRGBColor(0x5B, 0x9B, 0xD5)
1062
+
1063
+ # Content body
1064
+ tf2 = slide.placeholders[1].text_frame
1065
+ tf2.clear()
1066
+ tf2.word_wrap = True
1067
+
1068
+ content = slide_data.get("content", "")
1069
+ bullets = slide_data.get("bullets", [])
1070
+
1071
+ if content and not bullets:
1072
+ # Convert paragraphs to bullets
1073
+ bullets = [s.strip() for s in content.split('\n') if s.strip()]
1074
+
1075
+ for bi, bullet in enumerate(bullets[:8]):
1076
+ p2 = tf2.paragraphs[0] if bi == 0 else tf2.add_paragraph()
1077
+ p2.level = 0
1078
+ run2 = p2.add_run()
1079
+ run2.text = f"• {bullet}"
1080
+ run2.font.size = PPt(18)
1081
+ run2.font.color.rgb = PRGBColor(0xE0, 0xE8, 0xF0)
1082
+ run2.font.bold = (bi == 0)
1083
+
1084
+ # Slide number
1085
+ txb = slide.shapes.add_textbox(
1086
+ PInches(12.5), PInches(6.8), PInches(0.8), PInches(0.4))
1087
+ tf3 = txb.text_frame
1088
+ p3 = tf3.paragraphs[0]
1089
+ r3 = p3.add_run()
1090
+ r3.text = str(si + 1)
1091
+ r3.font.size = PPt(12)
1092
+ r3.font.color.rgb = PRGBColor(0x80, 0x80, 0x80)
1093
+ p3.alignment = PP_ALIGN.RIGHT
1094
+
1095
+ prs.save(str(path))
1096
+ os.startfile(str(path))
1097
+ _location_state["active_file"] = str(path)
1098
+ return f"✅ PPT bani: {path}\n📊 {len(slides_content_gen)} slides | Topic: {topic}"
1099
+
1100
+ return await asyncio.to_thread(_ppt)
1101
+
1102
+
1103
+ def _auto_generate_slides(topic: str, num: int = 7) -> list:
1104
+ """Topic ke hisab se auto slide content generate karo"""
1105
+ slides = [
1106
+ {
1107
+ "title": topic,
1108
+ "content": f"A comprehensive presentation on {topic}\n\nPrepared by TITAN AI Assistant\n{datetime.date.today()}"
1109
+ },
1110
+ {
1111
+ "title": "Table of Contents",
1112
+ "bullets": [
1113
+ "1. Introduction & Overview",
1114
+ "2. Key Concepts",
1115
+ "3. Main Features & Benefits",
1116
+ "4. Implementation / Process",
1117
+ "5. Case Study / Examples",
1118
+ "6. Challenges & Solutions",
1119
+ "7. Conclusion & Next Steps"
1120
+ ]
1121
+ },
1122
+ {
1123
+ "title": f"Introduction to {topic}",
1124
+ "bullets": [
1125
+ f"{topic} ek important concept hai jo aaj ke daur mein bahut relevant hai",
1126
+ "Yeh presentation iske key aspects cover karti hai",
1127
+ "Hum iske fayde, challenges aur applications dekhenge",
1128
+ f"Understanding {topic} is essential for modern professionals",
1129
+ "Let's dive deep into this fascinating subject"
1130
+ ]
1131
+ },
1132
+ {
1133
+ "title": "Key Concepts & Core Ideas",
1134
+ "bullets": [
1135
+ f"Fundamental principles of {topic}",
1136
+ "Core components and building blocks",
1137
+ "How the key elements work together",
1138
+ "Important terminology and definitions",
1139
+ "Theoretical framework and models"
1140
+ ]
1141
+ },
1142
+ {
1143
+ "title": "Features & Benefits",
1144
+ "bullets": [
1145
+ f"Primary advantages of {topic}",
1146
+ "Efficiency improvements and cost savings",
1147
+ "Enhanced performance and scalability",
1148
+ "Better user experience and satisfaction",
1149
+ "Long-term strategic value"
1150
+ ]
1151
+ },
1152
+ {
1153
+ "title": "Implementation & Process",
1154
+ "bullets": [
1155
+ "Step 1: Planning and preparation",
1156
+ "Step 2: Resource allocation",
1157
+ "Step 3: Execution and deployment",
1158
+ "Step 4: Testing and quality assurance",
1159
+ "Step 5: Monitoring and optimization"
1160
+ ]
1161
+ },
1162
+ {
1163
+ "title": "Challenges & Solutions",
1164
+ "bullets": [
1165
+ "Common challenges faced during implementation",
1166
+ "How to overcome technical barriers",
1167
+ "Risk mitigation strategies",
1168
+ "Best practices from industry experts",
1169
+ "Lessons learned from case studies"
1170
+ ]
1171
+ },
1172
+ {
1173
+ "title": "Conclusion & Next Steps",
1174
+ "bullets": [
1175
+ f"{topic} has transformative potential",
1176
+ "Key takeaways from this presentation",
1177
+ "Recommended action items",
1178
+ "Resources for further learning",
1179
+ "Q&A and open discussion"
1180
+ ]
1181
+ }
1182
+ ]
1183
+ return slides[:num]
1184
+
1185
+
1186
+ async def ppt_add_slide(title: str, content: str = "",
1187
+ bullets: list = None, slide_index: int = -1) -> str:
1188
+ """Existing PPT mein new slide add karo"""
1189
+ def _add():
1190
+ if not HAS_WIN32: return "❌ win32com chahiye"
1191
+ app = None
1192
+ try:
1193
+ pythoncom.CoInitialize()
1194
+ try: app = win32com.client.GetActiveObject("PowerPoint.Application")
1195
+ except: return "❌ PowerPoint open nahi hai"
1196
+ prs = app.ActivePresentation
1197
+ sls = prs.Slides
1198
+ n = sls.Count
1199
+ idx = n + 1 if slide_index < 0 else slide_index
1200
+ sl = sls.Add(idx, 2) # Layout 2 = Title, Content
1201
+ sl.Shapes.Title.TextFrame.TextRange.Text = title
1202
+ body = sl.Shapes(2).TextFrame.TextRange
1203
+ body.Text = ""
1204
+ items = bullets if bullets else (content.split('\n') if content else [])
1205
+ for item in items:
1206
+ if item.strip():
1207
+ body.InsertAfter(f"• {item}\r")
1208
+ prs.Save()
1209
+ return f"✅ Slide {idx} add ho gayi: '{title}'"
1210
+ except Exception as e:
1211
+ return f"❌ Error: {e}"
1212
+ return await asyncio.to_thread(_add)
1213
+
1214
+
1215
+ # ══════════════════════════════════════════════════════════════
1216
+ # FILE MANAGEMENT TOOLS
1217
+ # ══════════════════════════════════════════════════════════════
1218
+
1219
+ async def file_copy(source: str, destination: str,
1220
+ overwrite: bool = True) -> str:
1221
+ """File ya folder copy karo"""
1222
+ def _copy():
1223
+ src = _resolve_path(source)
1224
+ dst = _resolve_path(destination)
1225
+ if not src: return f"❌ Source nahi mila: {source}"
1226
+ try:
1227
+ if os.path.isdir(src):
1228
+ dst_full = os.path.join(dst, os.path.basename(src)) if os.path.isdir(dst) else dst
1229
+ shutil.copytree(src, dst_full, dirs_exist_ok=overwrite)
1230
+ return f"✅ Folder copy ho gaya: {dst_full}"
1231
+ else:
1232
+ os.makedirs(dst if os.path.isdir(dst) else os.path.dirname(dst), exist_ok=True)
1233
+ dst_file = os.path.join(dst, os.path.basename(src)) if os.path.isdir(dst) else dst
1234
+ shutil.copy2(src, dst_file)
1235
+ return f"✅ File copy ho gayi:\n From: {src}\n To: {dst_file}"
1236
+ except Exception as e:
1237
+ return f"❌ Error: {e}"
1238
+ return await asyncio.to_thread(_copy)
1239
+
1240
+
1241
+ async def file_move(source: str, destination: str) -> str:
1242
+ """File ya folder move karo"""
1243
+ def _move():
1244
+ src = _resolve_path(source)
1245
+ dst = _resolve_path(destination)
1246
+ if not src: return f"❌ Source nahi mila: {source}"
1247
+ try:
1248
+ os.makedirs(dst if os.path.isdir(dst) else os.path.dirname(dst), exist_ok=True)
1249
+ result = shutil.move(src, dst)
1250
+ return f"✅ Move ho gaya:\n From: {src}\n To: {result}"
1251
+ except Exception as e:
1252
+ return f"❌ Error: {e}"
1253
+ return await asyncio.to_thread(_move)
1254
+
1255
+
1256
+ async def file_delete(path: str, to_recycle: bool = True) -> str:
1257
+ """File ya folder delete karo (Recycle Bin ya permanent)"""
1258
+ def _del():
1259
+ fp = _resolve_path(path)
1260
+ if not fp: return f"❌ File nahi mili: {path}"
1261
+ try:
1262
+ if to_recycle and HAS_WIN32:
1263
+ import pythoncom
1264
+ from win32com.shell import shell as sh, shellcon
1265
+ sh.SHFileOperation((0, shellcon.FO_DELETE, fp, None,
1266
+ shellcon.FOF_ALLOWUNDO | shellcon.FOF_NOCONFIRMATION, None, None))
1267
+ return f"✅ Recycle Bin mein bheja: {fp}"
1268
+ else:
1269
+ if os.path.isdir(fp):
1270
+ shutil.rmtree(fp)
1271
+ return f"✅ Folder delete ho gaya: {fp}"
1272
+ else:
1273
+ os.remove(fp)
1274
+ return f"✅ File delete ho gayi: {fp}"
1275
+ except Exception as e:
1276
+ return f"❌ Error: {e}"
1277
+ return await asyncio.to_thread(_del)
1278
+
1279
+
1280
+ async def file_rename(path: str, new_name: str) -> str:
1281
+ """File ya folder rename karo"""
1282
+ def _ren():
1283
+ fp = _resolve_path(path)
1284
+ if not fp: return f"❌ File nahi mili: {path}"
1285
+ parent = os.path.dirname(fp)
1286
+ new_path = os.path.join(parent, new_name)
1287
+ try:
1288
+ os.rename(fp, new_path)
1289
+ return f"✅ Rename ho gaya: {os.path.basename(fp)} → {new_name}"
1290
+ except Exception as e:
1291
+ return f"❌ Error: {e}"
1292
+ return await asyncio.to_thread(_ren)
1293
+
1294
+
1295
+ async def folder_read(folder_path: str = "", show_hidden: bool = False,
1296
+ file_type: str = "") -> str:
1297
+ """
1298
+ Folder ka content padho — files, subfolders, sizes.
1299
+ Auto-detect current location agar path nahi diya.
1300
+ """
1301
+ def _read():
1302
+ path = _resolve_path(folder_path) if folder_path else _location_state.get("active_folder","")
1303
+ if not path:
1304
+ # Current directory
1305
+ path = os.getcwd()
1306
+
1307
+ if not os.path.exists(path):
1308
+ return f"❌ Folder nahi mila: {path}"
1309
+
1310
+ items = os.listdir(path)
1311
+ if not show_hidden:
1312
+ items = [i for i in items if not i.startswith('.') and
1313
+ not (HAS_WIN32 and _is_hidden(os.path.join(path, i)))]
1314
+
1315
+ if file_type:
1316
+ items = [i for i in items if i.lower().endswith(file_type.lower())]
1317
+
1318
+ dirs = [i for i in items if os.path.isdir(os.path.join(path, i))]
1319
+ files = [i for i in items if os.path.isfile(os.path.join(path, i))]
1320
+ dirs.sort(); files.sort()
1321
+
1322
+ lines = [f"📁 {path}", f" {len(dirs)} folders, {len(files)} files\n"]
1323
+ for d in dirs[:20]:
1324
+ lines.append(f" 📂 {d}/")
1325
+ for f in files[:30]:
1326
+ fp = os.path.join(path, f)
1327
+ size = os.path.getsize(fp)
1328
+ sz = f"{size/1024:.1f} KB" if size > 1024 else f"{size} B"
1329
+ ext = Path(f).suffix.lower()
1330
+ icon = {"pdf":"📕","docx":"📘","xlsx":"📗","pptx":"📙","mp3":"🎵",
1331
+ "mp4":"🎬","jpg":"🖼","png":"🖼","zip":"🗜","exe":"⚙"}.get(ext[1:],"📄")
1332
+ lines.append(f" {icon} {f} ({sz})")
1333
+ if len(dirs)>20: lines.append(f" ... aur {len(dirs)-20} folders")
1334
+ if len(files)>30: lines.append(f" ... aur {len(files)-30} files")
1335
+ return "\n".join(lines)
1336
+
1337
+ return await asyncio.to_thread(_read)
1338
+
1339
+
1340
+ def _is_hidden(path):
1341
+ try:
1342
+ attrs = ctypes.windll.kernel32.GetFileAttributesW(path)
1343
+ return bool(attrs & 2)
1344
+ except:
1345
+ return False
1346
+
1347
+
1348
+ async def file_extract(archive_path: str, destination: str = "") -> str:
1349
+ """ZIP/RAR/7z file extract karo"""
1350
+ def _extract():
1351
+ fp = _resolve_path(archive_path)
1352
+ if not fp: return f"❌ Archive nahi mila: {archive_path}"
1353
+ ext = Path(fp).suffix.lower()
1354
+ dst = destination or str(Path(fp).parent / Path(fp).stem)
1355
+ os.makedirs(dst, exist_ok=True)
1356
+ try:
1357
+ if ext == ".zip":
1358
+ import zipfile
1359
+ with zipfile.ZipFile(fp, 'r') as zf:
1360
+ zf.extractall(dst)
1361
+ n = len(os.listdir(dst))
1362
+ os.startfile(dst)
1363
+ return f"✅ {n} files extract ho gayi: {dst}"
1364
+ elif ext in ('.tar', '.gz', '.bz2', '.tgz'):
1365
+ import tarfile
1366
+ with tarfile.open(fp) as tf:
1367
+ tf.extractall(dst)
1368
+ os.startfile(dst)
1369
+ return f"✅ Extract ho gaya: {dst}"
1370
+ elif ext in ('.rar', '.7z'):
1371
+ # 7-zip se extract karo
1372
+ sevenz = _find_7zip()
1373
+ if sevenz:
1374
+ subprocess.run([sevenz, 'x', fp, f'-o{dst}', '-y'],
1375
+ capture_output=True)
1376
+ os.startfile(dst)
1377
+ return f"✅ Extract ho gaya: {dst}"
1378
+ return "❌ .rar/.7z ke liye 7-Zip install karo"
1379
+ else:
1380
+ return f"❌ Format supported nahi: {ext}"
1381
+ except Exception as e:
1382
+ return f"❌ Error: {e}"
1383
+ return await asyncio.to_thread(_extract)
1384
+
1385
+
1386
+ def _find_7zip():
1387
+ paths = [
1388
+ r"C:\Program Files\7-Zip\7z.exe",
1389
+ r"C:\Program Files (x86)\7-Zip\7z.exe",
1390
+ ]
1391
+ for p in paths:
1392
+ if os.path.exists(p): return p
1393
+ return None
1394
+
1395
+
1396
+ async def file_compress(source: str, output_name: str = "",
1397
+ format: str = "zip") -> str:
1398
+ """File ya folder compress karo"""
1399
+ def _compress():
1400
+ fp = _resolve_path(source)
1401
+ if not fp: return f"❌ Source nahi mila: {source}"
1402
+ out = output_name or f"{fp}.{format}"
1403
+ if not out.endswith(f".{format}"): out += f".{format}"
1404
+ try:
1405
+ if format == "zip":
1406
+ import zipfile
1407
+ with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED) as zf:
1408
+ if os.path.isdir(fp):
1409
+ for root, dirs, files in os.walk(fp):
1410
+ for f in files:
1411
+ full = os.path.join(root, f)
1412
+ arcname = os.path.relpath(full, os.path.dirname(fp))
1413
+ zf.write(full, arcname)
1414
+ else:
1415
+ zf.write(fp, os.path.basename(fp))
1416
+ size = os.path.getsize(out) / 1024
1417
+ return f"✅ Compressed: {out} ({size:.1f} KB)"
1418
+ return f"❌ Format {format} ke liye 7-Zip use karo"
1419
+ except Exception as e:
1420
+ return f"❌ Error: {e}"
1421
+ return await asyncio.to_thread(_compress)
1422
+
1423
+
1424
+ async def file_merge(files: list, output_path: str, merge_type: str = "text") -> str:
1425
+ """
1426
+ Multiple files merge karo.
1427
+ merge_type: text/pdf/excel
1428
+ files: list of file paths
1429
+ """
1430
+ def _merge():
1431
+ resolved = [_resolve_path(f) for f in files]
1432
+ missing = [f for f, r in zip(files, resolved) if not r]
1433
+ if missing: return f"❌ Files nahi mili: {', '.join(missing)}"
1434
+
1435
+ try:
1436
+ if merge_type == "text":
1437
+ with open(output_path, 'w', encoding='utf-8') as out_f:
1438
+ for fp in resolved:
1439
+ out_f.write(f"\n{'='*50}\n{os.path.basename(fp)}\n{'='*50}\n")
1440
+ try:
1441
+ with open(fp, encoding='utf-8', errors='ignore') as f:
1442
+ out_f.write(f.read())
1443
+ except: pass
1444
+ return f"✅ {len(resolved)} files merge ho gayi: {output_path}"
1445
+
1446
+ elif merge_type == "pdf":
1447
+ if not HAS_PYPDF: return "❌ pip install pypdf"
1448
+ merger = pypdf.PdfMerger()
1449
+ for fp in resolved:
1450
+ if fp.lower().endswith('.pdf'):
1451
+ merger.append(fp)
1452
+ merger.write(output_path)
1453
+ merger.close()
1454
+ return f"✅ {len(resolved)} PDFs merge ho gayi: {output_path}"
1455
+
1456
+ elif merge_type == "excel":
1457
+ if not HAS_OPENPYXL: return "❌ pip install openpyxl"
1458
+ out_wb = openpyxl.Workbook()
1459
+ out_wb.remove(out_wb.active)
1460
+ for fp in resolved:
1461
+ wb = openpyxl.load_workbook(fp, data_only=True)
1462
+ for ws in wb.worksheets:
1463
+ new_ws = out_wb.create_sheet(title=f"{Path(fp).stem}_{ws.title}"[:31])
1464
+ for row in ws.iter_rows():
1465
+ for cell in row:
1466
+ new_ws[cell.coordinate] = cell.value
1467
+ out_wb.save(output_path)
1468
+ return f"✅ {len(resolved)} Excel files merge ho gayi: {output_path}"
1469
+
1470
+ return f"❌ merge_type: text/pdf/excel"
1471
+ except Exception as e:
1472
+ return f"❌ Error: {e}"
1473
+ return await asyncio.to_thread(_merge)
1474
+
1475
+
1476
+ def _resolve_path(path: str) -> str:
1477
+ """File/folder ka full path resolve karo — relative, user-relative ya direct"""
1478
+ if not path: return ""
1479
+ # Direct path
1480
+ if os.path.exists(path): return os.path.abspath(path)
1481
+ # Home-relative
1482
+ home_path = os.path.join(str(HOME), path)
1483
+ if os.path.exists(home_path): return home_path
1484
+ # Common dirs
1485
+ for base in [HOME/"Desktop", HOME/"Downloads", HOME/"Documents",
1486
+ HOME/"Pictures", HOME/"Videos", HOME/"Music",
1487
+ EXCEL_DIR, WORD_DIR, PPT_DIR]:
1488
+ p = base / path
1489
+ if p.exists(): return str(p)
1490
+ # Active folder
1491
+ af = _location_state.get("active_folder","")
1492
+ if af:
1493
+ p = os.path.join(af, path)
1494
+ if os.path.exists(p): return p
1495
+ # Current dir
1496
+ p = os.path.join(os.getcwd(), path)
1497
+ if os.path.exists(p): return p
1498
+ return ""
1499
+
1500
+
1501
+ # ══════════════════════════════════════════════════════════════
1502
+ # PDF READER
1503
+ # ══════════════════════════════════════════════════════════════
1504
+
1505
+ async def pdf_read_file(file_path: str = "", pages: str = "all") -> str:
1506
+ """
1507
+ PDF file padho.
1508
+ file_path: Path ya naam. Khali ho toh active PDF detect karta hai.
1509
+ pages: 'all', '1-5', '3' etc.
1510
+ """
1511
+ def _read():
1512
+ if not HAS_PYPDF: return "❌ pip install pypdf"
1513
+
1514
+ # Auto detect
1515
+ fp = file_path
1516
+ if not fp:
1517
+ fp = _location_state.get("active_file", "")
1518
+ if not fp:
1519
+ wi = _get_active_window_info()
1520
+ fp = wi.get("file","")
1521
+
1522
+ fp = _resolve_path(fp)
1523
+ if not fp or not fp.lower().endswith('.pdf'):
1524
+ # Search for recent PDFs
1525
+ downloads = HOME / "Downloads"
1526
+ pdfs = sorted(downloads.glob("*.pdf"), key=lambda x: x.stat().st_mtime, reverse=True)
1527
+ if pdfs: fp = str(pdfs[0])
1528
+ else: return "❌ Koi PDF nahi mila. Path do ya PDF kholo."
1529
+
1530
+ try:
1531
+ reader = pypdf.PdfReader(fp)
1532
+ total = len(reader.pages)
1533
+
1534
+ # Page range parse karo
1535
+ if pages == "all":
1536
+ page_nums = list(range(total))
1537
+ elif '-' in pages:
1538
+ s, e = pages.split('-')
1539
+ page_nums = list(range(int(s)-1, min(int(e), total)))
1540
+ else:
1541
+ page_nums = [int(pages)-1]
1542
+
1543
+ text_parts = []
1544
+ for pn in page_nums[:10]: # Max 10 pages
1545
+ pt = reader.pages[pn].extract_text()
1546
+ if pt: text_parts.append(f"[Page {pn+1}]\n{pt.strip()}")
1547
+
1548
+ full_text = "\n\n".join(text_parts)
1549
+ if not full_text.strip():
1550
+ return f"📄 {os.path.basename(fp)} ({total} pages) — Text extract nahi ho saka (scanned PDF ho sakta hai)"
1551
+
1552
+ summary = full_text[:3000] + ("..." if len(full_text)>3000 else "")
1553
+ return f"📄 {os.path.basename(fp)} ({total} pages):\n\n{summary}"
1554
+ except Exception as e:
1555
+ return f"❌ Error reading PDF: {e}"
1556
+ return await asyncio.to_thread(_read)
1557
+
1558
+
1559
+ async def pdf_get_info(file_path: str = "") -> str:
1560
+ """PDF ka metadata aur info dekho"""
1561
+ def _info():
1562
+ if not HAS_PYPDF: return "❌ pip install pypdf"
1563
+ fp = _resolve_path(file_path) if file_path else _location_state.get("active_file","")
1564
+ if not fp: return "❌ PDF path do"
1565
+ try:
1566
+ reader = pypdf.PdfReader(fp)
1567
+ meta = reader.metadata
1568
+ size = os.path.getsize(fp) / 1024
1569
+ lines = [
1570
+ f"📄 {os.path.basename(fp)}",
1571
+ f" Pages: {len(reader.pages)}",
1572
+ f" Size: {size:.1f} KB",
1573
+ ]
1574
+ if meta:
1575
+ if meta.title: lines.append(f" Title: {meta.title}")
1576
+ if meta.author: lines.append(f" Author: {meta.author}")
1577
+ if meta.subject:lines.append(f" Subject: {meta.subject}")
1578
+ return "\n".join(lines)
1579
+ except Exception as e:
1580
+ return f"❌ Error: {e}"
1581
+ return await asyncio.to_thread(_info)
1582
+
1583
+
1584
+ # ══════════════════════════════════════════════════════════════
1585
+ # WINDOWS MANAGEMENT
1586
+ # ══════════════════════════════════════════════════════════════
1587
+
1588
+ async def window_list_all() -> str:
1589
+ """Sab open windows list karo — title, size, position"""
1590
+ def _list():
1591
+ if not HAS_WIN32: return "❌ win32gui chahiye"
1592
+ windows = []
1593
+ def _enum(hwnd, _):
1594
+ if win32gui.IsWindowVisible(hwnd):
1595
+ title = win32gui.GetWindowText(hwnd)
1596
+ if title and len(title) > 2:
1597
+ try:
1598
+ rect = win32gui.GetWindowRect(hwnd)
1599
+ w = rect[2]-rect[0]; h = rect[3]-rect[1]
1600
+ if w > 50 and h > 50:
1601
+ windows.append({"hwnd":hwnd,"title":title,"w":w,"h":h,"x":rect[0],"y":rect[1]})
1602
+ except: pass
1603
+ win32gui.EnumWindows(_enum, None)
1604
+ windows.sort(key=lambda x: x['title'])
1605
+ lines = [f"🪟 {len(windows)} Open Windows:"]
1606
+ for w in windows[:25]:
1607
+ lines.append(f" [{w['hwnd']}] {w['title'][:50]} ({w['w']}×{w['h']} @ {w['x']},{w['y']})")
1608
+ return "\n".join(lines)
1609
+ return await asyncio.to_thread(_list)
1610
+
1611
+
1612
+ async def window_focus(title_or_hwnd: str) -> str:
1613
+ """Window ko focus/foreground mein lao"""
1614
+ def _focus():
1615
+ if not HAS_WIN32: return "❌ win32gui chahiye"
1616
+ hwnd = None
1617
+ try: hwnd = int(title_or_hwnd)
1618
+ except:
1619
+ def _enum(h, _):
1620
+ nonlocal hwnd
1621
+ t = win32gui.GetWindowText(h)
1622
+ if title_or_hwnd.lower() in t.lower(): hwnd = h
1623
+ win32gui.EnumWindows(_enum, None)
1624
+ if not hwnd: return f"❌ Window nahi mila: {title_or_hwnd}"
1625
+ try:
1626
+ if win32gui.IsIconic(hwnd): win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
1627
+ win32gui.SetForegroundWindow(hwnd)
1628
+ title = win32gui.GetWindowText(hwnd)
1629
+ return f"✅ Focus: {title}"
1630
+ except Exception as e:
1631
+ return f"❌ Error: {e}"
1632
+ return await asyncio.to_thread(_focus)
1633
+
1634
+
1635
+ async def window_resize(title: str, width: int, height: int,
1636
+ x: int = -1, y: int = -1) -> str:
1637
+ """Window resize aur reposition karo"""
1638
+ def _resize():
1639
+ if not HAS_WIN32: return "❌ win32gui chahiye"
1640
+ hwnd = None
1641
+ def _enum(h, _):
1642
+ nonlocal hwnd
1643
+ if title.lower() in win32gui.GetWindowText(h).lower(): hwnd = h
1644
+ win32gui.EnumWindows(_enum, None)
1645
+ if not hwnd: return f"❌ Window nahi mila: {title}"
1646
+ try:
1647
+ rect = win32gui.GetWindowRect(hwnd)
1648
+ nx = x if x >= 0 else rect[0]
1649
+ ny = y if y >= 0 else rect[1]
1650
+ win32gui.MoveWindow(hwnd, nx, ny, width, height, True)
1651
+ return f"✅ {title}: {width}×{height} @ {nx},{ny}"
1652
+ except Exception as e:
1653
+ return f"❌ Error: {e}"
1654
+ return await asyncio.to_thread(_resize)
1655
+
1656
+
1657
+ async def window_snap_to(title: str, position: str) -> str:
1658
+ """
1659
+ Window ko position pe snap karo.
1660
+ position: left_half, right_half, top_half, bottom_half,
1661
+ top_left, top_right, bottom_left, bottom_right, maximize, minimize
1662
+ """
1663
+ def _snap():
1664
+ if not HAS_WIN32: return "❌ win32gui chahiye"
1665
+ hwnd = None
1666
+ def _enum(h, _):
1667
+ nonlocal hwnd
1668
+ if title.lower() in win32gui.GetWindowText(h).lower(): hwnd = h
1669
+ win32gui.EnumWindows(_enum, None)
1670
+ if not hwnd: return f"❌ Window nahi mila: {title}"
1671
+
1672
+ sw = win32api.GetSystemMetrics(0)
1673
+ sh = win32api.GetSystemMetrics(1)
1674
+ positions = {
1675
+ "left_half": (0, 0, sw//2, sh),
1676
+ "right_half": (sw//2, 0, sw//2, sh),
1677
+ "top_half": (0, 0, sw, sh//2),
1678
+ "bottom_half": (0, sh//2, sw, sh//2),
1679
+ "top_left": (0, 0, sw//2, sh//2),
1680
+ "top_right": (sw//2, 0, sw//2, sh//2),
1681
+ "bottom_left": (0, sh//2, sw//2, sh//2),
1682
+ "bottom_right": (sw//2, sh//2, sw//2, sh//2),
1683
+ }
1684
+ if position == "maximize":
1685
+ win32gui.ShowWindow(hwnd, win32con.SW_MAXIMIZE)
1686
+ return f"✅ Maximize: {title}"
1687
+ if position == "minimize":
1688
+ win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
1689
+ return f"✅ Minimize: {title}"
1690
+ if position not in positions:
1691
+ return f"❌ Position: {', '.join(positions.keys())}"
1692
+ x, y, w, h = positions[position]
1693
+ win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
1694
+ win32gui.MoveWindow(hwnd, x, y, w, h, True)
1695
+ return f"✅ {title} → {position} ({w}×{h})"
1696
+ return await asyncio.to_thread(_snap)
1697
+
1698
+
1699
+ async def window_close(title: str, force: bool = False) -> str:
1700
+ """Window band karo"""
1701
+ def _close():
1702
+ if not HAS_WIN32: return "❌ win32gui chahiye"
1703
+ hwnd = None
1704
+ def _enum(h, _):
1705
+ nonlocal hwnd
1706
+ if title.lower() in win32gui.GetWindowText(h).lower(): hwnd = h
1707
+ win32gui.EnumWindows(_enum, None)
1708
+ if not hwnd: return f"❌ Window nahi mila: {title}"
1709
+ try:
1710
+ if force:
1711
+ win32gui.PostMessage(hwnd, win32con.WM_QUIT, 0, 0)
1712
+ else:
1713
+ win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
1714
+ return f"✅ Window closed: {title}"
1715
+ except Exception as e:
1716
+ return f"❌ Error: {e}"
1717
+ return await asyncio.to_thread(_close)
1718
+
1719
+
1720
+ async def window_get_active() -> str:
1721
+ """Abhi jo window active hai uski full info"""
1722
+ def _active():
1723
+ if not HAS_WIN32: return "❌ win32gui chahiye"
1724
+ try:
1725
+ hwnd = win32gui.GetForegroundWindow()
1726
+ title = win32gui.GetWindowText(hwnd)
1727
+ rect = win32gui.GetWindowRect(hwnd)
1728
+ w = rect[2]-rect[0]; h = rect[3]-rect[1]
1729
+ return (f"🪟 Active Window:\n"
1730
+ f" Title: {title}\n"
1731
+ f" HWND: {hwnd}\n"
1732
+ f" Size: {w}×{h}\n"
1733
+ f" Pos: x={rect[0]}, y={rect[1]}")
1734
+ except Exception as e:
1735
+ return f"❌ Error: {e}"
1736
+ return await asyncio.to_thread(_active)
1737
+
1738
+
1739
+ async def window_arrange_side_by_side(title1: str, title2: str) -> str:
1740
+ """Do windows ko side-by-side arrange karo"""
1741
+ def _arrange():
1742
+ if not HAS_WIN32: return "❌ win32gui chahiye"
1743
+ hwnds = [None, None]
1744
+ titles = [title1.lower(), title2.lower()]
1745
+ def _enum(h, _):
1746
+ t = win32gui.GetWindowText(h).lower()
1747
+ for i, tl in enumerate(titles):
1748
+ if tl in t and hwnds[i] is None: hwnds[i] = h
1749
+ win32gui.EnumWindows(_enum, None)
1750
+ if not hwnds[0]: return f"❌ Window 1 nahi mila: {title1}"
1751
+ if not hwnds[1]: return f"❌ Window 2 nahi mila: {title2}"
1752
+ sw = win32api.GetSystemMetrics(0)
1753
+ sh = win32api.GetSystemMetrics(1)
1754
+ win32gui.ShowWindow(hwnds[0], win32con.SW_RESTORE)
1755
+ win32gui.ShowWindow(hwnds[1], win32con.SW_RESTORE)
1756
+ win32gui.MoveWindow(hwnds[0], 0, 0, sw//2, sh, True)
1757
+ win32gui.MoveWindow(hwnds[1], sw//2, 0, sw//2, sh, True)
1758
+ return f"✅ Side-by-side:\n Left: {title1}\n Right: {title2}"
1759
+ return await asyncio.to_thread(_arrange)
1760
+
1761
+
1762
+ # ══════════════════════════════════════════════════════════════
1763
+ # TOOL DECLARATIONS (Gemini Compatible)
1764
+ # ══════════════════════════════════════════════════════════════
1765
+
1766
+ def _p(props: dict, req: list = []):
1767
+ return {"type": "object",
1768
+ "properties": {k: {"type": v} for k, v in props.items()},
1769
+ "required": req}
1770
+
1771
+ def _pa(props: dict, req: list = []):
1772
+ """Props with array support"""
1773
+ out = {}
1774
+ for k, v in props.items():
1775
+ if isinstance(v, dict): out[k] = v
1776
+ else: out[k] = {"type": v}
1777
+ return {"type":"object","properties":out,"required":req}
1778
+
1779
+
1780
+ TITAN_OFFICE_TOOLS = [
1781
+ # ── Location Awareness ─────────────────────────────────
1782
+ {"name": "get_current_location",
1783
+ "description": "User abhi kahan hai — active window, file, folder, app automatically detect karo. Kuch poochne ki zaroorat nahi.",
1784
+ "parameters": _p({})},
1785
+ {"name": "watch_location_realtime",
1786
+ "description": "Background mein location tracking start karo — har N seconds mein update",
1787
+ "parameters": _p({"interval_seconds": "integer"})},
1788
+
1789
+ # ── Excel ──────────────────────────────────────────────
1790
+ {"name": "excel_create_file",
1791
+ "description": "Naya Excel file banao aur open karo",
1792
+ "parameters": _p({"filename": "string", "sheet_name": "string"})},
1793
+ {"name": "excel_navigate",
1794
+ "description": "Excel mein navigate karo — cell address (e.g. B5), row+col, ya direction (up/down/left/right)",
1795
+ "parameters": _p({"cell":"string","row":"integer","col":"integer","direction":"string"})},
1796
+ {"name": "excel_get_position",
1797
+ "description": "Excel mein abhi kahan hain — current cell, row, column, sheet",
1798
+ "parameters": _p({})},
1799
+ {"name": "excel_write_cell",
1800
+ "description": "Excel cell mein value likho. Cell khali hone par current cell mein likhta hai aur next pe move karta hai.",
1801
+ "parameters": _p({"value":"string","cell":"string","row":"integer","col":"integer","next_row":"boolean"},["value"])},
1802
+ {"name": "excel_add_headers",
1803
+ "description": "Excel mein header row banao — bold, colored headers",
1804
+ "parameters": _pa({"headers":{"type":"array","items":{"type":"string"}},"row":"integer","start_col":"integer","bold":"boolean","bg_color":"string"},["headers"])},
1805
+ {"name": "excel_add_data_row",
1806
+ "description": "Excel mein next empty row mein data add karo",
1807
+ "parameters": _pa({"data":{"type":"array","items":{}},"row":"integer"},["data"])},
1808
+ {"name": "excel_formula",
1809
+ "description": "Excel cell mein formula daalo — =SUM(B2:B10), =AVERAGE etc.",
1810
+ "parameters": _p({"cell":"string","formula":"string"},["cell","formula"])},
1811
+ {"name": "excel_calculate",
1812
+ "description": "Excel range ka SUM/AVERAGE/COUNT/MAX/MIN nikalo",
1813
+ "parameters": _p({"operation":"string","range_addr":"string","result_cell":"string"},["operation","range_addr"])},
1814
+ {"name": "excel_create_salary_sheet",
1815
+ "description": "Company employees ki monthly salary sheet banao — working days, leaves, deductions, net salary sab calculate karta hai",
1816
+ "parameters": _pa({
1817
+ "workers": {"type":"array","items":{"type":"object"}},
1818
+ "month": "string",
1819
+ "company_name": "string"
1820
+ },["workers"])},
1821
+ {"name": "excel_read_cell",
1822
+ "description": "Excel cell ki value padho",
1823
+ "parameters": _p({"cell":"string"})},
1824
+ {"name": "excel_select_range",
1825
+ "description": "Excel mein range select karo",
1826
+ "parameters": _p({"start_cell":"string","end_cell":"string"},["start_cell","end_cell"])},
1827
+ {"name": "excel_auto_fit",
1828
+ "description": "Excel columns aur rows autofit karo",
1829
+ "parameters": _p({})},
1830
+ {"name": "excel_add_chart",
1831
+ "description": "Excel mein chart add karo — bar/column/line/pie/area/scatter",
1832
+ "parameters": _p({"chart_type":"string","data_range":"string","title":"string"})},
1833
+
1834
+ # ── MS Word ────────────────────────────────────────────
1835
+ {"name": "word_create_file",
1836
+ "description": "Naya Word document banao",
1837
+ "parameters": _p({"filename":"string"})},
1838
+ {"name": "word_type_text",
1839
+ "description": "Active Word document mein text type karo. heading_level: 0=normal, 1-3=heading",
1840
+ "parameters": _p({"text":"string","heading_level":"integer","bold":"boolean","italic":"boolean","font_size":"integer","new_line":"boolean"},["text"])},
1841
+ {"name": "word_click_button",
1842
+ "description": "Word ka ribbon button click karo — bold, italic, underline, align_center, align_left, save, undo, redo, heading1, heading2, bullet_list, numbered_list, find, replace, print, page_break, spell_check",
1843
+ "parameters": _p({"button_name":"string"},["button_name"])},
1844
+ {"name": "word_insert_table",
1845
+ "description": "Word mein table insert karo",
1846
+ "parameters": _pa({"rows":"integer","cols":"integer","data":{"type":"array","items":{"type":"array","items":{}}}},["rows","cols"])},
1847
+ {"name": "word_format_text",
1848
+ "description": "Word mein specific text ko format karo — bold, italic, color, size",
1849
+ "parameters": _p({"text_to_find":"string","bold":"boolean","italic":"boolean","color":"string","size":"integer"},["text_to_find"])},
1850
+ {"name": "word_get_position",
1851
+ "description": "Word mein abhi kahan hain — page number, line, column",
1852
+ "parameters": _p({})},
1853
+
1854
+ # ── PowerPoint ─────────────────────────────────────────
1855
+ {"name": "ppt_create_presentation",
1856
+ "description": "Full PowerPoint presentation banao — 7-8 slides, full content ke saath. Topic batao, baaki TITAN karti hai.",
1857
+ "parameters": _pa({
1858
+ "topic": "string",
1859
+ "slides_content": {"type":"array","items":{"type":"object"}},
1860
+ "title": "string",
1861
+ "num_slides": "integer"
1862
+ },["topic"])},
1863
+ {"name": "ppt_add_slide",
1864
+ "description": "Existing PPT mein new slide add karo",
1865
+ "parameters": _pa({"title":"string","content":"string","bullets":{"type":"array","items":{"type":"string"}},"slide_index":"integer"},["title"])},
1866
+
1867
+ # ── File Management ────────────────────────────────────
1868
+ {"name": "file_copy",
1869
+ "description": "File ya folder copy karo",
1870
+ "parameters": _p({"source":"string","destination":"string","overwrite":"boolean"},["source","destination"])},
1871
+ {"name": "file_move",
1872
+ "description": "File ya folder move karo",
1873
+ "parameters": _p({"source":"string","destination":"string"},["source","destination"])},
1874
+ {"name": "file_delete",
1875
+ "description": "File ya folder delete karo. to_recycle=True se Recycle Bin mein jaata hai",
1876
+ "parameters": _p({"path":"string","to_recycle":"boolean"},["path"])},
1877
+ {"name": "file_rename",
1878
+ "description": "File ya folder rename karo",
1879
+ "parameters": _p({"path":"string","new_name":"string"},["path","new_name"])},
1880
+ {"name": "folder_read",
1881
+ "description": "Folder ka content padho — files, subfolders, sizes. Khali hone par current active folder dekho.",
1882
+ "parameters": _p({"folder_path":"string","show_hidden":"boolean","file_type":"string"})},
1883
+ {"name": "file_extract",
1884
+ "description": "ZIP/RAR/7z file extract karo",
1885
+ "parameters": _p({"archive_path":"string","destination":"string"},["archive_path"])},
1886
+ {"name": "file_compress",
1887
+ "description": "File ya folder ko ZIP mein compress karo",
1888
+ "parameters": _p({"source":"string","output_name":"string","format":"string"},["source"])},
1889
+ {"name": "file_merge",
1890
+ "description": "Multiple files merge karo — text/pdf/excel",
1891
+ "parameters": _pa({"files":{"type":"array","items":{"type":"string"}},"output_path":"string","merge_type":"string"},["files","output_path"])},
1892
+
1893
+ # ── PDF ────────────────────────────────────────────────
1894
+ {"name": "pdf_read_file",
1895
+ "description": "PDF file padho. file_path khali ho toh currently active/open PDF detect karta hai automatically.",
1896
+ "parameters": _p({"file_path":"string","pages":"string"})},
1897
+ {"name": "pdf_get_info",
1898
+ "description": "PDF ka metadata — pages, size, title, author",
1899
+ "parameters": _p({"file_path":"string"})},
1900
+
1901
+ # ── Windows Management ─────────────────────────────────
1902
+ {"name": "window_list_all",
1903
+ "description": "Sab open windows list karo — title, size, position",
1904
+ "parameters": _p({})},
1905
+ {"name": "window_focus",
1906
+ "description": "Window ko foreground mein lao — title ya HWND se",
1907
+ "parameters": _p({"title_or_hwnd":"string"},["title_or_hwnd"])},
1908
+ {"name": "window_resize",
1909
+ "description": "Window resize aur reposition karo",
1910
+ "parameters": _p({"title":"string","width":"integer","height":"integer","x":"integer","y":"integer"},["title","width","height"])},
1911
+ {"name": "window_snap_to",
1912
+ "description": "Window ko snap karo — left_half, right_half, top_half, bottom_half, top_left, top_right, bottom_left, bottom_right, maximize, minimize",
1913
+ "parameters": _p({"title":"string","position":"string"},["title","position"])},
1914
+ {"name": "window_close",
1915
+ "description": "Window band karo",
1916
+ "parameters": _p({"title":"string","force":"boolean"},["title"])},
1917
+ {"name": "window_get_active",
1918
+ "description": "Abhi jo window active/foreground mein hai uski info",
1919
+ "parameters": _p({})},
1920
+ {"name": "window_arrange_side_by_side",
1921
+ "description": "Do windows ko side-by-side arrange karo",
1922
+ "parameters": _p({"title1":"string","title2":"string"},["title1","title2"])},
1923
+ ]
1924
+
1925
+ # ══════════════════════════════════════════════════════════════
1926
+ # DISPATCHER
1927
+ # ══════════════════════════════════════════════════════════════
1928
+
1929
+ OFFICE_TOOL_MAP = {
1930
+ # Location
1931
+ "get_current_location": get_current_location,
1932
+ "watch_location_realtime": watch_location_realtime,
1933
+ # Excel
1934
+ "excel_create_file": excel_create_file,
1935
+ "excel_navigate": excel_navigate,
1936
+ "excel_get_position": excel_get_position,
1937
+ "excel_write_cell": excel_write_cell,
1938
+ "excel_add_headers": excel_add_headers,
1939
+ "excel_add_data_row": excel_add_data_row,
1940
+ "excel_formula": excel_formula,
1941
+ "excel_calculate": excel_calculate,
1942
+ "excel_create_salary_sheet": excel_create_salary_sheet,
1943
+ "excel_read_cell": excel_read_cell,
1944
+ "excel_select_range": excel_select_range,
1945
+ "excel_auto_fit": excel_auto_fit,
1946
+ "excel_add_chart": excel_add_chart,
1947
+ # Word
1948
+ "word_create_file": word_create_file,
1949
+ "word_type_text": word_type_text,
1950
+ "word_click_button": word_click_button,
1951
+ "word_insert_table": word_insert_table,
1952
+ "word_format_text": word_format_text,
1953
+ "word_get_position": word_get_position,
1954
+ # PPT
1955
+ "ppt_create_presentation": ppt_create_presentation,
1956
+ "ppt_add_slide": ppt_add_slide,
1957
+ # File Management
1958
+ "file_copy": file_copy,
1959
+ "file_move": file_move,
1960
+ "file_delete": file_delete,
1961
+ "file_rename": file_rename,
1962
+ "folder_read": folder_read,
1963
+ "file_extract": file_extract,
1964
+ "file_compress": file_compress,
1965
+ "file_merge": file_merge,
1966
+ # PDF
1967
+ "pdf_read_file": pdf_read_file,
1968
+ "pdf_get_info": pdf_get_info,
1969
+ # Windows
1970
+ "window_list_all": window_list_all,
1971
+ "window_focus": window_focus,
1972
+ "window_resize": window_resize,
1973
+ "window_snap_to": window_snap_to,
1974
+ "window_close": window_close,
1975
+ "window_get_active": window_get_active,
1976
+ "window_arrange_side_by_side":window_arrange_side_by_side,
1977
+ }
1978
+
1979
+ OFFICE_TOOL_NAMES = {t["name"] for t in TITAN_OFFICE_TOOLS}
1980
+
1981
+ async def office_dispatch(name: str, args: dict) -> str:
1982
+ fn = OFFICE_TOOL_MAP.get(name)
1983
+ if fn:
1984
+ return await fn(**args)
1985
+ return f"❌ Office tool nahi mila: {name}"