Skip to content

feat(wren-ai-service): create Streamlit UI for configuring LLM models #1690

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

Open
wants to merge 75 commits into
base: main
Choose a base branch
from

Conversation

yichieh-lu
Copy link
Contributor

@yichieh-lu yichieh-lu commented May 27, 2025

Create a UI to help users set up the model.

Summary by CodeRabbit

  • New Features

    • Introduced an interactive web-based UI for configuring custom provider settings using Streamlit, accessible via Docker container.
    • Added support for importing, editing, and validating YAML configurations, API keys, LLM models, embedding models, and pipeline settings through a user-friendly interface.
    • Enabled dry-run testing for LLM and embedding model configurations directly from the UI.
    • Provided options to preview and export finalized configuration files.
    • Added automatic detection and waiting mechanism for configuration completion during custom provider setup.
    • Integrated a new Docker-based workflow for building and publishing multi-architecture images of the provider setup UI.
    • Added a Python 3.12-based container environment tailored for the Streamlit app with Poetry-managed dependencies.
  • Chores

    • Automated building and publishing of multi-architecture Docker images for the provider setup UI.
    • Streamlined setup by automatically downloading missing configuration files and managing Docker container lifecycle for the setup process.

yichieh-lu and others added 30 commits April 10, 2025 15:05
…_state.py; combine init and override behaviors (import yaml files)
1. Rename custom_llm_ui.py to app.py for better naming convention
2. Extract Embedder and Document Store configuration from app.py to ui_components.py to improve code organization
3. Fix data type issue: ensure "timeout" value is properly converted to integer
4. Optimize alias handling: only include alias field in dictionary when it has a value
- Implemented pipeline configuration UI.
- Set init_pipeline in session_state.py to manage pipeline state.
yichieh-lu and others added 19 commits May 6, 2025 16:53
…guration handling

- Removed the document store configuration section and its associated rendering logic.
- Updated the embedder configuration to include dimension input for embedding models.
- Simplified the rendering of pipeline configurations and improved the selection process for LLM models.
- Enhanced the preview and generation functionality for configuration files.
- Adjusted the layout and organization of UI components for better clarity and usability.
… component integration

- Refactored configuration path retrieval by introducing dedicated functions for fetching paths to config.yaml, config.done, and .env files.
- Updated UI components to utilize the new configuration path functions, enhancing maintainability and clarity.
- Simplified file operations in the API key management section to ensure consistent access to the environment file.
…prove error handling

- Implemented validation for required configuration blocks (LLM, Embedder, Document Store, Pipeline) to ensure they are present and not empty.
- Enhanced error handling by replacing print statements with Streamlit error messages for better user feedback during configuration loading failures.
- Improved user experience by warning users about missing configuration blocks and indicating that default values will be used.
…ontainer launch logging

- Updated the config extraction logic to return the first block of each type, improving clarity and efficiency.
- Modified the launch command to dynamically find and log the available port for the Streamlit UI, enhancing user feedback during startup.
…ing configurations

fix(ui): update save button key to include original index for correct LLM updates
Copy link
Contributor

coderabbitai bot commented May 27, 2025

Walkthrough

This update introduces a new provider setup workflow for Wren AI, including a Streamlit-based UI for configuring custom LLM providers. It adds a Dockerfile, supporting Python modules, and a GitHub Actions workflow for building and publishing multi-architecture images. The launcher and utility code are updated to integrate the interactive setup flow using Docker containers and session state management.

Changes

File(s) Change Summary
.github/workflows/setup-provider-image.yaml Adds a GitHub Actions workflow to build and publish multi-arch Docker images for the provider UI.
wren-ai-service/tools/providers-setup/Dockerfile Adds a Dockerfile for a Python 3.12 Streamlit app managed by Poetry, tailored for provider setup.
wren-ai-service/tools/providers-setup/pyproject.toml Introduces project metadata and dependencies for the providers-setup Python project.
wren-ai-service/tools/providers-setup/app.py Adds a Streamlit app for interactive provider configuration via web UI.
wren-ai-service/tools/providers-setup/config_loader.py Adds functions for loading, parsing, and grouping YAML configuration files.
wren-ai-service/tools/providers-setup/constants.py Introduces configuration constants, release version fetching, and path utilities.
wren-ai-service/tools/providers-setup/dry_run_test.py Adds dry-run test functions for LLM completion and embedding using LiteLLM.
wren-ai-service/tools/providers-setup/session_state.py Adds a ConfigState class for initializing and managing Streamlit session state.
wren-ai-service/tools/providers-setup/ui_components.py Adds Streamlit UI components for YAML import, API key management, model config, and pipeline setup.
wren-launcher/commands/launch.go Updates the launch flow to run the provider setup UI container for custom LLM providers.
wren-launcher/utils/docker.go Enhances Docker file preparation and adds functions to manage the Streamlit UI container lifecycle.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Launcher (CLI)
    participant Docker
    participant Streamlit UI
    participant Local FS

    User->>Launcher (CLI): Run launch command with custom provider
    Launcher (CLI)->>Local FS: Check for ~/.wrenai directory
    alt Directory exists
        Launcher (CLI)->>Docker: Remove existing setup container (if any)
        Launcher (CLI)->>Docker: Build Streamlit UI image
        Launcher (CLI)->>Local FS: Ensure config.yaml, .env, config.done exist
        Launcher (CLI)->>Docker: Run Streamlit UI container with mounted configs
        User->>Streamlit UI: Open browser and configure provider
        Streamlit UI->>Local FS: Write config.yaml, .env, set config.done=true
        Launcher (CLI)->>Local FS: Poll config.done until ready
        Launcher (CLI)->>Docker: Remove setup container
    else Directory missing
        Launcher (CLI)->>Launcher (CLI): Log warning and skip UI setup
    end
    Launcher (CLI)->>Docker: Continue with main Wren AI Docker Compose launch
