x-ipe 1.0.22__py3-none-any.whl → 1.0.24__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.
x_ipe/app.py CHANGED
@@ -126,6 +126,15 @@ def _init_services(app):
126
126
  if saved_root and saved_root != '.' and not app.config.get('TESTING'):
127
127
  if os.path.exists(saved_root) and os.path.isdir(saved_root):
128
128
  app.config['PROJECT_ROOT'] = saved_root
129
+
130
+ # Cleanup old UIUX feedback on startup (TASK-237)
131
+ if not app.config.get('TESTING'):
132
+ project_root = app.config.get('PROJECT_ROOT', '.')
133
+ from x_ipe.services.uiux_feedback_service import UiuxFeedbackService
134
+ feedback_service = UiuxFeedbackService(project_root)
135
+ deleted = feedback_service.cleanup_old_feedback(days=7)
136
+ if deleted > 0:
137
+ print(f"[X-IPE] Cleaned up {deleted} old feedback entries")
129
138
 
130
139
 
131
140
  def _register_blueprints(app):
@@ -91,6 +91,48 @@ def upload_ideas():
91
91
  return jsonify(result), 400
92
92
 
93
93
 
94
+ @ideas_bp.route('/api/ideas/create-folder', methods=['POST'])
95
+ def create_idea_folder():
96
+ """
97
+ POST /api/ideas/create-folder
98
+
99
+ Create a new empty folder in ideas directory.
100
+
101
+ Request body:
102
+ - folder_name: string - Name for the new folder
103
+ - parent_folder: string (optional) - Parent folder path
104
+
105
+ Response:
106
+ - success: true/false
107
+ - folder_name: string
108
+ - folder_path: string
109
+ """
110
+ project_root = current_app.config.get('PROJECT_ROOT', os.getcwd())
111
+ service = IdeasService(project_root)
112
+
113
+ if not request.is_json:
114
+ return jsonify({
115
+ 'success': False,
116
+ 'error': 'JSON required'
117
+ }), 400
118
+
119
+ data = request.get_json()
120
+ folder_name = data.get('folder_name')
121
+ parent_folder = data.get('parent_folder')
122
+
123
+ if not folder_name:
124
+ return jsonify({
125
+ 'success': False,
126
+ 'error': 'folder_name is required'
127
+ }), 400
128
+
129
+ result = service.create_folder(folder_name, parent_folder)
130
+
131
+ if result['success']:
132
+ return jsonify(result)
133
+ return jsonify(result), 400
134
+
135
+
94
136
  @ideas_bp.route('/api/ideas/rename', methods=['POST'])
95
137
  def rename_idea_folder():
