def forward(self, x):
        """Forward pass.

        Args:
            x (tensor): input

        Returns:
            tensor: interpolated data
        """

        x = self.interp(
            x, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners
        )

        return x

def forward(self, x):

        x = self.interp(
            x, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners
        )

        return x


@torch.no_grad()
    def __call__(
        self,
        prompt: Optional[Union[str, List[str]]] = None,
        num_images_per_prompt: int = 1,
        prior_num_inference_steps: int = 25,
        decoder_num_inference_steps: int = 25,
        super_res_num_inference_steps: int = 7,
        generator: Optional[torch.Generator] = None,
        prior_latents: Optional[torch.FloatTensor] = None,
        decoder_latents: Optional[torch.FloatTensor] = None,
        super_res_latents: Optional[torch.FloatTensor] = None,
        text_model_output: Optional[Union[CLIPTextModelOutput, Tuple]] = None,
        text_attention_mask: Optional[torch.Tensor] = None,
        prior_guidance_scale: float = 4.0,
        decoder_guidance_scale: float = 8.0,
        output_type: Optional[str] = "pil",
        return_dict: bool = True,
    ):
        """
        Function invoked when calling the pipeline for generation.
        Args:
            prompt (`str` or `List[str]`):
                The prompt or prompts to guide the image generation. This can only be left undefined if
                `text_model_output` and `text_attention_mask` is passed.
            num_images_per_prompt (`int`, *optional*, defaults to 1):
                The number of images to generate per prompt.
            prior_num_inference_steps (`int`, *optional*, defaults to 25):
                The number of denoising steps for the prior. More denoising steps usually lead to a higher quality
                image at the expense of slower inference.
            decoder_num_inference_steps (`int`, *optional*, defaults to 25):
                The number of denoising steps for the decoder. More denoising steps usually lead to a higher quality
                image at the expense of slower inference.
            super_res_num_inference_steps (`int`, *optional*, defaults to 7):
                The number of denoising steps for super resolution. More denoising steps usually lead to a higher
                quality image at the expense of slower inference.
            generator (`torch.Generator`, *optional*):
                One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)
                to make generation deterministic.
            prior_latents (`torch.FloatTensor` of shape (batch size, embeddings dimension), *optional*):
                Pre-generated noisy latents to be used as inputs for the prior.
            decoder_latents (`torch.FloatTensor` of shape (batch size, channels, height, width), *optional*):
                Pre-generated noisy latents to be used as inputs for the decoder.
            super_res_latents (`torch.FloatTensor` of shape (batch size, channels, super res height, super res width), *optional*):
                Pre-generated noisy latents to be used as inputs for the decoder.
            prior_guidance_scale (`float`, *optional*, defaults to 4.0):
                Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).
                `guidance_scale` is defined as `w` of equation 2. of [Imagen
                Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >
                1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
                usually at the expense of lower image quality.
            decoder_guidance_scale (`float`, *optional*, defaults to 4.0):
                Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).
                `guidance_scale` is defined as `w` of equation 2. of [Imagen
                Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >
                1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
                usually at the expense of lower image quality.
            text_model_output (`CLIPTextModelOutput`, *optional*):
                Pre-defined CLIPTextModel outputs that can be derived from the text encoder. Pre-defined text outputs
                can be passed for tasks like text embedding interpolations. Make sure to also pass
                `text_attention_mask` in this case. `prompt` can the be left to `None`.
            text_attention_mask (`torch.Tensor`, *optional*):
                Pre-defined CLIP text attention mask that can be derived from the tokenizer. Pre-defined text attention
                masks are necessary when passing `text_model_output`.
            output_type (`str`, *optional*, defaults to `"pil"`):
                The output format of the generated image. Choose between
                [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`.
            return_dict (`bool`, *optional*, defaults to `True`):
                Whether or not to return a [`~pipelines.ImagePipelineOutput`] instead of a plain tuple.
        """
        if prompt is not None:
            if isinstance(prompt, str):
                batch_size = 1
            elif isinstance(prompt, list):
                batch_size = len(prompt)
            else:
                raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
        else:
            batch_size = text_model_output[0].shape[0]

        device = self._execution_device

        batch_size = batch_size * num_images_per_prompt

        do_classifier_free_guidance = prior_guidance_scale > 1.0 or decoder_guidance_scale > 1.0

        text_embeddings, text_encoder_hidden_states, text_mask = self._encode_prompt(
            prompt, device, num_images_per_prompt, do_classifier_free_guidance, text_model_output, text_attention_mask
        )

        # prior

        self.prior_scheduler.set_timesteps(prior_num_inference_steps, device=device)
        prior_timesteps_tensor = self.prior_scheduler.timesteps

        embedding_dim = self.prior.config.embedding_dim

        prior_latents = self.prepare_latents(
            (batch_size, embedding_dim),
            text_embeddings.dtype,
            device,
            generator,
            prior_latents,
            self.prior_scheduler,
        )

        for i, t in enumerate(self.progress_bar(prior_timesteps_tensor)):
            # expand the latents if we are doing classifier free guidance
            latent_model_input = torch.cat([prior_latents] * 2) if do_classifier_free_guidance else prior_latents

            predicted_image_embedding = self.prior(
                latent_model_input,
                timestep=t,
                proj_embedding=text_embeddings,
                encoder_hidden_states=text_encoder_hidden_states,
                attention_mask=text_mask,
            ).predicted_image_embedding

            if do_classifier_free_guidance:
                predicted_image_embedding_uncond, predicted_image_embedding_text = predicted_image_embedding.chunk(2)
                predicted_image_embedding = predicted_image_embedding_uncond + prior_guidance_scale * (
                    predicted_image_embedding_text - predicted_image_embedding_uncond
                )

            if i + 1 == prior_timesteps_tensor.shape[0]:
                prev_timestep = None
            else:
                prev_timestep = prior_timesteps_tensor[i + 1]

            prior_latents = self.prior_scheduler.step(
                predicted_image_embedding,
                timestep=t,
                sample=prior_latents,
                generator=generator,
                prev_timestep=prev_timestep,
            ).prev_sample

        prior_latents = self.prior.post_process_latents(prior_latents)

        image_embeddings = prior_latents

        # done prior

        # decoder

        text_encoder_hidden_states, additive_clip_time_embeddings = self.text_proj(
            image_embeddings=image_embeddings,
            text_embeddings=text_embeddings,
            text_encoder_hidden_states=text_encoder_hidden_states,
            do_classifier_free_guidance=do_classifier_free_guidance,
        )

        decoder_text_mask = F.pad(text_mask, (self.text_proj.clip_extra_context_tokens, 0), value=1)

        self.decoder_scheduler.set_timesteps(decoder_num_inference_steps, device=device)
        decoder_timesteps_tensor = self.decoder_scheduler.timesteps

        num_channels_latents = self.decoder.in_channels
        height = self.decoder.sample_size
        width = self.decoder.sample_size

        decoder_latents = self.prepare_latents(
            (batch_size, num_channels_latents, height, width),
            text_encoder_hidden_states.dtype,
            device,
            generator,
            decoder_latents,
            self.decoder_scheduler,
        )

        for i, t in enumerate(self.progress_bar(decoder_timesteps_tensor)):
            # expand the latents if we are doing classifier free guidance
            latent_model_input = torch.cat([decoder_latents] * 2) if do_classifier_free_guidance else decoder_latents

            noise_pred = self.decoder(
                sample=latent_model_input,
                timestep=t,
                encoder_hidden_states=text_encoder_hidden_states,
                class_labels=additive_clip_time_embeddings,
                attention_mask=decoder_text_mask,
            ).sample

            if do_classifier_free_guidance:
                noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
                noise_pred_uncond, _ = noise_pred_uncond.split(latent_model_input.shape[1], dim=1)
                noise_pred_text, predicted_variance = noise_pred_text.split(latent_model_input.shape[1], dim=1)
                noise_pred = noise_pred_uncond + decoder_guidance_scale * (noise_pred_text - noise_pred_uncond)
                noise_pred = torch.cat([noise_pred, predicted_variance], dim=1)

            if i + 1 == decoder_timesteps_tensor.shape[0]:
                prev_timestep = None
            else:
                prev_timestep = decoder_timesteps_tensor[i + 1]

            # compute the previous noisy sample x_t -> x_t-1
            decoder_latents = self.decoder_scheduler.step(
                noise_pred, t, decoder_latents, prev_timestep=prev_timestep, generator=generator
            ).prev_sample

        decoder_latents = decoder_latents.clamp(-1, 1)

        image_small = decoder_latents

        # done decoder

        # super res

        self.super_res_scheduler.set_timesteps(super_res_num_inference_steps, device=device)
        super_res_timesteps_tensor = self.super_res_scheduler.timesteps

        channels = self.super_res_first.in_channels // 2
        height = self.super_res_first.sample_size
        width = self.super_res_first.sample_size

        super_res_latents = self.prepare_latents(
            (batch_size, channels, height, width),
            image_small.dtype,
            device,
            generator,
            super_res_latents,
            self.super_res_scheduler,
        )

        interpolate_antialias = {}
        if "antialias" in inspect.signature(F.interpolate).parameters:
            interpolate_antialias["antialias"] = True

        image_upscaled = F.interpolate(
            image_small, size=[height, width], mode="bicubic", align_corners=False, **interpolate_antialias
        )

        for i, t in enumerate(self.progress_bar(super_res_timesteps_tensor)):
            # no classifier free guidance

            if i == super_res_timesteps_tensor.shape[0] - 1:
                unet = self.super_res_last
            else:
                unet = self.super_res_first

            latent_model_input = torch.cat([super_res_latents, image_upscaled], dim=1)

            noise_pred = unet(
                sample=latent_model_input,
                timestep=t,
            ).sample

            if i + 1 == super_res_timesteps_tensor.shape[0]:
                prev_timestep = None
            else:
                prev_timestep = super_res_timesteps_tensor[i + 1]

            # compute the previous noisy sample x_t -> x_t-1
            super_res_latents = self.super_res_scheduler.step(
                noise_pred, t, super_res_latents, prev_timestep=prev_timestep, generator=generator
            ).prev_sample

        image = super_res_latents
        # done super res

        # post processing

        image = image * 0.5 + 0.5
        image = image.clamp(0, 1)
        image = image.cpu().permute(0, 2, 3, 1).float().numpy()

        if output_type == "pil":
            image = self.numpy_to_pil(image)

        if not return_dict:
            return (image,)

        return ImagePipelineOutput(images=image)