Loading

Suggested labels

wren-ai-service, ci/ai-service, deployment

Poem

🐇
A Streamlit hop, a Docker spin,
Providers set up with a bunny grin.
YAMLs loaded, keys in place,
Custom models join the race!
From CLI to UI, the journey’s begun—
Wren AI’s setup is second to none.
🥕✨

✨ 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.

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: 10

🧹 Nitpick comments (9)
wren-ai-service/tools/providers-setup/constants.py (1)

2-2: Remove unused import.

The os module is imported but never used in this file.

-import os
🧰 Tools
🪛 Ruff (0.11.9)

2-2: os imported but unused

Remove unused import: os

(F401)

wren-launcher/commands/launch.go (1)

251-251: Fix the typo in the comment.

There's a spelling error in the comment.

-		// chech if  ~/.wrenai is exist
+		// check if ~/.wrenai exists
.github/workflows/setup-provider-image.yaml (2)

29-29: Remove trailing spaces.

Static analysis detected trailing spaces that should be removed for cleaner code.

-          - runner: ubuntu-latest         
+          - runner: ubuntu-latest
             platform: linux/amd64
-          - runner: ubuntu-24.04-arm      
+          - runner: ubuntu-24.04-arm
             platform: linux/arm64

Also applies to: 31-31

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 29-29: trailing spaces

(trailing-spaces)


130-131: Remove extra blank line.

There's an unnecessary blank line at the end of the file.

           $(printf '${{ env.REGISTRY }}/${{ env.PROVIDER_UI_IMAGE }}@sha256:%s ' *) \
           $TAGS
-
🧰 Tools
🪛 YAMLlint (1.37.1)

[warning] 130-130: too many blank lines

(1 > 0) (empty-lines)

wren-ai-service/tools/providers-setup/config_loader.py (2)

88-99: Clarify the fallback logic in group_blocks function.

The logic for handling blocks without a "type" field could be more explicit and documented.

def group_blocks(blocks: List[Dict[str, Any]]) -> Dict[str, Any]:
    """
    Group YAML blocks by their 'type' field.
    If multiple blocks share the same type, they are stored as a list.
+    Falls back to 'settings' key if 'type' field is missing.
    """
    save_blocks = {}
    for block in blocks:
-        key = block.get("type") or ("settings" if "settings" in block else None)
+        key = block.get("type")
+        if not key and "settings" in block:
+            key = "settings"
        if not key:
            continue

47-48: Improve error message consistency.

The error message format should be consistent with other error messages in the function.

        if not config_list:
-            raise ValueError(f"⚠️ Received empty YAML content from: {url}")
+            raise ValueError(f"Received empty YAML content from: {url}")
wren-ai-service/tools/providers-setup/session_state.py (1)

167-167: Consider making the .env path configurable.

The hardcoded path /app/data/.env assumes a containerized environment. Consider making this configurable or using a constant to improve portability.

Consider importing the path from the constants module:

+from constants import get_env_path
+env_path = get_env_path()
-env_path = Path("/app/data/.env")
wren-ai-service/tools/providers-setup/ui_components.py (2)

219-220: Remove unnecessary f-string prefix.

The f-string on line 220 doesn't contain any placeholders.

-    with st.expander(f"{default_name}", expanded=False):
+    with st.expander(default_name, expanded=False):
🧰 Tools
🪛 Ruff (0.11.9)

220-220: f-string without any placeholders

Remove extraneous f prefix

(F541)


294-342: Remove commented code block.

This large block of commented code should be removed to keep the codebase clean. If this functionality might be needed later, consider tracking it in version control history or a separate feature branch.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e874b9a and bdd4f9f.

