diff --git a/src/components/port/CustomDynaPortModel.tsx b/src/components/port/CustomDynaPortModel.tsx index db32b61a..b89f7939 100644 --- a/src/components/port/CustomDynaPortModel.tsx +++ b/src/components/port/CustomDynaPortModel.tsx @@ -3,7 +3,7 @@ import { CustomPortModel, CustomPortModelOptions } from '../port/CustomPortModel import { CustomNodeModel } from '../node/CustomNodeModel'; export const DYNAMIC_PARAMETER_NODE_TYPES = [ - 'dynalist', 'dynadict', 'dynatuple' + 'dynalist', 'dynatuple' ]; export interface DynaPortRef { @@ -59,11 +59,6 @@ export class CustomDynaPortModel extends CustomPortModel { return true; // Accepts anything } - // if thisLinkedPortType is dynadict, accept only dict - if (thisLinkedPortType === 'dynadict' && thisNodeModelType !== 'dict') { - return false; - } - // default check return super.isTypeCompatible(thisNodeModelType, thisLinkedPortType); } diff --git a/src/components/port/CustomPortLabel.tsx b/src/components/port/CustomPortLabel.tsx index fa129eb9..a914366a 100644 --- a/src/components/port/CustomPortLabel.tsx +++ b/src/components/port/CustomPortLabel.tsx @@ -79,7 +79,6 @@ export class CustomPortLabel extends React.Component<CustomPortLabelProps> { "dict": '{ }', "dynalist": '«[]»', "dynatuple": '«()»', - "dynadict": '«{}»', "union": ' U', "secret": '🗝️', "chat": '🗨', diff --git a/src/components/port/CustomPortModel.ts b/src/components/port/CustomPortModel.ts index 89afd5b7..2b833946 100644 --- a/src/components/port/CustomPortModel.ts +++ b/src/components/port/CustomPortModel.ts @@ -154,19 +154,35 @@ export class CustomPortModel extends DefaultPortModel { return PARAMETER_NODE_TYPES.includes(nodeModelType); } + static typeCompatibilityMap = { + "chat": ["list"], + "secret": ["string", "int", "float"], + }; + isTypeCompatible(thisNodeModelType, dataType) { - if(thisNodeModelType !== dataType){ - // Skip 'any' type check - if(dataType === 'any'){ - return true; - } - // if multiple types are accepted by target node port, check if source port type is among them - if(dataType.includes(thisNodeModelType)) { + // Check for direct compatibility or 'any' type + if (thisNodeModelType === dataType || dataType === 'any') { + return true; + } + + // Check if the thisNodeModelType exists in the compatibility map + if (CustomPortModel.typeCompatibilityMap.hasOwnProperty(thisNodeModelType)) { + // Get the array of compatible data types for thisNodeModelType + const compatibleDataTypes = CustomPortModel.typeCompatibilityMap[thisNodeModelType]; + + // Check if dataType is in the array of compatible types + if (compatibleDataTypes.includes(dataType)) { return true; } - return false; // types are incompatible } - return true; + + // If multiple types are accepted by target node port, check if source port type is among them + if (dataType.includes(thisNodeModelType)) { + return true; + } + + // If none of the above checks pass, the types are incompatible + return false; } canTriangleLinkToTriangle = (thisPort, port) => { diff --git a/xai_components/base.py b/xai_components/base.py index 953e1321..d3810ea2 100644 --- a/xai_components/base.py +++ b/xai_components/base.py @@ -1,40 +1,46 @@ from argparse import Namespace -from typing import TypeVar, Generic, Tuple, NamedTuple, List +from typing import TypeVar, Generic, Tuple, NamedTuple, Callable, List T = TypeVar('T') - class InArg(Generic[T]): - value: T + def __init__(self, value: T = None, getter: Callable[[T], any] = lambda x: x) -> None: + self._value = value + self._getter = getter - def __init__(self, value: T) -> None: - self.value = value - - @classmethod - def empty(cls): - return InArg(None) + @property + def value(self): + return self._getter(self._value) + @value.setter + def value(self, value: T): + self._value = value class OutArg(Generic[T]): - value: T - - def __init__(self, value: T) -> None: + def __init__(self, value: T = None, getter: Callable[[T], any] = lambda x: x) -> None: self.value = value + self._getter = getter - @classmethod - def empty(cls): - return OutArg(None) + @property + def value(self): + return self._getter(self._value) -class InCompArg(Generic[T]): - value: T + @value.setter + def value(self, value: T): + self._value = value - def __init__(self, value: T) -> None: +class InCompArg(Generic[T]): + def __init__(self, value: T = None, getter: Callable[[T], any] = lambda x: x) -> None: self.value = value + self._getter = getter - @classmethod - def empty(cls): - return InCompArg(None) + @property + def value(self): + return self._getter(self._value) + @value.setter + def value(self, value: T): + self._value = value def xai_component(*args, **kwargs): # Passthrough element without any changes. @@ -54,19 +60,24 @@ class ExecutionContext: def __init__(self, args: Namespace): self.args = args - class BaseComponent: - def __init__(self): all_ports = self.__annotations__ for key, type_arg in all_ports.items(): - if isinstance(type_arg, InArg[any].__class__): - setattr(self, key, InArg.empty()) - elif isinstance(type_arg, InCompArg[any].__class__): - setattr(self, key, InCompArg.empty()) - elif isinstance(type_arg, OutArg[any].__class__): - setattr(self, key, OutArg.empty()) - elif type_arg == str(self.__class__): + port_class = type_arg.__origin__ + port_type = type_arg.__args__[0] + if port_class in (InArg, InCompArg, OutArg): + if hasattr(port_type, 'initial_value'): + port_value = port_type.initial_value() + else: + port_value = None + + if hasattr(port_type, 'getter'): + port_getter = port_type.getter + else: + port_getter = lambda x: x + setattr(self, key, port_class(port_value, port_getter)) + else: setattr(self, key, None) @classmethod @@ -79,7 +90,6 @@ def execute(self, ctx) -> None: def do(self, ctx) -> Tuple[bool, 'BaseComponent']: pass - class Component(BaseComponent): next: BaseComponent @@ -125,14 +135,8 @@ def execute_graph(args: Namespace, start: BaseComponent, ctx) -> None: class secret: + pass - def __init__(self, value): - self.__value = value - - def get_value(self): - return self.__value - - class message(NamedTuple): role: str content: str @@ -140,14 +144,26 @@ class message(NamedTuple): class chat(NamedTuple): messages: List[message] -class dynalist: - def __init__(self, value): - self.value = value - -class dynatuple: - def __init__(self, value): - self.value = value - -class dynadict: - def __init__(self, value): - self.value = value \ No newline at end of file +class dynalist(list): + def __init__(self, *args): + super().__init__(args) + + @staticmethod + def getter(x): + if x is None: + return [] + return [item.value if isinstance(item, (InArg, OutArg)) else item for item in x] + +class dynatuple(tuple): + def __init__(self, *args): + super().__init__(args) + @staticmethod + def getter(x): + if x is None: + return tuple() + def resolve(item): + if isinstance(item, (InArg, InCompArg,OutArg)): + return item.value + else: + return item + return tuple(resolve(item) for item in x) \ No newline at end of file diff --git a/xai_components/xai_utils/utils.py b/xai_components/xai_utils/utils.py index 207eabbc..066bc6b7 100644 --- a/xai_components/xai_utils/utils.py +++ b/xai_components/xai_utils/utils.py @@ -1,4 +1,4 @@ -from xai_components.base import InArg, OutArg, InCompArg, Component, xai_component +from xai_components.base import InArg, OutArg, InCompArg, Component, xai_component, dynalist, dynatuple import os import sys @@ -168,6 +168,68 @@ def execute(self, ctx) -> None: print("Sleeping for " + str(sleep_timer) + " seconds.") time.sleep(sleep_timer) +@xai_component(color="grey") +class MakeList(Component): + """ + A component that takes values from its dynamic list port and output as a normal list. + ##### inPorts: + - list_values: Dynamic list port that can take any vars and append it in a list. + + ##### outPorts: + - output_list: The constructed list from the dynamic list inPorts. + """ + list_values: InArg[dynalist] + output_list: OutArg[list] + + def execute(self, ctx) -> None: + + self.output_list.value = self.list_values.value + print("Constructed List:", self.output_list.value) + +@xai_component(color="grey") +class MakeTuple(Component): + """ + A component that takes values from its dynamic tuple port and output as a normal tuple. + ##### inPorts: + - tuple_values: Dynamic tuple port that can take any vars and append it in a tuple. + + ##### outPorts: + - output_tuple: The constructed tuple from the dynamic tuple inPorts. + """ + tuple_values: InArg[dynatuple] + output_tuple: OutArg[tuple] + + def execute(self, ctx) -> None: + + self.output_tuple.value = self.tuple_values.value + print("Constructed Tuple:", self.output_tuple.value) + +@xai_component(color="grey") +class MakeDict(Component): + """ + A component that takes two dynamic lists (dynalists) as inputs - one for keys and one for values, + and constructs a dictionary from these lists. If there are more keys than values, the unmatched keys + will have None as their value. + + ##### inPorts: + - keys_list: Dynamic list of keys for the dictionary. + - values_list: Dynamic list of values for the dictionary. + + ##### outPorts: + - output_dict: The constructed dictionary from the provided keys and values. + """ + keys_list: InArg[dynalist] + values_list: InArg[dynalist] + output_dict: OutArg[dict] + + def execute(self, ctx) -> None: + keys = self.keys_list.value + values = self.values_list.value + + constructed_dict = {key: values[i] if i < len(values) else None for i, key in enumerate(keys)} + + self.output_dict.value = constructed_dict + print("Constructed Dictionary:", self.output_dict.value) @xai_component(color="orange") class ExecuteNotebook(Component): diff --git a/xircuits/compiler/generator.py b/xircuits/compiler/generator.py index 54a84d10..f794366a 100644 --- a/xircuits/compiler/generator.py +++ b/xircuits/compiler/generator.py @@ -1,5 +1,6 @@ import ast import itertools +from collections import namedtuple import re import json import sys @@ -156,8 +157,9 @@ def main(args): # Handle dynamic connections dynaports = [p for p in node.ports if p.direction == 'in' and p.type != 'triangle-link' and p.dataType in DYNAMIC_PORTS] + ports_by_varName = {} - ports_by_varName = {} + RefOrValue = namedtuple('RefOrValue', ['value', 'is_ref']) # Renamed to RefOrValue # Group ports by varName for port in dynaports: @@ -165,14 +167,12 @@ def main(args): ports_by_varName[port.varName] = [] ports_by_varName[port.varName].append(port) - # Iterate through grouped ports and append values for varName, ports in ports_by_varName.items(): - appended_list = [] - merged_dict = {} - convert_to_tuple = False + dynaport_values = [] for port in ports: if port.source.id not in named_nodes: + # Handle Literals if port.source.type == "string": value = port.sourceLabel elif port.source.type == "list": @@ -180,37 +180,31 @@ def main(args): elif port.source.type == "dict": value = json.loads("{" + port.sourceLabel + "}") elif port.source.type == "tuple": - value = list(eval(port.sourceLabel)) + value = tuple(eval(port.sourceLabel)) else: value = eval(port.sourceLabel) - + dynaport_values.append(RefOrValue(value, False)) else: - value = "%s.%s" % (named_nodes[port.source.id], port.sourceLabel) + # Handle named node references + value = "%s.%s" % (named_nodes[port.source.id], port.sourceLabel) # Variable reference + dynaport_values.append(RefOrValue(value, True)) - if port.dataType == 'dynadict': - merged_dict.update(value) - else: - appended_list.append(value) - - if port.dataType == 'dynatuple': - convert_to_tuple = True - - # Create a single AST node for each unique varName and append to code assignment_target = "%s.%s.value" % (named_nodes[ports[0].target.id], ports[0].varName) - - assignment_value = '' - - if merged_dict: - assignment_value = repr(merged_dict) - elif appended_list: - if convert_to_tuple: - assignment_value = repr(tuple(appended_list)) + + if ports[0].dataType == 'dynatuple': + tuple_elements = [item.value if item.is_ref else repr(item.value) for item in dynaport_values] + if len(tuple_elements) == 1: + assignment_value = '(' + tuple_elements[0] + ',)' else: - assignment_value = repr(appended_list) - + assignment_value = '(' + ', '.join(tuple_elements) + ')' + else: + list_elements = [item.value if item.is_ref else repr(item.value) for item in dynaport_values] + assignment_value = '[' + ', '.join(list_elements) + ']' + tpl = ast.parse("%s = %s" % (assignment_target, assignment_value)) code.append(tpl) + # Set up control flow for node in component_nodes: has_next = False diff --git a/xircuits/compiler/port.py b/xircuits/compiler/port.py index 303f95ec..c99f5647 100644 --- a/xircuits/compiler/port.py +++ b/xircuits/compiler/port.py @@ -13,4 +13,4 @@ class Port: sourceLabel: str direction: str -DYNAMIC_PORTS = ['dynalist', 'dynadict', 'dynatuple'] +DYNAMIC_PORTS = ['dynalist', 'dynatuple']