示例#1
0
    def with_cycles_removed(self):
        # type: () -> StoryGraph
        """Create a graph with the cyclic edges removed from this graph."""

        story_end_checkpoints = self.story_end_checkpoints.copy()
        cyclic_edges = self.cyclic_edges()
        # we need to remove the start steps and replace them with steps ending
        # in a special end checkpoint
        steps_to_be_removed = {start.id for start, _ in cyclic_edges}
        story_steps = [
            s for s in self.story_steps if s.id not in steps_to_be_removed
        ]

        # add changed start steps again
        for s, e in cyclic_edges:
            cid = utils.generate_id()
            start_cid = "CYCLE_S_" + cid
            end_cid = "CYCLE_E_" + cid
            story_end_checkpoints[start_cid] = end_cid

            modified_start = s.create_copy(use_new_id=True)
            modified_start.end_checkpoint = Checkpoint(start_cid)
            story_steps.append(modified_start)

            modified_end = e.create_copy(use_new_id=True)
            modified_end.start_checkpoint = Checkpoint(end_cid)
            story_steps.append(modified_end)

        return StoryGraph(story_steps, story_end_checkpoints)
示例#2
0
    def add_user_messages(self, messages):
        self.ensure_current_steps()

        if len(messages) == 1:
            # If there is only one possible intent, we'll keep things simple
            for t in self.current_steps:
                t.add_user_message(messages[0])
        else:
            # If there are multiple different intents the
            # user can use the express the same thing
            # we need to copy the blocks and create one
            # copy for each possible message
            generated_checkpoint = utils.generate_id("GENERATED_M_")
            updated_steps = []
            for t in self.current_steps:
                for m in messages:
                    copied = t.create_copy(use_new_id=True)
                    copied.add_user_message(m)
                    copied.end_checkpoint = Checkpoint(generated_checkpoint)
                    updated_steps.append(copied)
            self.current_steps = updated_steps
示例#3
0
    def add_user_messages(self, messages):
        self.ensure_current_steps()

        if len(messages) == 1:
            # If there is only one possible intent, we'll keep things simple
            for t in self.current_steps:
                t.add_user_message(messages[0])
        else:
            # If there are multiple different intents the
            # user can use the express the same thing
            # we need to copy the blocks and create one
            # copy for each possible message
            prefix = GENERATED_CHECKPOINT_PREFIX + "OR_"
            generated_checkpoint = utils.generate_id(prefix,
                                                     GENERATED_HASH_LENGTH)
            updated_steps = []
            for t in self.current_steps:
                for m in messages:
                    copied = t.create_copy(use_new_id=True)
                    copied.add_user_message(m)
                    copied.end_checkpoints = [Checkpoint(generated_checkpoint)]
                    updated_steps.append(copied)
            self.current_steps = updated_steps