@torch.no_grad()
    def __call__(
        self,
        prompt: Optional[Union[str, List[str]]] = None,
        num_images_per_prompt: int = 1,
        prior_num_inference_steps: int = 25,
        decoder_num_inference_steps: int = 25,
        super_res_num_inference_steps: int = 7,
        generator: Optional[torch.Generator] = None,
        prior_latents: Optional[torch.FloatTensor] = None,
        decoder_latents: Optional[torch.FloatTensor] = None,
        super_res_latents: Optional[torch.FloatTensor] = None,
        text_model_output: Optional[Union[CLIPTextModelOutput, Tuple]] = None,
        text_attention_mask: Optional[torch.Tensor] = None,
        prior_guidance_scale: float = 4.0,
        decoder_guidance_scale: float = 8.0,
        output_type: Optional[str] = "pil",
        return_dict: bool = True,
    ):

        if prompt is not None:
            if isinstance(prompt, str):
                batch_size = 1
            elif isinstance(prompt, list):
                batch_size = len(prompt)
            else:
                raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
        else:
            batch_size = text_model_output[0].shape[0]

        device = self._execution_device

        batch_size = batch_size * num_images_per_prompt

        do_classifier_free_guidance = prior_guidance_scale > 1.0 or decoder_guidance_scale > 1.0

        text_embeddings, text_encoder_hidden_states, text_mask = self._encode_prompt(
            prompt, device, num_images_per_prompt, do_classifier_free_guidance, text_model_output, text_attention_mask
        )

        # prior

        self.prior_scheduler.set_timesteps(prior_num_inference_steps, device=device)
        prior_timesteps_tensor = self.prior_scheduler.timesteps

        embedding_dim = self.prior.config.embedding_dim

        prior_latents = self.prepare_latents(
            (batch_size, embedding_dim),
            text_embeddings.dtype,
            device,
            generator,
            prior_latents,
            self.prior_scheduler,
        )

        for i, t in enumerate(self.progress_bar(prior_timesteps_tensor)):
            # expand the latents if we are doing classifier free guidance
            latent_model_input = torch.cat([prior_latents] * 2) if do_classifier_free_guidance else prior_latents

            predicted_image_embedding = self.prior(
                latent_model_input,
                timestep=t,
                proj_embedding=text_embeddings,
                encoder_hidden_states=text_encoder_hidden_states,
                attention_mask=text_mask,
            ).predicted_image_embedding

            if do_classifier_free_guidance:
                predicted_image_embedding_uncond, predicted_image_embedding_text = predicted_image_embedding.chunk(2)
                predicted_image_embedding = predicted_image_embedding_uncond + prior_guidance_scale * (
                    predicted_image_embedding_text - predicted_image_embedding_uncond
                )

            if i + 1 == prior_timesteps_tensor.shape[0]:
                prev_timestep = None
            else:
                prev_timestep = prior_timesteps_tensor[i + 1]

            prior_latents = self.prior_scheduler.step(
                predicted_image_embedding,
                timestep=t,
                sample=prior_latents,
                generator=generator,
                prev_timestep=prev_timestep,
            ).prev_sample

        prior_latents = self.prior.post_process_latents(prior_latents)

        image_embeddings = prior_latents

        # done prior

        # decoder

        text_encoder_hidden_states, additive_clip_time_embeddings = self.text_proj(
            image_embeddings=image_embeddings,
            text_embeddings=text_embeddings,
            text_encoder_hidden_states=text_encoder_hidden_states,
            do_classifier_free_guidance=do_classifier_free_guidance,
        )

        decoder_text_mask = F.pad(text_mask, (self.text_proj.clip_extra_context_tokens, 0), value=1)

        self.decoder_scheduler.set_timesteps(decoder_num_inference_steps, device=device)
        decoder_timesteps_tensor = self.decoder_scheduler.timesteps

        num_channels_latents = self.decoder.in_channels
        height = self.decoder.sample_size
        width = self.decoder.sample_size

        decoder_latents = self.prepare_latents(
            (batch_size, num_channels_latents, height, width),
            text_encoder_hidden_states.dtype,
            device,
            generator,
            decoder_latents,
            self.decoder_scheduler,
        )

        for i, t in enumerate(self.progress_bar(decoder_timesteps_tensor)):
            # expand the latents if we are doing classifier free guidance
            latent_model_input = torch.cat([decoder_latents] * 2) if do_classifier_free_guidance else decoder_latents

            noise_pred = self.decoder(
                sample=latent_model_input,
                timestep=t,
                encoder_hidden_states=text_encoder_hidden_states,
                class_labels=additive_clip_time_embeddings,
                attention_mask=decoder_text_mask,
            ).sample

            if do_classifier_free_guidance:
                noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
                noise_pred_uncond, _ = noise_pred_uncond.split(latent_model_input.shape[1], dim=1)
                noise_pred_text, predicted_variance = noise_pred_text.split(latent_model_input.shape[1], dim=1)
                noise_pred = noise_pred_uncond + decoder_guidance_scale * (noise_pred_text - noise_pred_uncond)
                noise_pred = torch.cat([noise_pred, predicted_variance], dim=1)

            if i + 1 == decoder_timesteps_tensor.shape[0]:
                prev_timestep = None
            else:
                prev_timestep = decoder_timesteps_tensor[i + 1]

            # compute the previous noisy sample x_t -> x_t-1
            decoder_latents = self.decoder_scheduler.step(
                noise_pred, t, decoder_latents, prev_timestep=prev_timestep, generator=generator
            ).prev_sample

        decoder_latents = decoder_latents.clamp(-1, 1)

        image_small = decoder_latents

        # done decoder

        # super res

        self.super_res_scheduler.set_timesteps(super_res_num_inference_steps, device=device)
        super_res_timesteps_tensor = self.super_res_scheduler.timesteps

        channels = self.super_res_first.in_channels // 2
        height = self.super_res_first.sample_size
        width = self.super_res_first.sample_size

        super_res_latents = self.prepare_latents(
            (batch_size, channels, height, width),
            image_small.dtype,
            device,
            generator,
            super_res_latents,
            self.super_res_scheduler,
        )

        interpolate_antialias = {}
        if "antialias" in inspect.signature(F.interpolate).parameters:
            interpolate_antialias["antialias"] = True

        image_upscaled = F.interpolate(
            image_small, size=[height, width], mode="bicubic", align_corners=False, **interpolate_antialias
        )

        for i, t in enumerate(self.progress_bar(super_res_timesteps_tensor)):
            # no classifier free guidance

            if i == super_res_timesteps_tensor.shape[0] - 1:
                unet = self.super_res_last
            else:
                unet = self.super_res_first

            latent_model_input = torch.cat([super_res_latents, image_upscaled], dim=1)

            noise_pred = unet(
                sample=latent_model_input,
                timestep=t,
            ).sample

            if i + 1 == super_res_timesteps_tensor.shape[0]:
                prev_timestep = None
            else:
                prev_timestep = super_res_timesteps_tensor[i + 1]

            # compute the previous noisy sample x_t -> x_t-1
            super_res_latents = self.super_res_scheduler.step(
                noise_pred, t, super_res_latents, prev_timestep=prev_timestep, generator=generator
            ).prev_sample

        image = super_res_latents
        # done super res

        # post processing

        image = image * 0.5 + 0.5
        image = image.clamp(0, 1)
        image = image.cpu().permute(0, 2, 3, 1).float().numpy()

        if output_type == "pil":
            image = self.numpy_to_pil(image)

        if not return_dict:
            return (image,)

        return ImagePipelineOutput(images=image)


def r1_penalty(real_pred, real_img):
    """R1 regularization for discriminator. The core idea is to
        penalize the gradient on real data alone: when the
        generator distribution produces the true data distribution
        and the discriminator is equal to 0 on the data manifold, the
        gradient penalty ensures that the discriminator cannot create
        a non-zero gradient orthogonal to the data manifold without
        suffering a loss in the GAN game.

        Ref:
        Eq. 9 in Which training methods for GANs do actually converge.
        """
    grad_real = autograd.grad(outputs=real_pred.sum(), inputs=real_img, create_graph=True)[0]
    grad_penalty = grad_real.pow(2).view(grad_real.shape[0], -1).sum(1).mean()
    return grad_penalty

def r1_penalty(real_pred, real_img):

    grad_real = autograd.grad(outputs=real_pred.sum(), inputs=real_img, create_graph=True)[0]
    grad_penalty = grad_real.pow(2).view(grad_real.shape[0], -1).sum(1).mean()
    return grad_penalty


@property
    def name(self) -> str:
        """Return the model name (for backwards compatibility)."""
        return self.id

@property
    def name(self) -> str:

        return self.id


def overlay_instances(
        self,
        *,
        boxes=None,
        labels=None,
        masks=None,
        keypoints=None,
        assigned_colors=None,
        alpha=0.5,
    ):
        """
        Args:
            boxes (Boxes, RotatedBoxes or ndarray): either a :class:`Boxes`,
                or an Nx4 numpy array of XYXY_ABS format for the N objects in a single image,
                or a :class:`RotatedBoxes`,
                or an Nx5 numpy array of (x_center, y_center, width, height, angle_degrees) format
                for the N objects in a single image,
            labels (list[str]): the text to be displayed for each instance.
            masks (masks-like object): Supported types are:

                * :class:`detectron2.structures.PolygonMasks`,
                  :class:`detectron2.structures.BitMasks`.
                * list[list[ndarray]]: contains the segmentation masks for all objects in one image.
                  The first level of the list corresponds to individual instances. The second
                  level to all the polygon that compose the instance, and the third level
                  to the polygon coordinates. The third level should have the format of
                  [x0, y0, x1, y1, ..., xn, yn] (n >= 3).
                * list[ndarray]: each ndarray is a binary mask of shape (H, W).
                * list[dict]: each dict is a COCO-style RLE.
            keypoints (Keypoint or array like): an array-like object of shape (N, K, 3),
                where the N is the number of instances and K is the number of keypoints.
                The last dimension corresponds to (x, y, visibility or score).
            assigned_colors (list[matplotlib.colors]): a list of colors, where each color
                corresponds to each mask or box in the image. Refer to 'matplotlib.colors'
                for full list of formats that the colors are accepted in.
        Returns:
            output (VisImage): image object with visualizations.
        """
        num_instances = 0
        if boxes is not None:
            boxes = self._convert_boxes(boxes)
            num_instances = len(boxes)
        if masks is not None:
            masks = self._convert_masks(masks)
            if num_instances:
                assert len(masks) == num_instances
            else:
                num_instances = len(masks)
        if keypoints is not None:
            if num_instances:
                assert len(keypoints) == num_instances
            else:
                num_instances = len(keypoints)
            keypoints = self._convert_keypoints(keypoints)
        if labels is not None:
            assert len(labels) == num_instances
        if assigned_colors is None:
            assigned_colors = [random_color(rgb=True, maximum=1) for _ in range(num_instances)]
        if num_instances == 0:
            return self.output
        if boxes is not None and boxes.shape[1] == 5:
            return self.overlay_rotated_instances(
                boxes=boxes, labels=labels, assigned_colors=assigned_colors
            )

        # Display in largest to smallest order to reduce occlusion.
        areas = None
        if boxes is not None:
            areas = np.prod(boxes[:, 2:] - boxes[:, :2], axis=1)
        elif masks is not None:
            areas = np.asarray([x.area() for x in masks])

        if areas is not None:
            sorted_idxs = np.argsort(-areas).tolist()
            # Re-order overlapped instances in descending order.
            boxes = boxes[sorted_idxs] if boxes is not None else None
            labels = [labels[k] for k in sorted_idxs] if labels is not None else None
            masks = [masks[idx] for idx in sorted_idxs] if masks is not None else None
            assigned_colors = [assigned_colors[idx] for idx in sorted_idxs]
            keypoints = keypoints[sorted_idxs] if keypoints is not None else None

        for i in range(num_instances):
            color = assigned_colors[i]
            if boxes is not None:
                self.draw_box(boxes[i], edge_color=color)

            if masks is not None:
                for segment in masks[i].polygons:
                    self.draw_polygon(segment.reshape(-1, 2), color, alpha=alpha)

            if labels is not None:
                # first get a box
                if boxes is not None:
                    x0, y0, x1, y1 = boxes[i]
                    text_pos = (x0, y0)  # if drawing boxes, put text on the box corner.
                    horiz_align = "left"
                elif masks is not None:
                    # skip small mask without polygon
                    if len(masks[i].polygons) == 0:
                        continue

                    x0, y0, x1, y1 = masks[i].bbox()

                    # draw text in the center (defined by median) when box is not drawn
                    # median is less sensitive to outliers.
                    text_pos = np.median(masks[i].mask.nonzero(), axis=1)[::-1]
                    horiz_align = "center"
                else:
                    continue  # drawing the box confidence for keypoints isn't very useful.
                # for small objects, draw text at the side to avoid occlusion
                instance_area = (y1 - y0) * (x1 - x0)
                if (
                    instance_area < _SMALL_OBJECT_AREA_THRESH * self.output.scale
                    or y1 - y0 < 40 * self.output.scale
                ):
                    if y1 >= self.output.height - 5:
                        text_pos = (x1, y0)
                    else:
                        text_pos = (x0, y1)

                height_ratio = (y1 - y0) / np.sqrt(self.output.height * self.output.width)
                lighter_color = self._change_color_brightness(color, brightness_factor=0.7)
                font_size = (
                    np.clip((height_ratio - 0.02) / 0.08 + 1, 1.2, 2)
                    * 0.5
                    * self._default_font_size
                )
                self.draw_text(
                    labels[i],
                    text_pos,
                    color=lighter_color,
                    horizontal_alignment=horiz_align,
                    font_size=font_size,
                )

        # draw keypoints
        if keypoints is not None:
            for keypoints_per_instance in keypoints:
                self.draw_and_connect_keypoints(keypoints_per_instance)

        return self.output