⛔ Files ignored due to path filters (1)
  • wren-ai-service/tools/providers-setup/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • .github/workflows/setup-provider-image.yaml (1 hunks)
  • wren-ai-service/tools/providers-setup/Dockerfile (1 hunks)
  • wren-ai-service/tools/providers-setup/app.py (1 hunks)
  • wren-ai-service/tools/providers-setup/config_loader.py (1 hunks)
  • wren-ai-service/tools/providers-setup/constants.py (1 hunks)
  • wren-ai-service/tools/providers-setup/dry_run_test.py (1 hunks)
  • wren-ai-service/tools/providers-setup/pyproject.toml (1 hunks)
  • wren-ai-service/tools/providers-setup/session_state.py (1 hunks)
  • wren-ai-service/tools/providers-setup/ui_components.py (1 hunks)
  • wren-launcher/commands/launch.go (1 hunks)
  • wren-launcher/utils/docker.go (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
wren-launcher/commands/launch.go (2)
wren-launcher/utils/docker.go (4)
  • TryGetWrenAIDir (552-564)
  • RemoveContainerIfExists (657-674)
  • RunStreamlitUIContainer (576-606)
  • IsCustomConfigReady (678-693)
wren-launcher/utils/network.go (1)
  • FindAvailablePort (16-34)
wren-ai-service/tools/providers-setup/app.py (3)
wren-ai-service/tools/providers-setup/config_loader.py (2)
  • load_config_yaml_blocks (9-22)
  • group_blocks (82-99)
wren-ai-service/tools/providers-setup/session_state.py (2)
  • ConfigState (5-206)
  • init (17-27)
wren-ai-service/tools/providers-setup/ui_components.py (7)
  • render_llm_config (143-206)
  • render_embedder_config (210-291)
  • render_import_yaml (9-32)
  • render_pipeline_config (343-379)
  • render_preview (383-390)
  • render_apikey (35-141)
  • render_generate_button (392-412)
wren-ai-service/tools/providers-setup/dry_run_test.py (1)
wren-ai-service/tools/providers-setup/session_state.py (1)
  • ConfigState (5-206)
wren-ai-service/tools/providers-setup/session_state.py (2)
wren-ai-service/src/config.py (1)
  • config_loader (87-97)
wren-ai-service/tools/providers-setup/config_loader.py (2)
  • fetch_yaml_from_url (37-54)
  • fetch_example_yaml_filenames (101-113)
🪛 actionlint (1.7.7)
.github/workflows/setup-provider-image.yaml

61-61: got unexpected character '/' while lexing expression, expecting 'a'..'z', 'A'..'Z', '_', '0'..'9', ''', '}', '(', ')', '[', ']', '.', '!', '<', '>', '=', '&', '|', '*', ',', ' '

(expression)

🪛 YAMLlint (1.37.1)
.github/workflows/setup-provider-image.yaml

[error] 29-29: trailing spaces

(trailing-spaces)


[error] 31-31: trailing spaces

(trailing-spaces)


[warning] 130-130: too many blank lines

(1 > 0) (empty-lines)

🪛 Ruff (0.11.9)
wren-ai-service/tools/providers-setup/ui_components.py

220-220: f-string without any placeholders

Remove extraneous f prefix

(F541)

wren-ai-service/tools/providers-setup/constants.py

2-2: os imported but unused

Remove unused import: os

(F401)

🔇 Additional comments (16)
wren-ai-service/tools/providers-setup/constants.py (1)

9-28: LGTM! Well-implemented GitHub API function.

The function correctly handles the GitHub API request with proper timeout, error handling, and fallback logic. The print statements provide useful debugging information.

wren-ai-service/tools/providers-setup/pyproject.toml (1)

1-18: LGTM! Well-configured Poetry project.

The project configuration is appropriate for a Streamlit-based provider setup tool. The dependency version ranges are reasonable and the build system configuration is correct.

wren-ai-service/tools/providers-setup/Dockerfile (2)

1-58: LGTM! Well-structured Dockerfile with good practices.

The Dockerfile follows best practices with proper layer caching, minimal base image, and appropriate environment configuration for Streamlit. The multi-stage approach and dependency installation strategy are well-implemented.


34-35:

❌ Incorrect review comment

Verify poetry.lock file exists.

The Dockerfile copies poetry.lock but this file isn't included in the PR. This could cause build failures if the lock file doesn't exist.


🏁 Script executed:

#!/bin/bash
# Check if poetry.lock exists in the providers-setup directory
fd -t f "poetry.lock" wren-ai-service/tools/providers-setup/

Length of output: 110


poetry.lock exists – no action needed

The poetry.lock file is present at wren-ai-service/tools/providers-setup/poetry.lock, so the COPY poetry.lock instruction in the Dockerfile is valid. No changes required.

Likely an incorrect or invalid review comment.

wren-ai-service/tools/providers-setup/app.py (3)

1-12: LGTM! Clean and well-organized imports.

The imports are well-structured, importing only the necessary functions from each module. The modular approach promotes good separation of concerns.


33-49: Excellent validation logic for configuration blocks.

The validation properly checks both type and content of required configuration blocks, providing clear user feedback when blocks are missing. This helps users understand what needs to be configured.


59-88: Well-designed UI layout with logical organization.

The two-column layout effectively separates input controls from preview functionality. The component rendering is modular and follows a logical flow from API keys through configuration to final generation.

wren-ai-service/tools/providers-setup/dry_run_test.py (1)

6-31: LGTM! Well-structured LLM completion test function.

The function properly handles the completion API call with good error handling and response validation.

wren-ai-service/tools/providers-setup/config_loader.py (2)

9-23: LGTM! Excellent configuration loading structure.

The functions provide robust configuration loading with proper error handling, fallback mechanisms, and clear separation of concerns.

Also applies to: 24-36, 37-55


115-127: LGTM! Clean integration with session state.

The apply_config_blocks function properly integrates the loaded configuration with the Streamlit session state management.

wren-ai-service/tools/providers-setup/session_state.py (3)

16-28: LGTM!

The initialization orchestration method is well-structured and provides appropriate control over state overwriting.


30-68: Well-implemented form initialization logic.

The method properly handles form creation with unique IDs and correctly flattens kwargs for the model list.


121-130:

✅ Verification successful

Consider handling empty response from remote config fetch.

When fetch_yaml_from_url returns an empty list (indicating a fetch error), the code continues as if there's no latest pipeline configuration. Consider adding explicit error handling to distinguish between fetch failures and legitimate empty configurations.


🏁 Script executed:

#!/bin/bash
# Check how fetch_yaml_from_url handles errors
ast-grep --pattern 'def fetch_yaml_from_url($_) -> $_:
  $$$
'

Length of output: 1745


No additional empty-response handling needed.

The fetch_yaml_from_url function already raises a ValueError on empty YAML content, logs the error via st.error, and returns an empty list only on failure. In init_pipeline, receiving an empty list (and thus latest_pipeline remaining None) correctly reflects a fetch/parsing error and skips the update as intended.

wren-launcher/utils/docker.go (2)

298-344: Great improvement to custom provider setup!

The automatic downloading of missing configuration files and USER_UUID injection significantly improves the user experience compared to the previous strict error returns.


576-693: Well-implemented container management functions.

The new functions for Streamlit UI container management are properly structured with good error handling, appropriate volume mounts, and clear separation of concerns.

wren-ai-service/tools/providers-setup/ui_components.py (1)

35-142: Well-implemented API key management.

The API key configuration section properly handles CRUD operations on the .env file with appropriate error handling and file format preservation.

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 (1)
wren-ai-service/tools/providers-setup/ui_components.py (1)

431-437: ⚠️ Potential issue

Critical: Remove eval() to prevent code injection.

Using eval() on user input is a serious security vulnerability that could allow arbitrary code execution.

🧹 Nitpick comments (4)
wren-ai-service/tools/providers-setup/ui_components.py (4)

25-33: Add success message after YAML upload.

The YAML upload section is missing a success message after successfully processing the uploaded file, unlike the GitHub import section.

         uploaded_file = st.file_uploader("Upload your own YAML file", type=["yaml", "yml"])
         if uploaded_file is not None and st.button("Import.yaml", key="import_own_yaml"):
             try:
                 blocks = list(yaml.safe_load_all(uploaded_file))
                 apply_config_blocks(blocks)
+                st.success("YAML import succeeded. Settings updated.")
             except Exception as e:
                 st.error(f"Failed to import YAML file: {e}")

221-221: Remove unnecessary f-string prefix.

The f-string prefix is not needed here since there are no placeholders.

-    with st.expander(f"{default_name}", expanded=False):
+    with st.expander(default_name, expanded=False):

233-233: Fix typo in comment.

There's a typo in the comment: "doucument_store" should be "document_store".

-        document_store_dim = st.text_input("Embedding_model_dim (placed in doucument_store)", key="embedding_model_dim", value="3072")
+        document_store_dim = st.text_input("Embedding_model_dim (placed in document_store)", key="embedding_model_dim", value="3072")

296-344: Remove commented-out code.

This entire function is commented out and marked for deletion. Dead code should be removed to maintain code cleanliness.

Remove lines 296-344 entirely instead of keeping them as comments.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bdd4f9f and 0d236be.

📒 Files selected for processing (2)
  • wren-ai-service/tools/providers-setup/session_state.py (1 hunks)
  • wren-ai-service/tools/providers-setup/ui_components.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wren-ai-service/tools/providers-setup/session_state.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
wren-ai-service/tools/providers-setup/ui_components.py (5)
wren-ai-service/tools/providers-setup/session_state.py (1)
  • ConfigState (5-208)
wren-ai-service/src/config.py (1)
  • config_loader (87-97)
wren-ai-service/tools/providers-setup/config_loader.py (2)
  • load_selected_example_yaml (24-35)
  • apply_config_blocks (115-127)
wren-ai-service/tools/providers-setup/dry_run_test.py (2)
  • llm_completion_test (6-31)
  • llm_embedding_test (34-63)
wren-ai-service/tools/providers-setup/constants.py (3)
  • get_env_path (80-87)
  • get_config_path (69-77)
  • get_config_done_path (59-67)
🪛 Ruff (0.11.9)
wren-ai-service/tools/providers-setup/ui_components.py

222-222: f-string without any placeholders

Remove extraneous f prefix

(F541)

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 (7)
wren-ai-service/tools/providers-setup/constants.py (1)

59-87: The directory naming is now consistent.

All path utility functions correctly use the .wrenai directory name (lowercase), which resolves the previous naming inconsistency concern.

🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 64-67: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)


[refactor] 74-77: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)


[refactor] 84-87: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)

wren-ai-service/tools/providers-setup/dry_run_test.py (2)

44-48: Excellent bounds checking implementation.

The code now properly checks for empty models list before accessing the first element, addressing the previous IndexError concern.


74-77: Good error handling for session state.

The function now uses .get() with a default value and checks for non-empty API keys, addressing the previous KeyError concern.

.github/workflows/setup-provider-image.yaml (2)

11-13: Permissions are correctly configured.

The permissions section is now properly uncommented and configured for GitHub Container Registry access.


62-62: String replacement syntax is now correct.

The expression now uses the proper tr command syntax instead of the invalid // operator.

wren-launcher/utils/docker.go (1)

613-615: Path validation is now properly implemented.

The build path is now validated before attempting to build, addressing the previous robustness concern.

wren-ai-service/tools/providers-setup/ui_components.py (1)

435-438: Security improvement confirmed - eval() replaced with ast.literal_eval().

Great work addressing the previous security concern! The code now safely evaluates literals using ast.literal_eval() instead of the dangerous eval() function, preventing potential code injection attacks.

🧹 Nitpick comments (10)
wren-ai-service/tools/providers-setup/constants.py (2)

2-2: Remove unused import.

The os module is imported but never used in this file.

-import os
🧰 Tools
🪛 Ruff (0.11.9)

2-2: os imported but unused

Remove unused import: os

(F401)


19-28: Consider simplifying control flow.

The else clause after return statements can be removed to reduce nesting levels.

-        if response.status_code == 200:
-            data = response.json()
-            return data["tag_name"]
-        else:
-            print(f"Failed to get latest release: {response.status_code}")
+        if response.status_code == 200:
+            data = response.json()
+            return data["tag_name"]
+        print(f"Failed to get latest release: {response.status_code}")
🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 20-24: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)

wren-ai-service/tools/providers-setup/dry_run_test.py (1)

25-28: Consider simplifying control flow.

The else clauses after return statements can be removed to reduce nesting levels.

-        if choices and choices[0].get("message", {}).get("content"):
-            return True, response_completion["choices"][0]["message"]["content"]
-        else:
-            return False, "No valid response content"
+        if choices and choices[0].get("message", {}).get("content"):
+            return True, response_completion["choices"][0]["message"]["content"]
+        return False, "No valid response content"
-            if isinstance(embedding_vector, list) and len(embedding_vector) > 0:
-                return True, f"Embedding length: {len(embedding_vector)}"
-            else:
-                return False, "Embedding vector is empty or invalid"
+            if isinstance(embedding_vector, list) and len(embedding_vector) > 0:
+                return True, f"Embedding length: {len(embedding_vector)}"
+            return False, "Embedding vector is empty or invalid"

Also applies to: 58-61

🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 25-28: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)

.github/workflows/setup-provider-image.yaml (1)

29-29: Fix formatting issues.

Remove trailing spaces and extra blank lines to comply with YAML formatting standards.

-          - runner: ubuntu-latest         
+          - runner: ubuntu-latest
-          - runner: ubuntu-24.04-arm      
+          - runner: ubuntu-24.04-arm
-

+

Also applies to: 31-31, 130-130

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 29-29: trailing spaces

(trailing-spaces)

wren-launcher/utils/docker.go (1)

662-681: Consider adding logging for container removal operations.

The function properly handles container existence checking, but consider adding more detailed logging for troubleshooting.

 func RemoveContainerIfExists(name string) error {
 	// Check if the container exists (inspect will fail if not)
 	err := exec.Command("docker", "inspect", name).Run()
 	if err != nil {
-		pterm.Info.Println("🔍 Container does not exist, skipping:", name)
+		pterm.Debug.Println("🔍 Container does not exist, skipping:", name)
 		return nil
 	}
 
+	pterm.Info.Println("🧹 Removing existing container:", name)
 	// Remove container forcefully
 	cmd := exec.Command("docker", "rm", "-f", name)
 	out, err := cmd.CombinedOutput()
 	if err != nil {
 		return fmt.Errorf("❌ Failed to force-remove container: %v\n%s", err, string(out))
 	}
 
 	pterm.Info.Println("🧹 Container forcibly removed:", name)
 	return nil
 }
wren-ai-service/tools/providers-setup/ui_components.py (5)

30-34: Add success message for custom YAML import.

The success message is missing when importing a custom YAML file, unlike the example YAML import which shows a success message on line 24.

             try:
                 blocks = list(yaml.safe_load_all(uploaded_file))
                 apply_config_blocks(blocks)
-                
+                st.success("YAML import succeeded. Settings updated.")
             except Exception as e:
                 st.error(f"Failed to import YAML file: {e}")

37-143: Consider refactoring this function to reduce complexity.

This function handles too many responsibilities (adding, editing, saving, deleting API keys) and has high complexity according to static analysis (21 local variables, 18 branches, 62 statements).

Consider breaking this into smaller functions:

  • _render_api_key_form() for form rendering
  • _save_api_keys() for save logic
  • _delete_api_key() for deletion logic

This would improve maintainability and testability.

🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 37-37: Too many local variables (21/15)

(R0914)


[refactor] 37-37: Too many branches (18/12)

(R0912)


[refactor] 37-37: Too many statements (62/50)

(R0915)


224-224: Remove unnecessary f-string prefix.

The f-string has no placeholders, making the f prefix unnecessary.

-        st.markdown(f"**type:** `embedder`")
+        st.markdown("**type:** `embedder`")
🧰 Tools
🪛 Ruff (0.11.9)

224-224: f-string without any placeholders

Remove extraneous f prefix

(F541)


298-346: Remove commented-out code.

This large block of commented code should be removed to keep the codebase clean, unless there's a specific reason to keep it for reference.

-# DELETE 
-# def render_document_store_config():
-#     """
-#     Render the document store configuration section.
-#     Displays current settings and allows updating index location, dimensions, timeout, etc.
-#     """
-#     with st.expander("Document Store Configuration", expanded=False):
-        [... rest of commented code ...]

460-466: Remove unnecessary else clause after return.

The else clause is unnecessary since the previous block contains a return statement.

     existing_ids = [m.get("id") for m in st.session_state[ConfigState.LLM_MODELS_KEY]]
     if form_id in existing_ids:
         index = existing_ids.index(form_id)
         st.session_state[ConfigState.LLM_MODELS_KEY][index] = {**saved_entry, "id": form_id}
         return True, f"Updated model: {form['model']}"
-    else:
-        st.session_state[ConfigState.LLM_MODELS_KEY].append({**saved_entry, "id": form_id})
-        return True, f"Added new model: {form['model']}"
+    
+    st.session_state[ConfigState.LLM_MODELS_KEY].append({**saved_entry, "id": form_id})
+    return True, f"Added new model: {form['model']}"
🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 460-466: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0d236be and 0b991c6.

📒 Files selected for processing (6)
  • .github/workflows/setup-provider-image.yaml (1 hunks)
  • wren-ai-service/tools/providers-setup/constants.py (1 hunks)
  • wren-ai-service/tools/providers-setup/dry_run_test.py (1 hunks)
  • wren-ai-service/tools/providers-setup/ui_components.py (1 hunks)
  • wren-launcher/commands/launch.go (1 hunks)
  • wren-launcher/utils/docker.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wren-launcher/commands/launch.go
🧰 Additional context used
🪛 Ruff (0.11.9)
wren-ai-service/tools/providers-setup/ui_components.py

224-224: f-string without any placeholders

Remove extraneous f prefix

(F541)

wren-ai-service/tools/providers-setup/constants.py

2-2: os imported but unused

Remove unused import: os

(F401)

🪛 Pylint (3.3.7)
wren-ai-service/tools/providers-setup/ui_components.py

[refactor] 37-37: Too many local variables (21/15)

(R0914)


[refactor] 37-37: Too many branches (18/12)

(R0912)


[refactor] 37-37: Too many statements (62/50)

(R0915)


[refactor] 145-145: Too many branches (14/12)

(R0912)


[refactor] 214-214: Too many branches (15/12)

(R0912)


[error] 382-382: Possibly using variable 'selected_llm' before assignment

(E0606)


[refactor] 460-466: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)

wren-ai-service/tools/providers-setup/constants.py

[refactor] 20-24: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)


[refactor] 64-67: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)


[refactor] 74-77: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)


[refactor] 84-87: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)

wren-ai-service/tools/providers-setup/dry_run_test.py

[refactor] 25-28: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)


[refactor] 58-61: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)

🪛 YAMLlint (1.37.1)
.github/workflows/setup-provider-image.yaml

[error] 29-29: trailing spaces

(trailing-spaces)


[error] 31-31: trailing spaces

(trailing-spaces)


[warning] 130-130: too many blank lines

(1 > 0) (empty-lines)

🔇 Additional comments (3)
wren-launcher/utils/docker.go (3)

297-344: Excellent improvement to custom provider handling.

The logic now gracefully handles missing configuration files by downloading defaults instead of failing with errors. This provides a much better user experience for the custom provider setup flow.


573-606: Well-structured Streamlit UI container management.

The RunStreamlitUIContainer function properly orchestrates the container lifecycle with good error handling and clear separation of concerns.


683-700: Robust configuration readiness checking.

The IsCustomConfigReady function implements proper error handling and case-insensitive comparison for the configuration state check.

The selected_llm variable may be used before assignment if the pipeline has no llm key or if the current LLM value is not in the options list.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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

🧹 Nitpick comments (1)
wren-ai-service/tools/providers-setup/ui_components.py (1)

298-346: Remove commented-out code to improve maintainability.

Large blocks of commented-out code should be removed rather than left in the codebase. If this functionality might be needed in the future, consider documenting the decision or using version control history instead.

-# DELETE 
-# def render_document_store_config():
-#     """
-#     Render the document store configuration section.
-#     Displays current settings and allows updating index location, dimensions, timeout, etc.
-#     """
-#     with st.expander("Document Store Configuration", expanded=False):
-#         st.markdown(f"**type:** `document_store`")
-#         st.markdown(f"**provider:** `{st.session_state[ConfigState.DOC_STORE_KEY].get('provider')}`")
-#         st.markdown(f"**location:** `{st.session_state[ConfigState.DOC_STORE_KEY].get('location')}`")
-
-#         document_store_timeout = st.text_input("Timeout (default: 120)", key="document_store_timeout", value="120")
-#         st.markdown(f"**timeout:** `120`")
-#         st.markdown(f"**recreate_index:** `{st.session_state[ConfigState.DOC_STORE_KEY].get('recreate_index')}`")
-
-#         document_store_dim = st.text_input("Embedding_model_dim", value="3072")
-
-#         if st.button("💾  save", key="save_document_store"):
-#             errors = []
-#             if not document_store_dim:
-#                 errors.append("Embedding model dim is required.")
-#             else:
-#                 try:
-#                     int(document_store_dim)
-#                 except ValueError:
-#                     errors.append("Embedding model dim must be an integer.")
-
-#             if not document_store_timeout:
-#                 errors.append("Timeout is required.")
-#             else:
-#                 try:
-#                     int(document_store_timeout)
-#                 except ValueError:
-#                     errors.append("Timeout must be an integer.")
-
-#             if errors:
-#                 for error in errors:
-#                     st.error(error)
-#             else:
-#                 st.session_state.document_store = {
-#                     "type": "document_store",
-#                     "provider": st.session_state[ConfigState.DOC_STORE_KEY].get("provider"),
-#                     "location": st.session_state[ConfigState.DOC_STORE_KEY].get("location"),
-#                     "embedding_model_dim": document_store_dim,
-#                     "timeout": document_store_timeout,
-#                     "recreate_index": st.session_state[ConfigState.DOC_STORE_KEY].get("recreate_index")
-#                 }
-#                 st.success("Updated document store configuration")
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0b991c6 and d43e7cf.

📒 Files selected for processing (1)
  • wren-ai-service/tools/providers-setup/ui_components.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
wren-ai-service/tools/providers-setup/ui_components.py (5)
wren-ai-service/tools/providers-setup/session_state.py (1)
  • ConfigState (5-208)
wren-ai-service/src/config.py (1)
  • config_loader (87-97)
wren-ai-service/tools/providers-setup/config_loader.py (2)
  • load_selected_example_yaml (24-35)
  • apply_config_blocks (115-127)
wren-ai-service/tools/providers-setup/dry_run_test.py (2)
  • llm_completion_test (6-31)
  • llm_embedding_test (34-66)
wren-ai-service/tools/providers-setup/constants.py (3)
  • get_env_path (80-87)
  • get_config_path (69-77)
  • get_config_done_path (59-67)
🪛 Ruff (0.11.9)
wren-ai-service/tools/providers-setup/ui_components.py

370-371: SyntaxError: Expected an indented block after if statement


395-395: SyntaxError: Unexpected indentation


399-399: SyntaxError: Expected a statement

🪛 Pylint (3.3.7)
wren-ai-service/tools/providers-setup/ui_components.py

[error] 371-371: Parsing failed: 'expected an indented block after 'if' statement on line 370 (ui_components, line 371)'

(E0001)

🔇 Additional comments (7)
wren-ai-service/tools/providers-setup/ui_components.py (7)

1-9: LGTM: Clean and appropriate imports.

The imports are well-organized and include the necessary modules for the Streamlit UI functionality. The ast import indicates proper security practices for safe evaluation.


10-35: LGTM: Secure YAML loading with proper error handling.

The function correctly uses yaml.safe_load_all() for secure YAML parsing and includes appropriate error handling for both GitHub examples and user uploads.


37-143: LGTM: Comprehensive API key management with proper validation.

The function provides robust API key management with appropriate validation, duplicate checking, and secure .env file handling. The logic for updating existing keys and adding new ones is well-implemented.


145-211: LGTM: Well-structured LLM configuration interface.

The function provides a comprehensive interface for managing LLM models with dynamic form handling, validation, and testing capabilities. The separation of concerns with validation in a separate function is good practice.


214-296: LGTM: Robust embedding model configuration with validation.

The function provides comprehensive embedding model configuration with proper input validation, type checking, and testing capabilities. The error handling for integer conversion is well-implemented.


399-429: LGTM: Clean preview and configuration generation logic.

Both functions are well-implemented with proper error handling and clear separation of concerns between preview display and configuration saving.

🧰 Tools
🪛 Ruff (0.11.9)

399-399: SyntaxError: Expected a statement


432-563: LGTM: Security issue resolved and comprehensive validation.

The utility functions are well-implemented with proper validation and error handling. Notably, the security concern from previous reviews has been addressed by using ast.literal_eval() instead of eval() on line 448, which safely evaluates string literals without the risk of code injection.

Comment on lines +347 to +396
def render_pipeline_config():
"""
Render the pipeline configuration section.
Allows selecting LLM models for each defined pipeline step.
"""
pipeline_llm_options = []
all_pipelines = st.session_state[ConfigState.PIPELINE_KEY].get("pipes", [])

# set all LLM models options
for model in st.session_state[ConfigState.LLM_MODELS_KEY]:
if model.get("alias"):
pipeline_llm_options.append("litellm_llm." + model["alias"])
elif model.get("model"):
pipeline_llm_options.append("litellm_llm." + model["model"])

# enumerate all pipelines, record original index
for original_idx, pipe in enumerate(all_pipelines):
if not pipe.get("llm"):
continue

pipe_name = pipe.get("name", f"Unnamed Pipeline {original_idx}")
with st.expander(f"🔧 Pipeline: {pipe_name}", expanded=False):
for key, value in pipe.items():
if key == "llm":
for key, value in pipe.items():
if key == "llm":
# Ensure we have a default value
default_index = 0
if value in pipeline_llm_options:
default_index = pipeline_llm_options.index(value)

selected_llm = st.selectbox(
"LLM Model",
options=pipeline_llm_options,
index=default_index,
key=f"llm_{original_idx}"
)
else:
st.markdown(f"**{key}:** `{value}`")

if st.button("💾 Save this llm", key=f"save_{pipe_name}_{original_idx}"):
# Only proceed if we have a selected LLM
if 'selected_llm' in locals():
# ✅ use original index to update llm
st.session_state[ConfigState.PIPELINE_KEY]["pipes"][original_idx]["llm"] = selected_llm
st.success(f"✅ Updated pipeline `{pipe_name}` LLM to `{selected_llm}`")
else:
st.error("No LLM selected for this pipeline.")
st.success(f"✅ Updated pipeline `{pipe_name}` LLM to `{selected_llm}`")

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Fix syntax errors and restructure pipeline configuration logic.

This section contains multiple critical syntax errors that prevent the code from running:

  1. Syntax Error: The for loop at line 369 has no body (missing indented block)
  2. Duplicate Code: The for loop is duplicated at lines 369 and 371
  3. Incorrect Indentation: Lines 371-396 have wrong indentation levels
  4. Duplicate Code: Line 395 duplicates the success message from line 392

Apply this diff to fix the syntax errors and logic:

-            for key, value in pipe.items():
-                if key == "llm":
-for key, value in pipe.items():
-    if key == "llm":
-        # Ensure we have a default value
-        default_index = 0
-        if value in pipeline_llm_options:
-            default_index = pipeline_llm_options.index(value)
-
-        selected_llm = st.selectbox(
-            "LLM Model",
-            options=pipeline_llm_options,
-            index=default_index,
-            key=f"llm_{original_idx}"
-        )
-    else:
-        st.markdown(f"**{key}:** `{value}`")
-
-if st.button("💾  Save this llm", key=f"save_{pipe_name}_{original_idx}"):
-    # Only proceed if we have a selected LLM
-    if 'selected_llm' in locals():
-        # ✅ use original index to update llm
-        st.session_state[ConfigState.PIPELINE_KEY]["pipes"][original_idx]["llm"] = selected_llm
-        st.success(f"✅ Updated pipeline `{pipe_name}` LLM to `{selected_llm}`")
-    else:
-        st.error("No LLM selected for this pipeline.")
-                st.success(f"✅ Updated pipeline `{pipe_name}` LLM to `{selected_llm}`")
+            selected_llm = None
+            for key, value in pipe.items():
+                if key == "llm":
+                    # Ensure we have a default value
+                    default_index = 0
+                    if value in pipeline_llm_options:
+                        default_index = pipeline_llm_options.index(value)
+
+                    selected_llm = st.selectbox(
+                        "LLM Model",
+                        options=pipeline_llm_options,
+                        index=default_index,
+                        key=f"llm_{original_idx}"
+                    )
+                else:
+                    st.markdown(f"**{key}:** `{value}`")
+
+            if st.button("💾  Save this llm", key=f"save_{pipe_name}_{original_idx}"):
+                # Only proceed if we have a selected LLM
+                if selected_llm is not None:
+                    # ✅ use original index to update llm
+                    st.session_state[ConfigState.PIPELINE_KEY]["pipes"][original_idx]["llm"] = selected_llm
+                    st.success(f"✅ Updated pipeline `{pipe_name}` LLM to `{selected_llm}`")
+                else:
+                    st.error("No LLM selected for this pipeline.")
📝 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
def render_pipeline_config():
"""
Render the pipeline configuration section.
Allows selecting LLM models for each defined pipeline step.
"""
pipeline_llm_options = []
all_pipelines = st.session_state[ConfigState.PIPELINE_KEY].get("pipes", [])
# set all LLM models options
for model in st.session_state[ConfigState.LLM_MODELS_KEY]:
if model.get("alias"):
pipeline_llm_options.append("litellm_llm." + model["alias"])
elif model.get("model"):
pipeline_llm_options.append("litellm_llm." + model["model"])
# enumerate all pipelines, record original index
for original_idx, pipe in enumerate(all_pipelines):
if not pipe.get("llm"):
continue
pipe_name = pipe.get("name", f"Unnamed Pipeline {original_idx}")
with st.expander(f"🔧 Pipeline: {pipe_name}", expanded=False):
for key, value in pipe.items():
if key == "llm":
for key, value in pipe.items():
if key == "llm":
# Ensure we have a default value
default_index = 0
if value in pipeline_llm_options:
default_index = pipeline_llm_options.index(value)
selected_llm = st.selectbox(
"LLM Model",
options=pipeline_llm_options,
index=default_index,
key=f"llm_{original_idx}"
)
else:
st.markdown(f"**{key}:** `{value}`")
if st.button("💾 Save this llm", key=f"save_{pipe_name}_{original_idx}"):
# Only proceed if we have a selected LLM
if 'selected_llm' in locals():
# ✅ use original index to update llm
st.session_state[ConfigState.PIPELINE_KEY]["pipes"][original_idx]["llm"] = selected_llm
st.success(f"✅ Updated pipeline `{pipe_name}` LLM to `{selected_llm}`")
else:
st.error("No LLM selected for this pipeline.")
st.success(f"✅ Updated pipeline `{pipe_name}` LLM to `{selected_llm}`")
def render_pipeline_config():
"""
Render the pipeline configuration section.
Allows selecting LLM models for each defined pipeline step.
"""
pipeline_llm_options = []
all_pipelines = st.session_state[ConfigState.PIPELINE_KEY].get("pipes", [])
# set all LLM models options
for model in st.session_state[ConfigState.LLM_MODELS_KEY]:
if model.get("alias"):
pipeline_llm_options.append("litellm_llm." + model["alias"])
elif model.get("model"):
pipeline_llm_options.append("litellm_llm." + model["model"])
# enumerate all pipelines, record original index
for original_idx, pipe in enumerate(all_pipelines):
if not pipe.get("llm"):
continue
pipe_name = pipe.get("name", f"Unnamed Pipeline {original_idx}")
with st.expander(f"🔧 Pipeline: {pipe_name}", expanded=False):
# initialize selection
selected_llm = None
for key, value in pipe.items():
if key == "llm":
# Ensure we have a default value
default_index = 0
if value in pipeline_llm_options:
default_index = pipeline_llm_options.index(value)
selected_llm = st.selectbox(
"LLM Model",
options=pipeline_llm_options,
index=default_index,
key=f"llm_{original_idx}"
)
else:
st.markdown(f"**{key}:** `{value}`")
if st.button("💾 Save this llm", key=f"save_{pipe_name}_{original_idx}"):
# Only proceed if we have a selected LLM
if selected_llm is not None:
st.session_state[ConfigState.PIPELINE_KEY]["pipes"][original_idx]["llm"] = selected_llm
st.success(f"✅ Updated pipeline `{pipe_name}` LLM to `{selected_llm}`")
else:
st.error("No LLM selected for this pipeline.")
🧰 Tools
🪛 Ruff (0.11.9)

370-371: SyntaxError: Expected an indented block after if statement


395-395: SyntaxError: Unexpected indentation

🪛 Pylint (3.3.7)

[error] 371-371: Parsing failed: 'expected an indented block after 'if' statement on line 370 (ui_components, line 371)'

(E0001)

🤖 Prompt for AI Agents
In wren-ai-service/tools/providers-setup/ui_components.py around lines 347 to
396, fix the syntax errors by removing the duplicated for loop at line 371 and
ensuring the for loop at line 369 has a proper indented body. Correct the
indentation of lines 371 to 396 so they are nested correctly inside the pipeline
expander and the for loop. Remove the duplicate success message on line 395.
This will resolve the missing block error, eliminate duplicate code, and
properly structure the pipeline configuration rendering logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants