Skip to content

Update APIpie components with the following: #16810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 18 commits into from

Conversation

Toocky
Copy link

@Toocky Toocky commented May 23, 2025

  • Updated Readme Docs
  • Create Chat completions request with optional tools
  • Create image
  • Create text to speech
  • Retrieve list of LLM models
  • Retrieve list of Image models
  • Retrieve list of Voice models
  • Retrieve list of Voices

WHY

APipie integration was only partially built in pipedream this provides basic actions for users to generate content and include in workflows.

Summary by CodeRabbit

  • New Features

    • Introduced integration with APIpie.ai, enabling unified access to multiple AI models for language, image, and voice tasks.
    • Added actions to generate images from prompts, convert text to speech, send chat completion requests, and retrieve available models and voices.
    • Enhanced model selection with dynamic loading of options for chat, image, and TTS models.
    • Included support for configuring advanced parameters and tools in chat completions.
  • Documentation

    • Added comprehensive README detailing setup, core features, and business use cases for the APIpie.ai integration.
  • Chores

    • Updated package metadata and added required dependencies.

- Updated Readme Docs
- Create Chat completions request with optional tools
- Create image
- Create text to speech
- Retrive list of LLM models
- Retrieve list of Image models
- Retrieve list of Voice models
- Retrieve list of Voices
Copy link

vercel bot commented May 23, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
pipedream-docs-redirect-do-not-edit ⬜️ Ignored (Inspect) Jun 6, 2025 6:05pm

Copy link

vercel bot commented May 23, 2025

@Toocky is attempting to deploy a commit to the Pipedreamers Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented May 23, 2025

Walkthrough

This update introduces a comprehensive integration for APIpie.ai, adding a new README, a fully implemented app client, and multiple actions for listing models, generating images, converting text to speech, and sending chat completions. Supporting utility and constants modules are also included, and package metadata is updated to reflect new dependencies.

Changes

File(s) Change Summary
components/apipie_ai/README.md Added a new README detailing APIpie.ai integration, usage, and business use cases.
components/apipie_ai/apipie_ai.app.mjs Converted from stub to full-featured app client with prop definitions and API methods for models, images, TTS, etc.
components/apipie_ai/common/constants.mjs New module exporting constants for effort levels, response formats, image sizes, tool types, etc.
components/apipie_ai/common/utils.mjs New module exporting a parseObject utility for safely parsing JSON strings or arrays.
components/apipie_ai/package.json Updated keywords, homepage, and added dependencies for @pipedream/platform and axios.
components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs New action for generating audio from text using the APIpie AI TTS service.
components/apipie_ai/actions/create-image/create-image.mjs New action for generating images from prompts via APIpie AI.
components/apipie_ai/actions/retrieve-available-image-models/retrieve-available-image-models.mjs New action for listing available image models.
components/apipie_ai/actions/retrieve-available-llm-models/retrieve-available-llm-models.mjs New action for listing available large language models (LLMs).
components/apipie_ai/actions/retrieve-available-tts-models/retrieve-available-tts-models.mjs New action for listing available text-to-speech models.
components/apipie_ai/actions/retrieve-available-tts-voices/retrieve-available-tts-voices.mjs New action for listing available TTS voices.
components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs New action for sending chat completion requests to LLMs with support for function tools and advanced parameters.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Action
    participant AppClient
    participant APIpieAI

    User->>Action: Trigger action (e.g., Create Image, TTS, Chat)
    Action->>AppClient: Call relevant method (e.g., createImage, createSpeech, sendChatCompletionRequest)
    AppClient->>APIpieAI: Make HTTP request to APIpie.ai endpoint
    APIpieAI-->>AppClient: Return response (image, audio, chat completion, etc.)
    AppClient-->>Action: Return processed result
    Action-->>User: Output result (file path, image URL, response data, etc.)
Loading

Suggested labels

action, enhancement, docs, ai-assisted

Suggested reviewers

  • michelle0927

Poem

In the garden of code where the AI models bloom,
New actions now flourish, dispelling the gloom.
Images appear with a prompt and a wish,
Voices are woven from text in a swish!
So hop along, dear dev, and see what’s anew—
APIpie’s delights are now waiting for you!
🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs

Oops! Something went wrong! :(

ESLint: 8.57.1

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'jsonc-eslint-parser' imported from /eslint.config.mjs
at Object.getPackageJSONURL (node:internal/modules/package_json_reader:255:9)
at packageResolve (node:internal/modules/esm/resolve:767:81)
at moduleResolve (node:internal/modules/esm/resolve:853:18)
at defaultResolve (node:internal/modules/esm/resolve:983:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:799:12)
at #cachedDefaultResolve (node:internal/modules/esm/loader:723:25)
at ModuleLoader.resolve (node:internal/modules/esm/loader:706:38)
at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:307:38)
at #link (node:internal/modules/esm/module_job:170:49)


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb98535 and 7942a9c.