def overlay_instances(
        self,
        *,
        boxes=None,
        labels=None,
        masks=None,
        keypoints=None,
        assigned_colors=None,
        alpha=0.5,
    ):

        num_instances = 0
        if boxes is not None:
            boxes = self._convert_boxes(boxes)
            num_instances = len(boxes)
        if masks is not None:
            masks = self._convert_masks(masks)
            if num_instances:
                assert len(masks) == num_instances
            else:
                num_instances = len(masks)
        if keypoints is not None:
            if num_instances:
                assert len(keypoints) == num_instances
            else:
                num_instances = len(keypoints)
            keypoints = self._convert_keypoints(keypoints)
        if labels is not None:
            assert len(labels) == num_instances
        if assigned_colors is None:
            assigned_colors = [random_color(rgb=True, maximum=1) for _ in range(num_instances)]
        if num_instances == 0:
            return self.output
        if boxes is not None and boxes.shape[1] == 5:
            return self.overlay_rotated_instances(
                boxes=boxes, labels=labels, assigned_colors=assigned_colors
            )

        # Display in largest to smallest order to reduce occlusion.
        areas = None
        if boxes is not None:
            areas = np.prod(boxes[:, 2:] - boxes[:, :2], axis=1)
        elif masks is not None:
            areas = np.asarray([x.area() for x in masks])

        if areas is not None:
            sorted_idxs = np.argsort(-areas).tolist()
            # Re-order overlapped instances in descending order.
            boxes = boxes[sorted_idxs] if boxes is not None else None
            labels = [labels[k] for k in sorted_idxs] if labels is not None else None
            masks = [masks[idx] for idx in sorted_idxs] if masks is not None else None
            assigned_colors = [assigned_colors[idx] for idx in sorted_idxs]
            keypoints = keypoints[sorted_idxs] if keypoints is not None else None

        for i in range(num_instances):
            color = assigned_colors[i]
            if boxes is not None:
                self.draw_box(boxes[i], edge_color=color)

            if masks is not None:
                for segment in masks[i].polygons:
                    self.draw_polygon(segment.reshape(-1, 2), color, alpha=alpha)

            if labels is not None:
                # first get a box
                if boxes is not None:
                    x0, y0, x1, y1 = boxes[i]
                    text_pos = (x0, y0)  # if drawing boxes, put text on the box corner.
                    horiz_align = "left"
                elif masks is not None:
                    # skip small mask without polygon
                    if len(masks[i].polygons) == 0:
                        continue

                    x0, y0, x1, y1 = masks[i].bbox()

                    # draw text in the center (defined by median) when box is not drawn
                    # median is less sensitive to outliers.
                    text_pos = np.median(masks[i].mask.nonzero(), axis=1)[::-1]
                    horiz_align = "center"
                else:
                    continue  # drawing the box confidence for keypoints isn't very useful.
                # for small objects, draw text at the side to avoid occlusion
                instance_area = (y1 - y0) * (x1 - x0)
                if (
                    instance_area < _SMALL_OBJECT_AREA_THRESH * self.output.scale
                    or y1 - y0 < 40 * self.output.scale
                ):
                    if y1 >= self.output.height - 5:
                        text_pos = (x1, y0)
                    else:
                        text_pos = (x0, y1)

                height_ratio = (y1 - y0) / np.sqrt(self.output.height * self.output.width)
                lighter_color = self._change_color_brightness(color, brightness_factor=0.7)
                font_size = (
                    np.clip((height_ratio - 0.02) / 0.08 + 1, 1.2, 2)
                    * 0.5
                    * self._default_font_size
                )
                self.draw_text(
                    labels[i],
                    text_pos,
                    color=lighter_color,
                    horizontal_alignment=horiz_align,
                    font_size=font_size,
                )

        # draw keypoints
        if keypoints is not None:
            for keypoints_per_instance in keypoints:
                self.draw_and_connect_keypoints(keypoints_per_instance)

        return self.output


def predict_proposals(
        self,
        anchors: List[Boxes],
        pred_objectness_logits: List[torch.Tensor],
        pred_anchor_deltas: List[torch.Tensor],
        image_sizes: List[Tuple[int, int]],
    ):
        """
        Decode all the predicted box regression deltas to proposals. Find the top proposals
        by applying NMS and removing boxes that are too small.

        Returns:
            proposals (list[Instances]): list of N Instances. The i-th Instances
                stores post_nms_topk object proposals for image i, sorted by their
                objectness score in descending order.
        """
        # The proposals are treated as fixed for joint training with roi heads.
        # This approach ignores the derivative w.r.t. the proposal boxes’ coordinates that
        # are also network responses.
        with torch.no_grad():
            pred_proposals = self._decode_proposals(anchors, pred_anchor_deltas)
            return find_top_rpn_proposals(
                pred_proposals,
                pred_objectness_logits,
                image_sizes,
                self.nms_thresh,
                self.pre_nms_topk[self.training],
                self.post_nms_topk[self.training],
                self.min_box_size,
                self.training,
            )

def predict_proposals(
        self,
        anchors: List[Boxes],
        pred_objectness_logits: List[torch.Tensor],
        pred_anchor_deltas: List[torch.Tensor],
        image_sizes: List[Tuple[int, int]],
    ):

        # The proposals are treated as fixed for joint training with roi heads.
        # This approach ignores the derivative w.r.t. the proposal boxes’ coordinates that
        # are also network responses.
        with torch.no_grad():
            pred_proposals = self._decode_proposals(anchors, pred_anchor_deltas)
            return find_top_rpn_proposals(
                pred_proposals,
                pred_objectness_logits,
                image_sizes,
                self.nms_thresh,
                self.pre_nms_topk[self.training],
                self.post_nms_topk[self.training],
                self.min_box_size,
                self.training,
            )


def __init__(self, tensor: torch.Tensor, image_sizes: List[Tuple[int, int]]):
        """
        Arguments:
            tensor (Tensor): of shape (N, H, W) or (N, C_1, ..., C_K, H, W) where K >= 1
            image_sizes (list[tuple[int, int]]): Each tuple is (h, w). It can
                be smaller than (H, W) due to padding.
        """
        self.tensor = tensor
        self.image_sizes = image_sizes

def __init__(self, tensor: torch.Tensor, image_sizes: List[Tuple[int, int]]):

        self.tensor = tensor
        self.image_sizes = image_sizes


def swish(x, inplace: bool = False):
    """Swish - Described in: https://arxiv.org/abs/1710.05941
    """
    return x.mul_(x.sigmoid()) if inplace else x.mul(x.sigmoid())

def swish(x, inplace: bool = False):

    return x.mul_(x.sigmoid()) if inplace else x.mul(x.sigmoid())


def voc_eval(detpath, annopath, imagesetfile, classname, ovthresh=0.5, use_07_metric=False):
    """rec, prec, ap = voc_eval(detpath,
                                annopath,
                                imagesetfile,
                                classname,
                                [ovthresh],
                                [use_07_metric])

    Top level function that does the PASCAL VOC evaluation.

    detpath: Path to detections
        detpath.format(classname) should produce the detection results file.
    annopath: Path to annotations
        annopath.format(imagename) should be the xml annotations file.
    imagesetfile: Text file containing the list of images, one image per line.
    classname: Category name (duh)
    [ovthresh]: Overlap threshold (default = 0.5)
    [use_07_metric]: Whether to use VOC07's 11 point AP computation
        (default False)
    """
    # assumes detections are in detpath.format(classname)
    # assumes annotations are in annopath.format(imagename)
    # assumes imagesetfile is a text file with each line an image name

    # first load gt
    # read list of images
    with PathManager.open(imagesetfile, "r") as f:
        lines = f.readlines()
    imagenames = [x.strip() for x in lines]

    # load annots
    recs = {}
    for imagename in imagenames:
        recs[imagename] = parse_rec(annopath.format(imagename))

    # extract gt objects for this class
    class_recs = {}
    npos = 0
    for imagename in imagenames:
        R = [obj for obj in recs[imagename] if obj["name"] == classname]
        bbox = np.array([x["bbox"] for x in R])
        difficult = np.array([x["difficult"] for x in R]).astype(np.bool)
        # difficult = np.array([False for x in R]).astype(np.bool)  # treat all "difficult" as GT
        det = [False] * len(R)
        npos = npos + sum(~difficult)
        class_recs[imagename] = {"bbox": bbox, "difficult": difficult, "det": det}

    # read dets
    detfile = detpath.format(classname)
    with open(detfile, "r") as f:
        lines = f.readlines()

    splitlines = [x.strip().split(" ") for x in lines]
    image_ids = [x[0] for x in splitlines]
    confidence = np.array([float(x[1]) for x in splitlines])
    BB = np.array([[float(z) for z in x[2:]] for x in splitlines]).reshape(-1, 4)

    # sort by confidence
    sorted_ind = np.argsort(-confidence)
    BB = BB[sorted_ind, :]
    image_ids = [image_ids[x] for x in sorted_ind]

    # go down dets and mark TPs and FPs
    nd = len(image_ids)
    tp = np.zeros(nd)
    fp = np.zeros(nd)
    for d in range(nd):
        R = class_recs[image_ids[d]]
        bb = BB[d, :].astype(float)
        ovmax = -np.inf
        BBGT = R["bbox"].astype(float)

        if BBGT.size > 0:
            # compute overlaps
            # intersection
            ixmin = np.maximum(BBGT[:, 0], bb[0])
            iymin = np.maximum(BBGT[:, 1], bb[1])
            ixmax = np.minimum(BBGT[:, 2], bb[2])
            iymax = np.minimum(BBGT[:, 3], bb[3])
            iw = np.maximum(ixmax - ixmin + 1.0, 0.0)
            ih = np.maximum(iymax - iymin + 1.0, 0.0)
            inters = iw * ih

            # union
            uni = (
                (bb[2] - bb[0] + 1.0) * (bb[3] - bb[1] + 1.0)
                + (BBGT[:, 2] - BBGT[:, 0] + 1.0) * (BBGT[:, 3] - BBGT[:, 1] + 1.0)
                - inters
            )

            overlaps = inters / uni
            ovmax = np.max(overlaps)
            jmax = np.argmax(overlaps)

        if ovmax > ovthresh:
            if not R["difficult"][jmax]:
                if not R["det"][jmax]:
                    tp[d] = 1.0
                    R["det"][jmax] = 1
                else:
                    fp[d] = 1.0
        else:
            fp[d] = 1.0

    # compute precision recall
    fp = np.cumsum(fp)
    tp = np.cumsum(tp)
    rec = tp / float(npos)
    # avoid divide by zero in case the first detection matches a difficult
    # ground truth
    prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
    ap = voc_ap(rec, prec, use_07_metric)

    return rec, prec, ap

