creek

The AI Image Editor of 2030
commit 918ec834ea27304772a8eb032dd0efa647291115
parent ad616574131050e2b2cb297f2d63c8307ed293d8
Author: ajcoder13 <avnijhalani@gmail.com>
Date:   Wed,  3 Dec 2025 01:41:23 +0530

Caption from AI coming only when Magic Draw actiavted, prompt not necessary for model to work

Diffstat:
Mlib/ui/pages/canvas_board_page.dart | 191++++++++++++++++++-------------------------------------------------------------
Mlib/ui/pages/canvas_toolbar/magic_draw_overlay.dart | 96+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
2 files changed, 103 insertions(+), 184 deletions(-)

diff --git a/lib/ui/pages/canvas_board_page.dart b/lib/ui/pages/canvas_board_page.dart @@ -1165,7 +1165,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { // ... (Rest of your UI: AI Description, MagicDrawTools, etc.) ... // I have truncated the bottom part as it remains unchanged. - if (_aiDescription != null && !_isMagicDrawActive) + if (_aiDescription != null && _isMagicDrawActive) Positioned( top: 10, left: 16, @@ -1254,6 +1254,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { isMagicPanelDisabled: _isMagicPanelDisabled, onViewModeToggle: (enabled) => setState(() => _isViewMode = enabled), + hasImageLayers: elements.any( + (e) => e['type'] == 'file_image', + ), ), TextToolsOverlay( @@ -1440,18 +1443,12 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { }); } - Future<void> _processInpainting(String prompt, String serviceId) async { - if (prompt.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text("Please enter a prompt!"))); - return; - } - + Future<void> _processInpainting(String prompt, String modelId) async { setState(() => _isInpainting = true); _resetInactivityTimer(); try { + // Check if there are image layers (which dictates the context) bool hasImageLayers = elements.any((e) => e['type'] == 'file_image'); if (_tempBaseImage == null) { @@ -1464,34 +1461,42 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { _canvasSize, _tempBaseImage, ); + if (maskFile == null) throw Exception("Failed to generate mask"); String? newImageUrl; - // --- SWITCH SERVICE --- - if (serviceId == 'api') { - // Call the dedicated API endpoint - newImageUrl = await FlaskService().inpaintApiImage( - imagePath: _tempBaseImage!.path, - maskPath: maskFile.path, - prompt: prompt, - ); - } else { - // Default 'flask' service logic - if (hasImageLayers) { - newImageUrl = await FlaskService().inpaintImage( + // --- LOGIC SWITCHING BASED ON MODEL ID --- + + // CASE 1: INPAINTING (When hasImageLayers is TRUE) + if (hasImageLayers) { + if (modelId == 'inpaint_api') { + // Call API Inpainting + newImageUrl = await FlaskService().inpaintApiImage( imagePath: _tempBaseImage!.path, maskPath: maskFile.path, prompt: prompt, ); } else { - newImageUrl = await FlaskService().sketchToImage( - sketchPath: _tempBaseImage!.path, - userPrompt: prompt, - stylePrompt: "high quality, realistic", + // Default: Standard Flask Inpainting ('inpaint_standard') + newImageUrl = await FlaskService().inpaintImage( + imagePath: _tempBaseImage!.path, + maskPath: maskFile.path, + prompt: prompt, ); } } + // CASE 2: SKETCH TO IMAGE (When hasImageLayers is FALSE) + else { + // Currently all sketch IDs map to the main sketch endpoint, + // but you can pass the ID if your backend supports different sketch models. + newImageUrl = await FlaskService().sketchToImage( + sketchPath: _tempBaseImage!.path, + userPrompt: prompt, + stylePrompt: + "high quality, realistic", // You could vary this based on modelId + ); + } if (newImageUrl != null) { _addGeneratedImage(newImageUrl); @@ -1816,7 +1821,7 @@ class _ManipulatingBoxState extends State<_ManipulatingBox> { late Offset _pos; late Size _size; late double _rot; - + // Gesture state double _initialRotation = 0.0; double _initialScale = 1.0; @@ -1853,8 +1858,6 @@ class _ManipulatingBoxState extends State<_ManipulatingBox> { final double zoom = matrix.getMaxScaleOnAxis(); final double handleScale = (1 / zoom).clamp(0.2, 5.0); final double edgeThickness = 18 * handleScale; - final double buttonSize = 32 * handleScale; - final double iconSize = 14 * handleScale; return Positioned( left: _pos.dx, @@ -1889,21 +1892,25 @@ class _ManipulatingBoxState extends State<_ManipulatingBox> { if (_isTwoFingerGesture && details.pointerCount == 2) { // Two-finger: handle rotation and zoom final newRotation = _initialRotation + details.rotation; - + // Handle scale (zoom) - maintain aspect ratio final scaleFactor = details.scale; - final newArea = _initialScale * scaleFactor * scaleFactor; + final newArea = + _initialScale * scaleFactor * scaleFactor; final aspectRatio = _size.width / _size.height; - final newWidth = math.sqrt(newArea * aspectRatio).clamp(20.0, 5000.0); + final newWidth = math + .sqrt(newArea * aspectRatio) + .clamp(20.0, 5000.0); final newHeight = newWidth / aspectRatio; - + setState(() { _rot = newRotation % (2 * math.pi); _size = Size(newWidth, newHeight); }); - + widget.onUpdate(_pos, _size, _rot); - } else if (!_isTwoFingerGesture && details.pointerCount == 1) { + } else if (!_isTwoFingerGesture && + details.pointerCount == 1) { // Single-finger: handle drag using incremental focal point delta final currentFocalPoint = details.focalPoint; final delta = currentFocalPoint - _previousFocalPoint; @@ -1914,7 +1921,8 @@ class _ManipulatingBoxState extends State<_ManipulatingBox> { final rotated = _rotateVector(scaledDelta, -_rot); setState(() { _pos += rotated; - _previousFocalPoint = currentFocalPoint; // Update for next frame + _previousFocalPoint = + currentFocalPoint; // Update for next frame }); widget.onUpdate(_pos, _size, _rot); } @@ -2045,92 +2053,6 @@ class _ManipulatingBoxState extends State<_ManipulatingBox> { ), ), ), - - // ====================== - // CORNER RESIZE HANDLES - // ====================== - if (widget.isSelected && !widget.isEditing) ...[ - // TOP-LEFT corner - Positioned( - top: -10 * handleScale, - left: -10 * handleScale, - child: _cornerHandle( - size: 20 * handleScale, - onDrag: (d) { - final local = _rotateVector(d.delta, -_rot); - setState(() { - _pos += Offset(local.dx, local.dy); - _size = Size( - (_size.width - local.dx).clamp(20, 5000), - (_size.height - local.dy).clamp(20, 5000), - ); - }); - widget.onUpdate(_pos, _size, _rot); - }, - ), - ), - - // TOP-RIGHT corner - Positioned( - top: -10 * handleScale, - right: -10 * handleScale, - child: _cornerHandle( - size: 20 * handleScale, - onDrag: (d) { - final local = _rotateVector(d.delta, -_rot); - setState(() { - _pos += Offset(0, local.dy); - _size = Size( - (_size.width + local.dx).clamp(20, 5000), - (_size.height - local.dy).clamp(20, 5000), - ); - }); - widget.onUpdate(_pos, _size, _rot); - }, - ), - ), - - // BOTTOM-LEFT corner - Positioned( - bottom: -10 * handleScale, - left: -10 * handleScale, - child: _cornerHandle( - size: 20 * handleScale, - onDrag: (d) { - final local = _rotateVector(d.delta, -_rot); - setState(() { - _pos += Offset(local.dx, 0); - _size = Size( - (_size.width - local.dx).clamp(20, 5000), - (_size.height + local.dy).clamp(20, 5000), - ); - }); - widget.onUpdate(_pos, _size, _rot); - }, - ), - ), - - // BOTTOM-RIGHT corner - Positioned( - bottom: -10 * handleScale, - right: -10 * handleScale, - child: _cornerHandle( - size: 20 * handleScale, - onDrag: (d) { - final local = _rotateVector(d.delta, -_rot); - setState(() { - _size = Size( - (_size.width + local.dx).clamp(20, 5000), - (_size.height + local.dy).clamp(20, 5000), - ); - }); - widget.onUpdate(_pos, _size, _rot); - }, - ), - ), - ], - - ], ), ), @@ -2139,33 +2061,6 @@ class _ManipulatingBoxState extends State<_ManipulatingBox> { ); } - Widget _cornerHandle({ - required double size, - required Function(DragUpdateDetails) onDrag, - }) { - return GestureDetector( - behavior: HitTestBehavior.translucent, - onPanStart: (_) => widget.onDragStart(), - onPanUpdate: onDrag, - onPanEnd: (_) => widget.onDragEnd(_pos, _size, _rot), - child: Container( - width: size, // large invisible touch area - height: size, - alignment: Alignment.center, - color: Colors.transparent, - child: Container( - width: 6 * (1 / widget.viewScale), // <<< tiny visual square - height: 6 * (1 / widget.viewScale), // <<< tiny visual square - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.white, width: 1), - shape: BoxShape.rectangle, - ), - ), - ), - ); - } - Widget _buildCircleButton({ required double size, required IconData icon, diff --git a/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart b/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart @@ -4,9 +4,9 @@ import 'package:flutter_svg/flutter_svg.dart'; // --- HELPER CLASS FOR DROPDOWN --- class AIModelOption { - final String id; // 'flask' or 'api' - final String name; // Display Name - final String? badge; // Optional badge + final String id; + final String name; + final String? badge; AIModelOption({required this.id, required this.name, this.badge}); } @@ -20,16 +20,16 @@ class MagicDrawTools extends StatefulWidget { final Function(double) onWidthChanged; final Function(bool) onEraserToggle; final VoidCallback onClose; - - // CHANGED: Accepts Prompt AND Model ID final Function(String prompt, String modelId) onPromptSubmit; - final bool isProcessing; final List<Color> brandColors; final Function(bool) onViewModeToggle; final Function(bool) onMagicPanelActivityToggle; final bool isMagicPanelDisabled; + // NEW: To decide which dropdown list to show + final bool hasImageLayers; + const MagicDrawTools({ super.key, required this.isActive, @@ -46,6 +46,7 @@ class MagicDrawTools extends StatefulWidget { required this.onViewModeToggle, required this.onMagicPanelActivityToggle, required this.isMagicPanelDisabled, + required this.hasImageLayers, // <-- Added }); @override @@ -65,21 +66,55 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { ]; bool _isViewMode = false; - - // --- DROPDOWN STATE --- bool _showModelMenu = false; late AIModelOption _selectedModel; - // --- DEFINE YOUR SERVICES HERE --- - final List<AIModelOption> _aiModels = [ - AIModelOption(id: 'flask', name: 'Inpainting', badge: null), - AIModelOption(id: 'api', name: 'Inpainting API', badge: null), + // --- 1. LIST FOR INPAINTING (When Image Exists) --- + final List<AIModelOption> _inpaintingModels = [ + AIModelOption( + id: 'inpaint_standard', + name: 'Standard Inpainting', + badge: null, + ), + AIModelOption(id: 'inpaint_api', name: 'Inpainting API', badge: 'Pro'), + ]; + + // --- 2. LIST FOR SKETCH TO IMAGE (When No Image Exists) --- + final List<AIModelOption> _sketchModels = [ + AIModelOption(id: 'sketch_fusion', name: 'Slider Fusion', badge: null), + AIModelOption( + id: 'sketch_advanced', + name: 'Advanced Fusion', + badge: 'Fastest', + ), + AIModelOption(id: 'sketch_creative', name: 'Creative Flow', badge: 'New'), ]; @override void initState() { super.initState(); - _selectedModel = _aiModels.first; // Default to first option + _updateSelectedModelGroup(); + } + + @override + void didUpdateWidget(MagicDrawTools oldWidget) { + super.didUpdateWidget(oldWidget); + // If the layer state changes (image added/removed), reset selection to correct group + if (oldWidget.hasImageLayers != widget.hasImageLayers) { + _updateSelectedModelGroup(); + } + } + + void _updateSelectedModelGroup() { + if (widget.hasImageLayers) { + _selectedModel = _inpaintingModels.first; + } else { + _selectedModel = _sketchModels.first; + } + } + + List<AIModelOption> get _currentModelList { + return widget.hasImageLayers ? _inpaintingModels : _sketchModels; } @override @@ -92,10 +127,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { } void _handleSubmit() { - if (_promptController.text.trim().isNotEmpty && - !widget.isProcessing && - !widget.isMagicPanelDisabled) { - // PASS SELECTED MODEL ID + if (!widget.isProcessing && !widget.isMagicPanelDisabled) { widget.onPromptSubmit(_promptController.text.trim(), _selectedModel.id); } } @@ -105,16 +137,14 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { setState(() { _isViewMode = newViewMode; _showStrokeSlider = false; - _showModelMenu = false; // Close menu if view mode toggled + _showModelMenu = false; }); widget.onViewModeToggle(newViewMode); } - // NEW: Toggle Dropdown Visibility void _toggleModelMenu() { setState(() { - if (!_showModelMenu) - _showStrokeSlider = false; // Close slider if menu opens + if (!_showModelMenu) _showStrokeSlider = false; _showModelMenu = !_showModelMenu; }); } @@ -122,7 +152,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { void _handleToolTap(VoidCallback toolAction) { setState(() { _showStrokeSlider = false; - _showModelMenu = false; // Close menu on tool tap + _showModelMenu = false; if (_isViewMode) { _isViewMode = false; widget.onViewModeToggle(false); @@ -148,7 +178,6 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { if (_showStrokeSlider) _buildTaperedStrokeSlider(), const SizedBox(height: 8), - // WRAPPER CONTAINER FOR MENU + PANEL Container( decoration: BoxDecoration( color: Colors.white, @@ -164,9 +193,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { child: Column( mainAxisSize: MainAxisSize.min, children: [ - // RENDER DROPDOWN ON TOP if (_showModelMenu) _buildModelDropdown(), - _buildMagicDrawPanel(), ], ), @@ -204,20 +231,19 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { ); } - // --- NEW: DROPDOWN MENU UI --- Widget _buildModelDropdown() { return Container( padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Local', - style: TextStyle(color: Colors.grey, fontSize: 12), + Text( + widget.hasImageLayers ? 'Inpainting Models' : 'Sketch Models', + style: const TextStyle(color: Colors.grey, fontSize: 12), ), const SizedBox(height: 4), - ..._aiModels.map((model) { + ..._currentModelList.map((model) { final isSelected = model.id == _selectedModel.id; return GestureDetector( onTap: () { @@ -283,7 +309,6 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { padding: const EdgeInsets.all(8.0), child: Row( children: [ - // --- TRIGGER ICON FOR DROPDOWN --- GestureDetector( onTap: _toggleModelMenu, child: Container( @@ -294,7 +319,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { borderRadius: BorderRadius.circular(8), ), child: const Icon( - Icons.star_half, // Using star icon as requested + Icons.star_half, color: Color(0xFFD8705D), size: 20, ), @@ -350,7 +375,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { children: [ _buildToolIcon(Icons.pan_tool, widget.isMagicPanelDisabled, () { setState(() => _showStrokeSlider = false); - _showModelMenu = false; // Close menu + _showModelMenu = false; if (!widget.isMagicPanelDisabled) { widget.onEraserToggle(false); @@ -366,7 +391,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { () { setState(() { _showStrokeSlider = false; - _showModelMenu = false; // Close menu + _showModelMenu = false; }); if (_isViewMode) { @@ -421,7 +446,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { setState(() { _showStrokeSlider = !_showStrokeSlider; - _showModelMenu = false; // Close menu + _showModelMenu = false; }); }, child: Container( @@ -1002,7 +1027,6 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> ], ), ), - // EXCLUDED: Gradient feature as requested. ], ), ),