Compare commits
2 Commits
63058ef4a9
...
e90e7b01dd
| Author | SHA1 | Date | |
|---|---|---|---|
| e90e7b01dd | |||
| fe5668fbed |
@@ -62,7 +62,7 @@ class JsonViewerHelper:
|
|||||||
class JsonViewer(BaseComponent):
|
class JsonViewer(BaseComponent):
|
||||||
def __init__(self, session, _id, owner, user_id, data, hooks=None, key=None, boundaries=None):
|
def __init__(self, session, _id, owner, user_id, data, hooks=None, key=None, boundaries=None):
|
||||||
super().__init__(session, _id)
|
super().__init__(session, _id)
|
||||||
self._key = key
|
self._key = key # for comparison between two jsonviewer components
|
||||||
self._owner = owner # debugger component
|
self._owner = owner # debugger component
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
self.data = data
|
self.data = data
|
||||||
@@ -88,6 +88,10 @@ class JsonViewer(BaseComponent):
|
|||||||
|
|
||||||
self._helper = JsonViewerHelper()
|
self._helper = JsonViewerHelper()
|
||||||
|
|
||||||
|
def set_data(self, data):
|
||||||
|
self.data = data
|
||||||
|
self.node = self._create_node(None, data)
|
||||||
|
|
||||||
def set_node_folding(self, node_id, folding):
|
def set_node_folding(self, node_id, folding):
|
||||||
if folding == self._folding_mode:
|
if folding == self._folding_mode:
|
||||||
self._nodes_to_track.remove(node_id)
|
self._nodes_to_track.remove(node_id)
|
||||||
|
|||||||
@@ -14,4 +14,13 @@ repositories_app, rt = fast_app()
|
|||||||
def get(session, _id: str, entry: str):
|
def get(session, _id: str, entry: str):
|
||||||
logger.debug(f"Entering {Routes.Select} with args {debug_session(session)}, {_id=}, {entry=}")
|
logger.debug(f"Entering {Routes.Select} with args {debug_session(session)}, {_id=}, {entry=}")
|
||||||
instance = InstanceManager.get(session, _id)
|
instance = InstanceManager.get(session, _id)
|
||||||
return instance.select_entry(entry)
|
to_update = instance.select_entry(entry)
|
||||||
|
|
||||||
|
res = [instance]
|
||||||
|
if res is None:
|
||||||
|
return instance
|
||||||
|
if isinstance(to_update, (list, tuple)):
|
||||||
|
res.extend(to_update)
|
||||||
|
else:
|
||||||
|
res.append(to_update)
|
||||||
|
return tuple(res)
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
display: inline-block; /* Ensure entries align horizontally if needed */
|
display: inline-block; /* Ensure entries align horizontally if needed */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.es-entry-selected {
|
||||||
|
border: 2px solid var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.es-entry:hover {
|
.es-entry:hover {
|
||||||
background-color: var(--color-base-300);
|
background-color: var(--color-base-300);
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ class EntrySelectorCommandManager(BaseCommandManager):
|
|||||||
def select_entry(self, entry):
|
def select_entry(self, entry):
|
||||||
return {
|
return {
|
||||||
"hx-get": f"{ROUTE_ROOT}{Routes.Select}",
|
"hx-get": f"{ROUTE_ROOT}{Routes.Select}",
|
||||||
"hx-target": f"#{self._owner.content_id}",
|
"hx-target": f"#{self._id}",
|
||||||
"hx-swap": "innerHTML",
|
"hx-swap": "outerHTML",
|
||||||
"hx-vals": f'{{"_id": "{self._id}", "entry": "{entry}"}}',
|
"hx-vals": f'{{"_id": "{self._id}", "entry": "{entry}"}}',
|
||||||
}
|
}
|
||||||
@@ -9,12 +9,12 @@ logger = logging.getLogger("EntrySelector")
|
|||||||
|
|
||||||
|
|
||||||
class EntrySelector(BaseComponentMultipleInstance):
|
class EntrySelector(BaseComponentMultipleInstance):
|
||||||
def __init__(self, session, _id, owner, content_id, data=None, hooks=None, key=None, boundaries=None):
|
def __init__(self, session, _id, owner, data=None, hooks=None, key=None, boundaries=None):
|
||||||
super().__init__(session, _id)
|
super().__init__(session, _id)
|
||||||
self._key = key
|
self._key = key
|
||||||
self._owner = owner # debugger component
|
self._owner = owner # debugger component
|
||||||
self.data = data
|
self.data = data
|
||||||
self.content_id = content_id
|
self.selected = None
|
||||||
self.hooks = hooks
|
self.hooks = hooks
|
||||||
self._boundaries = boundaries if boundaries else {"width": "300"}
|
self._boundaries = boundaries if boundaries else {"width": "300"}
|
||||||
self._commands = EntrySelectorCommandManager(self)
|
self._commands = EntrySelectorCommandManager(self)
|
||||||
@@ -22,20 +22,31 @@ class EntrySelector(BaseComponentMultipleInstance):
|
|||||||
def set_data(self, data):
|
def set_data(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
|
def set_selected(self, selected):
|
||||||
|
if selected is None:
|
||||||
|
self.selected = None
|
||||||
|
else:
|
||||||
|
self.selected = int(selected)
|
||||||
|
|
||||||
def set_boundaries(self, boundaries):
|
def set_boundaries(self, boundaries):
|
||||||
self._boundaries = boundaries
|
self._boundaries = boundaries
|
||||||
|
|
||||||
def select_entry(self, entry):
|
def select_entry(self, entry):
|
||||||
logger.debug(f"Selecting entry {entry}")
|
logger.debug(f"Selecting entry {entry}")
|
||||||
# return self._owner.select_entry(entry)
|
self.set_selected(entry)
|
||||||
|
if self.hooks is not None and (on_entry_selected := self.hooks.get("on_entry_selected", None)) is not None:
|
||||||
|
return on_entry_selected(entry)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def _mk_content(self):
|
def _mk_content(self):
|
||||||
if self.data is None:
|
if not self.data:
|
||||||
return [Div("no entry")]
|
return [Div("no entry")]
|
||||||
|
|
||||||
return [Div(index,
|
return [Div(index,
|
||||||
**self._commands.select_entry(index),
|
**self._commands.select_entry(index),
|
||||||
cls="es-entry") for index in range(self.data)]
|
cls=f"es-entry {'es-entry-selected' if index == self.selected else ''}")
|
||||||
|
for index in range(self.data)]
|
||||||
|
|
||||||
def __ft__(self):
|
def __ft__(self):
|
||||||
return Div(
|
return Div(
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class WorkflowDesigner(BaseComponent):
|
|||||||
self.properties = WorkflowDesignerProperties(self._session, f"{self._id}", self)
|
self.properties = WorkflowDesignerProperties(self._session, f"{self._id}", self)
|
||||||
|
|
||||||
workflow_name = self._designer_settings.workflow_name
|
workflow_name = self._designer_settings.workflow_name
|
||||||
self._player = InstanceManager.get(self._session,
|
self.player = InstanceManager.get(self._session,
|
||||||
WorkflowPlayer.create_component_id(self._session, workflow_name),
|
WorkflowPlayer.create_component_id(self._session, workflow_name),
|
||||||
WorkflowPlayer,
|
WorkflowPlayer,
|
||||||
settings_manager=self._settings_manager,
|
settings_manager=self._settings_manager,
|
||||||
@@ -222,22 +222,23 @@ class WorkflowDesigner(BaseComponent):
|
|||||||
def play_workflow(self, boundaries: dict):
|
def play_workflow(self, boundaries: dict):
|
||||||
self._error_message = None
|
self._error_message = None
|
||||||
|
|
||||||
self._player.run()
|
self.player.run()
|
||||||
if self._player.global_error:
|
if self.player.global_error:
|
||||||
# Show the error message in the same tab
|
# Show the error message in the same tab
|
||||||
self._error_message = self._player.global_error
|
self._error_message = self.player.global_error
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
self.properties.set_entry_selector_data(self.player.nb_items)
|
||||||
# change the tab and display the results
|
# change the tab and display the results
|
||||||
self._player.set_boundaries(boundaries)
|
self.player.set_boundaries(boundaries)
|
||||||
self.tabs_manager.add_tab(f"Workflow {self._designer_settings.workflow_name}", self._player, self._player.key)
|
self.tabs_manager.add_tab(f"Workflow {self._designer_settings.workflow_name}", self.player, self.player.key)
|
||||||
|
|
||||||
return self.tabs_manager.refresh()
|
return self.tabs_manager.refresh()
|
||||||
|
|
||||||
def stop_workflow(self):
|
def stop_workflow(self):
|
||||||
self._error_message = None
|
self._error_message = None
|
||||||
self._player.stop()
|
self.player.stop()
|
||||||
|
self.properties.set_entry_selector_data(0)
|
||||||
return self.tabs_manager.refresh()
|
return self.tabs_manager.refresh()
|
||||||
|
|
||||||
def on_processor_details_event(self, component_id: str, event_name: str, details: dict):
|
def on_processor_details_event(self, component_id: str, event_name: str, details: dict):
|
||||||
@@ -314,7 +315,7 @@ class WorkflowDesigner(BaseComponent):
|
|||||||
|
|
||||||
def _mk_component(self, component: WorkflowComponent):
|
def _mk_component(self, component: WorkflowComponent):
|
||||||
|
|
||||||
runtime_state = self._player.get_component_runtime_state(component.id)
|
runtime_state = self.player.get_component_runtime_state(component.id)
|
||||||
|
|
||||||
info = COMPONENT_TYPES[component.type]
|
info = COMPONENT_TYPES[component.type]
|
||||||
is_selected = self._state.selected_component_id == component.id
|
is_selected = self._state.selected_component_id == component.id
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from fasthtml.common import *
|
from fasthtml.common import *
|
||||||
|
|
||||||
from components.BaseComponent import BaseComponent
|
from components.BaseComponent import BaseComponent
|
||||||
|
from components.debugger.components.JsonViewer import JsonViewer
|
||||||
from components.entryselector.components.EntrySelector import EntrySelector
|
from components.entryselector.components.EntrySelector import EntrySelector
|
||||||
from components.workflows.constants import COMPONENT_TYPES, PROCESSOR_TYPES
|
from components.workflows.constants import COMPONENT_TYPES, PROCESSOR_TYPES
|
||||||
from components_helpers import mk_dialog_buttons
|
from components_helpers import mk_dialog_buttons
|
||||||
@@ -26,15 +27,24 @@ class WorkflowDesignerProperties(BaseComponent):
|
|||||||
self._component = None
|
self._component = None
|
||||||
self.update_layout()
|
self.update_layout()
|
||||||
self.update_component(self._owner.get_state().selected_component_id)
|
self.update_component(self._owner.get_state().selected_component_id)
|
||||||
self._input_entry_selector = InstanceManager.new(self._session,
|
self.entry_selector: EntrySelector = InstanceManager.new(self._session,
|
||||||
EntrySelector,
|
EntrySelector,
|
||||||
owner=self,
|
owner=self,
|
||||||
content_id=f"pic_{self._id}",
|
hooks={
|
||||||
data=100)
|
"on_entry_selected": self.on_entry_selector_changed})
|
||||||
self._output_entry_selector = InstanceManager.new(self._session,
|
self._input_jsonviewer: JsonViewer = InstanceManager.new(self._session,
|
||||||
EntrySelector,
|
JsonViewer,
|
||||||
owner=self,
|
owner=self,
|
||||||
content_id=f"poc_{self._id}")
|
user_id=None,
|
||||||
|
data=None)
|
||||||
|
self._output_jsonviewer: JsonViewer = InstanceManager.new(self._session,
|
||||||
|
JsonViewer,
|
||||||
|
owner=self,
|
||||||
|
user_id=None,
|
||||||
|
data=None)
|
||||||
|
|
||||||
|
def set_entry_selector_data(self, data):
|
||||||
|
self.entry_selector.set_data(data)
|
||||||
|
|
||||||
def update_layout(self):
|
def update_layout(self):
|
||||||
if self._owner.get_state().properties_input_width is None:
|
if self._owner.get_state().properties_input_width is None:
|
||||||
@@ -65,31 +75,54 @@ class WorkflowDesignerProperties(BaseComponent):
|
|||||||
|
|
||||||
return self.__ft__(oob=oob)
|
return self.__ft__(oob=oob)
|
||||||
|
|
||||||
|
def on_entry_selector_changed(self, entry):
|
||||||
|
entry = int(entry)
|
||||||
|
|
||||||
|
input_data, output_data = None, None
|
||||||
|
selected_component_id = self._owner.get_state().selected_component_id
|
||||||
|
if selected_component_id is not None:
|
||||||
|
runtime_state = self._owner.player.runtime_states.get(selected_component_id, None)
|
||||||
|
if runtime_state is not None:
|
||||||
|
input_content = runtime_state.input[entry] if len(runtime_state.input) > entry else None
|
||||||
|
output_content = runtime_state.output[entry] if len(runtime_state.output) > entry else None
|
||||||
|
if input_content is not None:
|
||||||
|
self._input_jsonviewer.set_data(input_content.item.as_dict())
|
||||||
|
input_data = self._input_jsonviewer
|
||||||
|
if output_content is not None:
|
||||||
|
self._output_jsonviewer.set_data(output_content.item.as_dict())
|
||||||
|
output_data = self._output_jsonviewer
|
||||||
|
|
||||||
|
return (self._mk_input(content=input_data, oob=True),
|
||||||
|
self._mk_output(content=output_data, oob=True))
|
||||||
|
|
||||||
def _mk_layout(self):
|
def _mk_layout(self):
|
||||||
return Div(
|
return Div(
|
||||||
|
self.entry_selector,
|
||||||
|
Div(
|
||||||
self._mk_input(),
|
self._mk_input(),
|
||||||
self._mk_properties(),
|
self._mk_properties(),
|
||||||
self._mk_output(),
|
self._mk_output(),
|
||||||
cls="flex",
|
cls="flex",
|
||||||
style="height: 100%; width: 100%; flex: 1;"
|
style="height: 100%; width: 100%; flex: 1;"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _mk_input(self):
|
|
||||||
return Div(
|
|
||||||
self._input_entry_selector,
|
|
||||||
Div(id=f"pic_{self._id}"),
|
|
||||||
id=f"pi_{self._id}",
|
|
||||||
style=f"width: {self.layout.input_width}px;",
|
|
||||||
cls="wkf-properties-input"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _mk_output(self):
|
def _mk_input(self, content=None, oob=False):
|
||||||
return Div(
|
return Div(
|
||||||
self._output_entry_selector,
|
content,
|
||||||
"Output Content",
|
id=f"pi_{self._id}",
|
||||||
|
style=f"width: {self.layout.input_width}px;",
|
||||||
|
cls="wkf-properties-input",
|
||||||
|
hx_swap_oob=f'true' if oob else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _mk_output(self, content=None, oob=False):
|
||||||
|
return Div(
|
||||||
|
content,
|
||||||
id=f"po_{self._id}",
|
id=f"po_{self._id}",
|
||||||
style=f"width: {self.layout.output_width}px;",
|
style=f"width: {self.layout.output_width}px;",
|
||||||
cls="wkf-properties-output"
|
cls="wkf-properties-output",
|
||||||
|
hx_swap_oob=f'true' if oob else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _mk_properties(self):
|
def _mk_properties(self):
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class WorkflowPlayer(BaseComponent):
|
|||||||
self.runtime_states = {}
|
self.runtime_states = {}
|
||||||
self.global_error = None
|
self.global_error = None
|
||||||
self.has_error = False
|
self.has_error = False
|
||||||
|
self.nb_items = 0
|
||||||
|
|
||||||
def set_boundaries(self, boundaries: dict):
|
def set_boundaries(self, boundaries: dict):
|
||||||
self._datagrid.set_boundaries(boundaries)
|
self._datagrid.set_boundaries(boundaries)
|
||||||
@@ -93,6 +94,7 @@ class WorkflowPlayer(BaseComponent):
|
|||||||
self.global_error = engine.global_error
|
self.global_error = engine.global_error
|
||||||
|
|
||||||
else: # loop through the components and update the runtime states
|
else: # loop through the components and update the runtime states
|
||||||
|
self.nb_items = engine.nb_items
|
||||||
for component in sorted_components:
|
for component in sorted_components:
|
||||||
runtime_state = self.runtime_states.get(component.id)
|
runtime_state = self.runtime_states.get(component.id)
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ class Expando:
|
|||||||
return self._props.copy()
|
return self._props.copy()
|
||||||
|
|
||||||
def to_dict(self, mappings: dict) -> dict:
|
def to_dict(self, mappings: dict) -> dict:
|
||||||
|
"""
|
||||||
|
Return the information as a dictionary, with the given mappings
|
||||||
|
"""
|
||||||
return {prop_name: self.get(path) for path, prop_name in mappings.items() if prop_name is not None}
|
return {prop_name: self.get(path) for path, prop_name in mappings.items() if prop_name is not None}
|
||||||
|
|
||||||
def __hasattr__(self, item):
|
def __hasattr__(self, item):
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ class WorkflowEngine:
|
|||||||
self.global_error = None
|
self.global_error = None
|
||||||
self.errors = {}
|
self.errors = {}
|
||||||
self.debug = {}
|
self.debug = {}
|
||||||
self.item_count = -1
|
self.nb_items = -1
|
||||||
|
|
||||||
def add_processor(self, processor: DataProcessor) -> 'WorkflowEngine':
|
def add_processor(self, processor: DataProcessor) -> 'WorkflowEngine':
|
||||||
"""Add a data processor to the pipeline."""
|
"""Add a data processor to the pipeline."""
|
||||||
@@ -201,10 +201,10 @@ class WorkflowEngine:
|
|||||||
if not self.processors:
|
if not self.processors:
|
||||||
self.has_error = False
|
self.has_error = False
|
||||||
self.global_error = "No processors in the pipeline"
|
self.global_error = "No processors in the pipeline"
|
||||||
self.item_count = -1
|
self.nb_items = -1
|
||||||
raise ValueError(self.global_error)
|
raise ValueError(self.global_error)
|
||||||
|
|
||||||
self.item_count = 0
|
self.nb_items = 0
|
||||||
first_processor = self.processors[0]
|
first_processor = self.processors[0]
|
||||||
|
|
||||||
if not isinstance(first_processor, DataProducer):
|
if not isinstance(first_processor, DataProducer):
|
||||||
@@ -215,7 +215,7 @@ class WorkflowEngine:
|
|||||||
self.debug[first_processor.component_id] = {"input": [], "output": []}
|
self.debug[first_processor.component_id] = {"input": [], "output": []}
|
||||||
|
|
||||||
for item_linkage_id, item in enumerate(first_processor.process(None)):
|
for item_linkage_id, item in enumerate(first_processor.process(None)):
|
||||||
self.item_count += 1
|
self.nb_items += 1
|
||||||
self.debug[first_processor.component_id]["output"].append(WorkflowPayload(
|
self.debug[first_processor.component_id]["output"].append(WorkflowPayload(
|
||||||
processor_name=first_processor.__class__.__name__,
|
processor_name=first_processor.__class__.__name__,
|
||||||
component_id=first_processor.component_id,
|
component_id=first_processor.component_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user