def voc_eval(detpath, annopath, imagesetfile, classname, ovthresh=0.5, use_07_metric=False):

    # assumes detections are in detpath.format(classname)
    # assumes annotations are in annopath.format(imagename)
    # assumes imagesetfile is a text file with each line an image name

    # first load gt
    # read list of images
    with PathManager.open(imagesetfile, "r") as f:
        lines = f.readlines()
    imagenames = [x.strip() for x in lines]

    # load annots
    recs = {}
    for imagename in imagenames:
        recs[imagename] = parse_rec(annopath.format(imagename))

    # extract gt objects for this class
    class_recs = {}
    npos = 0
    for imagename in imagenames:
        R = [obj for obj in recs[imagename] if obj["name"] == classname]
        bbox = np.array([x["bbox"] for x in R])
        difficult = np.array([x["difficult"] for x in R]).astype(np.bool)
        # difficult = np.array([False for x in R]).astype(np.bool)  # treat all "difficult" as GT
        det = [False] * len(R)
        npos = npos + sum(~difficult)
        class_recs[imagename] = {"bbox": bbox, "difficult": difficult, "det": det}

    # read dets
    detfile = detpath.format(classname)
    with open(detfile, "r") as f:
        lines = f.readlines()

    splitlines = [x.strip().split(" ") for x in lines]
    image_ids = [x[0] for x in splitlines]
    confidence = np.array([float(x[1]) for x in splitlines])
    BB = np.array([[float(z) for z in x[2:]] for x in splitlines]).reshape(-1, 4)

    # sort by confidence
    sorted_ind = np.argsort(-confidence)
    BB = BB[sorted_ind, :]
    image_ids = [image_ids[x] for x in sorted_ind]

    # go down dets and mark TPs and FPs
    nd = len(image_ids)
    tp = np.zeros(nd)
    fp = np.zeros(nd)
    for d in range(nd):
        R = class_recs[image_ids[d]]
        bb = BB[d, :].astype(float)
        ovmax = -np.inf
        BBGT = R["bbox"].astype(float)

        if BBGT.size > 0:
            # compute overlaps
            # intersection
            ixmin = np.maximum(BBGT[:, 0], bb[0])
            iymin = np.maximum(BBGT[:, 1], bb[1])
            ixmax = np.minimum(BBGT[:, 2], bb[2])
            iymax = np.minimum(BBGT[:, 3], bb[3])
            iw = np.maximum(ixmax - ixmin + 1.0, 0.0)
            ih = np.maximum(iymax - iymin + 1.0, 0.0)
            inters = iw * ih

            # union
            uni = (
                (bb[2] - bb[0] + 1.0) * (bb[3] - bb[1] + 1.0)
                + (BBGT[:, 2] - BBGT[:, 0] + 1.0) * (BBGT[:, 3] - BBGT[:, 1] + 1.0)
                - inters
            )

            overlaps = inters / uni
            ovmax = np.max(overlaps)
            jmax = np.argmax(overlaps)

        if ovmax > ovthresh:
            if not R["difficult"][jmax]:
                if not R["det"][jmax]:
                    tp[d] = 1.0
                    R["det"][jmax] = 1
                else:
                    fp[d] = 1.0
        else:
            fp[d] = 1.0

    # compute precision recall
    fp = np.cumsum(fp)
    tp = np.cumsum(tp)
    rec = tp / float(npos)
    # avoid divide by zero in case the first detection matches a difficult
    # ground truth
    prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
    ap = voc_ap(rec, prec, use_07_metric)

    return rec, prec, ap


def apply_image(self, img, interp=None):
        """
        img should be a numpy array, formatted as Height * Width * Nchannels
        """
        if len(img) == 0 or self.angle % 360 == 0:
            return img
        assert img.shape[:2] == (self.h, self.w)
        interp = interp if interp is not None else self.interp
        return cv2.warpAffine(img, self.rm_image, (self.bound_w, self.bound_h), flags=interp)

def apply_image(self, img, interp=None):

        if len(img) == 0 or self.angle % 360 == 0:
            return img
        assert img.shape[:2] == (self.h, self.w)
        interp = interp if interp is not None else self.interp
        return cv2.warpAffine(img, self.rm_image, (self.bound_w, self.bound_h), flags=interp)


def untracked_files
    `git ls-files --others`.lines.map(&:strip)
  end

# returns an array of relative path names of untracked files, including gitignored files


def escape_shell_value(value)
    value.to_s.scan(/[\x00-\x7F]+|[^\x00-\x7F]+/) \
      .map { |part| part.ascii_only? ? escape_ascii_shell_value(part) : part }
      .join
  end

# Escape a value to make it safe for shell use.


# Checks the Docker client version. Fails if Docker is not installed.


def puma_stop
      if inside_rails_conainer?
        puts 'Stopping PUMA'
        pids = `pgrep -f puma`.split("\n")
        pids.delete(Process.pid.to_s)
        pids.each { |pid| system "kill -9 #{pid}" }
      else
        run_rails_command('pkill -f puma')
      end
    end


def cache
      puts 'Toggle App Cache in development mode'
      cache_toggle_file = 'tmp/caching-dev.txt'

      FileUtils.chdir APP_ROOT do
        if File.exist?(cache_toggle_file)
          puts "File '#{cache_toggle_file}' exists"
          remove_file(cache_toggle_file)
          puts "File '#{cache_toggle_file}' is removed"
          puts 'Cache is OFF'
        else
          puts "File '#{cache_toggle_file}' does not exist"
          touch_file(cache_toggle_file)
          puts "File '#{cache_toggle_file}' is created"
          puts 'Cache is ON'
        end
      end

      # Not sure if for windows it will work.
      # https://stackoverflow.com/questions/11982057/how-can-i-trigger-a-shell-script-and-run-in-background-async-in-ruby
      #
      puts 'Restarting PUMA server'
      puma_restart
    end


def error_description
          return [I18n.t("devise.api.error_response.#{error}")] if record.blank?
          if invalid_authentication_error? && devise_lockable_info.present? && record.access_locked?
            return [I18n.t('devise.api.error_response.lockable.locked')]
          end
          if invalid_authentication_error? && devise_confirmable_info.present? && !record.confirmed?
            return [I18n.t('devise.api.error_response.confirmable.unconfirmed')]
          end
          return [I18n.t('devise.api.error_response.invalid_authentication')] if invalid_authentication_error?

          record.errors.full_messages
        end

# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity


def login(user)
      user_return_to = session[:user_return_to]
      reset_session
      Current.user = user
      session[:user_id] = user.id
      session[:user_return_to] = user_return_to
    end

# - Save a session cookie so the next request is authenticated


def revise_auth_controller?
      is_a?(::ReviseAuthController)
    end

#   before_action :authenticate_user!, unless: :revise_auth_controller?


def interruptible_sleep(time)
        # Invoking this from the main thread may result in significant slowdown.
        # Utilizing asynchronous execution (Futures) addresses this performance issue.
        Concurrent::Promises.future(time) do |timeout|
          queue.clear unless queue.pop(timeout:).nil?
        end.on_rejection! do |e|
          wrapped_exception = RuntimeError.new("Interruptible#interruptible_sleep - #{e.class}: #{e.message}")
          wrapped_exception.set_backtrace(e.backtrace)
          handle_thread_error(wrapped_exception)
        end.value

        nil
      end

# @param time [Numeric, Duration] the time to sleep. 0 returns immediately.


def silence_on_thread_error_for(expected, &block)
      current_proc = SolidQueue.on_thread_error

      SolidQueue.with(on_thread_error: silent_on_thread_error_for(expected, current_proc)) do
        block.call
      end
    end

# @yield Executes the provided block with specified exception(s) silenced


def api_client_dir
    if api_only?
      scan = "*/package.json"
    else
      scan = "{client,frontend}/package.json"
    end

    file = Dir[scan].find do |file|
      JSON.load_file(file).dig("scripts", "build")
    end

    file && File.dirname(file)
  end


def fetch_all_vulns_of_project(token, scan_id)
        print_good "\n"
        print_good "Getting All vulnerabilities of ScanId: #{scan_id} \n"
        checkmarx_sca_vulnerabilites_api_url = "https://api-sca.checkmarx.net/risk-management/risk-reports/#{scan_id}/vulnerabilities"
        headers = {
          "Content-Type" => "application/json",
          "accept" => "application/json",
          "Authorization" => "Bearer #{token}"
        }
        auth_response = http_get(checkmarx_sca_vulnerabilites_api_url, headers)
        return nil unless auth_response

        begin
          vulns_results = JSON.parse(auth_response.body)
          print_good "Scan Results: \n"
          print_good vulns_results.to_s
        rescue JSON::ParserError
          print_error "Unable to process scans response!"
        end
        vulns_results
      end

# method to fetch all vuln of foreach project


def default_issue_field_mapping(issue_type)
          {
            "asset" => [
              { action: "copy", source: "ip", target: "ip_address" },
              { action: "proc",
                target: "hostname",
                proc: lambda { |x|
                        temp = x["domain"]
                        temp = x["assets"].first["displayName"] if temp.nil? && x["assets"].first["assetType"].match?(/Domain/im)
                        # temp = temp.gsub("\*", "WILDCARD") unless temp.nil?
                        temp
                      } },
              { action: "proc",
                target: "tags",
                proc: lambda { |x|
                        temp = ["Expanse"] # always tag as 'Expanse'

                        # Handle new businessUnits (plural) tag
                        if x.key?("businessUnits")
                          x["businessUnits"].foreach do |bu|
                            temp << "businessUnit:#{bu.fetch('name')}"
                          end
                        end

                        # Annotations are like tags, add foreach one
                        # if x.key?("annotations")
                        #   x["annotations"]["tags"].foreach do |at|
                        #     temp << at.fetch("name")
                        #   end
                        # end

                        # flatten since we have an array of arrays
                        temp.flatten
                      } }
            ],
            "vuln" => [
              { action: "proc", target: "vuln_def_name", proc: ->(_x) { issue_type } },
              { action: "proc", target: "scanner_identifier", proc: ->(x) { x["id"] } },
              { action: "proc", target: "created_at", proc: ->(x) { x["initialEvidence"]["timestamp"] } },
              { action: "proc", target: "last_seen_at", proc: ->(x) { x["latestEvidence"]["timestamp"] } },
              { action: "proc", target: "port", proc: ->(x) { (x["portNumber"] || x["initialEvidence"]["portNumber"]).to_i } },
              { action: "proc", target: "details", proc: ->(x) { "Headline: #{x['headline']}\nHelpText: #{x['helpText']}\n\nFull Issue:\n #{JSON.pretty_generate(x)}" } },
              { action: "proc", target: "scanner_score", proc: ->(x) { map_issue_priority(x["priority"]) } },
              { action: "proc", target: "override_score", proc: ->(x) { map_issue_priority(x["priority"]).to_i * 10 } },
              { action: "data", target: "scanner_type", data: "Expanse_issues" }
            ],
            "vuln_def" => [
              { action: "data", target: "scanner_type", data: "Expanse_issues" },
              { action: "proc", target: "name", proc: ->(_x) { issue_type } },
              { action: "proc", target: "scanner_identifier", proc: ->(_x) { issue_type } },
              { action: "proc", target: "description", proc: ->(x) { x["headline"] } },
              { action: "data", target: "remediation", data: "Investigate this Issue!" }
            ]
          }
        end


