- 
                Notifications
    You must be signed in to change notification settings 
- Fork 7.9k
Feat: Add Murf TextToSpeech Support #10447
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
base: main
Are you sure you want to change the base?
Conversation
| WalkthroughAdds Murf Text-to-Speech integration: new backend component with dynamic voice/listing and TTS generation, frontend UI/icon/sidebar registration, documentation, tests, and murf dependency updates; also updates secrets metadata timestamps/line numbers. Changes
 Sequence Diagram(s)sequenceDiagram
    participant User
    participant UI as Frontend UI
    participant Component as MurfTextToSpeech
    participant MurfAPI as Murf API
    User->>UI: Open Murf TTS node / provide API key
    UI->>Component: initialize_voice_list_data()
    Component->>MurfAPI: GET voices/list
    MurfAPI-->>Component: voices with locales
    Component->>UI: populate locale/voice options
    User->>UI: Submit text + options
    UI->>Component: generate_speech()
    Component->>MurfAPI: POST TTS request (voice, locale, params)
    alt Success
        MurfAPI-->>Component: audio URL / data
        Component-->>UI: Data(response=audio metadata)
    else Error
        MurfAPI-->>Component: error
        Component-->>UI: Data(error details)
    end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 
 Possibly related PRs
 Suggested labels
 Suggested reviewers
 Pre-merge checks and finishing touches❌ Failed checks (1 error, 1 warning)
 ✅ Passed checks (5 passed)
 ✨ Finishing touches
 🧪 Generate unit tests (beta)
 📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (2)
 📒 Files selected for processing (16)
 ✅ Files skipped from review due to trivial changes (1)
 🚧 Files skipped from review as they are similar to previous changes (10)
 🧰 Additional context used📓 Path-based instructions (10)docs/**/*.{md,mdx}📄 CodeRabbit inference engine (.cursor/rules/docs_development.mdc) 
 Files: 
 docs/docs/**/*.{md,mdx}📄 CodeRabbit inference engine (.cursor/rules/docs_development.mdc) 
 Files: 
 src/backend/tests/unit/components/**/*.py📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc) 
 Files: 
 {src/backend/**/*.py,tests/**/*.py,Makefile}📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc) 
 Files: 
 src/backend/tests/unit/**/*.py📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc) 
 Files: 
 src/backend/tests/**/*.py📄 CodeRabbit inference engine (.cursor/rules/testing.mdc) 
 Files: 
 src/backend/**/*component*.py📄 CodeRabbit inference engine (.cursor/rules/icons.mdc) 
 Files: 
 src/backend/**/components/**/*.py📄 CodeRabbit inference engine (.cursor/rules/icons.mdc) 
 Files: 
 **/{test_*.py,*.test.ts,*.test.tsx}📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt) 
 Files: 
 **/test_*.py📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt) 
 Files: 
 🧠 Learnings (11)📚 Learning: 2025-09-07T05:44:46.715ZApplied to files: 
 📚 Learning: 2025-07-18T18:25:54.486ZApplied to files: 
 📚 Learning: 2025-07-21T14:16:14.125ZApplied to files: 
 📚 Learning: 2025-07-21T14:16:14.125ZApplied to files: 
 📚 Learning: 2025-07-18T18:25:54.486ZApplied to files: 
 📚 Learning: 2025-07-21T14:16:14.125ZApplied to files: 
 📚 Learning: 2025-08-05T22:51:27.961ZApplied to files: 
 📚 Learning: 2025-07-21T14:16:14.125ZApplied to files: 
 📚 Learning: 2025-07-21T14:16:14.125ZApplied to files: 
 📚 Learning: 2025-07-18T18:25:54.487ZApplied to files: 
 📚 Learning: 2025-07-21T14:16:14.125ZApplied to files: 
 🪛 Gitleaks (8.28.0).secrets.baseline[high] 1296-1296: Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (generic-api-key) [high] 1304-1304: Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (generic-api-key) 🪛 LanguageTooldocs/docs/Components/bundles-murf.mdx[grammar] ~35-~35: Use a hyphen to join words. (QB_NEW_EN_HYPHEN) [grammar] ~35-~35: Use a hyphen to join words. (QB_NEW_EN_HYPHEN) 🔇 Additional comments (21)
 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. Comment  | 
| Hi! I'm  I would like to apply some automated changes to this pull request, but it looks like I don't have the necessary permissions to do so. To get this pull request into a mergeable state, please do one of the following two things: 
 | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (3)
