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.
- myra_ai_assistant-0.1.0/Aria_office_manage.py +1985 -0
- myra_ai_assistant-0.1.0/LICENSE +21 -0
- myra_ai_assistant-0.1.0/MANIFEST.in +13 -0
- myra_ai_assistant-0.1.0/Main_normal.py +175 -0
- myra_ai_assistant-0.1.0/PKG-INFO +113 -0
- myra_ai_assistant-0.1.0/README.md +44 -0
- myra_ai_assistant-0.1.0/aria.py +1844 -0
- myra_ai_assistant-0.1.0/aria_daemon.py +302 -0
- myra_ai_assistant-0.1.0/aria_modules/__init__.py +0 -0
- myra_ai_assistant-0.1.0/aria_modules/aria_activation.py +1341 -0
- myra_ai_assistant-0.1.0/aria_modules/aria_advanced_features.py +1256 -0
- myra_ai_assistant-0.1.0/aria_modules/aria_advanced_tools.py +1097 -0
- myra_ai_assistant-0.1.0/aria_modules/aria_login.py +674 -0
- myra_ai_assistant-0.1.0/aria_modules/cloud_database.py +10 -0
- myra_ai_assistant-0.1.0/aria_modules/face_auth.py +1161 -0
- myra_ai_assistant-0.1.0/aria_modules/firebase_connection.py +43 -0
- myra_ai_assistant-0.1.0/aria_modules/game_install.py +1048 -0
- myra_ai_assistant-0.1.0/aria_modules/minibar_aria.py +354 -0
- myra_ai_assistant-0.1.0/aria_modules/tools.py +2198 -0
- myra_ai_assistant-0.1.0/aria_startup.py +216 -0
- myra_ai_assistant-0.1.0/camera_vision_module.py +503 -0
- myra_ai_assistant-0.1.0/main_hotkey.py +530 -0
- myra_ai_assistant-0.1.0/myra/__init__.py +3 -0
- myra_ai_assistant-0.1.0/myra/__main__.py +6 -0
- myra_ai_assistant-0.1.0/myra/cli.py +33 -0
- myra_ai_assistant-0.1.0/myra/config.py +22 -0
- myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/PKG-INFO +113 -0
- myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/SOURCES.txt +49 -0
- myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/dependency_links.txt +1 -0
- myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/entry_points.txt +2 -0
- myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/requires.txt +46 -0
- myra_ai_assistant-0.1.0/myra_ai_assistant.egg-info/top_level.txt +14 -0
- myra_ai_assistant-0.1.0/normal_prompt.py +41 -0
- myra_ai_assistant-0.1.0/prompt.py +243 -0
- myra_ai_assistant-0.1.0/pyproject.toml +99 -0
- myra_ai_assistant-0.1.0/screen_share_module.py +452 -0
- myra_ai_assistant-0.1.0/setup.cfg +4 -0
- myra_ai_assistant-0.1.0/website_builder/Config.py +36 -0
- myra_ai_assistant-0.1.0/website_builder/Content_agent.py +152 -0
- myra_ai_assistant-0.1.0/website_builder/Generator_agent.py +243 -0
- myra_ai_assistant-0.1.0/website_builder/Github_service.py +111 -0
- myra_ai_assistant-0.1.0/website_builder/Planner_agent.py +162 -0
- myra_ai_assistant-0.1.0/website_builder/Preview_server.py +62 -0
- myra_ai_assistant-0.1.0/website_builder/Project_manager.py +49 -0
- myra_ai_assistant-0.1.0/website_builder/Projects_db.py +50 -0
- myra_ai_assistant-0.1.0/website_builder/Qr_display.py +214 -0
- myra_ai_assistant-0.1.0/website_builder/Vercel_service.py +116 -0
- myra_ai_assistant-0.1.0/website_builder/Webforge_integration.py +220 -0
- myra_ai_assistant-0.1.0/website_builder/Webforge_manager.py +267 -0
- myra_ai_assistant-0.1.0/website_builder/__init__.py +1 -0
- 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}"
|