77from PyQt5 .QtWidgets import QComboBox , QFileDialog , QFrame , QGridLayout , QHBoxLayout , QMenu
88from PyQt5 .QtWidgets import QLabel , QLineEdit , QListWidgetItem , QMessageBox , QSpinBox , QAction
99from PyQt5 .QtWidgets import QToolButton , QVBoxLayout , QWidget , QSlider , QDoubleSpinBox
10+ from PyQt5 .QtWidgets import QScrollArea , QTextEdit , QSizePolicy
1011
1112from ..custom_workflow import CustomParam , ParamKind , SortedWorkflows , WorkflowSource
1213from ..custom_workflow import CustomGenerationMode
14+ from ..client import TextOutput
1315from ..jobs import JobKind
1416from ..model import Model , ApplyBehavior
1517from ..properties import Binding , Bind , bind , bind_combo
2224from .live import LivePreviewArea
2325from .switch import SwitchWidget
2426from .widget import TextPromptWidget , WorkspaceSelectWidget , StyleSelectWidget
27+ from .settings_widgets import ExpanderButton
2528from . import theme
2629
2730
@@ -384,6 +387,93 @@ def value(self, values: dict[str, Any]):
384387 widget .value = value
385388
386389
390+ class WorkflowOutputsWidget (QWidget ):
391+ def __init__ (self , parent : QWidget ):
392+ super ().__init__ (parent )
393+ self ._value : dict [str , TextOutput ] = {}
394+
395+ self ._scroll_area = QScrollArea (self )
396+ self ._scroll_area .setFrameShape (QFrame .Shape .NoFrame )
397+ self ._scroll_area .setHorizontalScrollBarPolicy (Qt .ScrollBarPolicy .ScrollBarAlwaysOff )
398+
399+ self .expander = ExpanderButton (_ ("Text Output" ), self )
400+ self .expander .setStyleSheet ("QToolButton { border: none; }" )
401+ self .expander .setChecked (True )
402+ self .expander .toggled .connect (self ._scroll_area .setVisible )
403+
404+ layout = QVBoxLayout (self )
405+ layout .setContentsMargins (0 , 0 , 0 , 0 )
406+ layout .addWidget (self .expander )
407+ layout .addWidget (self ._scroll_area )
408+
409+ @property
410+ def value (self ):
411+ return self ._value
412+
413+ @value .setter
414+ def value (self , value : dict [str , TextOutput ]):
415+ self ._value = value
416+ self ._update ()
417+
418+ def _update (self ):
419+ if len (self ._value ) == 0 :
420+ self .expander .hide ()
421+ self ._scroll_area .hide ()
422+ return
423+ elif not self .expander .isVisible ():
424+ self .expander .show ()
425+ self ._scroll_area .show ()
426+
427+ widget = QWidget (self ._scroll_area )
428+ layout = QGridLayout ()
429+ layout .setContentsMargins (0 , 0 , 0 , 0 )
430+ layout .setColumnMinimumWidth (1 , 8 )
431+ layout .setColumnStretch (2 , 1 )
432+ widget .setLayout (layout )
433+
434+ line = 0
435+ text_areas : list [QTextEdit ] = []
436+ for output in self ._value .values ():
437+ label = QLabel (output .name , widget )
438+ if (not output .mime or output .mime == "text/plain" ) and len (output .text ) < 40 :
439+ value = QLabel (output .text , widget )
440+ value .setWordWrap (True )
441+ value .setMinimumWidth (40 )
442+ layout .addWidget (label , line , 0 )
443+ layout .addWidget (value , line , 2 )
444+ line += 1
445+ else :
446+ value = QTextEdit (widget )
447+ value .setFrameShape (QFrame .Shape .StyledPanel )
448+ value .setStyleSheet (
449+ "QTextEdit { background: transparent; border-left: 1px solid %s; padding-left: 2px; }"
450+ % theme .line
451+ )
452+ value .setReadOnly (True )
453+ match output .mime :
454+ case "" | "text/plain" :
455+ value .setPlainText (output .text )
456+ case "text/html" :
457+ value .setHtml (output .text )
458+ case "text/markdown" :
459+ value .setMarkdown (output .text )
460+ layout .addWidget (label , line , 0 , 1 , 3 )
461+ layout .addWidget (value , line + 1 , 0 , 1 , 3 )
462+ text_areas .append (value )
463+ line += 2
464+
465+ layout .setRowStretch (line , 1 )
466+ widget .setFixedWidth (self ._scroll_area .width () - 8 )
467+ self ._scroll_area .setWidget (widget )
468+ if self .expander .isChecked ():
469+ widget .show ()
470+
471+ for w in text_areas :
472+ size = ensure (w .document ()).size ().toSize ()
473+ w .setFixedHeight (max (size .height () + 2 , self .fontMetrics ().height () + 6 ))
474+ widget .adjustSize ()
475+
476+
387477def popup_on_error (func ):
388478 @wraps (func )
389479 def wrapper (self , * args , ** kwargs ):
@@ -487,6 +577,9 @@ def __init__(self):
487577 self ._progress_bar = ProgressBar (self )
488578 self ._error_text = create_error_label (self )
489579
580+ self ._outputs = WorkflowOutputsWidget (self )
581+ self ._outputs .expander .toggled .connect (self ._update_layout )
582+
490583 self ._history = HistoryWidget (self )
491584 self ._history .item_activated .connect (self .apply_result )
492585
@@ -525,12 +618,17 @@ def __init__(self):
525618 self ._layout .addLayout (actions_layout )
526619 self ._layout .addWidget (self ._progress_bar )
527620 self ._layout .addWidget (self ._error_text )
528- self ._layout .addWidget (self ._history )
529- self ._layout .addWidget (self ._live_preview )
621+ self ._layout .addWidget (self ._outputs , stretch = 1 )
622+ self ._layout .addWidget (self ._history , stretch = 3 )
623+ self ._layout .addWidget (self ._live_preview , stretch = 5 )
530624 self .setLayout (self ._layout )
531625
532626 self ._update_ui ()
533627
628+ def _update_layout (self ):
629+ stretch = 1 if self ._outputs .expander .isChecked () else 0
630+ self ._layout .setStretchFactor (self ._outputs , stretch )
631+
534632 @property
535633 def model (self ):
536634 return self ._model
@@ -543,6 +641,7 @@ def model(self, model: Model):
543641 self ._model_bindings = [
544642 bind (model , "workspace" , self ._workspace_select , "value" , Bind .one_way ),
545643 bind_combo (model .custom , "workflow_id" , self ._workflow_select , Bind .one_way ),
644+ bind (model .custom , "outputs" , self ._outputs , "value" , Bind .one_way ),
546645 model .workspace_changed .connect (self ._cancel_name ),
547646 model .custom .graph_changed .connect (self ._update_current_workflow ),
548647 model .error_changed .connect (self ._error_text .setText ),
0 commit comments