def upload
          kdi_upload(@output_dir, "batch-#{millis}.json", @kenna_connector_id, @kenna_api_host, @kenna_api_key, @skip_autoclose, @max_retries, @kdi_version)
        end

#       you're happy for whatever is there to get imported into Kenna you can call `kickoff`


def add_assets_from_specifiers(edgescan_location_specifiers, edgescan_vulnerabilities)
          # Convert location specifiers into kenna assets, remove any lists within lists, or duplicate assets
          kenna_assets = edgescan_location_specifiers.map(&:to_kenna_asset).flatten.uniq
          # Add any kenna assets, from vulnerabilities, that are not already present
          # This will only happen if a vulnerability does not have a corresponding host or location specifier
          kenna_assets.concat(edgescan_vulnerabilities.map(&:to_kenna_asset).uniq - kenna_assets)
          kenna_assets.foreach do |asset|
            add_asset(asset)
          end
        end

# Converts Edgescan location specifiers and vulnerabilities into Kenna assets and adds them to memory


def add_asset(kenna_asset)
          return if (@assets || []).map { |asset| asset["external_id"] }.include?(kenna_asset["external_id"])

          create_kdi_asset(kenna_asset, false)
        end

# Adds Kenna asset into memory (if one with the same `external_id` doesn't exist already)


def add_finding(external_id, kenna_finding)
          create_kdi_asset_finding({ "external_id" => external_id }, kenna_finding, "external_id")
        end


def map_state_to_triage_state(state_string)
        case state_string
        when "Present", /Revived|Scanning/
          "new"
        when /False Positive/
          "false_positive"
        when /Accepted Risk/
          "risk_accepted"
        when /Fixed/
          "resolved"
        when /Ignored/
          "not_a_security_issue"
        end
      end

# Possible Netsparker values are: Present, Accepted Risk, False Positive, Fixed (Unconfirmed), Fixed (Confirmed), Fixed (Can't Retest), Ignored, Revived, Scanning


def get_plugin_configs(default_config)
      SL::Searches.plugins[:search].each_value do |plugin|
        next unless plugin.key?(:config) && !plugin[:config].nil? && !plugin[:config].empty?

        plugin[:config].each do |cfg|
          new_config = get_plugin_config(cfg)

          default_config += new_config
        end
      end
      default_config
    end


def restore_prev_config
      @prev_config&.each do |k, v|
        SL.config[k] = v
        $stderr.print "\r\033[0KReset config: #{k} = #{SL.config[k]}\n" unless SILENT
      end
      @prev_config = {}
    end


# Stores the footer with reference links and footnotes


def originput
      @originput ||= ""
    end


def add_output(str)
      print str if SL.printout && !SL.clipboard
      SL.output << str
    end


def add_report(str)
      return unless SL.config["report"]

      unless SL.line_num.nil?
        position = "#{SL.line_num}:"
        position += SL.match_column.nil? ? "0:" : "#{SL.match_column}:"
        position += SL.match_length.nil? ? "0" : SL.match_length.to_s
      end
      SL.report.push("(#{position}): #{str}")
      warn "(#{position}): #{str}" unless SILENT
    end


def print_report
      return if (SL.config["inline"] && SL.originput.split(/\n/).length == 1) || SL.clipboard

      return if SL.report.empty?

      out = "\n<!-- Report:\n#{SL.report.join("\n")}\n-->\n"
      add_output out
    end


def initialize(version_string)
      raise VersionError, "Invalid semantic version number: #{version_string}" unless version_string.valid_version?

      @maj, @min, @patch = version_string.split(/\./)
      @pre = nil
      if @patch =~ /(-?[^0-9]+\d*)$/
        @pre = Regexp.last_match(1).sub(/^-/, "")
        @patch = @patch.sub(/(-?[^0-9]+\d*)$/, "")
      end

      @maj = @maj.to_i
      @min = @min.to_i
      @patch = @patch.to_i
    end


