signalpilot-ai-internal 0.5.1__py3-none-any.whl → 0.6.0__py3-none-any.whl

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.

Potentially problematic release.


This version of signalpilot-ai-internal might be problematic. Click here for more details.

Files changed (46) hide show
  1. signalpilot_ai_internal/_version.py +1 -1
  2. signalpilot_ai_internal/cache_service.py +152 -1
  3. signalpilot_ai_internal/file_scanner_service.py +1252 -0
  4. signalpilot_ai_internal/handlers.py +262 -0
  5. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/package.json +2 -2
  6. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/schemas/signalpilot-ai-internal/package.json.orig +1 -1
  7. signalpilot_ai_internal-0.6.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/188.1ace26ac1a5e246783bb.js +1 -0
  8. signalpilot_ai_internal-0.6.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/839.7d9a99d0566aa6743c69.js +1 -0
  9. signalpilot_ai_internal-0.6.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/898.4e9edb7f224152c1dcb4.js +2 -0
  10. signalpilot_ai_internal-0.6.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/remoteEntry.410a42566793b732952f.js +1 -0
  11. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/third-party-licenses.json +0 -6
  12. {signalpilot_ai_internal-0.5.1.dist-info → signalpilot_ai_internal-0.6.0.dist-info}/METADATA +3 -1
  13. signalpilot_ai_internal-0.6.0.dist-info/RECORD +46 -0
  14. signalpilot_ai_internal-0.5.1.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/104.04e170724f369fcbaf19.js +0 -2
  15. signalpilot_ai_internal-0.5.1.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/104.04e170724f369fcbaf19.js.LICENSE.txt +0 -24
  16. signalpilot_ai_internal-0.5.1.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/188.e781cc4c87f2dbf290ec.js +0 -1
  17. signalpilot_ai_internal-0.5.1.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/606.90aaaae46b73dc3c08fb.js +0 -1
  18. signalpilot_ai_internal-0.5.1.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/839.7ea0c8f6af45369912f3.js +0 -1
  19. signalpilot_ai_internal-0.5.1.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/898.5251a593584dd5d131d5.js +0 -2
  20. signalpilot_ai_internal-0.5.1.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/remoteEntry.59ffb91489066223094b.js +0 -1
  21. signalpilot_ai_internal-0.5.1.dist-info/RECORD +0 -48
  22. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/etc/jupyter/jupyter_server_config.d/signalpilot_ai.json +0 -0
  23. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/install.json +0 -0
  24. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/schemas/signalpilot-ai-internal/plugin.json +0 -0
  25. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/122.e2dadf63dc64d7b5f1ee.js +0 -0
  26. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/220.328403b5545f268b95c6.js +0 -0
  27. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/262.726e1da31a50868cb297.js +0 -0
  28. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/353.72484b768a04f89bd3dd.js +0 -0
  29. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/364.dbec4c2dc12e7b050dcc.js +0 -0
  30. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/384.fa432bdb7fb6b1c95ad6.js +0 -0
  31. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/439.37e271d7a80336daabe2.js +0 -0
  32. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/476.9b4f05a99f5003f82094.js +0 -0
  33. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/481.73c7a9290b7d35a8b9c1.js +0 -0
  34. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/512.b58fc0093d080b8ee61c.js +0 -0
  35. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/553.b4042a795c91d9ff71ef.js +0 -0
  36. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/553.b4042a795c91d9ff71ef.js.LICENSE.txt +0 -0
  37. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/635.9720593ee20b768da3ca.js +0 -0
  38. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/713.8e6edc9a965bdd578ca7.js +0 -0
  39. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/741.dc49867fafb03ea2ba4d.js +0 -0
  40. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/742.91e7b516c8699eea3373.js +0 -0
  41. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/785.3aa564fc148b37d1d719.js +0 -0
  42. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/888.34054db17bcf6e87ec95.js +0 -0
  43. /signalpilot_ai_internal-0.5.1.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/898.5251a593584dd5d131d5.js.LICENSE.txt → /signalpilot_ai_internal-0.6.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/898.4e9edb7f224152c1dcb4.js.LICENSE.txt +0 -0
  44. {signalpilot_ai_internal-0.5.1.data → signalpilot_ai_internal-0.6.0.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/style.js +0 -0
  45. {signalpilot_ai_internal-0.5.1.dist-info → signalpilot_ai_internal-0.6.0.dist-info}/WHEEL +0 -0
  46. {signalpilot_ai_internal-0.5.1.dist-info → signalpilot_ai_internal-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -12,6 +12,7 @@ from .cache_service import get_cache_service
12
12
  from .cache_handlers import ChatHistoriesHandler, AppValuesHandler, CacheInfoHandler
13
13
  from .unified_database_schema_service import UnifiedDatabaseSchemaHandler, UnifiedDatabaseQueryHandler
14
14
  from .snowflake_schema_service import SnowflakeSchemaHandler, SnowflakeQueryHandler
15
+ from .file_scanner_service import get_file_scanner_service
15
16
  from .schema_search_service import SchemaSearchHandler
16
17
 
17
18
 
@@ -258,6 +259,253 @@ class ReadAllFilesHandler(APIHandler):
258
259
  return '\n'.join(lines)
259
260
 
260
261
 
262
+ class SelectFolderHandler(APIHandler):
263
+ """Handler to open a native folder picker and return the selected absolute path"""
264
+
265
+ # Class-level flag to prevent multiple dialogs
266
+ _dialog_open = False
267
+
268
+ @tornado.web.authenticated
269
+ def get(self):
270
+ # Check if a dialog is already open
271
+ if SelectFolderHandler._dialog_open:
272
+ self.set_status(409) # Conflict status
273
+ self.finish(json.dumps({
274
+ "error": "A folder selection dialog is already open"
275
+ }))
276
+ return
277
+
278
+ try:
279
+ import tkinter as tk
280
+ from tkinter import filedialog
281
+ import threading
282
+ import time
283
+
284
+ # Set flag to prevent multiple dialogs
285
+ SelectFolderHandler._dialog_open = True
286
+
287
+ # Create a fresh tkinter instance
288
+ root = tk.Tk()
289
+
290
+ # Position the root window in the center of the screen BEFORE withdrawing
291
+ try:
292
+ # Get screen dimensions
293
+ screen_width = root.winfo_screenwidth()
294
+ screen_height = root.winfo_screenheight()
295
+
296
+ # Calculate center position
297
+ x = (screen_width // 2) - 200 # Dialog is roughly 400px wide
298
+ y = (screen_height // 2) - 150 # Dialog is roughly 300px tall
299
+
300
+ # Set window position and make it visible briefly for positioning
301
+ root.geometry(f"400x300+{x}+{y}")
302
+ root.update_idletasks()
303
+
304
+ # Now withdraw the window
305
+ root.withdraw()
306
+
307
+ # Enhanced topmost settings for better visibility
308
+ root.attributes('-topmost', True)
309
+ root.lift()
310
+ root.focus_force()
311
+
312
+ # Final positioning update
313
+ root.update_idletasks()
314
+ except Exception:
315
+ # Fallback: just withdraw if positioning fails
316
+ root.withdraw()
317
+
318
+ folder = None
319
+
320
+ try:
321
+ # Show the dialog with proper positioning
322
+ folder = filedialog.askdirectory(
323
+ parent=root,
324
+ title="Select Folder",
325
+ initialdir=os.path.expanduser("~") # Start in user's home directory
326
+ )
327
+ except Exception as e:
328
+ raise e
329
+ finally:
330
+ # Comprehensive cleanup
331
+ try:
332
+ # Force close and destroy all tkinter components
333
+ root.quit()
334
+ root.destroy()
335
+
336
+ # Additional cleanup for macOS - ensure complete destruction
337
+ try:
338
+ root.update_idletasks()
339
+ root.update()
340
+ # Force garbage collection of tkinter objects
341
+ import gc
342
+ gc.collect()
343
+ except Exception:
344
+ pass
345
+
346
+ except Exception:
347
+ pass
348
+ finally:
349
+ # Reset flag and add small delay to ensure cleanup
350
+ SelectFolderHandler._dialog_open = False
351
+ time.sleep(0.1) # Small delay to ensure cleanup completes
352
+
353
+ # Normalize and return absolute path or null
354
+ if folder:
355
+ folder_path = os.path.abspath(folder)
356
+ self.finish(json.dumps({"path": folder_path}))
357
+ else:
358
+ self.finish(json.dumps({"path": None}))
359
+
360
+ except Exception as e:
361
+ # Reset flag on error
362
+ SelectFolderHandler._dialog_open = False
363
+ self.set_status(400)
364
+ self.finish(json.dumps({
365
+ "error": str(e)
366
+ }))
367
+
368
+ class FileScanHandler(APIHandler):
369
+ """Handler for scanning directories for files"""
370
+
371
+ @tornado.web.authenticated
372
+ async def post(self):
373
+ try:
374
+ data = json.loads(self.request.body.decode('utf-8'))
375
+ paths = data.get('paths', [])
376
+
377
+ if not paths:
378
+ self.set_status(400)
379
+ self.finish(json.dumps({
380
+ "error": "No paths provided"
381
+ }))
382
+ return
383
+
384
+ file_scanner = get_file_scanner_service()
385
+ # Pass the current working directory as the workspace root for relative path calculation
386
+ result = await file_scanner.scan_directories(paths, workspace_root=os.getcwd())
387
+
388
+ # Update scanned directories tracking
389
+ scanned_dirs = file_scanner.get_scanned_directories()
390
+ current_dirs = scanned_dirs.get('directories', [])
391
+
392
+ # Update directory metadata
393
+ for new_dir in result['scanned_directories']:
394
+ # Check if directory already exists
395
+ existing_dir = None
396
+ for existing in current_dirs:
397
+ if existing['path'] == new_dir['path']:
398
+ existing_dir = existing
399
+ break
400
+
401
+ if existing_dir:
402
+ # Update existing directory
403
+ existing_dir['file_count'] = new_dir['file_count']
404
+ existing_dir['scanned_at'] = new_dir['scanned_at']
405
+ else:
406
+ # Add new directory
407
+ current_dirs.append(new_dir)
408
+
409
+ # Save updated directories list
410
+ file_scanner.update_scanned_directories(current_dirs)
411
+
412
+ self.finish(json.dumps(result))
413
+
414
+ except Exception as e:
415
+ self.set_status(500)
416
+ self.finish(json.dumps({
417
+ "error": str(e)
418
+ }))
419
+
420
+
421
+ class ScannedDirectoriesHandler(APIHandler):
422
+ """Handler for getting scanned directories list"""
423
+
424
+ @tornado.web.authenticated
425
+ def get(self):
426
+ try:
427
+ file_scanner = get_file_scanner_service()
428
+ result = file_scanner.get_scanned_directories()
429
+
430
+ self.finish(json.dumps(result))
431
+
432
+ except Exception as e:
433
+ self.set_status(500)
434
+ self.finish(json.dumps({
435
+ "error": str(e)
436
+ }))
437
+
438
+
439
+ class WorkDirHandler(APIHandler):
440
+ """Handler for returning current working directory"""
441
+
442
+ @tornado.web.authenticated
443
+ def get(self):
444
+ try:
445
+ self.finish(json.dumps({"workdir": os.getcwd()}))
446
+ except Exception as e:
447
+ self.set_status(500)
448
+ self.finish(json.dumps({
449
+ "error": str(e)
450
+ }))
451
+
452
+
453
+ class DeleteScannedDirectoryHandler(APIHandler):
454
+ """Handler for deleting a scanned directory"""
455
+
456
+ @tornado.web.authenticated
457
+ def post(self):
458
+ try:
459
+ data = json.loads(self.request.body.decode('utf-8'))
460
+ directory_path = data.get('path')
461
+
462
+ if not directory_path:
463
+ self.set_status(400)
464
+ self.finish(json.dumps({
465
+ "error": "No directory path provided"
466
+ }))
467
+ return
468
+
469
+ file_scanner = get_file_scanner_service()
470
+
471
+ # Get current scanned directories
472
+ current_directories = file_scanner.get_scanned_directories()
473
+ directories = current_directories.get('directories', [])
474
+
475
+ # Filter out the directory to be deleted
476
+ filtered_directories = [
477
+ dir_info for dir_info in directories
478
+ if dir_info.get('path') != directory_path
479
+ ]
480
+
481
+ # Check if directory was actually found and removed
482
+ if len(filtered_directories) == len(directories):
483
+ self.set_status(404)
484
+ self.finish(json.dumps({
485
+ "error": f"Directory '{directory_path}' not found in scanned directories"
486
+ }))
487
+ return
488
+
489
+ # Update the scanned directories list
490
+ success = file_scanner.update_scanned_directories(filtered_directories)
491
+
492
+ if success:
493
+ self.finish(json.dumps({
494
+ "success": True,
495
+ "message": f"Directory '{directory_path}' removed from scanning"
496
+ }))
497
+ else:
498
+ self.set_status(500)
499
+ self.finish(json.dumps({
500
+ "error": "Failed to update scanned directories"
501
+ }))
502
+
503
+ except Exception as e:
504
+ self.set_status(500)
505
+ self.finish(json.dumps({
506
+ "error": str(e)
507
+ }))
508
+
261
509
  def setup_handlers(web_app):
262
510
  host_pattern = ".*$"
263
511
  base_url = web_app.settings["base_url"]
@@ -268,6 +516,13 @@ def setup_handlers(web_app):
268
516
  # Read all files endpoint
269
517
  read_all_files_route = url_path_join(base_url, "signalpilot-ai-internal", "read-all-files")
270
518
 
519
+ # File scanning endpoints
520
+ file_scan_route = url_path_join(base_url, "signalpilot-ai-internal", "files", "scan")
521
+ scanned_directories_route = url_path_join(base_url, "signalpilot-ai-internal", "files", "directories")
522
+ select_folder_route = url_path_join(base_url, "signalpilot-ai-internal", "files", "select-folder")
523
+ workdir_route = url_path_join(base_url, "signalpilot-ai-internal", "files", "workdir")
524
+ delete_scanned_directory_route = url_path_join(base_url, "signalpilot-ai-internal", "files", "directories", "delete")
525
+
271
526
  # Cache service endpoints
272
527
  chat_histories_route = url_path_join(base_url, "signalpilot-ai-internal", "cache", "chat-histories")
273
528
  chat_history_route = url_path_join(base_url, "signalpilot-ai-internal", "cache", "chat-histories", "([^/]+)")
@@ -297,6 +552,13 @@ def setup_handlers(web_app):
297
552
  # Read all files endpoint
298
553
  (read_all_files_route, ReadAllFilesHandler),
299
554
 
555
+ # File scanning endpoints
556
+ (file_scan_route, FileScanHandler),
557
+ (scanned_directories_route, ScannedDirectoriesHandler),
558
+ (select_folder_route, SelectFolderHandler),
559
+ (workdir_route, WorkDirHandler),
560
+ (delete_scanned_directory_route, DeleteScannedDirectoryHandler),
561
+
300
562
  # Chat histories endpoints
301
563
  (chat_histories_route, ChatHistoriesHandler),
302
564
  (chat_history_route, ChatHistoriesHandler),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalpilot-ai-internal",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "SignalPilot Agent - Your Jupyter Notebook Assistant",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -134,7 +134,7 @@
134
134
  "outputDir": "signalpilot_ai_internal/labextension",
135
135
  "schemaDir": "schema",
136
136
  "_build": {
137
- "load": "static/remoteEntry.59ffb91489066223094b.js",
137
+ "load": "static/remoteEntry.410a42566793b732952f.js",
138
138
  "extension": "./extension",
139
139
  "style": "./style"
140
140
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalpilot-ai-internal",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "SignalPilot Agent - Your Jupyter Notebook Assistant",
5
5
  "keywords": [
6
6
  "jupyter",