96
138
  """
@@ -264,6 +306,236 @@ def save_ideas_toolbox():
264
306
  return jsonify(result), 400
265
307
 
266
308
 
309
+ # ==========================================================================
310
+ # CR-006: Folder Tree UX Enhancement API Endpoints
311
+ # ==========================================================================
312
+
313
+ @ideas_bp.route('/api/ideas/move', methods=['POST'])
314
+ def move_idea_item():
315
+ """
316
+ POST /api/ideas/move
317
+
318
+ Move a file or folder to a new location.
319
+
320
+ Request body:
321
+ - source_path: string - Path of item to move
322
+ - target_folder: string - Destination folder path
323
+
324
+ Response:
325
+ - success: true/false
326
+ - new_path: string
327
+ """
328
+ project_root = current_app.config.get('PROJECT_ROOT', os.getcwd())
329
+ service = IdeasService(project_root)
330
+
331
+ if not request.is_json:
332
+ return jsonify({'success': False, 'error': 'JSON required'}), 400
333
+
334
+ data = request.get_json()
335
+ source_path = data.get('source_path')
336
+ target_folder = data.get('target_folder')
337
+
338
+ if source_path is None:
339
+ return jsonify({'success': False, 'error': 'source_path is required'}), 400
340
+
341
+ if target_folder is None:
342
+ return jsonify({'success': False, 'error': 'target_folder is required'}), 400
343
+
344
+ result = service.move_item(source_path, target_folder)
345
+
346
+ if result['success']:
347
+ return jsonify(result)
348
+ return jsonify(result), 400
349
+
350
+
351
+ @ideas_bp.route('/api/ideas/duplicate', methods=['POST'])
352
+ def duplicate_idea_item():
353
+ """
354
+ POST /api/ideas/duplicate
355
+
356
+ Duplicate a file or folder with -copy suffix.
357
+
358
+ Request body:
359
+ - path: string - Path of item to duplicate
360
+
361
+ Response:
362
+ - success: true/false
363
+ - new_path: string
364
+ """
365
+ project_root = current_app.config.get('PROJECT_ROOT', os.getcwd())
366
+ service = IdeasService(project_root)
367
+
368
+ if not request.is_json:
369
+ return jsonify({'success': False, 'error': 'JSON required'}), 400
370
+
371
+ data = request.get_json()
372
+ path = data.get('path')
373
+
374
+ if not path:
375
+ return jsonify({'success': False, 'error': 'path is required'}), 400
376
+
377
+ result = service.duplicate_item(path)
378
+
379
+ if result['success']:
380
+ return jsonify(result)
381
+ return jsonify(result), 400
382
+
383
+
384
+ @ideas_bp.route('/api/ideas/download', methods=['GET'])
385
+ def download_idea_file():
386
+ """
387
+ GET /api/ideas/download?path=...
388
+
389
+ Download a file from ideas folder.
390
+
391
+ Query params:
392
+ - path: string - Path of file to download
393
+
394
+ Response:
395
+ File download with appropriate Content-Type
396
+ """
397
+ from flask import send_file
398
+ import io
399
+
400
+ project_root = current_app.config.get('PROJECT_ROOT', os.getcwd())
401
+ service = IdeasService(project_root)
402
+
403
+ path = request.args.get('path')
404
+
405
+ if not path:
406
+ return jsonify({'success': False, 'error': 'path is required'}), 400
407
+
408
+ result = service.get_download_info(path)
409
+
410
+ if not result['success']:
411
+ # Return 404 for file not found
412
+ status_code = 404 if 'not found' in result['error'].lower() else 400
413
+ return jsonify(result), status_code
414
+
415
+ # Handle both string and bytes content
416
+ content = result['content']
417
+ if isinstance(content, str):
418
+ content = content.encode('utf-8')
419
+
420
+ return send_file(
421
+ io.BytesIO(content),
422
+ mimetype=result['mime_type'],
423
+ as_attachment=True,
424
+ download_name=result['filename']
425
+ )
426
+
427
+
428
+ @ideas_bp.route('/api/ideas/folder-contents', methods=['GET'])
429
+ def get_folder_contents():
430
+ """
431
+ GET /api/ideas/folder-contents?path=...
432
+
433
+ Get contents of a specific folder.
434
+
435
+ Query params:
436
+ - path: string - Folder path (optional, defaults to ideas root)
437
+
438
+ Response:
439
+ - success: true/false
440
+ - items: array of file/folder objects
441
+ """
442
+ project_root = current_app.config.get('PROJECT_ROOT', os.getcwd())
443
+ service = IdeasService(project_root)
444
+
445
+ path = request.args.get('path', '')
446
+
447
+ result = service.get_folder_contents(path)
448
+
449
+ if result['success']:
450
+ return jsonify(result)
451
+ return jsonify(result), 400
452
+
453
+
454
+ @ideas_bp.route('/api/ideas/search', methods=['GET'])
455
+ def search_ideas():
456
+ """
457
+ GET /api/ideas/search?q=...
458
+
459
+ Search/filter ideas tree by query.
460
+
461
+ Query params:
462
+ - q: string - Search query
463
+
464
+ Response:
465
+ - success: true/false
466
+ - tree: filtered tree structure
467
+ """
468
+ project_root = current_app.config.get('PROJECT_ROOT', os.getcwd())
469
+ service = IdeasService(project_root)
470
+
471
+ query = request.args.get('q', '')
472
+
473
+ try:
474
+ tree = service.filter_tree(query)
475
+ return jsonify({'success': True, 'tree': tree})
476
+ except Exception as e:
477
+ return jsonify({'success': False, 'error': str(e)}), 500
478
+
479
+
480
+ @ideas_bp.route('/api/ideas/delete-info', methods=['GET'])
481
+ def get_delete_info():
482
+ """
483
+ GET /api/ideas/delete-info?path=...
484
+
485
+ Get item info for delete confirmation dialog.
486
+
487
+ Query params:
488
+ - path: string - Path of item to delete
489
+
490
+ Response:
491
+ - success: true/false
492
+ - name: string
493
+ - type: 'file' | 'folder'
494
+ - item_count: number (for folders)
495
+ """
496
+ project_root = current_app.config.get('PROJECT_ROOT', os.getcwd())
497
+ service = IdeasService(project_root)
498
+
499
+ path = request.args.get('path')
500
+
501
+ if not path:
502
+ return jsonify({'success': False, 'error': 'path is required'}), 400
503
+
504
+ result = service.get_delete_info(path)
505
+
506
+ if result['success']:
507
+ return jsonify(result)
508
+ return jsonify(result), 400
509
+
510
+
511
+ @ideas_bp.route('/api/ideas/validate-drop', methods=['POST'])
512
+ def validate_drop_target():
513
+ """
514
+ POST /api/ideas/validate-drop
515
+
516
+ Validate if drop target is valid for drag source.
517
+
518
+ Request body:
519
+ - source_path: string - Path being dragged
520
+ - target_folder: string - Drop target folder
521
+
522
+ Response:
523
+ - valid: true/false
524
+ """
525
+ project_root = current_app.config.get('PROJECT_ROOT', os.getcwd())
526
+ service = IdeasService(project_root)
527
+
528
+ if not request.is_json:
529
+ return jsonify({'valid': False}), 400
530
+
531
+ data = request.get_json()
532
+ source_path = data.get('source_path')
533
+ target_folder = data.get('target_folder', '')
534
+
535
+ valid = service.is_valid_drop_target(source_path, target_folder)
536
+ return jsonify({'valid': valid})
537
+
538
+
267
539
  # ==========================================================================
268
540
  # SKILLS API
269
541
  #
@@ -46,15 +46,21 @@ def proxy_url():
46
46
  result = service.fetch_and_rewrite(url)
47
47
 
48
48
  if result.success:
49
+ # TASK-235: Handle binary content (fonts, images, etc.)
50
+ if result.binary_content is not None:
51
+ return Response(
52
+ result.binary_content,
53
+ mimetype=result.content_type.split(';')[0].strip()
54
+ )
49
55
  # For HTML, return JSON (used by frontend to set iframe.srcdoc)
50
- if 'text/html' in result.content_type:
56
+ elif 'text/html' in result.content_type:
51
57
  return jsonify({
52
58
  'success': True,
53
59
  'html': result.html,
54
60
  'content_type': result.content_type
55
61
  })
56
62
  else:
57
- # For non-HTML (JS, CSS, images, etc.), return raw content
63
+ # For non-HTML text (JS, CSS, etc.), return raw content
58
64
  return Response(
59
65
  result.html, # Contains raw content for non-HTML
60
66
  mimetype=result.content_type.split(';')[0].strip()
@@ -10,6 +10,26 @@ from ..services.uiux_feedback_service import UiuxFeedbackService
10
10
  uiux_feedback_bp = Blueprint('uiux_feedback', __name__)
11
11
 
12
12
 
13
+ @uiux_feedback_bp.route('/api/uiux-feedback', methods=['GET'])
14
+ def list_feedback():
15
+ """
16
+ List recent feedback entries.
17
+
18
+ Query params:
19
+ days: Number of days to look back (default 2)
20
+
21
+ Returns:
22
+ 200: {"entries": [...]}
23
+ """
24
+ project_root = current_app.config.get('PROJECT_ROOT', '.')
25
+ days = request.args.get('days', 2, type=int)
26
+
27
+ service = UiuxFeedbackService(project_root)
28
+ entries = service.list_feedback(days=days)
29
+
30
+ return jsonify({'entries': entries}), 200
31
+
32
+
13
33
  @uiux_feedback_bp.route('/api/uiux-feedback', methods=['POST'])
14
34
  def submit_feedback():
15
35
  """