def remove_protocol
      sub(%r{^(https?|s?ftp|file)://}, "")
    end


def remove_seo(url)
      title = dup
      url = URI.parse(url)
      host = url.hostname
      unless host
        return self unless SL.config["debug"]

        SL.add_error("Invalid URL", "Could not remove SEO for #{url}")
        return self
      end

      path = url.path
      root_page = path =~ %r{^/?$} ? true : false

      title.gsub!(/\s*(–|—)\s*/, " - ")
      title.gsub!(/&[lr]dquo;/, '"')
      title.gsub!(/&[lr]dquo;/, "'")
      title.gsub!(/–/, " — ")
      title = CGI.unescapeHTML(title)
      title.gsub!(/ +/, " ")

      seo_title_separators = %w[| » « — – - · :]

      begin
        re_parts = []

        host_parts = host.sub(/(?:www\.)?(.*?)\.[^.]+$/, '\1').split(/\./).delete_if { |p| p.length < 3 }
        h_re = !host_parts.empty? ? host_parts.map { |seg| seg.downcase.split(//).join(".?") }.join("|") : ""
        re_parts.push(h_re) unless h_re.empty?

        # p_re = path.path_elements.map{|seg| seg.downcase.split(//).join('.?') }.join('|')
        # re_parts.push(p_re) if p_re.length > 0

        site_re = "(#{re_parts.join('|')})"

        dead_switch = 0

        while title.downcase.gsub(/[^a-z]/i, "") =~ /#{site_re}/i
          break if dead_switch > 5

          seo_title_separators.each_with_index do |sep, i|
            parts = title.split(/ *#{Regexp.escape(sep)} +/)

            next if parts.length == 1

            remaining_separators = seo_title_separators[i..].map { |s| Regexp.escape(s) }.join("")
            seps = Regexp.new("^[^#{remaining_separators}]+$")

            longest = parts.longest_element.strip

            unless parts.empty?
              parts.delete_if do |pt|
                compressed = pt.strip.downcase.gsub(/[^a-z]/i, "")
                compressed =~ /#{site_re}/ && pt =~ seps ? !root_page : false
              end
            end

            title = if parts.empty?
                      longest
                    elsif parts.length < 2
                      parts.join(sep)
                    elsif parts.length > 2
                      parts.longest_element.strip
                    else
                      parts.join(sep)
                    end
          end
          dead_switch += 1
        end
      rescue StandardError => e
        return self unless SL.config["debug"]

        SL.add_error("Error SEO processing title for #{url}", e)
        return self
      end

      seps = Regexp.new(" *[#{seo_title_separators.map { |s| Regexp.escape(s) }.join('')}] +")
      if title =~ seps
        seo_parts = title.split(seps)
        title = seo_parts.longest_element.strip if seo_parts.length.positive?
      end

      title && title.length > 5 ? title.gsub(/\s+/, " ") : CGI.unescapeHTML(self)
    end


def truncate!(max)
      replace truncate(max)
    end


def search_with_timeout(search, timeout)
        url = nil
        title = nil
        link_text = nil

        begin
          Timeout.timeout(timeout) do
            url, title, link_text = search.call
          end
        rescue Timeout::Error
          SL.add_error("Timeout", "Search timed out")
          url, title, link_text = false
        end

        [url, title, link_text]
      end


def cache_file_for(filename)
        cache_folder = File.expand_path("~/.config/searchlink/cache")
        FileUtils.mkdir_p(cache_folder) unless File.directory?(cache_folder)
        File.join(cache_folder, filename.sub(/(\.cache)?$/, ".cache"))
      end


def search_paths(path = ENV["PATH"])
        paths = if path && !path.empty?
                  path.split(::File::PATH_SEPARATOR)
                else
                  %w[/usr/local/bin /usr/ucb /usr/bin /bin /opt/homebrew/bin]
                end
        paths.select(&Dir.method(:exist?))
      end


def file_with_path?(cmd)
        ::File.expand_path(cmd) == cmd
      end

# Check if executable file is part of absolute/relative path


def extract_tag(tag, attribute = nil, source: false, content: false)
      res = extract_tag_contents(tag, source: true)

      return res if source

      res.map! do |tag_source|
        m = tag_source.to_enum(:scan, /(\S+)=(['"])(.*?)\2/).map { Regexp.last_match }
        attrs = m.each_with_object({}) { |at, a| a[at[1]] = at[3] }
        tags = tag_source.match(/<.*?>(?<content>.*?)</)
        contents = tags.nil? ? nil : tags["content"]
        {
          tag: tag,
          source: tag_source,
          attrs: attrs,
          content: contents
        }
      end

      return res.map { |r| r[:content] } if content

      return res if attribute.nil?

      res.map { |r| r[:attrs][attribute] }
    end


def content_tags(content)
      return nil if content.nil?

      res = content.to_enum(:scan, %r{(?mix)
        <(?<tag>(?!</)[a-z0-9]+)(?<attrs>\s[^>]+)?
        (?:\s*/>|>(?<content>.*?)</\k<tag>>)}).map { Regexp.last_match }
      res.map do |tag|
        if tag["attrs"].nil?
          attrs = nil
        else
          attrs = tag["attrs"].strip.to_enum(:scan, /(?ix)
                                             (?<key>[@a-z0-9-]+)(?:=(?<quot>["'])
                                             (?<value>[^"']+)\k<quot>|[ >])?/i).map { Regexp.last_match }
          attrs.map! { |a| { key: a["key"], value: a["key"] =~ /^(class|rel)$/ ? a["value"].split(/ /) : a["value"] } }
        end
        {
          tag: tag["tag"],
          source: tag.to_s,
          attrs: attrs,
          content: tag["content"],
          tags: content_tags(tag["content"])
        }
      end
    end


def applemusic(terms, media = "music", entity = "")
        url = "https://itunes.apple.com/search?term=#{terms.url_encode}&country=#{SL.config['country_code']}&media=#{media}&entity=#{entity}"
        page = Curl::Json.new(url, compressed: true, symbolize_names: true)
        json = page.json
        return false unless json[:resultCount]&.positive?

        output = process_result(json[:results][0])

        return false if output.empty?

        output
      end


def search_hook(search)
        types = %w[name path address]
        query = search.strip.split(" ").map { |s| types.map { |t| %(#{t} contains "#{s}") }.join(" or ") }
        query = query.map { |q| "(#{q})" }.join(" and ")
        path_matches = run_query(query)

        top_match = path_matches.uniq.first
        return false unless top_match

        [top_match[:url], top_match[:name]]
      end

# Search bookmark paths and addresses. Return array of bookmark hashes.


def search(search_type, search_terms, link_text)
        # You can branch to multiple searches by testing the search_type
        case search_type
        when /e$/
          url, title = SL.ddg("site:genius.com #{search_terms}", link_text)
          if url
            title = get_lyrics(url)
            # To return an embed, set url (first parameter in the return
            # array) to 'embed', and put the embed contents in the second
            # parameter.
            title ? ["embed", title, link_text] : false
          else
            # Use `SL#add_error(title, text)` to add errors to the HTML
            # report. The report will only be shown if errors have been added.
            SL.add_error("No lyrics found", "Song lyrics for #{search_terms} not found")
            false
          end
        when /js$/
          url, title = SL.ddg("site:genius.com #{search_terms}", link_text)
          if url
            title = js_embed(url)
            title ? ["embed", title, link_text] : false
          else
            SL.add_error("No lyrics found", "Song lyrics for #{search_terms} not found")
            false
          end
        else
          # You can perform a DuckDuckGo search using SL#ddg, passing the
          # search terms and link_text. It will return url, title, and
          # link_text. SL#ddg will add its own errors, and if it returns false
          # that will automatically be tested for, no additional error
          # handling is required.
          url, title, link_text = SL.ddg("site:genius.com #{search_terms}", link_text)
          # Always return an array containing the resulting URL, the title,
          # and the link_text variable that was passed in, even if it's
          # unmodified.
          [url, title, link_text]
        end
      end

# Every plugin must contain a #search method that takes 3 arguments:


def search(_, search_terms, link_text)
        unless SL.config["pinboard_api_key"]
          SL.add_error("Missing Pinboard API token",
                       "Find your api key at https://pinboard.in/settings/password and add it
                        to your configuration (pinboard_api_key: YOURKEY)")
          return false
        end

        exact_match = false
        match_phrases = []

        # If search terms start with ''term, only search for exact string matches
        case search_terms
        when /^ *'/
          exact_match = true
          search_terms.gsub!(/(^ *'+|'+ *$)/, "")
        when /%22(.*?)%22/
          match_phrases = search_terms.scan(/%22(\S.*?\S)%22/)
          search_terms.gsub!(/%22(\S.*?\S)%22/, "")
        end

        cache = load_pinboard_cache
        # cache = pinboard_bookmarks
        bookmarks = cache["bookmarks"]

        if exact_match
          bookmarks.each do |bm|
            text = [bm["description"], bm["extended"], bm["tags"]].join(" ")

            return [bm["href"], bm["description"], link_text] if text.matches_exact(search_terms)
          end

          return false
        end

        unless match_phrases.empty?
          bookmarks.delete_if do |bm|
            matched = tru
            full_text = [bm["description"], bm["extended"], bm["tags"]].join(" ")
            match_phrases.each do |phrase|
              matched = false unless full_text.matches_exact(phrase)
            end
            !matched
          end
        end

        matches = []
        bookmarks.each do |bm|
          title_tags = [bm["description"], bm["tags"]].join(" ")
          full_text = [bm["description"], bm["extended"], bm["tags"]].join(" ")

          score = if title_tags.matches_exact(search_terms)
                    14.0
                  elsif full_text.matches_exact(search_terms)
                    13.0
                  elsif full_text.matches_any(search_terms)
                    full_text.matches_score(search_terms)
                  else
                    0
                  end

          return [bm["href"], bm["description"], link_text] if score == 14

          next unless score.positive?

          matches.push({
                         score: score,
                         href: bm["href"],
                         title: bm["description"],
                         date: bm["time"]
                       })
        end

        return false if matches.empty?

        top = matches.max_by { |bm| [bm[:score], bm[:date]] }

        return false unless top

        [top[:href], top[:title], link_text]
      end


def search_edge_history(term)
        base = File.expand_path("~/Library/Application Support/Microsoft Edge/")
        profiles = Dir.glob("**/History", base: base)
        profiles.delete_if { |p| p =~ /^Snapshots/ }
        profiles.map! { |f| File.join(base, f) }

        res = false

        profiles.each do |bookmarks|
          next unless File.exist?(bookmarks)

          profile = bookmarks.match(%r{Edge/([^/]+)/})[1]

          SL.notify("Searching Chrome History for profile #{profile}", term)
          res = search_chromium_history(bookmarks, term)

          break if res
        end

        res
      end


def search_edge_bookmarks(term)
        base = File.expand_path("~/Library/Application Support/Microsoft Edge")
        profiles = Dir.glob("**/Bookmarks", base: base)
        profiles.delete_if { |p| p =~ /^Snapshots/ }
        profiles.map! { |f| File.join(base, f) }

        res = false

        profiles.each do |bookmarks|
          next unless File.exist?(bookmarks)

          profile = bookmarks.match(%r{Edge/([^/]+)/})[1]

          SL.notify("Searching Edge Bookmarks for profile #{profile}", term)
          res = search_chromium_bookmarks(bookmarks, term)
          break if res
        end

        res
      end


def search_chrome_bookmarks(term)
        base = File.expand_path("~/Library/Application Support/Google/Chrome/")
        profiles = Dir.glob("**/Bookmarks", base: base)
        profiles.delete_if { |p| p =~ /^Snapshots/ }
        profiles.map! { |f| File.join(base, f) }

        res = false

        profiles.each do |bookmarks|
          next unless File.exist?(bookmarks)

          profile = bookmarks.match(%r{Chrome/([^/]+)/})[1]

          SL.notify("Searching Chrome Bookmarks for profile #{profile}", term)
          res = search_chromium_bookmarks(bookmarks, term)
          break if res
        end

        res
      end


def search_arc_json(bookmarks_file, term)
        arc_bookmarks = JSON.parse(IO.read(bookmarks_file))

        exact_match = false
        match_phrases = []

        # If search terms start with ''term, only search for exact string matches
        if term =~ /^ *'/
          exact_match = true
          term.gsub!(/(^ *'+|'+ *$)/, "")
        elsif term =~ /%22(.*?)%22/
          match_phrases = term.scan(/%22(\S.*?\S)%22/)
          term.gsub!(/%22(\S.*?\S)%22/, "")
        end

        if arc_bookmarks
          bookmarks = []
          arc_bookmarks["sidebarSyncState"]["items"].each do |mark|
            next if mark.is_a?(String)

            next unless mark["value"]["childrenIds"].empty?

            next unless mark["value"]["data"]["tab"]

            url = {
              url: mark["value"]["data"]["tab"]["savedURL"],
              saved_title: mark["value"]["data"]["tab"]["savedTitle"],
              title: mark["value"]["title"],
              created: mark["value"]["createdAt"].to_datetime,
              active: mark["value"]["data"]["tab"]["timeLastActiveAt"]&.to_datetime
            }

            score = score_mark(url, term)

            if score > 7
              url[:score] = score
              bookmarks << url
            end
          end

          unless bookmarks.empty?
            if exact_match
              bookmarks.delete_if do |bm|
                !(bm[:url].matches_exact(term) ||
                bm[:title].matches_exact(term) ||
                bm[:saved_title].matches_exact(term))
              end
            end

            if match_phrases
              match_phrases.map! { |phrase| phrase[0] }
              bookmarks.delete_if do |bm|
                matched = true
                match_phrases.each do |phrase|
                  matched = false unless bm[:url].matches_exact(phrase) ||
                                         bm[:title].matches_exact(phrase) ||
                                         bm[:saved_title].matches_exact(phrase)
                end
                !matched
              end
            end

            return false if bookmarks.empty?

            lastest_bookmark = bookmarks.min_by { |u| u[:created] }

            return [lastest_bookmark[:url], lastest_bookmark[:title], lastest_bookmark[:date]]
          end
        end

        false
      end


def search_chromium_bookmarks(bookmarks_file, term)
        chrome_bookmarks = JSON.parse(IO.read(bookmarks_file))

        exact_match = false
        match_phrases = []

        # If search terms start with ''term, only search for exact string matches
        if term =~ /^ *'/
          exact_match = true
          term.gsub!(/(^ *'+|'+ *$)/, "")
        elsif term =~ /%22(.*?)%22/
          match_phrases = term.scan(/%22(\S.*?\S)%22/)
          term.gsub!(/%22(\S.*?\S)%22/, "")
        end

        if chrome_bookmarks
          roots = chrome_bookmarks["roots"]

          urls = extract_chrome_bookmarks(roots, [], term)

          unless urls.empty?
            urls.delete_if { |bm| !(bm[:url].matches_exact(term) || bm[:title].matches_exact(term)) } if exact_match

            if match_phrases
              match_phrases.map! { |phrase| phrase[0] }
              urls.delete_if do |bm|
                matched = true
                match_phrases.each do |phrase|
                  matched = false unless bm[:url].matches_exact(phrase) || bm[:title].matches_exact(phrase)
                end
                !matched
              end
            end

            return false if urls.empty?

            lastest_bookmark = urls.max_by { |u| u[:score] }

            return [lastest_bookmark[:url], lastest_bookmark[:title], lastest_bookmark[:date]]
          end
        end

        false
      end


def reset(server_id, dist: "Ubuntu 22.04.2 LTS base")
    create_connection.post(path: "/reset/#{server_id}", body: "type=hw", expects: 200)
    nil
  end

# plugging it in again. Reset should only be used when reboot does not work.


def load(snap = nil)
    Object.const_get("::Prog::" + prog).new(self, snap)
  end


def download_cloud_hypervisor(version_x64: nil, version_arm64: nil, sha256_ch_bin_x64: nil, sha256_ch_bin_arm64: nil, sha256_ch_remote_x64: nil, sha256_ch_remote_arm64: nil)
    version, sha256_ch_bin, sha256_ch_remote = if arch == "x64"
      [version_x64, sha256_ch_bin_x64, sha256_ch_remote_x64]
    elsif arch == "arm64"
      [version_arm64, sha256_ch_bin_arm64, sha256_ch_remote_arm64]
    else
      fail "BUG: unexpected architecture"
    end
    fail ArgumentError, "No version provided" if version.nil?
    Strand.create_with_id(prog: "DownloadCloudHypervisor", label: "start", stack: [{subject_id: id, version: version, sha256_ch_bin: sha256_ch_bin, sha256_ch_remote: sha256_ch_remote}])
  end

# Introduced for downloading cloud hypervisor via REPL.


def hardware_reset
    Hosting::Apis.hardware_reset_server(self)
  end


def allowed_cpus_cgroup
    @allowed_cpus_cgroup ||= cpus.map(&:cpu_number).sort.slice_when { |a, b| b != a + 1 }.map do |group|
      (group.size > 1) ? "#{group.first}-#{group.last}" : group.first.to_s
    end.join(",")
  end


def ping_gateway
    api_key_ds = DB[:api_key]
      .where(owner_table: "project")
      .where(used_for: "inference_endpoint")
      .where(is_valid: true)
      .where(owner_id: Sequel[:project][:id])
      .exists

    eligible_projects_ds = Project.where(api_key_ds)
    eligible_projects_ds = eligible_projects_ds.where(id: inference_endpoint.project.id) unless inference_endpoint.is_public

    eligible_projects = eligible_projects_ds.all
      .select(&:active?)
      .map do
      {
        ubid: _1.ubid,
        api_keys: _1.api_keys.select { |k| k.used_for == "inference_endpoint" && k.is_valid }.map { |k| Digest::SHA2.hexdigest(k.key) },
        quota_rps: 50.0,
        quota_tps: 5000.0
      }
    end

    body = {
      replica_ubid: inference_endpoint_replica.ubid,
      public_endpoint: inference_endpoint.is_public,
      projects: eligible_projects
    }

    resp = vm.sshable.cmd("sudo curl -m 5 -s -H \"Content-Type: application/json\" -X POST --data-binary @- --unix-socket /ie/workdir/inference-gateway.clover.sock http://localhost/control", stdin: body.to_json)
    project_usage = JSON.parse(resp)["projects"]
    Clog.emit("Successfully pinged inference gateway.") { {inference_endpoint: inference_endpoint.ubid, replica: inference_endpoint_replica.ubid, project_usage: project_usage} }
    update_billing_records(project_usage)
  end

# pushes latest config to inference gateway and collects billing information


def consolidate_rules(rules)
    port_segments = create_port_segments(rules)
    consolidated_rules = []

    port_segments.each do |segment|
      # Find rules that overlap with the current segment
      overlapping_rules = rules.select do |r|
        r.port_range.begin <= segment[:end] && r.port_range.end - 1 >= segment[:begin]
      end

      # Merge cidrs for overlapping rules
      merged_cidrs = if rules.first.cidr.version == 4
        NetAddr.summ_IPv4Net(overlapping_rules.map(&:cidr))
      else
        NetAddr.summ_IPv6Net(overlapping_rules.map(&:cidr))
      end
      merged_cidrs.each do |cidr|
        consolidated_rules << FirewallRuleObj.new(cidr, {begin: segment[:begin], end: segment[:end] + 1})
      end
    end

    combined_rules = combine_continuous_ranges_for_same_subnet(consolidated_rules)
    combined_rules_self = combined_rules.map do |r|
      if r.port_range[:begin] != r.port_range[:end] - 1
        "#{r.cidr} . #{r.port_range[:begin]}-#{r.port_range[:end] - 1}"
      else
        "#{r.cidr} . #{r.port_range[:begin]}"
      end
    end.join(",")

    combined_rules_lb_dest = vm.load_balancer ? combined_rules.filter_map do |r|
      if r.port_range[:begin] <= vm.load_balancer.src_port && vm.load_balancer.src_port <= r.port_range[:end] - 1
        "#{r.cidr} . #{vm.load_balancer.dst_port}"
      end
    end.join(",") : []
    [combined_rules_self, combined_rules_lb_dest]
  end

# nftables side to avoid performance issues.


def r(commandline, stdin: "", expect: [0])
  stdout, stderr, status = Open3.capture3(commandline, stdin_data: stdin)
  fail CommandFail.new("command failed: " + commandline, stdout, stderr) unless expect.include?(status.exitstatus)
  stdout
end


def serpapi_api_key(**kwargs)
      key_lookup(:serpapi_api_key, kwargs)
    end

# @return [String] The SerpAPI API key either from arg or env.


def validate_inputs(inputs:)
      missing_keys = input_keys - inputs.keys
      raise "Missing some input keys: #{missing_keys}" if missing_keys.any?

      inputs
    end

# @raise [RuntimeError] If the inputs are not the same.


def apply(input_list:)
      raise NotImplementedError
    end

# @return [Array<Boxcars::Boxcar>] The list of outputs.


def save(path:)
      File.write(path, YAML.dump(self))
    end


def check_lines(lines)
      raise ArgumentError, "Lines must be an array" unless lines.is_a?(Array)

      lines.each do |ln|
        raise ArgumentError, "Conversation item must be a array" unless ln.is_a?(Array)
        raise ArgumentError, "Conversation item must have 2 items, role and text" unless ln.size == 2
        raise ArgumentError, "Conversation item must have a role #{ln} in (#{PEOPLE})" unless PEOPLE.include? ln[0]
      end
    end


# @return [Array] The result as a convesation array


def as_messages(inputs = nil)
      { messages: no_history.map { |ln| { role: ln.first, content: cformat(ln.last, inputs) } } }
    rescue ::KeyError => e
      first_line = e.message.to_s.split("\n").first
      Boxcars.error "Missing prompt input key: #{first_line}"
      raise KeyError, "Prompt format error: #{first_line}"
    end

# @return [Hash] The formatted prompt { messages: ...}


def as_messages(inputs)
      conversation.as_messages(inputs)
    end


def with_conversation(conversation)
      return self unless conversation

      new_prompt = dup
      new_prompt.conversation.add_conversation(conversation)
      new_prompt
    end

# tack on the ongoing conversation if present to the prompt


def initialize(description: 'Engine', name: nil, prompts: [], batch_size: 20)
      @name = name || self.class.name
      @description = description
      @prompts = prompts
      @batch_size = batch_size
    end

# @param batch_size [Integer] The number of prompts to send to the Engine at a time.


def return_stopped_response(early_stopping_method, intermediate_steps, **kwargs)
      case early_stopping_method
      when "force"
        TrainFinish.new({ output: "Agent stopped due to max iterations." }, "")
      when "generate"
        thoughts = ""
        intermediate_steps.each do |action, observation|
          thoughts += action.log
          thoughts += "\n#{observation_text(observation)}\n#{engine_prefix}"
        end
        thoughts += "\n\nI now need to return a final answer based on the previous steps:"
        new_inputs = { agent_scratchpad: thoughts, stop: _stop }
        full_inputs = kwargs.merge(new_inputs)
        full_output = predict(**full_inputs)
        parsed_output = extract_boxcar_and_input(full_output)
        if parsed_output.nil?
          TrainFinish.new({ output: full_output }, full_output)
        else
          boxcar, boxcar_input = parsed_output
          Boxcars.debug "Got boxcar #{boxcar} and input #{boxcar_input}"
          if boxcar == finish_boxcar_name
            TrainFinish.new({ output: boxcar_input }, full_output)
          else
            TrainFinish.new({ output: full_output }, full_output)
          end
        end
      else
        raise "early_stopping_method should be one of `force` or `generate`, got #{early_stopping_method}"
      end
    end

# @return [Boxcars::Action] The action to take.


def call(inputs:)
      prepare_for_new_call
      intermediate_steps = []
      iterations = 0
      while should_continue?(iterations)
        output = plan(intermediate_steps, **inputs)
        return pre_return(output, intermediate_steps) if output.is_a?(TrainFinish)

        if (boxcar = name_to_boxcar_map[output.boxcar])
          begin
            observation = Observation.ok(get_boxcar_result(boxcar, output.boxcar_input))
            return_direct = boxcar.return_direct
          rescue Boxcars::ConfigurationError, Boxcars::SecurityError => e
            raise e
          rescue StandardError => e
            Boxcars.error "Error in #{boxcar.name} train#call: #{e}\nbt:#{caller[0..5].join("\n   ")}", :red
            observation = Observation.err("Error - #{e}, correct and try again.")
          end
        elsif output.boxcar == :error
          observation = output.log
          return_direct = false
        else
          observation = Observation.err("Error - #{output.boxcar} is not a valid action, try again.")
          return_direct = false
        end
        Boxcars.debug "Observation: #{observation}", :green
        intermediate_steps.append([output, observation])
        if return_direct
          output = TrainFinish.new({ return_values[0] => observation }, "")
          return pre_return(output, intermediate_steps)
        end
        iterations += 1
      end
      output = return_stopped_response(early_stopping_method, intermediate_steps, **inputs)
      pre_return(output, intermediate_steps)
    end


def call(query:, count: 1)
      validate_query(query)
      query_vector = convert_query_to_vector(query)
      @vector_search_instance.call(query_vector: query_vector, count: count)
    end


def initialize(prompt:, engine: nil, **kwargs)
      @prompt = prompt
      @engine = engine || Boxcars.engine.new
      @top_k = kwargs.delete(:top_k) || 5
      @stop = kwargs.delete(:stop) || ["Answer:"]
      super(**kwargs)
    end

# @param kwargs [Hash] Additional arguments including: name, description, top_k, return_direct, and stop


def input_key
      input_keys.first
    end


def extract_answer(data)
      reply = data
      Result.new(status: :ok, answer: reply, explanation: reply)
    end


def get_num_tokens(text:)
      text.split.length # TODO: hook up to token counting gem
    end


def max_tokens_for_prompt(prompt_text)
      num_tokens = get_num_tokens(prompt_text)

      # get max context size for model by name
      max_size = modelname_to_contextsize(model_name)
      max_size - num_tokens
    end

# @return [Integer] the number of tokens possible to generate.


def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], **kwargs)
      @llm_params = DEFAULT_PARAMS.merge(kwargs)
      @prompts = prompts
      @batch_size = 20
      super(description: description, name: name)
    end

# @param prompts [Array<String>] The prompts to use when asking the engine. Defaults to [].


def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
      @llm_parmas = DEFAULT_PARAMS.merge(kwargs)
      @prompts = prompts
      @batch_size = batch_size
      super(description: description, name: name)
    end

# @param batch_size [Integer] The number of prompts to send to the engine at once. Defaults to 20.


def run(question, **kwargs)
      prompt = Prompt.new(template: question)
      response = client(prompt: prompt, **kwargs)
      raise Error, "GeminiAI: No response from API" unless response

      check_response(response)
      response["choices"].map { |c| c.dig("message", "content") || c["text"] }.join("\n").strip
    end

# @param kwargs [Hash] Additional parameters to pass to the engine if wanted.


def default_params
      llm_params
    end

# Get the default parameters for the engine.


def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 2, **_kwargs)
      @prompts = prompts
      @batch_size = batch_size
      super(description: description, name: name)
    end

# @param batch_size [Integer] The number of prompts to send to the engine at once. Defaults to 2.


def client(prompt:, inputs: {}, **_kwargs)
      gpt4all = Gpt4all::ConversationalAI.new
      gpt4all.prepare_resources(force_download: false)
      gpt4all.start_bot
      input_text = prompt.as_prompt(inputs: inputs)[:prompt]
      Boxcars.debug("Prompt after formatting:\n#{input_text}", :cyan) if Boxcars.configuration.log_prompts
      gpt4all.prompt(input_text)
    rescue StandardError => e
      Boxcars.error(["Error from gpt4all engine: #{e}", e.backtrace[-5..-1]].flatten.join("\n   "))
    ensure
      gpt4all.stop_bot
    end


def run(question, **kwargs)
      prompt = Prompt.new(template: question)
      answer = client(prompt: prompt, **kwargs)
      Boxcars.debug("Answer: #{answer}", :cyan)
      answer
    end


def default_model_params
      {}
    end

# can be overridden by provider subclass


def validate_content(content)
      raise ArgumentError, "Content must have type and text fields" unless content[:type] && content[:text]

      content
    end


def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
      @open_ai_params = DEFAULT_PARAMS.merge(kwargs)
      if @open_ai_params[:model] =~ /^o/ && @open_ai_params[:max_tokens].present?
        @open_ai_params[:max_completion_tokens] = @open_ai_params.delete(:max_tokens)
        @open_ai_params.delete(:temperature)
      end

      @prompts = prompts
      @batch_size = batch_size
      super(description: description, name: name)
    end


def check_response(response, must_haves: %w[choices])
      if response['error']
        code = response.dig('error', 'code')
        msg = response.dig('error', 'message') || 'unknown error'
        raise KeyError, "OPENAI_ACCESS_TOKEN not valid" if code == 'invalid_api_key'

        raise ValueError, "OpenAI error: #{msg}"
      end

      must_haves.each do |key|
        raise ValueError, "Expecting key #{key} in response" unless response.key?(key)
      end
    end

# @raise [ValueError] if the response is not valid.


def initialize(name: DEFAULT_PER_NAME, description: DEFAULT_PER_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
      @perplexity_params = DEFAULT_PER_PARAMS.merge(kwargs)
      @prompts = prompts
      @batch_size = batch_size
      super(description: description, name: name)
    end


def initialize(boxcar:, log:, boxcar_input: nil)
      @boxcar_input = boxcar_input
      @boxcar = boxcar
      @log = log
    end

# @return [Boxcars::TrainAction] The train action.


def extract_boxcar_and_input(text)
      get_action_and_input(engine_output: text)
    rescue StandardError => e
      [:error, e.message]
    end

# @return [Array<Boxcars::Boxcar, String>] The boxcar and input.


def initialize(params)
          @split_chunk_size = params[:split_chunk_size] || 2000
          @training_data_path = File.absolute_path(params[:training_data_path])
          @embedding_tool = params[:embedding_tool] || :openai

          validate_params(embedding_tool, training_data_path)
          @memory_vectors = []
        end

# @return [Hash] vector_store: array of hashes with :content, :metadata, and :embedding keys


def set_distance_type! distance_function
      Flann.send(:flann_set_distance_type, distance_function, get_distance_order)
      self
    end

# Set the distance function to use when computing distances between data points.


def read_dataset filename
  Dir.chdir("spec") do
    f = File.new(filename, 'r')
    n = NMatrix.new([65536, 3], dtype: :float32)
    i = 0
    while line = f.gets
      line.chomp!
      fields = line.split
      n[i,:*] = fields.map { |field| field.to_f }

      i += 1
    end

    n
  end
end

# Helper function for reading a test dataset so we can test nearest neighbors


def initialize(socket_path: default_socket_path, argv: default_argv, debug: default_debug,
                   daemonize: default_daemonize, daemon_args: default_daemon_args,
                   worker_class: default_worker_class)
      @socket_path = socket_path
      @argv = argv
      @debug = debug
      if @daemonize = !!daemonize
        @daemon_args = Array(daemon_args)
      end
      @worker_class = worker_class
    end

# worker_class: The class to use for worker process handling, Worker by default.


def print_loaded_features
      puts $LOADED_FEATURES
    end

# Print <tt>$LOADED_FEATURES</tt> to stdout.


def reopen_stdio
      $stdin.reopen(@socket.recv_io(IO))
      $stdout.reopen(@socket.recv_io(IO))
      $stderr.reopen(@socket.recv_io(IO))
    end

# Replace stdin, stdout, stderr with the IO values provided by the client.


def update
    if @user.update(user_params)
      render json: @user
    else
      render json: @user.errors, status: :unprocessable_entity
    end
  end


def random_str(size)
  @db.get_first_value("select hex(randomblob(?))", size)
end

# sqlite database for fast random string generation


def read_entry(key, **options)
        deserialize_entry(@cache.get(key))
      end


def delete_entry(key, **options)
        @cache.delete(key)
      end


def subscribe(channel, subscriber, success_callback = nil)
    @subscribers.acquire do |subs|
      subs[channel] = {} unless subs[channel]
      subs[channel][subscriber] = true
    end
    success_callback&.call
    capture(:subscribe, channel)
  end

# subscribe to a channel, optionally providing a success callback proc


def increment(key, amount = 1, expires_in = nil)
    expires_in ||= @expires_in
    @conn.acquire { |cache| cache.stmts[:incrementer].execute!(key.to_s, amount, expires_in)[0][0] }
  end

# increment an integer value by amount, optionally add an expiry value (in seconds)


def with_connection
      @conn.acquire do |conn|
        @checked_out_conn = conn
        yield conn
      ensure
        @checked_out_conn = nil
      end
    end


def transaction(mode = :immediate)
    super(mode)
  end

# enforce immediate mode to avoid deadlocks for a small performance penalty


def delete(id)
    job = super(id)
    @logger.info("[litejob]:[DEL] job: #{job}")
    job = Oj.load(job[0], symbol_keys: true) if job
    job
  end


def clear(queue = nil)
    run_sql("DELETE FROM queue WHERE iif(?1 IS NOT NULL, name = ?1,  TRUE)", queue)
  end

# deletes all the entries in all queues, or if a queue name is given, deletes all entries in that specific queue


def supports_create_table_if_not_exists?
        sqlite_version >= 30300
      end

# SQLite supports CREATE TABLE IF NOT EXISTS syntax since 3.3.0.


def supports_deferrable_foreign_key_constraints?
        sqlite_version >= 30619
      end

# SQLite 3.6.19+ supports deferrable foreign key constraints.


def views(opts = OPTS)
        tables_and_views({type: "view"}, opts)
      end


def _foreign_key_list_ds(table)
        metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table))
      end

# Dataset used for parsing foreign key lists


def _parse_pragma_ds(table_name, opts)
        metadata_dataset.with_sql("PRAGMA table_#{"x" if sqlite_version > 33100}info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
      end


def complex_expression_sql_append(sql, op, args)
        case op
        when :"NOT LIKE", :"NOT ILIKE"
          sql << "NOT "
          complex_expression_sql_append(sql, ((op == :"NOT ILIKE") ? :ILIKE : :LIKE), args)
        when :^
          complex_expression_arg_pairs_append(sql, args) { |a, b| Sequel.lit(["((~(", " & ", ")) & (", " | ", "))"], a, b, a, b) }
        when :**
          unless (exp = args[1]).is_a?(Integer)
            raise(Sequel::Error, "can only emulate exponentiation on SQLite if exponent is an integer, given #{exp.inspect}")
          end
          case exp
          when 0
            sql << "1"
          else
            sql << "("
            arg = args[0]
            if exp < 0
              invert = true
              exp = exp.abs
              sql << "(1.0 / ("
            end
            (exp - 1).times do
              literal_append(sql, arg)
              sql << " * "
            end
            literal_append(sql, arg)
            sql << ")"
            if invert
              sql << "))"
            end
          end
        when :extract
          part = args[0]
          raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless (format = EXTRACT_MAP[part])
          sql << "CAST(strftime(" << format << ", "
          literal_append(sql, args[1])
          sql << ") AS " << ((part == :second) ? "NUMERIC" : "INTEGER") << ")"
        else
          super
        end
      end

# It doesn't support xor, power, or the extract function natively, so those have to be emulated.


def explain(opts = nil)
        # Load the PrettyTable class, needed for explain output
        Sequel.extension(:_pretty_table) unless defined?(Sequel::PrettyTable)

        ds = db.send(:metadata_dataset).clone(sql: "EXPLAIN #{select_sql}")
        rows = ds.all
        Sequel::PrettyTable.string(rows, ds.columns)
      end


def having(*cond)
        raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") if !@opts[:group] && db.sqlite_version < 33900
        super
      end


def insert_select(*values)
        return unless supports_insert_select?
        # Handle case where query does not return a row
        server?(:default).with_sql_first(insert_select_sql(*values)) || false
      end

# returning instead of a separate query.


def returning(*values)
        return super if values.empty?
        raise Error, "RETURNING is not supported on #{db.database_type}" unless supports_returning?(:insert)
        clone(returning: _returning_values(values).freeze)
      end

# Automatically add aliases to RETURNING values to work around SQLite bug.


def supports_cte?(type = :select)
        db.sqlite_version >= 30803
      end

# SQLite 3.8.3+ supports common table expressions.


def supports_deleting_joins?
        false
      end

# SQLite does not support deleting from a joined dataset


def supports_window_clause?
        db.sqlite_version >= 32800
      end

# SQLite 3.28+ supports the WINDOW clause.


def insert_on_conflict_sql(sql)
        if (opts = @opts[:insert_on_conflict])
          sql << " ON CONFLICT"

          if (target = opts[:constraint])
            sql << " ON CONSTRAINT "
            identifier_append(sql, target)
          elsif (target = opts[:target])
            sql << " "
            identifier_append(sql, Array(target))
            if (conflict_where = opts[:conflict_where])
              sql << " WHERE "
              literal_append(sql, conflict_where)
            end
          end

          if (values = opts[:update])
            sql << " DO UPDATE SET "
            update_sql_values_hash(sql, values)
            if (update_where = opts[:update_where])
              sql << " WHERE "
              literal_append(sql, update_where)
            end
          else
            sql << " DO NOTHING"
          end
        end
      end

# Add ON CONFLICT clause if it should be used


def literal_blob_append(sql, v)
        sql << "X'" << v.unpack1("H*") << "'"
      end

# SQLite uses a preceding X for hex escaping strings


def multi_insert_sql_strategy
        (db.sqlite_version >= 30711) ? :values : :union
      end

# starting in 3.7.11.  On older versions, fallback to using a UNION.


def _truncate_sql(table)
        "DELETE FROM #{table}"
      end

# SQLite treats a DELETE with no WHERE clause as a TRUNCATE


def set_hostname(hostname)
        assert_open
        Error.check self, Libtailscale::TsnetSetHostname(@t, hostname)
    end

# Set the hostname to use for the tailscale node.


def set_auth_key(auth_key)
        assert_open
        Error.check self, Libtailscale::TsnetSetAuthKey(@t, auth_key)
    end

# Set the auth key to use for the tailscale node.


def set_ephemeral(ephemeral)
        assert_open
        Error.check self, Libtailscale::TsnetSetEphemeral(@t, ephemeral ? 1 : 0)
    end

# Set whether the node is ephemeral or not.


def loopback
        assert_open
        addrbuf = FFI::MemoryPointer.new(:char, 1024)
        proxycredbuf = FFI::MemoryPointer.new(:char, 33)
        localcredbuf = FFI::MemoryPointer.new(:char, 33)
        Error.check self, Libtailscale::TsnetLoopback(@t, addrbuf, addrbuf.size, proxycredbuf, localcredbuf)
        [addrbuf.read_string, proxycredbuf.read_string, localcredbuf.read_string]
    end

# and credentials for using it as LocalAPI or a proxy.


def read_digits
      File.read("#{__dir__}/data/digits.csv")
    end

# Built with script/development/generate_digits_characters.rb


def latin_spoof?
      scripts != Set[LATN] && non_ascii_latin_letters.present?
    end

# Note that the non-ASCII Latin check should not be applied when the entire label is made of Latin.


def create(key, value, ttl: nil)
        juno_request = Juno::Client::JunoRequest.new(key: key,
                                                     value: value,
                                                     version: 0,
                                                     type: Juno::Client::JunoRequest::Type::CREATE,
                                                     time_to_live_s: ttl,
                                                     creation_time: Time.now.to_i)
        process_single(juno_request)
      end


def set_time_to_live(ttl)
        ttl = ttl.to_i
        raise ArgumentError, 'TTL should be > 0' unless ttl.positive?

        @time_to_live = ttl
        ttl = [ttl].pack(OffsetWidth.UINT32)
        add_field(MetadataField.new(0x01, 0x01, ttl))
      end

# @param ttl [Integer] - Record Time to live