📒 Files selected for processing (1)
  • components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@adolfo-pd adolfo-pd added the User submitted Submitted by a user label May 23, 2025
@pipedream-component-development
Copy link
Collaborator

Thank you so much for submitting this! We've added it to our backlog to review, and our team has been notified.

@pipedream-component-development
Copy link
Collaborator

Thanks for submitting this PR! When we review PRs, we follow the Pipedream component guidelines. If you're not familiar, here's a quick checklist:

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (8)
components/apipie_ai/common/utils.mjs (1)

1-24: Well-implemented JSON parsing utility

This utility function safely parses JSON strings or arrays of JSON strings, with good error handling to prevent exceptions. The implementation handles various input types gracefully.

Consider adding JSDoc comments to document the function's purpose, parameters, and return value for better maintainability:

+/**
+ * Safely parses JSON strings or arrays of JSON strings into JavaScript objects
+ * @param {any} obj - Input that may be a JSON string, array of JSON strings, or any other value
+ * @returns {any} - Parsed object(s) or the original input if parsing fails
+ */
export const parseObject = (obj) => {
  // Rest of function unchanged
components/apipie_ai/common/constants.mjs (1)

27-27: Remove unnecessary blank lines

Consider removing extra blank lines for consistent spacing throughout the file.

Also applies to: 63-63

components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs (1)

42-46: Add validation for outputFile parameter

The outputFile parameter is used for writing the audio file, but there's no validation to ensure it's a valid filename.

Consider adding validation or sanitization for the outputFile parameter:

  outputFile: {
    type: "string",
    label: "Output Filename",
    description: "The filename of the output audio file that will be written to the `/tmp` folder, e.g. `/tmp/myFile.mp3`",
+   validate: {
+     type: "string",
+     minLength: 1,
+   },
  },
components/apipie_ai/actions/retrieve-available-tts-voices-models/retrieve-available-tts-voices-models.mjs (1)

4-4: Rename key for consistency with action name

The key apipie_ai-retrieve-available-tts-voices-models includes "models" at the end, which is inconsistent with the action name "Retrieve Available TTS Voices".

Consider renaming the key for better consistency:

-  key: "apipie_ai-retrieve-available-tts-voices-models",
+  key: "apipie_ai-retrieve-available-tts-voices",
components/apipie_ai/actions/create-image/create-image.mjs (1)

55-67: Handle response_format directly & consider undefined props

response_format can already be taken verbatim from the UI (it is limited by the prop definition), therefore the url/b64_json ternary is redundant and silently overrides any future formats the API might add (e.g. webp). You can simply pass the value if it exists, letting the API perform validation, and omit the key when the user leaves the field blank.

-        response_format: this.responseFormat === "url"
-          ? this.responseFormat
-          : "b64_json",
+        ...(this.responseFormat && { response_format: this.responseFormat }),
components/apipie_ai/README.md (1)

14-24: Fix heading level & punctuation to satisfy markdown-lint

markdownlint-cli2 flagged MD001 (heading increments) and LanguageTool flagged a missing comma. Increment the heading level consistently (#####) and add the comma after scenarios, e.g.

-### **Multi-Model AI Experimentation Framework**
+## **Multi-Model AI Experimentation Framework**

-... business scenarios while maintaining ...
+... business scenarios, while maintaining ...
🧰 Tools
🪛 LanguageTool

[uncategorized] ~24-~24: Possible missing comma found.
Context: ... models work best for specific business scenarios while maintaining the flexibility to sw...

(AI_HYDRA_LEO_MISSING_COMMA)

🪛 markdownlint-cli2 (0.17.2)

14-14: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (1)

130-150: _buildTools returns silent undefined for non-function tools

Because the options array is limited to "function" only (line 83), the first filter removes every element, so the map in line 130 always receives an empty array.
If you plan to support future tool types, drop the first filter and rely on the options list itself:

-const tools = this.toolTypes?.filter((toolType) => toolType !== "function")?.map((toolType) => ({
-  type: toolType,
-})) || [];
+const tools = this.toolTypes?.filter((toolType) => toolType !== "function")
+  ?.map((type) => ({ type })) || [];

Otherwise, remove the dead code to keep things simple.

components/apipie_ai/apipie_ai.app.mjs (1)

215-223: Default $ argument may break outside action context

When option loaders (e.g. chatCompletionModelId.options()) call _makeRequest without passing $, the default this (the app object) is forwarded to axios, which expects the step context. Consider falling back to this.$ (provided by Pipedream) when $ is not supplied:

 _makeRequest({
-  $ = this, path, ...opts
+  $, path, ...opts
 }) {
+  $ = $ || this.$;
   return axios($, {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d91decd and c75d023.

📒 Files selected for processing (12)
  • components/apipie_ai/README.md (1 hunks)
  • components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs (1 hunks)
  • components/apipie_ai/actions/create-image/create-image.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-image-models/retrieve-available-image-models.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-llm-models/retrieve-available-llm-models.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-tts-models/retrieve-available-tts-models.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-tts-voices-models/retrieve-available-tts-voices-models.mjs (1 hunks)
  • components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (1 hunks)
  • components/apipie_ai/apipie_ai.app.mjs (1 hunks)
  • components/apipie_ai/common/constants.mjs (1 hunks)
  • components/apipie_ai/common/utils.mjs (1 hunks)
  • components/apipie_ai/package.json (1 hunks)
🧰 Additional context used
🪛 LanguageTool
components/apipie_ai/README.md

[uncategorized] ~24-~24: Possible missing comma found.
Context: ... models work best for specific business scenarios while maintaining the flexibility to sw...

(AI_HYDRA_LEO_MISSING_COMMA)

🪛 markdownlint-cli2 (0.17.2)
components/apipie_ai/README.md

14-14: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🔇 Additional comments (5)
components/apipie_ai/package.json (3)

8-8: Keyword format improvement

Good change - using a space in "apipie ai" rather than underscore makes for a more natural keyword representation.


10-10: URL format consistency

Good change to use hyphens in the URL path, which is more consistent with web URL conventions.


15-17: Proper dependencies added

Good addition of the required dependencies with appropriate version constraints. The axios dependency is essential for making API requests to the APIpie service, and @pipedream/platform provides the necessary Pipedream functionality.

components/apipie_ai/common/constants.mjs (1)

1-72: Well-organized constants module

Good organization of constants related to AI capabilities. Centralizing these values will help maintain consistency across the integration.

The mix of simple arrays and label/value objects is appropriate based on how they'll be used in UI components versus direct API calls.

components/apipie_ai/actions/retrieve-available-llm-models/retrieve-available-llm-models.mjs (1)

7-7: Good use of external reference

Nice touch including a link to the dashboard in the description, which helps users understand where to find more information about the models.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
components/apipie_ai/apipie_ai.app.mjs (2)

244-252: ⚠️ Potential issue

Fix voice endpoint path construction.

This addresses the issue mentioned in previous reviews. The current path construction creates an invalid URL models?voices&model=... which will result in a 404 error.

Apply this fix to construct the correct endpoint:

     listVoices(opts = {}) {
-      let queryString = "voices";
+      let path = "voices";
       if (opts.model) {
-        queryString += `&model=${encodeURIComponent(opts.model)}`;
+        path += `?model=${encodeURIComponent(opts.model)}`;
       }
       return this._makeRequest({
-        path: `models?${queryString}`,
+        path,
       });
     },

253-259: 🛠️ Refactor suggestion

Fix typo in method name.

The method name contains a typo that should be corrected for consistency and professionalism.

-    sendChatCompetionRequest(opts = {}) {
+    sendChatCompletionRequest(opts = {}) {
       return this._makeRequest({
         method: "POST",
         path: "chat/completions",
         ...opts,
       });
     },
🧹 Nitpick comments (2)
components/apipie_ai/apipie_ai.app.mjs (2)

60-65: Consider using numeric types for numeric parameters.

Several properties that represent numeric ranges are defined as strings, which may cause confusion and require additional validation. Consider using "number" type instead of "string" for parameters with numeric ranges:

  • temperature (range: [0, 2])
  • topP (range: (0, 1])
  • frequencyPenalty (range: [-2, 2])
  • presencePenalty (range: [-2, 2])
  • repetitionPenalty (range: (0, 2])

Example for temperature:

-    temperature: {
-      type: "string",
+    temperature: {
+      type: "number",
       label: "Temperature",
       description: "Sampling temperature. **(range: [0, 2])**.",
+      min: 0,
+      max: 2,
       optional: true,
     },

Also applies to: 72-77, 85-102


119-135: Improve robustness of voice options loading.

The voice options loading could be more robust in handling missing model references. The current implementation assumes opts.model or this.model will be available, but this might not always be the case depending on how the component is used.

Consider adding more defensive programming:

     async options(opts) {
       // Get the selected model from the component props
-      const model = opts.model || this.model;
+      const model = opts.model || this.model || opts.parent?.model;
       if (!model) {
-        return [];
+        return [{
+          label: "Please select a model first",
+          value: "",
+          disabled: true
+        }];
       }
       
-      const { data } = await this.listVoices({ model });
+      try {
+        const { data } = await this.listVoices({ model });
+        return data.map(({
+          voice_id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      } catch (error) {
+        console.error("Failed to load voices:", error);
+        return [];
+      }
-
-      return data.map(({
-        voice_id: value, name: label,
-      }) => ({
-        label,
-        value,
-      }));
     },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c75d023 and 4d485b4.

📒 Files selected for processing (2)
  • components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (1 hunks)
  • components/apipie_ai/apipie_ai.app.mjs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs
🔇 Additional comments (4)
components/apipie_ai/apipie_ai.app.mjs (4)

202-223: Good implementation of base API infrastructure.

The base methods for API key handling, URL construction, headers, and request wrapper are well-structured and follow good practices. The User-Agent header properly identifies the Pipedream integration.


260-280: URL path construction is now correct.

The paths for createImage, createSpeech, and createEmbeddings no longer have leading slashes, which prevents the double-slash issue that was identified in previous reviews. Good fix!


7-52: Well-structured async model loading.

The propDefinitions for model selection (chat completion, image, and TTS models) are well-implemented with async options that dynamically load available models from the API. This provides a good user experience by showing only valid model options.


155-199: Comprehensive image generation properties.

The image generation properties are well-defined with appropriate defaults, validation, and use of constants for enum values. The reloadProps feature for response format is a nice touch for dynamic UI updates.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
components/apipie_ai/README.md (2)

16-16: Fix heading level hierarchy.

The heading structure violates markdown conventions by jumping from h2 to h3 without an intermediate level.

- ### **Customer Support Automation**
+ ## **Customer Support Automation**

Apply the same fix to other h3 headings at lines 20, 24, and 28 to maintain proper heading hierarchy.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

16-16: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)


30-30: Add missing comma for clarity.

The sentence could benefit from a comma to improve readability.

- ...models work best for specific business scenarios while maintaining the flexibility to switch providers as new models become available.
+ ...models work best for specific business scenarios, while maintaining the flexibility to switch providers as new models become available.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~30-~30: Possible missing comma found.
Context: ... models work best for specific business scenarios while maintaining the flexibility to sw...

(AI_HYDRA_LEO_MISSING_COMMA)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d485b4 and 92f4ee3.

📒 Files selected for processing (3)
  • components/apipie_ai/README.md (1 hunks)
  • components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs (1 hunks)
  • components/apipie_ai/apipie_ai.app.mjs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs
🧰 Additional context used
🪛 LanguageTool
components/apipie_ai/README.md

[uncategorized] ~30-~30: Possible missing comma found.
Context: ... models work best for specific business scenarios while maintaining the flexibility to sw...

(AI_HYDRA_LEO_MISSING_COMMA)

🪛 markdownlint-cli2 (0.17.2)
components/apipie_ai/README.md

16-16: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🔇 Additional comments (4)
components/apipie_ai/apipie_ai.app.mjs (3)

196-217: LGTM! Well-structured API helper methods.

The helper methods for API key retrieval, URL construction, headers, and request making follow good practices with proper encapsulation and reusability.


218-241: LGTM! API methods are well-implemented.

The model listing and voice listing methods correctly construct query parameters and follow consistent patterns. The endpoint structure appears to be correct based on the author's previous clarification.


242-262: LGTM! Request methods follow good patterns.

The chat completion, image creation, and speech creation methods properly delegate to the base _makeRequest method with appropriate HTTP methods and paths.

components/apipie_ai/README.md (1)

1-47: Excellent comprehensive documentation.

The README provides clear, well-structured documentation that effectively explains the APIpie integration's capabilities, use cases, and getting started process. The business use cases are particularly valuable for users understanding practical applications.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~30-~30: Possible missing comma found.
Context: ... models work best for specific business scenarios while maintaining the flexibility to sw...

(AI_HYDRA_LEO_MISSING_COMMA)

🪛 markdownlint-cli2 (0.17.2)

16-16: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
components/apipie_ai/apipie_ai.app.mjs (1)

8-57: Consider refactoring the complex modelId prop logic.

The modelId prop contains complex conditional logic that branches based on modelType and handles TTS models differently. This violates the single responsibility principle and makes the prop definition hard to maintain.

Consider creating separate, more focused prop definitions:

+    llmModelId: {
+      type: "string",
+      label: "LLM Model",
+      description: "The ID of the LLM model to use.",
+      async options() {
+        const { data } = await this.listLlmModels();
+        return this._formatModelOptions(data);
+      },
+    },
+    imageModelId: {
+      type: "string", 
+      label: "Image Model",
+      description: "The ID of the image model to use.",
+      async options() {
+        const { data } = await this.listImageModels();
+        return this._formatModelOptions(data);
+      },
+    },
+    ttsModelId: {
+      type: "string",
+      label: "TTS Model", 
+      description: "The ID of the TTS model to use.",
+      async options() {
+        const { data } = await this.listTtsModels();
+        return this._formatTtsModelOptions(data);
+      },
+    },

And add helper methods:

_formatModelOptions(data) {
  const uniqueModels = new Map();
  data.forEach(({ id, name }) => {
    if (!uniqueModels.has(id)) {
      uniqueModels.set(id, name);
    }
  });
  return Array.from(uniqueModels.entries())
    .map(([value, label]) => ({ label, value }))
    .sort((a, b) => a.label.localeCompare(b.label));
},

_formatTtsModelOptions(data) {
  const uniqueModels = new Map();
  data.forEach(({ id, name, route }) => {
    if (!uniqueModels.has(id)) {
      uniqueModels.set(id, { name, route });
    }
  });
  return Array.from(uniqueModels.entries())
    .map(([id, { name, route }]) => ({
      label: name,
      value: JSON.stringify({ id, route }),
    }))
    .sort((a, b) => a.label.localeCompare(b.label));
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92f4ee3 and 07b7160.

📒 Files selected for processing (8)
  • components/apipie_ai/README.md (1 hunks)
  • components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs (1 hunks)
  • components/apipie_ai/actions/create-image/create-image.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-tts-voices/retrieve-available-tts-voices.mjs (1 hunks)
  • components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (1 hunks)
  • components/apipie_ai/apipie_ai.app.mjs (1 hunks)
  • components/apipie_ai/common/constants.mjs (1 hunks)
  • components/apipie_ai/common/utils.mjs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • components/apipie_ai/common/constants.mjs
🚧 Files skipped from review as they are similar to previous changes (5)
  • components/apipie_ai/common/utils.mjs
  • components/apipie_ai/README.md
  • components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs
  • components/apipie_ai/actions/create-image/create-image.mjs
  • components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs
🔇 Additional comments (2)
components/apipie_ai/actions/retrieve-available-tts-voices/retrieve-available-tts-voices.mjs (1)

1-21: LGTM! Clean and well-structured action implementation.

This action follows Pipedream best practices with proper metadata, clear description, and a straightforward implementation that calls the app's listVoices method and provides a useful summary export.

components/apipie_ai/apipie_ai.app.mjs (1)

197-262: LGTM! Well-structured API methods with good separation of concerns.

The methods section follows good practices with:

  • Private helper methods for configuration (_apiKey, _apiUrl, _getHeaders)
  • A centralized _makeRequest method for axios calls
  • Specific API methods that are clean and focused
  • Proper method naming and parameter handling

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
components/apipie_ai/apipie_ai.app.mjs (3)

8-66: 🛠️ Refactor suggestion

Refactor duplicated model selection logic to improve maintainability.

The options() methods for chatCompletionModelId, imageModelId, and ttsModelId contain nearly identical code for fetching, deduplicating, mapping, and sorting models. This violates the DRY principle and makes the code harder to maintain.

Consider extracting the common logic into a reusable helper method:

+ _createModelOptions(apiMethod) {
+   return async function() {
+     const { data } = await apiMethod.call(this);
+     const uniqueModels = new Map();
+     data.forEach(({ id, name }) => {
+       if (!uniqueModels.has(id)) {
+         uniqueModels.set(id, name);
+       }
+     });
+     return Array.from(uniqueModels.entries())
+       .map(([value, label]) => ({
+         label,
+         value,
+       }))
+       .sort((a, b) => a.label.localeCompare(b.label));
+   };
+ },

  chatCompletionModelId: {
    type: "string",
    label: "Completions Model",
    description: "The ID of the LLM model to use for completions.",
-   async options() {
-     const { data } = await this.listLlmModels();
-     const uniqueModels = new Map();
-     data.forEach(({ id, name }) => {
-       if (!uniqueModels.has(id)) {
-         uniqueModels.set(id, name);
-       }
-     });
-     return Array.from(uniqueModels.entries())
-       .map(([value, label]) => ({
-         label,
-         value,
-       }))
-       .sort((a, b) => a.label.localeCompare(b.label));
-   },
+   options: this._createModelOptions(this.listLlmModels),
  },

89-96: ⚠️ Potential issue

Fix range validation for topP to match documented bounds.

The description specifies the range as "(0, 1]" (greater than 0), but the minimum is set to 0, which contradicts the exclusive lower bound.

Apply this fix to properly exclude zero:

  topP: {
    type: "number",
    label: "Top P",
    description: "Top-p sampling value. **(range: (0, 1])**.",
-   min: 0,
+   min: 0.000001,
    max: 1.0,
    optional: true,
  },

120-127: ⚠️ Potential issue

Fix range validation for repetitionPenalty to match documented bounds.

The description states the range should be "(0, 2]" (greater than 0), but the minimum is set to 0, which allows zero values contrary to the documented exclusive lower bound.

Apply this fix to match the documented range:

  repetitionPenalty: {
    type: "number",
    label: "Repetition Penalty",
    description: "Repetition penalty. **(range: (0, 2])**.",
-   min: 0,
+   min: 0.000001,
    max: 2.0,
    optional: true,
  },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83f4701 and 3803778.

📒 Files selected for processing (1)
  • components/apipie_ai/apipie_ai.app.mjs (1 hunks)
🔇 Additional comments (2)
components/apipie_ai/apipie_ai.app.mjs (2)

207-230: LGTM! Well-structured helper methods.

The helper methods follow good practices with clear separation of concerns:

  • _apiKey(), _apiUrl(), and _getHeaders() provide clean access to configuration
  • _makeRequest() serves as a solid foundation for all API calls with proper error handling delegation to axios

231-271: LGTM! API methods are correctly implemented.

All API methods properly utilize the _makeRequest helper with appropriate HTTP methods and paths. The parameter handling through the spread operator allows for flexible option passing.

Comment on lines 156 to 160
toolOutputs: {
type: "string[]",
label: "Tool Outputs",
description: "The outputs from the tool calls. Each object in the array should contain properties `tool_call_id` and `output`.",
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Clarify toolOutputs property structure and validation.

The property accepts string[] but the description mentions objects with tool_call_id and output properties. This type mismatch could lead to confusion.

Consider either:

  1. Changing the type to object[] if expecting structured objects
  2. Updating the description to clarify the expected string format
  3. Adding validation logic to parse JSON strings
  toolOutputs: {
-   type: "string[]",
+   type: "object[]",
    label: "Tool Outputs",
    description: "The outputs from the tool calls. Each object in the array should contain properties `tool_call_id` and `output`.",
  },

Or if keeping as strings, clarify the format:

  toolOutputs: {
    type: "string[]",
    label: "Tool Outputs",
-   description: "The outputs from the tool calls. Each object in the array should contain properties `tool_call_id` and `output`.",
+   description: "The outputs from the tool calls as JSON strings. Each string should represent an object with properties `tool_call_id` and `output`.",
  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
toolOutputs: {
type: "string[]",
label: "Tool Outputs",
description: "The outputs from the tool calls. Each object in the array should contain properties `tool_call_id` and `output`.",
},
toolOutputs: {
type: "object[]",
label: "Tool Outputs",
description: "The outputs from the tool calls. Each object in the array should contain properties `tool_call_id` and `output`.",
},
🤖 Prompt for AI Agents
In components/apipie_ai/apipie_ai.app.mjs around lines 156 to 160, the
toolOutputs property is typed as string[] but described as containing objects
with tool_call_id and output properties, causing a type-description mismatch. To
fix this, either change the type to object[] to match the described structure,
update the description to specify the exact string format expected if keeping
string[], or add validation logic to parse the strings into objects. Choose one
approach to ensure the type and description are consistent and clear.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (13)
components/apipie_ai/actions/retrieve-available-tts-models/retrieve-available-tts-models.mjs (3)

12-18: Good improvement: Error handling has been properly implemented

The previous review feedback about missing error handling has been successfully addressed. The try/catch implementation is well done.


20-20: Improve error export consistency

The error export uses a custom key which is inconsistent with Pipedream patterns. Consider using $summary for error messages.

-      $.export("Error fetching TTS Models", e);
+      $.export("$summary", "Failed to retrieve TTS models");

21-21: ⚠️ Potential issue

Critical: Missing ConfigurationError import

The code uses ConfigurationError but doesn't import it, which will cause a ReferenceError at runtime.

Add the import at the top of the file:

 import apipieAi from "../../apipie_ai.app.mjs";
+import { ConfigurationError } from "@pipedream/platform";
components/apipie_ai/actions/retrieve-available-llm-models/retrieve-available-llm-models.mjs (3)

12-18: Good improvement: Error handling has been properly implemented

The previous review feedback about missing error handling has been successfully addressed. The try/catch implementation follows good practices.


20-20: Improve error export consistency

The error export uses a custom key which is inconsistent with Pipedream patterns. Consider using $summary for error messages.

-      $.export("Error fetching LLM Models", e);
+      $.export("$summary", "Failed to retrieve LLM models");

21-21: ⚠️ Potential issue

Critical: Missing ConfigurationError import

The code uses ConfigurationError but doesn't import it, which will cause a ReferenceError at runtime.

Add the import at the top of the file:

 import apipieAi from "../../apipie_ai.app.mjs";
+import { ConfigurationError } from "@pipedream/platform";
components/apipie_ai/actions/retrieve-available-image-models/retrieve-available-image-models.mjs (3)

12-18: Good improvement: Error handling has been properly implemented

The previous review feedback about missing error handling has been successfully addressed. The implementation is robust and user-friendly.


20-20: Improve error export consistency

The error export uses a custom key which is inconsistent with Pipedream patterns. Consider using $summary for error messages.

-      $.export("Error fetching Image Models", e);
+      $.export("$summary", "Failed to retrieve image models");

21-21: ⚠️ Potential issue

Critical: Missing ConfigurationError import

The code uses ConfigurationError but doesn't import it, which will cause a ReferenceError at runtime.

Add the import at the top of the file:

 import apipieAi from "../../apipie_ai.app.mjs";
+import { ConfigurationError } from "@pipedream/platform";
components/apipie_ai/apipie_ai.app.mjs (4)

109-109: Fix range validation for topP to exclude zero.

The topP parameter's minimum value contradicts the documented range "(0, 1]" which should exclude zero.

-     min: 0,
+     min: 0.000001,

12-32: 🛠️ Refactor suggestion

Extract duplicated model fetching logic.

The async options methods for chatCompletionModelId, imageModelId, and ttsModelId contain nearly identical code for fetching, deduplicating, and formatting model options. This violates DRY principles.

Consider extracting into a helper method:

+ _createModelOptions(apiMethod) {
+   return async function() {
+     try {
+       const { data } = await apiMethod.call(this);
+       const uniqueModels = new Map();
+       data.forEach(({ id, name }) => {
+         if (!uniqueModels.has(id)) {
+           uniqueModels.set(id, name);
+         }
+       });
+       return Array.from(uniqueModels.entries())
+         .map(([value, label]) => ({ label, value }))
+         .sort((a, b) => a.label.localeCompare(b.label));
+     } catch (e) {
+       $.export("Error fetching models", e);
+       throw new ConfigurationError(e.message || "Failed to fetch models");
+     }
+   };
+ },

Also applies to: 38-57, 63-82


172-176: 🛠️ Refactor suggestion

Clarify toolOutputs property type and usage.

The toolOutputs property type (string[]) conflicts with its description mentioning objects with tool_call_id and output properties.

Either change to object array:

     toolOutputs: {
-      type: "string[]",
+      type: "object[]",
       label: "Tool Outputs",
       description: "The outputs from the tool calls. Each object in the array should contain properties `tool_call_id` and `output`.",
     },

Or clarify the string format expected:

     toolOutputs: {
       type: "string[]",
       label: "Tool Outputs",
-      description: "The outputs from the tool calls. Each object in the array should contain properties `tool_call_id` and `output`.",
+      description: "The outputs from the tool calls as JSON strings. Each string should represent an object with properties `tool_call_id` and `output`.",
     },

140-140: ⚠️ Potential issue

Fix range validation for repetitionPenalty to exclude zero.

The repetitionPenalty parameter's minimum value contradicts the documented range "(0, 2]" which should exclude zero.

-     min: 0,
+     min: 0.000001,
🧹 Nitpick comments (2)
components/apipie_ai/actions/retrieve-available-tts-voices/retrieve-available-tts-voices.mjs (1)

20-20: Improve error export consistency

The error export uses a custom key which is inconsistent with Pipedream patterns. Consider using $summary for error messages.

-      $.export("Error fetching Voices", e);
+      $.export("$summary", "Failed to retrieve TTS voices");
components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (1)

83-83: Consider allowing all tool types from constants.

The toolTypes options are artificially limited to only "function" tools. If the API supports other tool types, consider allowing all types defined in constants.

-     options: constants.TOOL_TYPES?.filter((toolType) => toolType === "function") || ["function"],
+     options: constants.TOOL_TYPES || ["function"],

If the API truly only supports function tools currently, consider adding a comment explaining this limitation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3803778 and b38a10c.

📒 Files selected for processing (8)
  • components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs (1 hunks)
  • components/apipie_ai/actions/create-image/create-image.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-image-models/retrieve-available-image-models.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-llm-models/retrieve-available-llm-models.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-tts-models/retrieve-available-tts-models.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-tts-voices/retrieve-available-tts-voices.mjs (1 hunks)
  • components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (1 hunks)
  • components/apipie_ai/apipie_ai.app.mjs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs
  • components/apipie_ai/actions/create-image/create-image.mjs
🔇 Additional comments (4)
components/apipie_ai/actions/retrieve-available-tts-voices/retrieve-available-tts-voices.mjs (1)

12-18: LGTM: Well-structured action with proper error handling

The action follows good patterns with proper async/await, error handling, and user-friendly summary messages.

components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (2)

88-127: LGTM! Well-structured dynamic props generation.

The additionalProps method cleanly handles dynamic function definition based on user selection. The logic properly creates numbered function properties and handles the reloadProps mechanism effectively.


129-149: LGTM! Clean tool building logic.

The _buildTools method properly constructs the tools array with appropriate validation and returns undefined when no tools are configured, which is good for API compatibility.

components/apipie_ai/apipie_ai.app.mjs (1)

247-287: LGTM! Well-structured API methods.

The API methods are clean and properly leverage the _makeRequest helper. The endpoint paths and HTTP methods are correctly configured for each operation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (5)
components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (1)

186-189: Improve error handling consistency.

The catch block throws a ConfigurationError with the raw error object, which may not provide a meaningful error message to users.

} catch (e) {
  $.export("Error creating Chat Completion", e);
- throw new ConfigurationError(e);
+ throw new ConfigurationError(e.message || "Failed to create Chat Completion");
}
components/apipie_ai/apipie_ai.app.mjs (4)

109-110: Fix range validation for topP to exclude zero.

The description specifies the range as "(0, 1]" (greater than 0), but the minimum is set to 0, which contradicts the exclusive lower bound.

topP: {
  type: "number",
  label: "Top P",
  description: "Top-p sampling value. **(range: (0, 1])**.",
- min: 0,
+ min: 0.000001,
  max: 1.0,
  optional: true,
},

172-176: Clarify toolOutputs property structure and validation.

The property accepts string[] but the description mentions objects with tool_call_id and output properties. This type mismatch could lead to confusion.

Consider either:

  1. Changing the type to object[] if expecting structured objects
  2. Updating the description to clarify the expected string format
toolOutputs: {
- type: "string[]",
+ type: "object[]",
  label: "Tool Outputs",
  description: "The outputs from the tool calls. Each object in the array should contain properties `tool_call_id` and `output`.",
},

Or if keeping as strings, clarify the format:

toolOutputs: {
  type: "string[]",
  label: "Tool Outputs",
- description: "The outputs from the tool calls. Each object in the array should contain properties `tool_call_id` and `output`.",
+ description: "The outputs from the tool calls as JSON strings. Each string should represent an object with properties `tool_call_id` and `output`.",
},

13-82: 🛠️ Refactor suggestion

Refactor duplicated model selection logic.

The prop definitions for chatCompletionModelId, imageModelId, and ttsModelId contain nearly identical code for fetching and deduplicating models. This violates the DRY principle and makes maintenance difficult.

Consider extracting the common logic into a helper method:

+ _createModelOptions(apiMethod) {
+   return async function() {
+     try {
+       const { data } = await apiMethod.call(this);
+       const uniqueModels = new Map();
+       data.forEach(({ id, name }) => {
+         if (!uniqueModels.has(id)) {
+           uniqueModels.set(id, name);
+         }
+       });
+       return Array.from(uniqueModels.entries())
+         .map(([value, label]) => ({
+           label,
+           value,
+         }))
+         .sort((a, b) => a.label.localeCompare(b.label));
+     } catch (e) {
+       $.export(`Error fetching models`, e);
+       throw new ConfigurationError(e.message || "Failed to fetch models");
+     }
+   };
+ },

chatCompletionModelId: {
  type: "string",
  label: "Completions Model",
  description: "The ID of the LLM model to use for completions.",
- async options() {
-   try {
-     const { data } = await this.listLlmModels();
-     const uniqueModels = new Map();
-     data.forEach(({ id, name }) => {
-       if (!uniqueModels.has(id)) {
-         uniqueModels.set(id, name);
-       }
-     });
-     return Array.from(uniqueModels.entries())
-       .map(([value, label]) => ({
-         label,
-         value,
-       }))
-       .sort((a, b) => a.label.localeCompare(b.label));
-   } catch (e) {
-     $.export("Error fetching LLM Models", e);
-     throw new ConfigurationError(e.message || "Failed to fetch LLM models");
-   }
- },
+ options: this._createModelOptions(this.listLlmModels),
},

Apply similar changes to imageModelId and ttsModelId.


140-141: ⚠️ Potential issue

Fix inconsistent range validation for repetitionPenalty.

The description states the range should be "(0, 2]" (greater than 0, less than or equal to 2), but the validation allows 0, which contradicts the exclusive lower bound.

repetitionPenalty: {
  type: "number",
  label: "Repetition Penalty",
  description: "Repetition penalty. **(range: (0, 2])**.",
- min: 0,
+ min: 0.000001,
  max: 2.0,
  optional: true,
},
🧹 Nitpick comments (1)
components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (1)

83-83: Simplify the toolTypes options logic.

The current logic filters TOOL_TYPES to only include "function" but then provides ["function"] as a fallback. This is unnecessarily complex since the result will always be ["function"].

-options: constants.TOOL_TYPES?.filter((toolType) => toolType === "function") || ["function"],
+options: ["function"],

Alternatively, if you expect TOOL_TYPES to contain multiple values in the future:

-options: constants.TOOL_TYPES?.filter((toolType) => toolType === "function") || ["function"],
+options: constants.TOOL_TYPES || ["function"],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b38a10c and 0b7444c.

📒 Files selected for processing (8)
  • components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs (1 hunks)
  • components/apipie_ai/actions/create-image/create-image.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-image-models/retrieve-available-image-models.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-llm-models/retrieve-available-llm-models.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-tts-models/retrieve-available-tts-models.mjs (1 hunks)
  • components/apipie_ai/actions/retrieve-available-tts-voices/retrieve-available-tts-voices.mjs (1 hunks)
  • components/apipie_ai/actions/send-chat-completion-request/send-chat-completion-request.mjs (1 hunks)
  • components/apipie_ai/apipie_ai.app.mjs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • components/apipie_ai/actions/convert-text-to-speech/convert-text-to-speech.mjs
  • components/apipie_ai/actions/retrieve-available-tts-voices/retrieve-available-tts-voices.mjs
  • components/apipie_ai/actions/retrieve-available-tts-models/retrieve-available-tts-models.mjs
  • components/apipie_ai/actions/retrieve-available-llm-models/retrieve-available-llm-models.mjs
  • components/apipie_ai/actions/retrieve-available-image-models/retrieve-available-image-models.mjs
  • components/apipie_ai/actions/create-image/create-image.mjs

@Toocky Toocky closed this Jun 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
User submitted Submitted by a user
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants