From 03f9f25a51c78785b1c8f7af94d8f4627b9902d8 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Wed, 24 Sep 2025 14:20:29 -0500 Subject: [PATCH] pr feedback --- frontend/components/ui/select.tsx | 12 ++- frontend/src/app/settings/page.tsx | 3 +- src/api/settings.py | 153 ++++------------------------- src/services/flows_service.py | 122 +++++++++++++++++++++++ 4 files changed, 151 insertions(+), 139 deletions(-) diff --git a/frontend/components/ui/select.tsx b/frontend/components/ui/select.tsx index f9571262..b8e19381 100644 --- a/frontend/components/ui/select.tsx +++ b/frontend/components/ui/select.tsx @@ -2,7 +2,7 @@ import * as React from "react" import * as SelectPrimitive from "@radix-ui/react-select" -import { Check, ChevronDown, ChevronUp } from "lucide-react" +import { Check, ChevronDown, ChevronUp, Lock } from "lucide-react" import { cn } from "@/lib/utils" @@ -15,18 +15,24 @@ const SelectValue = SelectPrimitive.Value const SelectTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( +>(({ className, children, disabled, ...props }, ref) => ( span]:line-clamp-1", + disabled && "bg-muted", className )} + disabled={disabled} {...props} > {children} - + {disabled ? ( + + ) : ( + + )} )) diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/page.tsx index eea555c2..ed8b0eba 100644 --- a/frontend/src/app/settings/page.tsx +++ b/frontend/src/app/settings/page.tsx @@ -739,8 +739,9 @@ function KnowledgeSourcesPage() { settings.knowledge?.embedding_model || modelsData?.embedding_models?.find(m => m.default)?.value || "text-embedding-ada-002" } onValueChange={handleEmbeddingModelChange} + disabled={true} > - + diff --git a/src/api/settings.py b/src/api/settings.py index cba6c14a..37072c63 100644 --- a/src/api/settings.py +++ b/src/api/settings.py @@ -205,7 +205,8 @@ async def update_settings(request, session_manager): # Also update the chat flow with the new model try: - await _update_chat_flow_model(body["llm_model"]) + flows_service = _get_flows_service() + await flows_service.update_chat_flow_model(body["llm_model"]) logger.info(f"Successfully updated chat flow model to '{body['llm_model']}'") except Exception as e: logger.error(f"Failed to update chat flow model: {str(e)}") @@ -218,7 +219,8 @@ async def update_settings(request, session_manager): # Also update the chat flow with the new system prompt try: - await _update_chat_flow_system_prompt(body["system_prompt"]) + flows_service = _get_flows_service() + await flows_service.update_chat_flow_system_prompt(body["system_prompt"]) logger.info(f"Successfully updated chat flow system prompt") except Exception as e: logger.error(f"Failed to update chat flow system prompt: {str(e)}") @@ -240,7 +242,8 @@ async def update_settings(request, session_manager): # Also update the ingest flow with the new embedding model try: - await _update_ingest_flow_embedding_model(body["embedding_model"].strip()) + flows_service = _get_flows_service() + await flows_service.update_ingest_flow_embedding_model(body["embedding_model"].strip()) logger.info(f"Successfully updated ingest flow embedding model to '{body['embedding_model'].strip()}'") except Exception as e: logger.error(f"Failed to update ingest flow embedding model: {str(e)}") @@ -262,7 +265,8 @@ async def update_settings(request, session_manager): # Also update the flow with the new docling preset try: - await _update_flow_docling_preset(body["doclingPresets"], preset_configs[body["doclingPresets"]]) + flows_service = _get_flows_service() + await flows_service.update_flow_docling_preset(body["doclingPresets"], preset_configs[body["doclingPresets"]]) logger.info(f"Successfully updated docling preset in flow to '{body['doclingPresets']}'") except Exception as e: logger.error(f"Failed to update docling preset in flow: {str(e)}") @@ -279,7 +283,8 @@ async def update_settings(request, session_manager): # Also update the ingest flow with the new chunk size try: - await _update_ingest_flow_chunk_size(body["chunk_size"]) + flows_service = _get_flows_service() + await flows_service.update_ingest_flow_chunk_size(body["chunk_size"]) logger.info(f"Successfully updated ingest flow chunk size to {body['chunk_size']}") except Exception as e: logger.error(f"Failed to update ingest flow chunk size: {str(e)}") @@ -297,7 +302,8 @@ async def update_settings(request, session_manager): # Also update the ingest flow with the new chunk overlap try: - await _update_ingest_flow_chunk_overlap(body["chunk_overlap"]) + flows_service = _get_flows_service() + await flows_service.update_ingest_flow_chunk_overlap(body["chunk_overlap"]) logger.info(f"Successfully updated ingest flow chunk overlap to {body['chunk_overlap']}") except Exception as e: logger.error(f"Failed to update ingest flow chunk overlap: {str(e)}") @@ -582,136 +588,12 @@ async def onboarding(request, flows_service): ) -def _find_node_in_flow(flow_data, node_id=None, display_name=None): - """ - Helper function to find a node in flow data by ID or display name. - Returns tuple of (node, node_index) or (None, None) if not found. - """ - nodes = flow_data.get("data", {}).get("nodes", []) - - for i, node in enumerate(nodes): - node_data = node.get("data", {}) - node_template = node_data.get("node", {}) - - # Check by ID if provided - if node_id and node_data.get("id") == node_id: - return node, i - - # Check by display_name if provided - if display_name and node_template.get("display_name") == display_name: - return node, i - - return None, None -async def _update_flow_docling_preset(preset: str, preset_config: dict): - """Helper function to update docling preset in the ingest flow""" - if not LANGFLOW_INGEST_FLOW_ID: - raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured") - - await _update_flow_field(LANGFLOW_INGEST_FLOW_ID, "docling_serve_opts", preset_config, - node_id=DOCLING_COMPONENT_ID) - - -async def _update_ingest_flow_chunk_size(chunk_size: int): - """Helper function to update chunk size in the ingest flow""" - if not LANGFLOW_INGEST_FLOW_ID: - raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured") - - await _update_flow_field(LANGFLOW_INGEST_FLOW_ID, "chunk_size", chunk_size, - node_display_name="Split Text", - node_id="SplitText-3ZI5B") - - -async def _update_ingest_flow_chunk_overlap(chunk_overlap: int): - """Helper function to update chunk overlap in the ingest flow""" - if not LANGFLOW_INGEST_FLOW_ID: - raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured") - - await _update_flow_field(LANGFLOW_INGEST_FLOW_ID, "chunk_overlap", chunk_overlap, - node_display_name="Split Text", - node_id="SplitText-3ZI5B") - - -async def _update_ingest_flow_embedding_model(embedding_model: str): - """Helper function to update embedding model in the ingest flow""" - if not LANGFLOW_INGEST_FLOW_ID: - raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured") - - await _update_flow_field(LANGFLOW_INGEST_FLOW_ID, "model", embedding_model, - node_display_name="Embedding Model", - node_id="EmbeddingModel-eZ6bT") - - -async def _update_flow_field(flow_id: str, field_name: str, field_value: str, node_display_name: str = None, node_id: str = None): - """ - Generic helper function to update any field in any Langflow component. - - Args: - flow_id: The ID of the flow to update - field_name: The name of the field to update (e.g., 'model_name', 'system_message', 'docling_serve_opts') - field_value: The new value to set - node_display_name: The display name to search for (optional) - node_id: The node ID to search for (optional, used as fallback or primary) - """ - if not flow_id: - raise ValueError("flow_id is required") - - # Get the current flow data from Langflow - response = await clients.langflow_request( - "GET", f"/api/v1/flows/{flow_id}" - ) - - if response.status_code != 200: - raise Exception(f"Failed to get flow: HTTP {response.status_code} - {response.text}") - - flow_data = response.json() - - # Find the target component by display name first, then by ID as fallback - target_node, target_node_index = None, None - if node_display_name: - target_node, target_node_index = _find_node_in_flow(flow_data, display_name=node_display_name) - - if target_node is None and node_id: - target_node, target_node_index = _find_node_in_flow(flow_data, node_id=node_id) - - if target_node is None: - identifier = node_display_name or node_id - raise Exception(f"Component '{identifier}' not found in flow {flow_id}") - - # Update the field value directly in the existing node - template = target_node.get("data", {}).get("node", {}).get("template", {}) - if template.get(field_name): - flow_data["data"]["nodes"][target_node_index]["data"]["node"]["template"][field_name]["value"] = field_value - else: - identifier = node_display_name or node_id - raise Exception(f"{field_name} field not found in {identifier} component") - - # Update the flow via PATCH request - patch_response = await clients.langflow_request( - "PATCH", f"/api/v1/flows/{flow_id}", json=flow_data - ) - - if patch_response.status_code != 200: - raise Exception(f"Failed to update flow: HTTP {patch_response.status_code} - {patch_response.text}") - - -async def _update_chat_flow_model(model_name: str): - """Helper function to update the model in the chat flow""" - if not LANGFLOW_CHAT_FLOW_ID: - raise ValueError("LANGFLOW_CHAT_FLOW_ID is not configured") - await _update_flow_field(LANGFLOW_CHAT_FLOW_ID, "model_name", model_name, - node_display_name="Language Model", - node_id="LanguageModelComponent-0YME7") - - -async def _update_chat_flow_system_prompt(system_prompt: str): - """Helper function to update the system prompt in the chat flow""" - if not LANGFLOW_CHAT_FLOW_ID: - raise ValueError("LANGFLOW_CHAT_FLOW_ID is not configured") - await _update_flow_field(LANGFLOW_CHAT_FLOW_ID, "system_message", system_prompt, - node_display_name="Language Model", - node_id="LanguageModelComponent-0YME7") +def _get_flows_service(): + """Helper function to get flows service instance""" + from services.flows_service import FlowsService + return FlowsService() async def update_docling_preset(request, session_manager): @@ -741,7 +623,8 @@ async def update_docling_preset(request, session_manager): preset_config = preset_configs[preset] # Use the helper function to update the flow - await _update_flow_docling_preset(preset, preset_config) + flows_service = _get_flows_service() + await flows_service.update_flow_docling_preset(preset, preset_config) logger.info(f"Successfully updated docling preset to '{preset}' in ingest flow") diff --git a/src/services/flows_service.py b/src/services/flows_service.py index 4c3872ca..8993025a 100644 --- a/src/services/flows_service.py +++ b/src/services/flows_service.py @@ -400,6 +400,128 @@ class FlowsService: return node return None + def _find_node_in_flow(self, flow_data, node_id=None, display_name=None): + """ + Helper function to find a node in flow data by ID or display name. + Returns tuple of (node, node_index) or (None, None) if not found. + """ + nodes = flow_data.get("data", {}).get("nodes", []) + + for i, node in enumerate(nodes): + node_data = node.get("data", {}) + node_template = node_data.get("node", {}) + + # Check by ID if provided + if node_id and node_data.get("id") == node_id: + return node, i + + # Check by display_name if provided + if display_name and node_template.get("display_name") == display_name: + return node, i + + return None, None + + async def _update_flow_field(self, flow_id: str, field_name: str, field_value: str, node_display_name: str = None, node_id: str = None): + """ + Generic helper function to update any field in any Langflow component. + + Args: + flow_id: The ID of the flow to update + field_name: The name of the field to update (e.g., 'model_name', 'system_message', 'docling_serve_opts') + field_value: The new value to set + node_display_name: The display name to search for (optional) + node_id: The node ID to search for (optional, used as fallback or primary) + """ + if not flow_id: + raise ValueError("flow_id is required") + + # Get the current flow data from Langflow + response = await clients.langflow_request( + "GET", f"/api/v1/flows/{flow_id}" + ) + + if response.status_code != 200: + raise Exception(f"Failed to get flow: HTTP {response.status_code} - {response.text}") + + flow_data = response.json() + + # Find the target component by display name first, then by ID as fallback + target_node, target_node_index = None, None + if node_display_name: + target_node, target_node_index = self._find_node_in_flow(flow_data, display_name=node_display_name) + + if target_node is None and node_id: + target_node, target_node_index = self._find_node_in_flow(flow_data, node_id=node_id) + + if target_node is None: + identifier = node_display_name or node_id + raise Exception(f"Component '{identifier}' not found in flow {flow_id}") + + # Update the field value directly in the existing node + template = target_node.get("data", {}).get("node", {}).get("template", {}) + if template.get(field_name): + flow_data["data"]["nodes"][target_node_index]["data"]["node"]["template"][field_name]["value"] = field_value + else: + identifier = node_display_name or node_id + raise Exception(f"{field_name} field not found in {identifier} component") + + # Update the flow via PATCH request + patch_response = await clients.langflow_request( + "PATCH", f"/api/v1/flows/{flow_id}", json=flow_data + ) + + if patch_response.status_code != 200: + raise Exception(f"Failed to update flow: HTTP {patch_response.status_code} - {patch_response.text}") + + async def update_chat_flow_model(self, model_name: str): + """Helper function to update the model in the chat flow""" + if not LANGFLOW_CHAT_FLOW_ID: + raise ValueError("LANGFLOW_CHAT_FLOW_ID is not configured") + await self._update_flow_field(LANGFLOW_CHAT_FLOW_ID, "model_name", model_name, + node_display_name="Language Model", + node_id="LanguageModelComponent-0YME7") + + async def update_chat_flow_system_prompt(self, system_prompt: str): + """Helper function to update the system prompt in the chat flow""" + if not LANGFLOW_CHAT_FLOW_ID: + raise ValueError("LANGFLOW_CHAT_FLOW_ID is not configured") + await self._update_flow_field(LANGFLOW_CHAT_FLOW_ID, "system_message", system_prompt, + node_display_name="Language Model", + node_id="LanguageModelComponent-0YME7") + + async def update_flow_docling_preset(self, preset: str, preset_config: dict): + """Helper function to update docling preset in the ingest flow""" + if not LANGFLOW_INGEST_FLOW_ID: + raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured") + + from config.settings import DOCLING_COMPONENT_ID + await self._update_flow_field(LANGFLOW_INGEST_FLOW_ID, "docling_serve_opts", preset_config, + node_id=DOCLING_COMPONENT_ID) + + async def update_ingest_flow_chunk_size(self, chunk_size: int): + """Helper function to update chunk size in the ingest flow""" + if not LANGFLOW_INGEST_FLOW_ID: + raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured") + await self._update_flow_field(LANGFLOW_INGEST_FLOW_ID, "chunk_size", chunk_size, + node_display_name="Split Text", + node_id="SplitText-3ZI5B") + + async def update_ingest_flow_chunk_overlap(self, chunk_overlap: int): + """Helper function to update chunk overlap in the ingest flow""" + if not LANGFLOW_INGEST_FLOW_ID: + raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured") + await self._update_flow_field(LANGFLOW_INGEST_FLOW_ID, "chunk_overlap", chunk_overlap, + node_display_name="Split Text", + node_id="SplitText-3ZI5B") + + async def update_ingest_flow_embedding_model(self, embedding_model: str): + """Helper function to update embedding model in the ingest flow""" + if not LANGFLOW_INGEST_FLOW_ID: + raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured") + await self._update_flow_field(LANGFLOW_INGEST_FLOW_ID, "model", embedding_model, + node_display_name="Embedding Model", + node_id="EmbeddingModel-eZ6bT") + def _replace_node_in_flow(self, flow_data, old_id, new_node): """Replace a node in the flow data""" nodes = flow_data.get("data", {}).get("nodes", [])