src/lfx/pyproject.toml (1)
42-42: Consider alphabetical ordering for consistency.The
murf>=2.1.0dependency should appear beforevalidators>=0.34.0,<1.0.0(line 41) to maintain alphabetical order, which is a common practice in dependency lists.Apply this diff to reorder:
"langchain~=0.3.23", - "validators>=0.34.0,<1.0.0", - "murf>=2.1.0" + "murf>=2.1.0", + "validators>=0.34.0,<1.0.0"docs/docs/Components/bundles-murf.mdx (1)
24-24: Consider hyphenating "Text-to-Speech" for consistency.The component name "Text to Speech" should be "Text-to-Speech" to match:
- Murf's official branding
- Standard terminology in the industry
- Static analysis recommendations
This applies to lines 24, 35, and throughout the document where the component name appears.
src/lfx/src/lfx/components/Murf/text_to_speech.py (1)
228-245: Add type safety and simplify dict length check.
Line 229: Assumes
field_valueis always a string without validation. If a non-string type is passed,len(field_value)will raiseTypeError.
Line 231: Using
len(voices.keys())is unnecessary whenlen(voices)achieves the same result more efficiently.Apply this diff:
def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict: - if field_name == "api_key" and len(field_value) > 0: + if field_name == "api_key" and field_value: voices = build_config.get("murf_voice_list") - if voices is None or len(voices.keys()) == 0: + if not voices: voices = self.initialize_voice_list_data() build_config["murf_voice_list"] = voices build_config = self._update_build_config_locale(build_config)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
- src/frontend/src/icons/Murf/Murf-logo.svgis excluded by- !**/*.svg
- uv.lockis excluded by- !**/*.lock
📒 Files selected for processing (15)
- .secrets.baseline(2 hunks)
- docs/docs/Components/bundles-murf.mdx(1 hunks)
- docs/sidebars.js(2 hunks)
- pyproject.toml(1 hunks)
- src/backend/base/pyproject.toml(1 hunks)
- src/frontend/src/constants/constants.ts(1 hunks)
- src/frontend/src/icons/Murf/MurfSVG.jsx(1 hunks)
- src/frontend/src/icons/Murf/index.tsx(1 hunks)
- src/frontend/src/icons/eagerIconImports.ts(2 hunks)
- src/frontend/src/icons/lazyIconImports.ts(1 hunks)
- src/frontend/src/utils/styleUtils.ts(2 hunks)
- src/lfx/pyproject.toml(1 hunks)
- src/lfx/src/lfx/components/Murf/__init__.py(1 hunks)
- src/lfx/src/lfx/components/Murf/text_to_speech.py(1 hunks)
- src/lfx/src/lfx/components/__init__.py(3 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
src/frontend/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
src/frontend/src/**/*.{ts,tsx,js,jsx}: All frontend TypeScript and JavaScript code should be located under src/frontend/src/ and organized into components, pages, icons, stores, types, utils, hooks, services, and assets directories as per the specified directory layout.
Use React 18 with TypeScript for all UI components in the frontend.
Format all TypeScript and JavaScript code using the make format_frontend command.
Lint all TypeScript and JavaScript code using the make lint command.
Files:
- src/frontend/src/icons/eagerIconImports.ts
- src/frontend/src/utils/styleUtils.ts
- src/frontend/src/icons/Murf/index.tsx
- src/frontend/src/icons/Murf/MurfSVG.jsx
- src/frontend/src/constants/constants.ts
- src/frontend/src/icons/lazyIconImports.ts
src/frontend/src/icons/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
Use Lucide React for icons in the frontend.
Files:
- src/frontend/src/icons/eagerIconImports.ts
- src/frontend/src/icons/Murf/index.tsx
- src/frontend/src/icons/Murf/MurfSVG.jsx
- src/frontend/src/icons/lazyIconImports.ts
src/frontend/src/utils/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All utility functions should be placed in the utils directory.
Files:
- src/frontend/src/utils/styleUtils.ts
docs/sidebars.js
📄 CodeRabbit inference engine (.cursor/rules/docs_development.mdc)
Keep sidebars.js updated to include new/changed docs sections and items using Docusaurus category structure
Files:
- docs/sidebars.js
src/frontend/src/icons/*/*.@(js|jsx|ts|tsx)
📄 CodeRabbit inference engine (.cursor/rules/icons.mdc)
Create a new directory for your icon in
src/frontend/src/icons/YourIconName/and add your SVG as a React component (e.g.,YourIconName.jsx). The SVG component must use theisDarkprop to support both light and dark mode.
Files:
- src/frontend/src/icons/Murf/index.tsx
- src/frontend/src/icons/Murf/MurfSVG.jsx
src/frontend/src/icons/*/index.tsx
📄 CodeRabbit inference engine (.cursor/rules/icons.mdc)
Create an
index.tsxin your icon directory that exports your icon usingforwardRefand passes theisDarkprop.
Files:
- src/frontend/src/icons/Murf/index.tsx
docs/**/*.{md,mdx}
📄 CodeRabbit inference engine (.cursor/rules/docs_development.mdc)
docs/**/*.{md,mdx}: All Markdown/MDX pages must start with front matter including at least title and description; include sidebar_position for docs pages when applicable
Code blocks must specify a language and may include a title (```lang title="…")
Use sentence case for headings and keep paragraphs short and scannable
Write in second person, present tense, with a professional but approachable tone
Use inline code with backticks for code terms; use bold for UI elements and italics for emphasis; keep lists in parallel structure
Ensure internal links are functional and navigation works (update cross-references as needed)
Verify all code examples in docs and blog actually run as shown
Use correct terminology capitalization: Langflow, Component, Flow, API, JSON
Reference images with absolute paths under /img/... and provide descriptive alt text
Files:
- docs/docs/Components/bundles-murf.mdx
docs/docs/**/*.{md,mdx}
📄 CodeRabbit inference engine (.cursor/rules/docs_development.mdc)
Use Docusaurus admonitions (:::+tip|warning|danger) instead of custom callouts in docs pages
Files:
- docs/docs/Components/bundles-murf.mdx
src/frontend/src/icons/lazyIconImports.ts
📄 CodeRabbit inference engine (.cursor/rules/icons.mdc)
Add your icon to the
lazyIconsMappingobject insrc/frontend/src/icons/lazyIconImports.tswith a key that matches the backend icon string exactly.
Files:
- src/frontend/src/icons/lazyIconImports.ts
🧠 Learnings (14)
📚 Learning: 2025-07-28T15:56:47.865Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/icons.mdc:0-0
Timestamp: 2025-07-28T15:56:47.865Z
Learning: Applies to src/frontend/src/icons/lazyIconImports.ts : Add your icon to the `lazyIconsMapping` object in `src/frontend/src/icons/lazyIconImports.ts` with a key that matches the backend icon string exactly.
Applied to files:
- src/frontend/src/icons/eagerIconImports.ts
- src/frontend/src/icons/Murf/index.tsx
- src/frontend/src/icons/lazyIconImports.ts
📚 Learning: 2025-07-18T18:27:12.609Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/frontend_development.mdc:0-0
Timestamp: 2025-07-18T18:27:12.609Z
Learning: Applies to src/frontend/src/icons/**/*.{ts,tsx,js,jsx} : Use Lucide React for icons in the frontend.
Applied to files:
- src/frontend/src/icons/eagerIconImports.ts
- src/frontend/src/icons/Murf/index.tsx
📚 Learning: 2025-07-28T15:56:47.865Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/icons.mdc:0-0
Timestamp: 2025-07-28T15:56:47.865Z
Learning: Applies to src/frontend/src/icons/*/index.tsx : Create an `index.tsx` in your icon directory that exports your icon using `forwardRef` and passes the `isDark` prop.
Applied to files:
- src/frontend/src/icons/eagerIconImports.ts
- src/frontend/src/icons/Murf/index.tsx
📚 Learning: 2025-07-28T15:56:47.865Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/icons.mdc:0-0
Timestamp: 2025-07-28T15:56:47.865Z
Learning: Applies to src/frontend/src/icons/*/*.@(js|jsx|ts|tsx) : Create a new directory for your icon in `src/frontend/src/icons/YourIconName/` and add your SVG as a React component (e.g., `YourIconName.jsx`). The SVG component must use the `isDark` prop to support both light and dark mode.
Applied to files:
- src/frontend/src/icons/eagerIconImports.ts
- src/frontend/src/icons/Murf/index.tsx
- src/frontend/src/icons/Murf/MurfSVG.jsx
📚 Learning: 2025-07-28T15:56:47.865Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/icons.mdc:0-0
Timestamp: 2025-07-28T15:56:47.865Z
Learning: Always use clear, recognizable, and consistent icon names for both backend and frontend (e.g., "AstraDB", "Postgres", "OpenAI").
Applied to files:
- src/frontend/src/icons/eagerIconImports.ts
- src/frontend/src/utils/styleUtils.ts
📚 Learning: 2025-09-30T00:09:51.631Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/docs_development.mdc:0-0
Timestamp: 2025-09-30T00:09:51.631Z
Learning: Applies to docs/sidebars.js : Keep sidebars.js updated to include new/changed docs sections and items using Docusaurus category structure
Applied to files:
- docs/sidebars.js
📚 Learning: 2025-09-07T05:44:46.715Z
Learnt from: TensorNull
PR: langflow-ai/langflow#9735
File: docs/docs/Components/bundles-cometapi.mdx:9-9
Timestamp: 2025-09-07T05:44:46.715Z
Learning: In Langflow bundle documentation files (docs/docs/Components/bundles-*.mdx), the standard link pattern for referencing the main Bundles page is [Bundles](/components-bundle-components), not /components-bundles. This pattern is used consistently across all 37+ bundle documentation files.
Applied to files:
- docs/sidebars.js
- docs/docs/Components/bundles-murf.mdx
📚 Learning: 2025-09-30T00:09:51.631Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/docs_development.mdc:0-0
Timestamp: 2025-09-30T00:09:51.631Z
Learning: Applies to docs/**/*.{md,mdx} : Use correct terminology capitalization: Langflow, Component, Flow, API, JSON
Applied to files:
- docs/sidebars.js
📚 Learning: 2025-06-23T12:46:52.420Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/icons.mdc:0-0
Timestamp: 2025-06-23T12:46:52.420Z
Learning: Export custom icon components in React using React.forwardRef to ensure proper ref forwarding and compatibility with parent components.
Applied to files:
- src/frontend/src/icons/Murf/index.tsx
📚 Learning: 2025-06-16T11:14:04.200Z
Learnt from: dolfim-ibm
PR: langflow-ai/langflow#8394
File: src/frontend/src/icons/Docling/index.tsx:4-6
Timestamp: 2025-06-16T11:14:04.200Z
Learning: The Langflow codebase consistently uses `React.PropsWithChildren<{}>` as the prop type for all icon components using forwardRef, rather than `React.SVGProps<SVGSVGElement>`. This is an established pattern across hundreds of icon files in src/frontend/src/icons/.
Applied to files:
- src/frontend/src/icons/Murf/index.tsx
- src/frontend/src/icons/Murf/MurfSVG.jsx
📚 Learning: 2025-06-23T12:46:52.420Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/icons.mdc:0-0
Timestamp: 2025-06-23T12:46:52.420Z
Learning: When implementing a new component icon in Langflow, ensure the icon name is clear, recognizable, and used consistently across both backend (Python 'icon' attribute) and frontend (React/TypeScript mapping).
Applied to files:
- src/frontend/src/icons/Murf/index.tsx
📚 Learning: 2025-06-23T12:46:52.420Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/icons.mdc:0-0
Timestamp: 2025-06-23T12:46:52.420Z
Learning: Custom SVG icon components in React should always support both light and dark mode by accepting an 'isdark' prop and adjusting colors accordingly.
Applied to files:
- src/frontend/src/icons/Murf/index.tsx
📚 Learning: 2025-07-18T18:27:12.609Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/frontend_development.mdc:0-0
Timestamp: 2025-07-18T18:27:12.609Z
Learning: Applies to src/frontend/src/components/**/@(FlowGraph|nodes)/**/*.{ts,tsx,js,jsx} : Use React Flow for flow graph visualization components.
Applied to files:
- src/frontend/src/icons/Murf/MurfSVG.jsx
📚 Learning: 2025-07-18T18:25:54.486Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-07-18T18:25:54.486Z
Learning: Applies to src/backend/base/langflow/components/**/__init__.py : Update __init__.py with alphabetical imports when adding new components
Applied to files:
- src/lfx/src/lfx/components/__init__.py
🧬 Code graph analysis (4)
src/frontend/src/icons/eagerIconImports.ts (1)
src/frontend/src/icons/Murf/index.tsx (1)
MurfIcon(4-8)
src/lfx/src/lfx/components/Murf/text_to_speech.py (3)
src/lfx/src/lfx/schema/data.py (1)
Data(26-288)src/lfx/src/lfx/schema/message.py (1)
Message(34-299)src/lfx/src/lfx/custom/custom_component/component.py (1)
log(1480-1497)
src/lfx/src/lfx/components/Murf/__init__.py (1)
src/lfx/src/lfx/components/Murf/text_to_speech.py (1)
MurfTextToSpeech(10-245)
src/frontend/src/icons/Murf/index.tsx (1)
src/frontend/src/icons/Murf/MurfSVG.jsx (1)
MurfSVG(1-150)
🪛 Gitleaks (8.28.0)
.secrets.baseline
[high] 1296-1296: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 1304-1304: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 LanguageTool
docs/docs/Components/bundles-murf.mdx
[grammar] ~35-~35: Use a hyphen to join words.
Context: ..., and other audio formats  ### Murf Text to Speech Parameters  You can inspect th...
(QB_NEW_EN_HYPHEN)
[grammar] ~35-~35: Use a hyphen to join words.
Context: ...nd other audio formats  ### Murf Text to Speech Parameters  You can inspect the c...
(QB_NEW_EN_HYPHEN)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
- GitHub Check: Run Frontend Tests / Playwright Tests - Shard 7/13
- GitHub Check: Run Frontend Tests / Playwright Tests - Shard 3/13
- GitHub Check: Run Frontend Tests / Playwright Tests - Shard 8/13
- GitHub Check: Run Frontend Tests / Playwright Tests - Shard 11/13
- GitHub Check: Run Frontend Tests / Playwright Tests - Shard 1/13
- GitHub Check: Lint Backend / Run Mypy (3.12)
- GitHub Check: Lint Backend / Run Mypy (3.10)
- GitHub Check: Test Docker Images / Test docker images
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 2
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 1
- GitHub Check: Test Docs Build / Test Docs Build
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 5
- GitHub Check: Lint Backend / Run Mypy (3.11)
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 3
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 4
- GitHub Check: Run Backend Tests / Integration Tests - Python 3.10
- GitHub Check: Run Frontend Unit Tests / Frontend Jest Unit Tests
- GitHub Check: Test Starter Templates
🔇 Additional comments (19)
src/backend/base/pyproject.toml (1)
131-132: LGTM!The Murf dependency is correctly added to the dev dependencies with an appropriate version constraint.
.secrets.baseline (1)
1290-1307: LGTM! Expected metadata update.The line number adjustments and timestamp update are expected when new code is added to the repository. These changes reflect the secrets baseline being regenerated after adding the Murf integration.
Also applies to: 1405-1405
docs/sidebars.js (2)
170-170: LGTM! Formatting improvement.The added space after the colon improves consistency with JavaScript/JSON formatting conventions.
329-329: LGTM!The Murf bundle entry is correctly positioned alphabetically between MongoDB and Notion, following the established pattern for bundle documentation. Based on learnings
pyproject.toml (1)
191-191: LGTM!The Murf dependency is correctly added to the dev dependencies with an appropriate version constraint.
src/frontend/src/utils/styleUtils.ts (2)
303-303: LGTM!The Murf bundle entry is correctly added with consistent naming and proper alphabetical positioning. The naming follows the pattern for clear, recognizable icon names. Based on learnings
461-461: LGTM!The icon mapping is correctly added and maintains alphabetical order in the nodeIconToDisplayIconMap.
src/lfx/src/lfx/components/__init__.py (3)
11-11: LGTM!The Murf import is correctly added to the TYPE_CHECKING block with proper alphabetical ordering. Based on learnings
171-171: LGTM!The Murf entry is correctly added to the dynamic imports mapping with proper alphabetical ordering and the appropriate
"__module__"value for lazy loading.
245-245: LGTM!The Murf export is correctly added to
__all__with proper alphabetical ordering, consistent with the TYPE_CHECKING and dynamic imports entries. Based on learningsdocs/docs/Components/bundles-murf.mdx (4)
1-4: LGTM!The front matter includes the required title and slug fields, following Docusaurus conventions.
6-13: LGTM!The imports and introduction follow the established patterns. The Bundles link correctly uses
/components-bundle-componentsas per the standard pattern for bundle documentation. Based on learnings
14-23: LGTM!The setup section provides clear instructions with appropriate links to official Murf documentation.
57-88: LGTM!The outputs, usage examples, and troubleshooting sections are well-structured and provide practical guidance for users.
src/frontend/src/constants/constants.ts (1)
799-799: LGTM!The addition of "Murf" to the bundle sidebar folder names is consistent with the existing pattern.
src/frontend/src/icons/lazyIconImports.ts (1)
290-290: LGTM!The lazy icon import entry for Murf follows the established pattern and is correctly placed alphabetically.
src/frontend/src/icons/eagerIconImports.ts (1)
68-68: LGTM!The import statement and eager icon mapping for Murf are correctly implemented and alphabetically ordered.
Also applies to: 189-189
src/frontend/src/icons/Murf/index.tsx (1)
1-8: LGTM!The MurfIcon wrapper component correctly uses
forwardRefwith the establishedReact.PropsWithChildren<{}>prop type pattern and properly forwards the ref and props to MurfSVG.src/lfx/src/lfx/components/Murf/__init__.py (1)
1-32: LGTM!The lazy loading implementation is well-structured with proper error handling, TYPE_CHECKING guards, and module caching. The
__getattr__and__dir__implementations follow Python best practices.
| const MurfSVG = (props) => ( | ||
| <svg | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| fill="none" | ||
| viewBox="0 0 256 256" | ||
| {...props} | ||
| > | ||
| <g clipPath="url(#clip0_338_416)"> | ||
| <path | ||
| fill="url(#paint0_linear_338_416)" | ||
| d="M166.716 166.136c-32.005 37.493-75.573 58.871-113.04 68.844l-.04.014c-.938.244-1.867.489-2.791.72a30.4 30.4 0 0 1-6.231.644c-16.76 0-30.342-13.582-30.342-30.342V32.394c.004 38.55 8.226 97.604 29.39 128.577 5.552 8.125 11.863 14.965 18.703 20.445.009 0 .022.009.03.022.565.458 1.139.902 1.712 1.333 30.04 22.756 70.613 17.592 101.72-17.08z" | ||
| ></path> | ||
| <path | ||
| fill="url(#paint1_radial_338_416)" | ||
| d="m166.366 165.78-.143.169-.022.026-.986 1.169c-10.374 11.276-21.916 19.587-33.974 24.631a44 44 0 0 1-.644.263c-8.667 3.457-17.609 5.24-26.596 5.24q-2.239 0-4.47-.147c-22.432-1.48-42.819-14.053-57.405-35.409-9.676-14.16-17.067-35.013-21.96-61.982-.09-.498-.183-.996-.271-1.494a349.946 349.946 0 0 1-1.453-8.973c-.094-.627-.183-1.249-.276-1.867a445 445 0 0 1-.778-5.777l-.307-2.498q-.075-.654-.155-1.307-.136-1.173-.262-2.333c0-.018-.005-.036-.005-.05q-.252-2.293-.471-4.532l-.027-.28-.186-1.96a410.493 410.493 0 0 1-.427-4.982c-.089-1.13-.169-2.24-.249-3.338-.009-.098-.013-.2-.022-.298q-.101-1.493-.196-2.938-.032-.567-.07-1.133-.058-.994-.116-1.97c.004-.008 0-.017 0-.026-.036-.64-.067-1.271-.103-1.898v-.124c-.049-.929-.088-1.84-.128-2.733a318 318 0 0 1-.107-2.632l-.014-.377q-.034-.921-.062-1.8l-.013-.387-.04-1.356c-.009-.284-.013-.569-.022-.844-.01-.276-.014-.547-.023-.813q.001-.12-.004-.24-.015-.58-.022-1.138c-.018-.885-.031-1.734-.045-2.556l-.013-1.302v-.213c0-.396-.009-.782-.009-1.16q-.002-.32-.004-.64-.006-.734-.005-1.414v-.444a7.584 7.584 0 0 1 12.37-5.853l1.097 1.097L166.37 165.78z" | ||
| ></path> | ||
| <path fill="url(#paint2_radial_338_416)" d="M19.098 93.563"></path> | ||
| <path | ||
| fill="url(#paint3_linear_338_416)" | ||
| fillRule="evenodd" | ||
| d="M22.787 99.264c4.247 23.402 11.109 45.696 21.532 60.955 14.846 21.725 35.302 33.368 56.744 34.328 21.449.96 44.099-8.761 63.327-30.191l1.985 1.781c-19.66 21.912-43.044 32.076-65.431 31.074-22.393-1.003-43.575-13.168-58.827-35.488-10.741-15.724-17.685-38.46-21.954-61.982-4.275-23.554-5.892-48.043-5.892-67.384h2.667c0 19.212 1.608 43.538 5.849 66.907" | ||
| clipRule="evenodd" | ||
| ></path> | ||
| <path | ||
| fill="url(#paint4_linear_338_416)" | ||
| d="M89.383 89.69c32.004-37.493 75.573-58.871 113.04-68.844l.04-.014c.938-.244 1.866-.489 2.791-.72a30.3 30.3 0 0 1 6.231-.644c16.76 0 30.342 13.582 30.342 30.342v173.622c-.004-38.551-8.226-97.604-29.391-128.577-5.551-8.125-11.862-14.965-18.702-20.445q-.015-.001-.031-.022a60 60 0 0 0-1.711-1.334c-30.04-22.755-70.614-17.59-101.72 17.08z" | ||
| ></path> | ||
| <path | ||
| fill="url(#paint5_radial_338_416)" | ||
| d="m89.74 90.047.142-.17.023-.026.986-1.169c10.374-11.275 21.916-19.586 33.974-24.63.213-.09.431-.179.644-.263 8.667-3.458 17.609-5.24 26.596-5.24q2.238 0 4.471.147c22.431 1.48 42.818 14.053 57.404 35.408 9.676 14.16 17.067 35.014 21.96 61.983q.135.746.271 1.493.001.029.009.058.408 2.326.782 4.627.028.2.063.395.314 1.96.6 3.893.137.94.275 1.867.413 2.919.778 5.778l.307 2.498q.075.653.155 1.306.135 1.174.262 2.334c0 .017.005.035.005.049q.252 2.293.471 4.533l.027.28.186 1.96q.015.199.036.391.133 1.472.253 2.911.066.847.138 1.68c.089 1.129.169 2.24.249 3.338.009.098.013.2.022.298q.101 1.492.196 2.937.032.567.071 1.134c.04.662.075 1.32.115 1.969-.004.009 0 .017 0 .026.036.64.067 1.271.103 1.898v.125c.048.928.088 1.84.128 2.733a314 314 0 0 1 .12 3.009q.035.92.063 1.8l.013.386.04 1.356c.009.284.013.569.022.844q.012.414.022.814-.001.12.005.24.015.578.022 1.138.025 1.324.045 2.555l.013 1.302v.214c0 .395.009.782.009 1.16 0 .213.004.426.004.64q.006.733.005 1.413v.444a7.585 7.585 0 0 1-12.369 5.854l-1.098-1.098z" | ||
| ></path> | ||
| <path fill="url(#paint6_radial_338_416)" d="M237.005 162.263"></path> | ||
| <path | ||
| fill="url(#paint7_linear_338_416)" | ||
| fillRule="evenodd" | ||
| d="M155.038 61.28c-21.448-.96-44.098 8.76-63.327 30.19l-1.984-1.78c19.66-21.912 43.043-32.076 65.43-31.074 22.393 1.003 43.575 13.168 58.827 35.488 10.744 15.721 17.688 38.458 21.956 61.981 4.274 23.554 5.89 48.044 5.89 67.384h-2.667c0-19.211-1.606-43.538-5.846-66.908-4.247-23.402-11.109-45.697-21.534-60.953l1.1-.752-1.1.752c-14.846-21.725-35.302-33.368-56.745-34.328" | ||
| clipRule="evenodd" | ||
| ></path> | ||
| <path | ||
| fill="#fff" | ||
| d="M127.999 182.361c30.022 0 54.36-24.338 54.36-54.36 0-30.023-24.338-54.36-54.36-54.36-30.023 0-54.36 24.337-54.36 54.36 0 30.022 24.337 54.36 54.36 54.36" | ||
| ></path> | ||
| </g> | ||
| <defs> | ||
| <radialGradient | ||
| id="paint1_radial_338_416" | ||
| cx="0" | ||
| cy="0" | ||
| r="1" | ||
| gradientTransform="matrix(-152.007 0 0 -154.231 121.623 142.275)" | ||
| gradientUnits="userSpaceOnUse" | ||
| > | ||
| <stop offset="0.28" stopColor="#DAABFF"></stop> | ||
| <stop offset="0.87" stopColor="#735DFF"></stop> | ||
| </radialGradient> | ||
| <radialGradient | ||
| id="paint2_radial_338_416" | ||
| cx="0" | ||
| cy="0" | ||
| r="1" | ||
| gradientTransform="translate(19.706 96.318)scale(3.13778)" | ||
| gradientUnits="userSpaceOnUse" | ||
| > | ||
| <stop offset="0.3" stopColor="#DAABFF"></stop> | ||
| <stop offset="0.87" stopColor="#735DFF"></stop> | ||
| </radialGradient> | ||
| <radialGradient | ||
| id="paint5_radial_338_416" | ||
| cx="0" | ||
| cy="0" | ||
| r="1" | ||
| gradientTransform="matrix(152.003 0 0 154.231 134.486 113.539)" | ||
| gradientUnits="userSpaceOnUse" | ||
| > | ||
| <stop offset="0.22" stopColor="#FFD522"></stop> | ||
| <stop offset="0.27" stopColor="#FFCA27"></stop> | ||
| <stop offset="0.34" stopColor="#FFAC37"></stop> | ||
| <stop offset="0.45" stopColor="#FF7D50"></stop> | ||
| <stop offset="0.54" stopColor="#FF4B6C"></stop> | ||
| <stop offset="1" stopColor="#C81395"></stop> | ||
| </radialGradient> | ||
| <radialGradient | ||
| id="paint6_radial_338_416" | ||
| cx="0" | ||
| cy="0" | ||
| r="1" | ||
| gradientTransform="matrix(-3.13777 0 0 -3.13778 236.396 159.508)" | ||
| gradientUnits="userSpaceOnUse" | ||
| > | ||
| <stop offset="0.3" stopColor="#DAABFF"></stop> | ||
| <stop offset="0.87" stopColor="#735DFF"></stop> | ||
| </radialGradient> | ||
| <linearGradient | ||
| id="paint0_linear_338_416" | ||
| x1="-29.273" | ||
| x2="123.447" | ||
| y1="103.961" | ||
| y2="187.663" | ||
| gradientUnits="userSpaceOnUse" | ||
| > | ||
| <stop offset="0.42" stopColor="#C6B2FF"></stop> | ||
| <stop offset="0.84" stopColor="#735DFF"></stop> | ||
| </linearGradient> | ||
| <linearGradient | ||
| id="paint3_linear_338_416" | ||
| x1="63.123" | ||
| x2="112.042" | ||
| y1="19.004" | ||
| y2="197.948" | ||
| gradientUnits="userSpaceOnUse" | ||
| > | ||
| <stop stopColor="#fff" stopOpacity="0"></stop> | ||
| <stop offset="0.1" stopColor="#fff" stopOpacity="0.01"></stop> | ||
| <stop offset="0.3" stopColor="#fff" stopOpacity="0.073"></stop> | ||
| <stop offset="0.59" stopColor="#fff" stopOpacity="0.348"></stop> | ||
| <stop offset="0.94" stopColor="#fff"></stop> | ||
| </linearGradient> | ||
| <linearGradient | ||
| id="paint4_linear_338_416" | ||
| x1="122.821" | ||
| x2="259.519" | ||
| y1="137.555" | ||
| y2="116.348" | ||
| gradientUnits="userSpaceOnUse" | ||
| > | ||
| <stop offset="0.35" stopColor="#F02173"></stop> | ||
| <stop offset="0.61" stopColor="#E81DB8"></stop> | ||
| <stop offset="0.76" stopColor="#C516E1"></stop> | ||
| <stop offset="1" stopColor="#735DFF"></stop> | ||
| </linearGradient> | ||
| <linearGradient | ||
| id="paint7_linear_338_416" | ||
| x1="193.079" | ||
| x2="144.177" | ||
| y1="236.788" | ||
| y2="57.871" | ||
| gradientUnits="userSpaceOnUse" | ||
| > | ||
| <stop stopColor="#fff" stopOpacity="0"></stop> | ||
| <stop offset="0.1" stopColor="#fff" stopOpacity="0.01"></stop> | ||
| <stop offset="0.3" stopColor="#fff" stopOpacity="0.073"></stop> | ||
| <stop offset="0.59" stopColor="#fff" stopOpacity="0.348"></stop> | ||
| <stop offset="0.94" stopColor="#fff"></stop> | ||
| </linearGradient> | ||
| <clipPath id="clip0_338_416"> | ||
| <path fill="#fff" d="M14.22 14.223h227.557v227.556H14.22z"></path> | ||
| </clipPath> | ||
| </defs> | ||
| </svg> | ||
| ); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add theme support with isDark prop.
The SVG component should accept and use an isDark prop to support both light and dark themes. Currently, all colors are hardcoded, which doesn't align with Langflow's theming system.
Based on coding guidelines.
Consider adding the isDark prop and adjusting fill colors accordingly:
-const MurfSVG = (props) => (
+const MurfSVG = ({ isDark, ...props }) => (
   <svg
     xmlns="http://www.w3.org/2000/svg"
     fill="none"
     viewBox="0 0 256 256"
     {...props}
   >Then adjust gradient and fill definitions based on the isDark value to ensure proper contrast in both light and dark modes.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/frontend/src/icons/Murf/MurfSVG.jsx around lines 1-150, the component
currently hardcodes gradient and fill colors and does not accept an isDark prop;
add an isDark boolean prop (default false) to the component signature and use it
to conditionally select alternate gradient stopColor values and fill attributes
(e.g., swap lighter stopColors/white highlights for darker variants or
higher-contrast colors when isDark is true) by computing gradient/fill color
variables before returning the SVG; update gradient definitions and any
hardcoded white/opacity stops to reference those variables (or alternate
gradient IDs) so the SVG renders appropriate contrast in both light and dark
themes, and ensure existing props are still spread onto the <svg> element.
| def generate_speech(self) -> Data: | ||
| try: | ||
| from murf import Murf | ||
|  | ||
| # Initialize Murf client | ||
| client = Murf(api_key=self.api_key) | ||
|  | ||
| if not self.api_key: | ||
| msg = "Set your MURF_API_KEY as environment variable." | ||
| raise ValueError(msg) | ||
|  | ||
| # Generate speech | ||
| res = client.text_to_speech.generate( | ||
| text=self.text, | ||
| voice_id=self.voice_id, | ||
| encode_as_base_64=self.encode_as_base_64, | ||
| channel_type=self.channel_type.lower(), | ||
| format=self.format, | ||
| rate=self.rate, | ||
| pitch=self.pitch, | ||
| sample_rate=int(self.sample_rate), | ||
| **({"multi_native_locale": self.multi_native_locale} if self.multi_native_locale is not None else {}), | ||
| ) | ||
| return Data(data=res) | ||
| except Exception as e: # noqa: BLE001 | ||
| self.log(f"Error generating speech: {e!s}") | ||
| return Output(name="error", message=Message(text=f"API error: {e}"), error=True) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Fix return type inconsistency and API key validation order.
- 
Return type mismatch: Line 123 returns Output(...)but the method signature specifies-> Data. TheOutputclass is meant for defining component outputs in theoutputslist, not for returning error data from methods.
- 
Redundant API key validation: Lines 104-106 check if self.api_keyis empty after already using it to initialize the Murf client on line 102. If the key is invalid, the client initialization would fail first.
Apply this diff to fix both issues:
 def generate_speech(self) -> Data:
     try:
         from murf import Murf
 
+        if not self.api_key:
+            msg = "Set your MURF_API_KEY as environment variable."
+            raise ValueError(msg)
+
         # Initialize Murf client
         client = Murf(api_key=self.api_key)
 
-        if not self.api_key:
-            msg = "Set your MURF_API_KEY as environment variable."
-            raise ValueError(msg)
-
         # Generate speech
         res = client.text_to_speech.generate(
             text=self.text,
             voice_id=self.voice_id,
             encode_as_base_64=self.encode_as_base_64,
             channel_type=self.channel_type.lower(),
             format=self.format,
             rate=self.rate,
             pitch=self.pitch,
             sample_rate=int(self.sample_rate),
             **({"multi_native_locale": self.multi_native_locale} if self.multi_native_locale is not None else {}),
         )
         return Data(data=res)
     except Exception as e:  # noqa: BLE001
         self.log(f"Error generating speech: {e!s}")
-        return Output(name="error", message=Message(text=f"API error: {e}"), error=True)
+        return Data(data={"error": f"API error: {e}"})🤖 Prompt for AI Agents
In src/lfx/src/lfx/components/Murf/text_to_speech.py around lines 97 to 123, the
method validates the API key after initializing the Murf client and returns an
Output on error even though the signature declares -> Data; move the API key
presence check to before creating the Murf client so you never call
Murf(api_key=...) with a missing key, and on exception return a Data instance
representing the error (e.g., Data containing error details or None payload and
an error flag/message) instead of Output while keeping the logging of the
exception.
| def initialize_voice_list_data(self) -> dict: | ||
| try: | ||
| from murf import Murf | ||
|  | ||
| client = Murf(api_key=self.api_key) | ||
| voice_list = client.text_to_speech.get_voices() | ||
| voice_list_dict = [voice.__dict__ for voice in voice_list] | ||
| locales_to_voices = {} | ||
| for voice in voice_list_dict: | ||
| locale = voice["locale"] | ||
| if locale not in locales_to_voices: | ||
| locales_to_voices[locale] = { | ||
| "display_name": f"{voice['display_language']}({voice['accent']})", | ||
| "voice_list": {}, | ||
| } | ||
| locales_to_voices[locale]["voice_list"][voice["voice_id"]] = voice | ||
| return locales_to_voices # noqa: TRY300 | ||
| except (KeyError, AttributeError, ValueError) as e: | ||
| self.log(f"Error initializing Murf Voice List: {e}") | ||
| msg = f"Error initializing Murf Voice List: {e}" | ||
| return Data(data={"error": msg}) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix return type inconsistency in error handling.
The method signature specifies -> dict but line 145 returns Data(data={"error": msg}) on error. This type inconsistency can cause issues for callers expecting a dictionary.
Apply this diff to maintain consistent return types:
     except (KeyError, AttributeError, ValueError) as e:
         self.log(f"Error initializing Murf Voice List: {e}")
-        msg = f"Error initializing Murf Voice List: {e}"
-        return Data(data={"error": msg})
+        return {"error": f"Error initializing Murf Voice List: {e}"}🤖 Prompt for AI Agents
In src/lfx/src/lfx/components/Murf/text_to_speech.py around lines 125 to 145,
the method initialize_voice_list_data is declared to return a dict but on
exception returns a Data(...) object; change the error branch to return a plain
dict with the error message (e.g., {"error": msg}) instead of Data(...), keeping
the existing self.log call intact so callers always receive a dict as the return
type.
| def _update_build_config_voice_id(self, build_config: dict) -> dict: | ||
| selected_locale = build_config["locale"]["value"] | ||
| voices = build_config.get("murf_voice_list") | ||
| if voices and selected_locale in voices: | ||
| options = voices.get(selected_locale).get("voice_list") | ||
| build_config["voice_id"]["options"] = list(options.keys()) | ||
| build_config["voice_id"]["options_metadata"] = [ | ||
| { | ||
| "Name": voice.get("display_name"), | ||
| "Voice ID": voice.get("voice_id"), | ||
| "Description": voice.get("description"), | ||
| "Gender": voice.get("gender"), | ||
| "Locale": voice.get("locale"), | ||
| "Accent": voice.get("accent"), | ||
| "Styles": voice.get("available_styles"), | ||
| "Supported Locales": list(voice.get("supported_locales").keys()), | ||
| } | ||
| for voice in options.values() | ||
| ] | ||
| build_config["voice_id"]["value"] = next(iter(options.keys())) | ||
| build_config["voice_id"]["show"] = True | ||
| build_config = self._update_build_config_multi_native_locale( | ||
| build_config, build_config["voice_id"]["value"], voices | ||
| ) | ||
| else: | ||
| build_config["voice_id"]["show"] = False | ||
| return build_config | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add safety checks for dictionary access.
Two potential runtime errors:
- 
Line 162: Direct access to build_config["locale"]["value"]without checking if keys exist could raiseKeyError.
- 
Line 180: Using next(iter(options.keys()))without verifyingoptionsis non-empty could raiseStopIteration.
Apply this diff to add safety checks:
 def _update_build_config_voice_id(self, build_config: dict) -> dict:
-    selected_locale = build_config["locale"]["value"]
+    selected_locale = build_config.get("locale", {}).get("value")
+    if not selected_locale:
+        build_config["voice_id"]["show"] = False
+        return build_config
+        
     voices = build_config.get("murf_voice_list")
     if voices and selected_locale in voices:
         options = voices.get(selected_locale).get("voice_list")
+        if not options:
+            build_config["voice_id"]["show"] = False
+            return build_config
+            
         build_config["voice_id"]["options"] = list(options.keys())🤖 Prompt for AI Agents
In src/lfx/src/lfx/components/Murf/text_to_speech.py around lines 161 to 187,
the code assumes build_config and nested keys exist and that options is
non-empty; add defensive checks: verify build_config.get("locale") and
build_config["locale"].get("value") exist (use .get and default fallback) before
using selected_locale, handle missing voices or missing selected_locale by
setting voice_id.show=False and returning early, and ensure options is a
non-empty dict before calling next(iter(...))—if options is empty, skip setting
voice_id.value (or set to None/default) and keep show=False; also guard accesses
like voices.get(selected_locale).get("voice_list") with safe .get calls to avoid
KeyError.
| def _update_build_config_multi_native_locale( | ||
| self, build_config: dict, field_value: str, voices: dict | None = None | ||
| ) -> dict: | ||
| if voices is None: | ||
| voices = build_config["murf_voice_list"] | ||
| if voices is None or len(voices) == 0: | ||
| voices = self.initialize_voice_list_data() | ||
| build_config["murf_voice_list"] = voices | ||
| selected_locale = build_config["locale"]["value"] | ||
| selected_voice_id = field_value | ||
|  | ||
| selected_voice = voices[selected_locale]["voice_list"][selected_voice_id] | ||
| if selected_voice and selected_voice["supported_locales"]: | ||
| locale_options = selected_voice["supported_locales"].keys() | ||
| build_config["multi_native_locale"]["options"] = list(locale_options) | ||
| options_metadata = [] | ||
| for locale in selected_voice["supported_locales"].values(): | ||
| if locale is dict: | ||
| options_metadata.append({"locale": locale.get("detail")}) | ||
| else: | ||
| options_metadata.append({"locale": locale.__dict__.get("detail")}) | ||
| build_config["multi_native_locale"]["options_metadata"] = options_metadata | ||
| build_config["multi_native_locale"]["value"] = selected_voice.get("locale") | ||
| build_config["multi_native_locale"]["show"] = True | ||
| else: | ||
| build_config["multi_native_locale"]["show"] = False | ||
| return build_config | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Fix type checking bug and add safety checks.
- 
Line 206 - Logic bug: Uses locale is dictwhich checks iflocaleis thedictclass itself, not if it's an instance of dict. This condition will always beFalse. Should useisinstance(locale, dict).
- 
Line 200: Direct nested dictionary access without safety checks could raise KeyErrorif any key doesn't exist.
Apply this diff to fix both issues:
     selected_locale = build_config["locale"]["value"]
     selected_voice_id = field_value
 
-    selected_voice = voices[selected_locale]["voice_list"][selected_voice_id]
+    try:
+        selected_voice = voices[selected_locale]["voice_list"][selected_voice_id]
+    except (KeyError, TypeError):
+        build_config["multi_native_locale"]["show"] = False
+        return build_config
+        
     if selected_voice and selected_voice["supported_locales"]:
         locale_options = selected_voice["supported_locales"].keys()
         build_config["multi_native_locale"]["options"] = list(locale_options)
         options_metadata = []
         for locale in selected_voice["supported_locales"].values():
-            if locale is dict:
+            if isinstance(locale, dict):
                 options_metadata.append({"locale": locale.get("detail")})
             else:
                 options_metadata.append({"locale": locale.__dict__.get("detail")})📝 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.
| def _update_build_config_multi_native_locale( | |
| self, build_config: dict, field_value: str, voices: dict | None = None | |
| ) -> dict: | |
| if voices is None: | |
| voices = build_config["murf_voice_list"] | |
| if voices is None or len(voices) == 0: | |
| voices = self.initialize_voice_list_data() | |
| build_config["murf_voice_list"] = voices | |
| selected_locale = build_config["locale"]["value"] | |
| selected_voice_id = field_value | |
| selected_voice = voices[selected_locale]["voice_list"][selected_voice_id] | |
| if selected_voice and selected_voice["supported_locales"]: | |
| locale_options = selected_voice["supported_locales"].keys() | |
| build_config["multi_native_locale"]["options"] = list(locale_options) | |
| options_metadata = [] | |
| for locale in selected_voice["supported_locales"].values(): | |
| if locale is dict: | |
| options_metadata.append({"locale": locale.get("detail")}) | |
| else: | |
| options_metadata.append({"locale": locale.__dict__.get("detail")}) | |
| build_config["multi_native_locale"]["options_metadata"] = options_metadata | |
| build_config["multi_native_locale"]["value"] = selected_voice.get("locale") | |
| build_config["multi_native_locale"]["show"] = True | |
| else: | |
| build_config["multi_native_locale"]["show"] = False | |
| return build_config | |
| def _update_build_config_multi_native_locale( | |
| self, build_config: dict, field_value: str, voices: dict | None = None | |
| ) -> dict: | |
| if voices is None: | |
| voices = build_config["murf_voice_list"] | |
| if voices is None or len(voices) == 0: | |
| voices = self.initialize_voice_list_data() | |
| build_config["murf_voice_list"] = voices | |
| selected_locale = build_config["locale"]["value"] | |
| selected_voice_id = field_value | |
| try: | |
| selected_voice = voices[selected_locale]["voice_list"][selected_voice_id] | |
| except (KeyError, TypeError): | |
| build_config["multi_native_locale"]["show"] = False | |
| return build_config | |
| if selected_voice and selected_voice["supported_locales"]: | |
| locale_options = selected_voice["supported_locales"].keys() | |
| build_config["multi_native_locale"]["options"] = list(locale_options) | |
| options_metadata = [] | |
| for locale in selected_voice["supported_locales"].values(): | |
| if isinstance(locale, dict): | |
| options_metadata.append({"locale": locale.get("detail")}) | |
| else: | |
| options_metadata.append({"locale": locale.__dict__.get("detail")}) | |
| build_config["multi_native_locale"]["options_metadata"] = options_metadata | |
| build_config["multi_native_locale"]["value"] = selected_voice.get("locale") | |
| build_config["multi_native_locale"]["show"] = True | |
| else: | |
| build_config["multi_native_locale"]["show"] = False | |
| return build_config | 
🤖 Prompt for AI Agents
In src/lfx/src/lfx/components/Murf/text_to_speech.py around lines 189 to 215,
the function _update_build_config_multi_native_locale has two problems: it uses
“locale is dict” (should be isinstance(locale, dict)) and it performs unsafe
nested dict access that can raise KeyError; fix by replacing the identity check
with isinstance(locale, dict) and add defensive checks and .get() usage for
build_config keys (e.g., use build_config.get("murf_voice_list") and
build_config.get("locale", {}).get("value")), verify selected_locale exists in
voices, verify selected_voice and its "supported_locales" are present and are a
dict before iterating, and fallback safely (e.g., set show=False or empty lists)
when any required piece is missing so no KeyError occurs.
c78a5d2    to
    640178a      
    Compare
  
    | @coderabbitai review | 
| ✅ Actions performedReview triggered. 
 | 
feat: Add Murf Text-to-Speech Component
Description
This PR adds a new Langflow component that integrates with Murf AI's Text-to-Speech API, allowing users to convert text to high-quality speech audio with customizable voice parameters.
Changes
MurfTextToSpeechcomponent (src/lfx/src/lfx/components/Murf/text_to_speech.py)Features
Testing
Documentation
Note: This PR adds a new component that requires the
murfPython package to be installed and a valid Murf API key to use.Summary by CodeRabbit
New Features
UI / UX
Documentation
Tests
Chores