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 +9 -0
- x_ipe/routes/ideas_routes.py +272 -0
- x_ipe/routes/proxy_routes.py +8 -2
- x_ipe/routes/uiux_feedback_routes.py +20 -0
- x_ipe/services/ideas_service.py +516 -2
- x_ipe/services/proxy_service.py +33 -7
- x_ipe/services/uiux_feedback_service.py +116 -1
- x_ipe/static/css/terminal.css +22 -0
- x_ipe/static/css/uiux-feedback.css +7 -1
- x_ipe/static/css/workplace.css +616 -0
- x_ipe/static/js/features/confirm-dialog.js +169 -0
- x_ipe/static/js/features/folder-view.js +742 -0
- x_ipe/static/js/features/tree-drag.js +227 -0
- x_ipe/static/js/features/tree-search.js +224 -0
- x_ipe/static/js/features/workplace.js +473 -28
- x_ipe/static/js/terminal-v2.js +45 -14
- x_ipe/static/js/terminal.js +50 -49
- x_ipe/static/js/uiux-feedback.js +57 -14
- x_ipe/templates/base.html +5 -0
- x_ipe/templates/index.html +3 -0
- {x_ipe-1.0.22.dist-info → x_ipe-1.0.24.dist-info}/METADATA +1 -1
- {x_ipe-1.0.22.dist-info → x_ipe-1.0.24.dist-info}/RECORD +25 -22
- x_ipe/app.py.bak +0 -1333
- {x_ipe-1.0.22.dist-info → x_ipe-1.0.24.dist-info}/WHEEL +0 -0
- {x_ipe-1.0.22.dist-info → x_ipe-1.0.24.dist-info}/entry_points.txt +0 -0
- {x_ipe-1.0.22.dist-info → x_ipe-1.0.24.dist-info}/licenses/LICENSE +0 -0
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):
|
x_ipe/routes/ideas_routes.py
CHANGED
|
@@ -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
|
#
|
x_ipe/routes/proxy_routes.py
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
"""
|