示例#4
0
    def with_cycles_removed(self) -> 'StoryGraph':
        """Create a graph with the cyclic edges removed from this graph."""

        story_end_checkpoints = self.story_end_checkpoints.copy()
        cyclic_edge_ids = self.cyclic_edge_ids
        # we need to remove the start steps and replace them with steps ending
        # in a special end checkpoint
        story_steps = {s.id: s for s in self.story_steps}

        # collect all overlapping checkpoints
        # we will remove unused start ones
        all_overlapping_cps = set()

        if self.cyclic_edge_ids:
            # we are going to do this in a recursive way. we are going to
            # remove one cycle and then we are going to
            # let the cycle detection run again
            # this is not inherently necessary so if this becomes a performance
            # issue, we can change it. It is actually enough to run the cycle
            # detection only once and then remove one cycle after another, but
            # since removing the cycle is done by adding / removing edges and
            #  nodes
            # the logic is a lot easier if we only need to make sure the
            # change is consistent if we only change one compared to
            # changing all of them.

            for s, e in cyclic_edge_ids:
                cid = utils.generate_id(max_chars=GENERATED_HASH_LENGTH)
                prefix = GENERATED_CHECKPOINT_PREFIX + CHECKPOINT_CYCLE_PREFIX
                # need abbreviations otherwise they are not visualized well
                sink_cp_name = prefix + "SINK_" + cid
                connector_cp_name = prefix + "CONN_" + cid
                source_cp_name = prefix + "SRC_" + cid
                story_end_checkpoints[sink_cp_name] = source_cp_name

                overlapping_cps = self.overlapping_checkpoint_names(
                    story_steps[s].end_checkpoints,
                    story_steps[e].start_checkpoints)

                all_overlapping_cps.update(overlapping_cps)

                # change end checkpoints of starts
                start = story_steps[s].create_copy(use_new_id=False)
                start.end_checkpoints = [
                    cp for cp in start.end_checkpoints
                    if cp.name not in overlapping_cps
                ]
                start.end_checkpoints.append(Checkpoint(sink_cp_name))
                story_steps[s] = start

                needs_connector = False

                for k, step in list(story_steps.items()):
                    additional_ends = []
                    for original_cp in overlapping_cps:
                        for cp in step.start_checkpoints:
                            if cp.name == original_cp:
                                if k == e:
                                    cp_name = source_cp_name
                                else:
                                    cp_name = connector_cp_name
                                    needs_connector = True

                                if not self._is_checkpoint_in_list(
                                        cp_name, cp.conditions,
                                        step.start_checkpoints):
                                    # add checkpoint only if it was not added
                                    additional_ends.append(
                                        Checkpoint(cp_name, cp.conditions))

                    if additional_ends:
                        updated = step.create_copy(use_new_id=False)
                        updated.start_checkpoints.extend(additional_ends)
                        story_steps[k] = updated

                if needs_connector:
                    start.end_checkpoints.append(Checkpoint(connector_cp_name))

        # the process above may generate unused checkpoints
        # we need to find them and remove them
        self._remove_unused_generated_cps(story_steps, all_overlapping_cps,
                                          story_end_checkpoints)

        return StoryGraph(list(story_steps.values()), story_end_checkpoints)
示例#5
0
    def with_cycles_removed(self):
        # type: () -> StoryGraph
        """Create a graph with the cyclic edges removed from this graph."""

        if not self.cyclic_edge_ids:
            return self

        story_end_checkpoints = self.story_end_checkpoints.copy()
        cyclic_edge_ids = self.cyclic_edge_ids
        # we need to remove the start steps and replace them with steps ending
        # in a special end checkpoint
        story_steps = {s.id: s for s in self.story_steps}

        # we are going to do this in a recursive way. we are going to remove
        # one cycle and then we are going to let the cycle detection run again
        # this is not inherently necessary so if this becomes a performance
        # issue, we can change it. It is actually enough to run the cycle
        # detection only once and then remove one cycle after another, but
        # since removing the cycle is done by adding / removing edges and nodes
        # the logic is a lot easier if we only need to make sure the change is
        # consistent if we only change one compared to changing all of them.

        for s, e in cyclic_edge_ids:
            cid = utils.generate_id(max_chars=GENERATED_HASH_LENGTH)
            sink_cid = GENERATED_CHECKPOINT_PREFIX + "SINK_" + cid
            connector_cid = GENERATED_CHECKPOINT_PREFIX + "CONNECT_" + cid
            source_cid = GENERATED_CHECKPOINT_PREFIX + "SOURCE_" + cid
            story_end_checkpoints[sink_cid] = source_cid

            overlapping_cps = self.overlapping_checkpoint_names(
                story_steps[s].end_checkpoints,
                story_steps[e].start_checkpoints)

            # changed all starts
            start = story_steps[s].create_copy(use_new_id=False)
            start.end_checkpoints = [
                cp for cp in start.end_checkpoints
                if cp.name not in overlapping_cps
            ]
            start.end_checkpoints.append(Checkpoint(sink_cid))
            story_steps[s] = start

            needs_connector = False

            for k, step in list(story_steps.items()):
                additional_ends = []
                for original_cp in overlapping_cps:
                    for cp in step.start_checkpoints:
                        if cp.name == original_cp:
                            if k == e:
                                cid = source_cid
                            else:
                                cid = connector_cid
                                needs_connector = True

                            additional_ends.append(
                                Checkpoint(cid, cp.conditions))
                if additional_ends:
                    updated = step.create_copy(use_new_id=False)
                    updated.start_checkpoints.extend(additional_ends)
                    story_steps[k] = updated

            if needs_connector:
                start.end_checkpoints.append(Checkpoint(connector_cid))

        return StoryGraph(story_steps.values(), story_end_checkpoints)
示例#6
0
    def with_cycles_removed(self):
        # type: () -> StoryGraph
        """Create a graph with the cyclic edges removed from this graph."""

        story_end_checkpoints = self.story_end_checkpoints.copy()
        cyclic_edge_ids = self.cyclic_edge_ids
        # we need to remove the start steps and replace them with steps ending
        # in a special end checkpoint
        story_steps = {s.id: s for s in self.story_steps}

        # collect all overlapping checkpoints
        # we will remove unused start ones
        all_overlapping_cps = set()

        if self.cyclic_edge_ids:
            # we are going to do this in a recursive way. we are going to remove
            # one cycle and then we are going to let the cycle detection run again
            # this is not inherently necessary so if this becomes a performance
            # issue, we can change it. It is actually enough to run the cycle
            # detection only once and then remove one cycle after another, but
            # since removing the cycle is done by adding / removing edges and nodes
            # the logic is a lot easier if we only need to make sure the change is
            # consistent if we only change one compared to changing all of them.

            for s, e in cyclic_edge_ids:
                cid = utils.generate_id(max_chars=GENERATED_HASH_LENGTH)
                prefix = GENERATED_CHECKPOINT_PREFIX + CHECKPOINT_CYCLE_PREFIX
                # need abbreviations otherwise they are not visualized well
                sink_cp_name = prefix + "SINK_" + cid
                connector_cp_name = prefix + "CONN_" + cid
                source_cp_name = prefix + "SRC_" + cid
                story_end_checkpoints[sink_cp_name] = source_cp_name

                overlapping_cps = self.overlapping_checkpoint_names(
                        story_steps[s].end_checkpoints,
                        story_steps[e].start_checkpoints)

                all_overlapping_cps.update(overlapping_cps)

                # change end checkpoints of starts
                start = story_steps[s].create_copy(use_new_id=False)
                start.end_checkpoints = [cp
                                         for cp in start.end_checkpoints
                                         if cp.name not in overlapping_cps]
                start.end_checkpoints.append(Checkpoint(sink_cp_name))
                story_steps[s] = start

                needs_connector = False

                for k, step in list(story_steps.items()):
                    additional_ends = []
                    for original_cp in overlapping_cps:
                        for cp in step.start_checkpoints:
                            if cp.name == original_cp:
                                if k == e:
                                    cp_name = source_cp_name
                                else:
                                    cp_name = connector_cp_name
                                    needs_connector = True

                                if not self._is_checkpoint_in_list(
                                        cp_name, cp.conditions,
                                        step.start_checkpoints):
                                    # add checkpoint only if it was not added
                                    additional_ends.append(
                                            Checkpoint(cp_name, cp.conditions))

                    if additional_ends:
                        updated = step.create_copy(use_new_id=False)
                        updated.start_checkpoints.extend(additional_ends)
                        story_steps[k] = updated

                if needs_connector:
                    start.end_checkpoints.append(Checkpoint(connector_cp_name))

        # the process above may generate unused checkpoints
        # we need to find them and remove them
        self._remove_unused_generated_cps(story_steps,
                                          all_overlapping_cps,
                                          story_end_checkpoints)

        return StoryGraph(list(story_steps.values()),
                          story_end_checkpoints)