From 1d8ee4e185fee7b85d381cb93ba9c883df6d96b7 Mon Sep 17 00:00:00 2001 From: abdul-fission Date: Wed, 12 Feb 2025 11:52:58 +0530 Subject: [PATCH 01/32] feat: Added none option to Knowledege bases selection (#214) * feat: Added none option to Knowldege bases selection * kb label corrected * faithfulness, context precision showing 'na' if knowlwdge_base is false * updated footer, renamed inferecer to inferencing in breakdown --- .../components/Project/Create/DataStrategyStep.vue | 14 ++++++++++---- ui/app/components/Project/Create/Form.vue | 12 +++++++----- .../Project/Create/RetrievalStrategyStep.vue | 9 ++++++--- ui/app/components/Project/Experiment/List.vue | 8 ++++++++ ui/app/composables/shared.ts | 4 ++++ 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/ui/app/components/Project/Create/DataStrategyStep.vue b/ui/app/components/Project/Create/DataStrategyStep.vue index e285a034..d8656ed7 100644 --- a/ui/app/components/Project/Create/DataStrategyStep.vue +++ b/ui/app/components/Project/Create/DataStrategyStep.vue @@ -72,7 +72,11 @@ const updateKbModels = (event: any) => { }; const resetKbModel = (event: any) => { + if(state.kb_model === 'none'){ + state.kb_data = "none" + }else{ state.kb_data = []; + } }; @@ -113,13 +117,15 @@ const resetKbModel = (event: any) => { -
+
Create Bedrock Knowledge Bases
-

[Note]: Indexing Strategy step will be skipped if Bedrock Knowledge Bases is selected

+

[Note]: Indexing Strategy step will be skipped if Bedrock Knowledge Bases is selected

+

[Note]: Indexing Strategy step will be skipped if you don't select any Knowledge Base Type

+ - diff --git a/ui/app/components/Project/Experiment/List.vue b/ui/app/components/Project/Experiment/List.vue index 5aa73dda..17027dd7 100644 --- a/ui/app/components/Project/Experiment/List.vue +++ b/ui/app/components/Project/Experiment/List.vue @@ -136,6 +136,10 @@ const columns = ref[]>([ return Number(a) - Number(b); }, cell: ({ row }) => { + if(!row.original.config.knowledge_base){ + return "NA" + + } if ("faithfulness_score" in row.original.eval_metrics) { return row.original.eval_metrics.faithfulness_score ? parseFloat(row.original.eval_metrics.faithfulness_score.toString()).toFixed(2) : "-" } @@ -178,6 +182,10 @@ const columns = ref[]>([ return 0; }, cell: ({ row }) => { + if(!row.original.config.knowledge_base){ + return "NA" + + } if ("context_precision_score" in row.original.eval_metrics) { return row.original.eval_metrics.context_precision_score ? parseFloat(row.original.eval_metrics.context_precision_score.toString()).toFixed(2) : "-" } diff --git a/ui/app/composables/shared.ts b/ui/app/composables/shared.ts index 62b52d16..dd96beb4 100644 --- a/ui/app/composables/shared.ts +++ b/ui/app/composables/shared.ts @@ -877,6 +877,10 @@ export const useConvertStringToNumber = (value: string | number) => { export const useKnowledgeBaseModel = ()=>{ return { kb_model : [ + { + label: "None", + value: "none" + }, { label: "Bedrock Knowledge Bases", value: "Bedrock-Knowledge-Bases" From 6e13812792fe0e125a6547d6c1a5e9bd8f79b755 Mon Sep 17 00:00:00 2001 From: Kiran_George <111489449+kgeorge-fission@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:07:33 +0530 Subject: [PATCH 02/32] Feature/optional knowledge base (#215) * feat: Changes for optional knowledge base --- app/configuration_validation.py | 10 +- app/price_calculator.py | 2 +- config/experimental_config.py | 3 +- core/eval/ragas/ragas_llm_eval.py | 34 +++-- core/inference/bedrock/bedrock_inferencer.py | 24 +-- .../sagemaker/sagemaker_inferencer.py | 8 +- core/processors/inference_processor.py | 2 +- core/service/experimental_config_service.py | 1 + lambda_handlers/evaluation_handler.py | 14 +- lambda_handlers/retriever_handler.py | 1 + retriever/retriever.py | 143 ++++++++++-------- 11 files changed, 135 insertions(+), 107 deletions(-) diff --git a/app/configuration_validation.py b/app/configuration_validation.py index 5bb7d797..e6c8d08f 100644 --- a/app/configuration_validation.py +++ b/app/configuration_validation.py @@ -24,7 +24,7 @@ def is_valid_combination(config, data): # Define your rules here regions = ["us-east-1", "us-west-2"] - if config['bedrock_knowledge_base']: + if config['bedrock_knowledge_base'] or not config['knowledge_base']: return True if config["region"] not in regions: return False @@ -274,7 +274,7 @@ def generate_all_combinations(data): [num_prompts, num_chars] = read_gt_data(gt_data) avg_prompt_length = round(num_chars / num_prompts / 4) - if parameters_all["bedrock_knowledge_base"][0]: + if parameters_all["bedrock_knowledge_base"][0] or not parameters_all['knowledge_base'][0]: num_tokens_kb_data = 0 else: num_tokens_kb_data = count_characters_in_file(parameters_all["kb_data"][0]) / 4 @@ -313,12 +313,12 @@ def generate_all_combinations(data): configuration["eval_cost_estimate"] = 0 # kb data tokens would be zero if it is Bedrock knowledge bases - effective_num_tokens_kb_data = 0 if configuration["bedrock_knowledge_base"] else estimate_effective_kb_tokens(configuration, num_tokens_kb_data) + effective_num_tokens_kb_data = 0 if configuration["bedrock_knowledge_base"] or not configuration["knowledge_base"] else estimate_effective_kb_tokens(configuration, num_tokens_kb_data) indexing_time, retrieval_time, eval_time = estimate_times(effective_num_tokens_kb_data, num_prompts, configuration) # Bedrock knowledge bases price not supported at the moment - if configuration["bedrock_knowledge_base"]: + if configuration["bedrock_knowledge_base"] or not configuration["knowledge_base"]: configuration["indexing_cost_estimate"] = 0 elif configuration['embedding_service'] == "bedrock" : embedding_price = estimate_embedding_model_bedrock_price(bedrock_price_df, configuration, num_tokens_kb_data) @@ -358,7 +358,7 @@ def generate_all_combinations(data): configuration["directional_pricing"] +=configuration["directional_pricing"]*0.05 #extra configuration["directional_pricing"] = round(configuration["directional_pricing"],2) - return valid_configurations + return valid_configurations[:1000] def generate_all_combinations_in_background(execution_id: str, execution_config_data): """ diff --git a/app/price_calculator.py b/app/price_calculator.py index 4662f1ad..3e23ae02 100644 --- a/app/price_calculator.py +++ b/app/price_calculator.py @@ -127,7 +127,7 @@ def estimate_times(no_of_kb_tokens, num_prompts, configuration): indexing_service = configuration['embedding_service'] retrieval_service = configuration['retrieval_service'] - indexing_time = 0 if configuration["bedrock_knowledge_base"] else (no_of_kb_tokens/ 50000) * estimated_time['indexing'][indexing_service] + indexing_time = 0 if configuration["bedrock_knowledge_base"] or not configuration["knowledge_base"] else (no_of_kb_tokens/ 50000) * estimated_time['indexing'][indexing_service] retrieval_time = (num_prompts / 25) * estimated_time['retrieval'][retrieval_service] eval_time = (num_prompts / 25) * estimated_time['eval'][retrieval_service] diff --git a/config/experimental_config.py b/config/experimental_config.py index b098b30b..5b7fd751 100644 --- a/config/experimental_config.py +++ b/config/experimental_config.py @@ -24,7 +24,7 @@ class ExperimentalConfig(BaseModel): n_shot_prompts: int = Field(alias="n_shot_prompts") n_shot_prompt_guide: Optional[str] = None n_shot_prompt_guide_obj: 'NShotPromptGuide' = None - knn_num: int = Field(alias="knn_num") + knn_num: Union[int, List] = Field(alias="knn_num") temp_retrieval_llm: float = Field(alias="temp_retrieval_llm") retrieval_service: str = Field(alias="retrieval_service") retrieval_model: str = Field(alias="retrieval_model") @@ -45,6 +45,7 @@ class ExperimentalConfig(BaseModel): # Rerank model id rerank_model_id: str = Field(alias="rerank_model_id", default="none") bedrock_knowledge_base: bool = False + knowledge_base: bool = True class Config: alias_generator = lambda string: string.replace("-", "_") populate_by_name = True diff --git a/core/eval/ragas/ragas_llm_eval.py b/core/eval/ragas/ragas_llm_eval.py index cc0447b3..bd0b5bc0 100644 --- a/core/eval/ragas/ragas_llm_eval.py +++ b/core/eval/ragas/ragas_llm_eval.py @@ -57,7 +57,7 @@ def evaluate(self, experiment_id: str): if not experiment_id: raise ValueError("Experiment ID cannot be None") - questions = self.get_all_questions(experiment_id)['Items'] + questions = self.get_all_questions(experiment_id)['Items'] # Contains question, generated answer, GT answer and retrieved context metrics_records = [ExperimentQuestionMetrics(**question) for question in questions] metrics = self.evaluate_bulk_questions(metrics_records) @@ -77,17 +77,27 @@ def evaluate_bulk_questions(self, metrics_records: List[ExperimentQuestionMetric answer_samples = [] for metrics_record in metrics_records: - answer_sample = SingleTurnSample( - user_input=metrics_record.question, - response=metrics_record.generated_answer, - reference=metrics_record.gt_answer, - retrieved_contexts=metrics_record.reference_contexts - ) - answer_samples.append(answer_sample) - - evaluation_dataset = EvaluationDataset(answer_samples) - metrics = evaluate(evaluation_dataset, [self.faithfulness, self.context_precision, self.aspect_critic, self.answers_relevancy]) - + if self.experimental_config.knowledge_base: + answer_sample = SingleTurnSample( + user_input=metrics_record.question, + response=metrics_record.generated_answer, + reference=metrics_record.gt_answer, + retrieved_contexts=metrics_record.reference_contexts + ) + answer_samples.append(answer_sample) + evaluation_dataset = EvaluationDataset(answer_samples) + metrics = evaluate(evaluation_dataset, [self.faithfulness, self.context_precision, self.aspect_critic, self.answers_relevancy]) + + else: + answer_sample = SingleTurnSample( + user_input=metrics_record.question, + response=metrics_record.generated_answer, + reference=metrics_record.gt_answer, + ) + answer_samples.append(answer_sample) + evaluation_dataset = EvaluationDataset(answer_samples) + metrics = evaluate(evaluation_dataset, [self.aspect_critic, self.answers_relevancy]) + return metrics diff --git a/core/inference/bedrock/bedrock_inferencer.py b/core/inference/bedrock/bedrock_inferencer.py index 09eba3d9..184a2d2a 100644 --- a/core/inference/bedrock/bedrock_inferencer.py +++ b/core/inference/bedrock/bedrock_inferencer.py @@ -27,7 +27,7 @@ def _initialize_client(self) -> None: region_name=self.region_name ) - def generate_prompt(self, experiment_config: ExperimentalConfig, default_prompt: str, user_query: str, context: List[Dict]): + def generate_prompt(self, experiment_config: ExperimentalConfig, default_prompt: str, user_query: str, context: List[Dict] = None): # Get n_shot config values first to avoid repeated lookups n_shot_prompt_guide = experiment_config.n_shot_prompt_guide_obj n_shot_prompt = experiment_config.n_shot_prompts @@ -39,7 +39,9 @@ def generate_prompt(self, experiment_config: ExperimentalConfig, default_prompt: # Get system prompt system_prompt = default_prompt if n_shot_prompt_guide is None or n_shot_prompt_guide.system_prompt is None else n_shot_prompt_guide.system_prompt - context_text = self._format_context(context) + context_text = "" + if context: + context_text = self._format_context(context) base_prompt = n_shot_prompt_guide.user_prompt if n_shot_prompt_guide.user_prompt else "" @@ -49,9 +51,7 @@ def generate_prompt(self, experiment_config: ExperimentalConfig, default_prompt: logger.info("into zero shot prompt") prompt = ( system_prompt + "\n\n" + - "\n" + - context_text + "\n" + - "\n" + + context_text + base_prompt + "\n" + "Question: " + user_query ) @@ -76,9 +76,7 @@ def generate_prompt(self, experiment_config: ExperimentalConfig, default_prompt: system_prompt + "\n\n" + "Few examples:\n" + example_text + "\n" + - "\n" + context_text + "\n" + - "\n" + base_prompt + "\n" + "Question: " + user_query ) @@ -86,11 +84,11 @@ def generate_prompt(self, experiment_config: ExperimentalConfig, default_prompt: return prompt.strip() @BedRockRetryHander() - def generate_text(self, user_query: str, context: List[Dict], default_prompt: str, **kwargs) -> Tuple[Dict[Any, Any], str]: + def generate_text(self, user_query: str, default_prompt: str, context: List[Dict] = None, **kwargs) -> Tuple[Dict[Any, Any], str]: try: # Code to generate prompt considering the upload prompt config file converse_prompt = self.generate_prompt(self.experiment_config, default_prompt, user_query, context) - messages = self._prepare_payload(context, converse_prompt, user_query) + messages = self._prepare_payload(context = context, prompt = converse_prompt, user_query = user_query) inference_config={"maxTokens": 512, "temperature": self.experiment_config.temp_retrieval_llm, "topP": 0.9} response = self.client.converse( modelId=self.model_id, @@ -109,10 +107,11 @@ def generate_text(self, user_query: str, context: List[Dict], default_prompt: st logger.error(f"Error generating text with Bedrock: {str(e)}") raise - def _prepare_payload(self, context: List[Dict], prompt: str, user_query: str): + def _prepare_payload(self, prompt: str, user_query: str, context: List[Dict] = None): # Format context documents into a single string - context_text = self._format_context(context) - logger.debug(f"Formatted context text length: {len(context_text)}") + if context: + context_text = self._format_context(context) + logger.debug(f"Formatted context text length: {len(context_text)}") conversation = [ { @@ -129,6 +128,7 @@ def _format_context(self, context: List[Dict[str, str]]) -> str: for i, doc in enumerate(context) ]) logger.debug(f"Formatted context text length: {len(context_text)}") + context_text = "\n" + context_text + "\n" + "\n" return context_text def _extract_response(self, response: Dict) -> str: diff --git a/core/inference/sagemaker/sagemaker_inferencer.py b/core/inference/sagemaker/sagemaker_inferencer.py index 0bf444d0..845f2820 100644 --- a/core/inference/sagemaker/sagemaker_inferencer.py +++ b/core/inference/sagemaker/sagemaker_inferencer.py @@ -331,7 +331,7 @@ def _assign_predictor(self, predictor: sagemaker.predictor.Predictor, model_id: else: logger.error(f"Model ID {model_id} is not recognized as an inferencing model.") - def generate_prompt(self, experiment_config: ExperimentalConfig, default_prompt: str, user_query: str, context: List[Dict]): + def generate_prompt(self, experiment_config: ExperimentalConfig, default_prompt: str, user_query: str, context: List[Dict] = None): n_shot_prompt_guide = experiment_config.n_shot_prompt_guide_obj n_shot_prompt = experiment_config.n_shot_prompts # Input validation @@ -341,7 +341,9 @@ def generate_prompt(self, experiment_config: ExperimentalConfig, default_prompt: # Get system prompt system_prompt = default_prompt if n_shot_prompt_guide is None or n_shot_prompt_guide.system_prompt is None else n_shot_prompt_guide.system_prompt - context_text = self._format_context(user_query, context) + context_text = "" + if context: + context_text = self._format_context(user_query, context) base_prompt = n_shot_prompt_guide.user_prompt if n_shot_prompt_guide.user_prompt else "" @@ -431,7 +433,7 @@ def _format_context(self, user_query: str, context: List[Dict[str, str]]) -> str return formatted_context - def generate_text(self, user_query: str, context: List[Dict], default_prompt: str, **kwargs) -> str: + def generate_text(self, user_query: str, default_prompt: str, context: List[Dict] = None, **kwargs) -> str: """ Generates a response based on the provided user query and context. It formats the context, sends it to the model for text generation, and processes the response to return the generated text. diff --git a/core/processors/inference_processor.py b/core/processors/inference_processor.py index 9074b9c9..dd497f58 100644 --- a/core/processors/inference_processor.py +++ b/core/processors/inference_processor.py @@ -13,7 +13,7 @@ def __init__(self, experimentalConfig : ExperimentalConfig) -> None: self.experimentalConfig = experimentalConfig self.inferencer = InferencerFactory.create_inferencer(experimentalConfig) - def generate_text(self, user_query: str, context: List[Dict], default_prompt: str, **kwargs) -> Tuple[Dict[Any,Any], str]: + def generate_text(self, user_query: str, default_prompt: str, context: List[Dict] = None, **kwargs) -> Tuple[Dict[Any,Any], str]: try: metadata, answer = self.inferencer.generate_text( user_query=user_query, diff --git a/core/service/experimental_config_service.py b/core/service/experimental_config_service.py index 076da2cb..1f260d11 100644 --- a/core/service/experimental_config_service.py +++ b/core/service/experimental_config_service.py @@ -82,6 +82,7 @@ def create_experimental_config(self, exp_config_data: Dict[str, Any]) -> Experim eval_embedding_model=exp_config_data.get('eval_embedding_model', "amazon.titan-embed-text-v1"), eval_retrieval_model=exp_config_data.get('eval_retrieval_model', "mistral.mixtral-8x7b-instruct-v0:1"), bedrock_knowledge_base=exp_config_data.get('bedrock_knowledge_base', False), + knowledge_base=exp_config_data.get('knowledge_base', False), ) n_shot_prompt_guide = experiment.get('config').get('n_shot_prompt_guide') diff --git a/lambda_handlers/evaluation_handler.py b/lambda_handlers/evaluation_handler.py index 045bc579..f1367db5 100644 --- a/lambda_handlers/evaluation_handler.py +++ b/lambda_handlers/evaluation_handler.py @@ -2,7 +2,7 @@ from typing import Dict, Any from config.config import Config from config.experimental_config import ExperimentalConfig -from evaluation.eval import Evaluator +from evaluation.eval import evaluate import logging logger = logging.getLogger() @@ -40,19 +40,19 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: chunking_strategy=exp_config_data.get('chunking_strategy'), chunk_size=exp_config_data.get('chunk_size'), chunk_overlap=exp_config_data.get('chunk_overlap'), + hierarchical_parent_chunk_size=exp_config_data.get('hierarchical_parent_chunk_size'), + hierarchical_child_chunk_size=exp_config_data.get('hierarchical_child_chunk_size'), + hierarchical_chunk_overlap_percentage=exp_config_data.get('hierarchical_chunk_overlap_percentage'), kb_data=exp_config_data.get('kb_data'), n_shot_prompts=exp_config_data.get('n_shot_prompts'), n_shot_prompt_guide=exp_config_data.get('n_shot_prompt_guide'), - indexing_algorithm=exp_config_data.get('indexing_algorithm') + indexing_algorithm=exp_config_data.get('indexing_algorithm'), + knowledge_base=exp_config_data.get('knowledge_base', False) ) logger.info("Processing event: %s", json.dumps(event)) - # Load base configuration - config = Config.load_config() - evaluator = Evaluator(config) - - evaluator.perform_evaluation(experiment_id=exp_config.experiment_id) + evaluate(exp_config) return { "status": "success" diff --git a/lambda_handlers/retriever_handler.py b/lambda_handlers/retriever_handler.py index 5c17617c..4017ea1b 100644 --- a/lambda_handlers/retriever_handler.py +++ b/lambda_handlers/retriever_handler.py @@ -55,6 +55,7 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: enable_context_guardrails=exp_config_data.get('enable_context_guardrails', False), enable_response_guardrails=exp_config_data.get('enable_response_guardrails', False), bedrock_knowledge_base=exp_config_data.get('bedrock_knowledge_base', False), + knowledge_base=exp_config_data.get('knowledge_base', False) ) # Load base configuration diff --git a/retriever/retriever.py b/retriever/retriever.py index 2a71c6ae..dfc0918c 100644 --- a/retriever/retriever.py +++ b/retriever/retriever.py @@ -88,8 +88,8 @@ def initialize_components(config: Config, experimentalConfig: ExperimentalConfig """Initialize all required components for the retrieval process.""" try: # Initialize embedding processor if required - if experimentalConfig.bedrock_knowledge_base: - logger.info("Setting up knowledge base retriever") + if experimentalConfig.bedrock_knowledge_base or not experimentalConfig.knowledge_base: + logger.info("Skipping embed processor initialization") embed_processor = None else: @@ -101,19 +101,20 @@ def initialize_components(config: Config, experimentalConfig: ExperimentalConfig inference_processor = InferenceProcessor(experimentalConfig) # Initialize vector database - - if experimentalConfig.bedrock_knowledge_base: - logger.info("Connecting to Knowledge base") - vector_database = KnowledgeBaseVectorDatabase(region=experimentalConfig.aws_region) - else: - logger.info(f"Connecting to OpenSearch at {config.opensearch_host}") - vector_database = OpenSearchVectorDatabase( - host=config.opensearch_host, - is_serverless=config.opensearch_serverless, - region=config.aws_region, - username=config.opensearch_username, - password=config.opensearch_password - ) + vector_database = None + if experimentalConfig.knowledge_base: + if experimentalConfig.bedrock_knowledge_base: + logger.info("Connecting to Knowledge base") + vector_database = KnowledgeBaseVectorDatabase(region=experimentalConfig.aws_region) + else: + logger.info(f"Connecting to OpenSearch at {config.opensearch_host}") + vector_database = OpenSearchVectorDatabase( + host=config.opensearch_host, + is_serverless=config.opensearch_serverless, + region=config.aws_region, + username=config.opensearch_username, + password=config.opensearch_password + ) # Initialize DynamoDB connections logger.info("Initializing DynamoDB connections") @@ -194,14 +195,13 @@ def process_questions( logger.debug(f"Processing question {idx+1}: {question}") # Generate embeddings - if not experimentalConfig.bedrock_knowledge_base: + if experimentalConfig.bedrock_knowledge_base or not experimentalConfig.knowledge_base: + query_metadata, query_embedding = {'inputTokens': '0', 'latencyMs': '0'}, None + else: logger.info("Generating embeddings for the question using provided embedder") query_metadata, query_embedding = components["embed_processor"].embed_text( question ) - else: - query_metadata, query_embedding = {'inputTokens': '0', 'latencyMs': '0'}, None - query_results=None guardrail_input_assessment = None @@ -238,22 +238,23 @@ def process_questions( # Apply CONTEXT guardrails if not already blocked if experimentalConfig.enable_context_guardrails and guardrail_blocked == 'NONE': - # Search for relevant context once - if isinstance(components["vector_database"], OpenSearchVectorDatabase): - query_results = components["vector_database"].search( - experimentalConfig.index_id, query_embedding, experimentalConfig.knn_num - ) - elif isinstance(components["vector_database"], KnowledgeBaseVectorDatabase): - query_results = components["vector_database"].search( - question, experimentalConfig.kb_data, experimentalConfig.knn_num - ) + if experimentalConfig.knowledge_base: + # Search for relevant context once + if isinstance(components["vector_database"], OpenSearchVectorDatabase): + query_results = components["vector_database"].search( + experimentalConfig.index_id, query_embedding, experimentalConfig.knn_num + ) + elif isinstance(components["vector_database"], KnowledgeBaseVectorDatabase): + query_results = components["vector_database"].search( + question, experimentalConfig.kb_data, experimentalConfig.knn_num + ) - if experimentalConfig.chunking_strategy.lower() == 'hierarchical': - query_results = __duplicate_removal_for_heirarchical_config(query_results) + if experimentalConfig.chunking_strategy.lower() == 'hierarchical': + query_results = __duplicate_removal_for_heirarchical_config(query_results) - if experimentalConfig.rerank_model_id and experimentalConfig.rerank_model_id.lower() != 'none': - #Rerank the query results - query_results = __rerank_query_result(query_results, question, experimentalConfig, idx) + if experimentalConfig.rerank_model_id and experimentalConfig.rerank_model_id.lower() != 'none': + #Rerank the query results + query_results = __rerank_query_result(query_results, question, experimentalConfig, idx) if query_results: @@ -273,28 +274,33 @@ def process_questions( if guardrail_blocked == 'NONE': # Fetch context if not already done if query_results is None: - if isinstance(components["vector_database"], OpenSearchVectorDatabase): - query_results = components["vector_database"].search( - experimentalConfig.index_id, query_embedding, experimentalConfig.knn_num - ) - elif isinstance(components["vector_database"], KnowledgeBaseVectorDatabase): - query_results = components["vector_database"].search( - question, experimentalConfig.kb_data, experimentalConfig.knn_num - ) - - if experimentalConfig.chunking_strategy.lower() == 'hierarchical': - query_results = __duplicate_removal_for_heirarchical_config(query_results) - - if experimentalConfig.rerank_model_id and experimentalConfig.rerank_model_id.lower() != 'none': - #Rerank the query results - query_results = __rerank_query_result(query_results, question, experimentalConfig, idx) + if experimentalConfig.knowledge_base: + if isinstance(components["vector_database"], OpenSearchVectorDatabase): + query_results = components["vector_database"].search( + experimentalConfig.index_id, query_embedding, experimentalConfig.knn_num + ) + elif isinstance(components["vector_database"], KnowledgeBaseVectorDatabase): + query_results = components["vector_database"].search( + question, experimentalConfig.kb_data, experimentalConfig.knn_num + ) + if experimentalConfig.chunking_strategy.lower() == 'hierarchical': + query_results = __duplicate_removal_for_heirarchical_config(query_results) + if experimentalConfig.rerank_model_id and experimentalConfig.rerank_model_id.lower() != 'none': + #Rerank the query results + query_results = __rerank_query_result(query_results, question, experimentalConfig, idx) # Generate answer - answer_metadata, answer = components["inference_processor"].generate_text( + if experimentalConfig.knowledge_base: + answer_metadata, answer = components["inference_processor"].generate_text( user_query=question, context=query_results, default_prompt=config.inference_system_prompt, ) + else: + answer_metadata, answer = components["inference_processor"].generate_text( + user_query=question, + default_prompt=config.inference_system_prompt, + ) retrieval_input_tokens += int(answer_metadata["inputTokens"]) retrieval_output_tokens += int(answer_metadata["outputTokens"]) @@ -311,29 +317,36 @@ def process_questions( answer = modified_answer guardrail_blocked = 'OUTPUT' else: - # Search for relevant context - if isinstance(components["vector_database"], OpenSearchVectorDatabase): + if experimentalConfig.knowledge_base: + # Search for relevant context + if isinstance(components["vector_database"], OpenSearchVectorDatabase): query_results = components["vector_database"].search( experimentalConfig.index_id, query_embedding, experimentalConfig.knn_num ) - elif isinstance(components["vector_database"], KnowledgeBaseVectorDatabase): - query_results = components["vector_database"].search( - question, experimentalConfig.kb_data, experimentalConfig.knn_num - ) + elif isinstance(components["vector_database"], KnowledgeBaseVectorDatabase): + query_results = components["vector_database"].search( + question, experimentalConfig.kb_data, experimentalConfig.knn_num + ) - if experimentalConfig.chunking_strategy.lower() == 'hierarchical': - query_results = __duplicate_removal_for_heirarchical_config(query_results) + if experimentalConfig.chunking_strategy.lower() == 'hierarchical': + query_results = __duplicate_removal_for_heirarchical_config(query_results) - if experimentalConfig.rerank_model_id and experimentalConfig.rerank_model_id.lower() != 'none': - #Rerank the query results - query_results = __rerank_query_result(query_results, question, experimentalConfig, idx) + if experimentalConfig.rerank_model_id and experimentalConfig.rerank_model_id.lower() != 'none': + #Rerank the query results + query_results = __rerank_query_result(query_results, question, experimentalConfig, idx) # Generate answer - answer_metadata, answer = components["inference_processor"].generate_text( - user_query=question, - context=query_results, - default_prompt=config.inference_system_prompt, - ) + if experimentalConfig.knowledge_base: + answer_metadata, answer = components["inference_processor"].generate_text( + user_query=question, + context=query_results, + default_prompt=config.inference_system_prompt, + ) + else: + answer_metadata, answer = components["inference_processor"].generate_text( + user_query=question, + default_prompt=config.inference_system_prompt + ) retrieval_input_tokens += int(answer_metadata["inputTokens"]) retrieval_output_tokens += int(answer_metadata["outputTokens"]) From d1e5cf0ad9bfc1f8d66da3f479906a53763d1507 Mon Sep 17 00:00:00 2001 From: abdul-fission Date: Wed, 12 Feb 2025 15:52:40 +0530 Subject: [PATCH 03/32] fix: changed label from valid experiments to experiments and aspect critic to maliciousness (#232) --- ui/app/components/Project/Experiment/List.vue | 4 ++-- ui/app/pages/projects/[id]/index.vue | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/app/components/Project/Experiment/List.vue b/ui/app/components/Project/Experiment/List.vue index 17027dd7..a06b5ae3 100644 --- a/ui/app/components/Project/Experiment/List.vue +++ b/ui/app/components/Project/Experiment/List.vue @@ -201,7 +201,7 @@ const columns = ref[]>([ return h(UButton, { color: "neutral", variant: "ghost", - label: "Aspect Critic", + label: "Maliciousness", icon: isSorted ? isSorted === "asc" ? "i-lucide-arrow-up-narrow-wide" @@ -211,7 +211,7 @@ const columns = ref[]>([ onClick: () => column.toggleSorting(column.getIsSorted() === "asc"), }); }, - label: 'Aspect Critic', + label: 'Maliciousness', accessorKey: "eval_metrics.aspect_critic_score", enableHiding: true, sortingFn: (rowA, rowB) => { diff --git a/ui/app/pages/projects/[id]/index.vue b/ui/app/pages/projects/[id]/index.vue index faf15945..6c1712e7 100644 --- a/ui/app/pages/projects/[id]/index.vue +++ b/ui/app/pages/projects/[id]/index.vue @@ -40,7 +40,7 @@ const { data: experiments, isLoading } = useQuery({
Loading experiments... From cd7dd5883704ebd167bb11872debe791402f3c62 Mon Sep 17 00:00:00 2001 From: abdul-fission Date: Wed, 12 Feb 2025 16:57:12 +0530 Subject: [PATCH 04/32] SHowing NA if knowlwdge base is not selected in details tab chunking (#234) --- .../components/Project/Experiment/DetailsButton.vue | 12 ++++++------ .../projects/[id]/experiments/[experimentId].vue | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ui/app/components/Project/Experiment/DetailsButton.vue b/ui/app/components/Project/Experiment/DetailsButton.vue index 2635d0f7..8de5242b 100644 --- a/ui/app/components/Project/Experiment/DetailsButton.vue +++ b/ui/app/components/Project/Experiment/DetailsButton.vue @@ -29,19 +29,19 @@ const props = defineProps<{ Chunking - {{ props.experimentsData?.config?.bedrock_knowledge_base ? 'NA' : props.experimentsData?.config?.chunking_strategy }} + {{ (props.experimentsData?.config?.bedrock_knowledge_base || !props.experimentsData?.config?.knowledge_base) ? 'NA' : props.experimentsData?.config?.chunking_strategy }} Vector Dimensions - {{ props.experimentsData?.config?.bedrock_knowledge_base ? 'NA' : props.experimentsData?.config?.vector_dimension }} + {{ (props.experimentsData?.config?.bedrock_knowledge_base || !props.experimentsData?.config?.knowledge_base) ? 'NA' : props.experimentsData?.config?.vector_dimension }} Chunk Size - {{ props.experimentsData?.config?.bedrock_knowledge_base ? 'NA' : useHumanChunkingStrategy(props.experimentsData?.config?.chunking_strategy) === 'Fixed' ? props.experimentsData?.config?.chunk_size : [props.experimentsData?.config?.hierarchical_child_chunk_size, props.experimentsData?.config?.hierarchical_parent_chunk_size]}} + {{ (props.experimentsData?.config?.bedrock_knowledge_base || !props.experimentsData?.config?.knowledge_base) ? 'NA' : useHumanChunkingStrategy(props.experimentsData?.config?.chunking_strategy) === 'Fixed' ? props.experimentsData?.config?.chunk_size : [props.experimentsData?.config?.hierarchical_child_chunk_size, props.experimentsData?.config?.hierarchical_parent_chunk_size]}} Chunk Overlap Percentage - {{props.experimentsData?.config?.bedrock_knowledge_base ? 'NA' : useHumanChunkingStrategy(props.experimentsData?.config?.chunking_strategy) === 'Fixed' ? props.experimentsData?.config?.chunk_overlap : props.experimentsData?.config?.hierarchical_chunk_overlap_percentage}} + {{(props.experimentsData?.config?.bedrock_knowledge_base || !props.experimentsData?.config?.knowledge_base) ? 'NA' : useHumanChunkingStrategy(props.experimentsData?.config?.chunking_strategy) === 'Fixed' ? props.experimentsData?.config?.chunk_overlap : props.experimentsData?.config?.hierarchical_chunk_overlap_percentage}} N Shot Prompts @@ -57,12 +57,12 @@ const props = defineProps<{ Indexing Algorithm - {{props.experimentsData?.config?.bedrock_knowledge_base ? 'NA' : useHumanIndexingAlgorithm(props.experimentsData?.config?.indexing_algorithm!) }} + {{(props.experimentsData?.config?.bedrock_knowledge_base || !props.experimentsData?.config?.knowledge_base) ? 'NA' : useHumanIndexingAlgorithm(props.experimentsData?.config?.indexing_algorithm!) }} Embedding Model {{ useModelName("indexing", props.experimentsData?.config?.embedding_model!) }} {{ - props.experimentsData?.config?.bedrock_knowledge_base ? 'NA' : + (props.experimentsData?.config?.bedrock_knowledge_base || !props.experimentsData?.config?.knowledge_base) ? 'NA' : `(${useHumanModelService(props.experimentsData?.config?.embedding_service!)})` }} diff --git a/ui/app/pages/projects/[id]/experiments/[experimentId].vue b/ui/app/pages/projects/[id]/experiments/[experimentId].vue index a0f19d90..cb1aa406 100644 --- a/ui/app/pages/projects/[id]/experiments/[experimentId].vue +++ b/ui/app/pages/projects/[id]/experiments/[experimentId].vue @@ -248,6 +248,13 @@ const items = ref([
+ + +
+

No indexing cost incurred

+
+ +
From 9571f9d6e5c06ef98dff298cb9d4aa25f3215d14 Mon Sep 17 00:00:00 2001 From: Harish Pillarisetti <162275515+harishFL@users.noreply.github.com> Date: Thu, 13 Feb 2025 11:35:21 +0530 Subject: [PATCH 05/32] feat: Feat/cloudscape nuxt theme (#240) * feat: Add Haiku and Deepseek model support (#212) * feat:added mitt and few styles and components (#218) * added mitt and few styles and components * enabled comments in project types * added new components * added new components * Feature/header card UI (#223) * chore: updated instance type for qwen 1.5B and qwen7B (#217) Co-authored-by: nanda.teja1 * chore/update deepseek instance (#219) * breadcumb moved below card and header card added --------- Co-authored-by: nandateja Co-authored-by: nanda.teja1 * Dropdown header changes (#229) * chore: updated instance type for qwen 1.5B and qwen7B (#217) Co-authored-by: nanda.teja1 * chore/update deepseek instance (#219) * dropdown changes, header changes done * options border added * margins adjusted --------- Co-authored-by: nandateja Co-authored-by: nanda.teja1 * Tabs theme changes (#230) * chore: updated instance type for qwen 1.5B and qwen7B (#217) Co-authored-by: nanda.teja1 * chore/update deepseek instance (#219) * tabs css changed to look like cloudscape tabs --------- Co-authored-by: nandateja Co-authored-by: nanda.teja1 * Loader checkbox theme changes (#231) * chore: updated instance type for qwen 1.5B and qwen7B (#217) Co-authored-by: nanda.teja1 * chore/update deepseek instance (#219) * loader and check box theme changes done --------- Co-authored-by: nandateja Co-authored-by: nanda.teja1 * chore: info text and buttons (#233) * added info adjustments and html render along with buttons placement * added tooltip text-html formatting * added few changes * added few ui changes (#236) * Table height adjustments UI (#237) * chore: updated instance type for qwen 1.5B and qwen7B (#217) Co-authored-by: nanda.teja1 * chore/update deepseek instance (#219) * table height and cell width fixed --------- Co-authored-by: nandateja Co-authored-by: nanda.teja1 --------- Co-authored-by: Kiran_George <111489449+kgeorge-fission@users.noreply.github.com> Co-authored-by: abdul-fission Co-authored-by: nandateja Co-authored-by: nanda.teja1 --- ui/app/app.config.ts | 47 ++- ui/app/assets/css/main.css | 265 +++++++++++- ui/app/components/Breadcumb.vue | 3 +- ui/app/components/DownloadResultsButton.vue | 1 + ui/app/components/FetchKbModels.vue | 5 +- ui/app/components/FieldTooltip.vue | 12 +- ui/app/components/File/Upload.vue | 2 +- ui/app/components/File/UploadKb.vue | 2 +- ui/app/components/ModelSelect.vue | 2 +- ui/app/components/Page.vue | 17 +- .../Project/Create/DataStrategyStep.vue | 77 +++- .../Project/Create/EvalStrategyStep.vue | 69 +++- .../Project/Create/IndexingStrategyStep.vue | 121 ++++-- .../Project/Create/RetrievalStrategyStep.vue | 91 +++-- .../Project/DownloadConfigButton.vue | 4 +- ui/app/components/Project/Experiment/List.vue | 386 ++++++++++-------- .../Project/Experiment/ValidList.vue | 327 ++++++++------- ui/app/components/Project/List.vue | 119 +++--- .../components/Project/UploadConfigButton.vue | 4 +- ui/app/components/PromptGuideHelp.vue | 4 +- ui/app/components/PromptGuideSelect.vue | 2 +- ui/app/components/RegionSelect.vue | 2 +- ui/app/components/VectorDimensionSelect.vue | 2 +- ui/app/composables/shared.ts | 48 +++ ui/app/composables/tooltip.ts | 149 +++++-- ui/app/composables/useShareData.ts | 5 + ui/app/layouts/default.vue | 66 ++- ui/app/pages/index.vue | 19 +- ui/app/pages/projects/[id].vue | 13 +- ui/app/pages/projects/[id]/execute.vue | 9 +- .../[id]/experiments/[experimentId].vue | 14 +- .../pages/projects/[id]/validexperiments.vue | 16 +- ui/app/pages/projects/create.vue | 6 + ui/app/pages/projects/index.vue | 17 +- ui/app/plugins/mitt.ts | 14 + ui/package.json | 1 + ui/pnpm-lock.yaml | 3 + ui/public/logo.png | Bin 33116 -> 27004 bytes 38 files changed, 1371 insertions(+), 573 deletions(-) create mode 100644 ui/app/composables/useShareData.ts create mode 100644 ui/app/plugins/mitt.ts diff --git a/ui/app/app.config.ts b/ui/app/app.config.ts index d8a98064..64a7f14a 100644 --- a/ui/app/app.config.ts +++ b/ui/app/app.config.ts @@ -1,7 +1,7 @@ export default defineAppConfig({ ui: { colors: { - primary: "orange", + // primary: "orange", }, form: { base: "space-y-3", @@ -20,6 +20,13 @@ export default defineAppConfig({ // @ts-expect-error type inference size: "xl", }, + slots : { + group: 'p-1 isolate-custom', + item : [ + 'custom-options-group w-full' + ], + input: 'h-6', + } }, inputNumber: { slots: { @@ -30,6 +37,44 @@ export default defineAppConfig({ slots: { td: "!whitespace-normal", }, + compoundVariants : [ + { + loading: true, + loadingColor: 'primary', + class: { + thead: 'after:bg-blue-300 ' + } + }, + ] }, + tabs: { + slots : { + root : "gap-2", + list: "custom-tab-list-group", + indicator : "h-10px custom-tab-indicator", + trigger : ['custom-tabs-trigger'], + content : "focus-outline" + } + }, + checkbox : { + slots : { + base : "", + }, + + compoundVariants : [ + { + color: 'primary', + checked: true, + class: 'secondery-color ' + }, + ] + + + }, + card : { + slots : { + root : 'rounded-[16px]' + } + } }, }); diff --git a/ui/app/assets/css/main.css b/ui/app/assets/css/main.css index c7764c57..e1147850 100644 --- a/ui/app/assets/css/main.css +++ b/ui/app/assets/css/main.css @@ -1,22 +1,43 @@ +@import url('https://fonts.cdnfonts.com/css/amazon-ember'); + @import "tailwindcss"; @import "@nuxt/ui"; + +* { + font-family: 'Amazon Ember', open-sans; +} + +:root { + --ui-primary: #000000; +} + /* Table Styles */ table { @apply w-full border-collapse; } th { - @apply bg-gray-100 text-left p-4 font-medium text-gray-700; + @apply bg-transparent text-left p-4; + border-block-end-color: rgb(198, 198, 205); + border-block-end-style: solid; + border-block-end-width: 1px; + color: rgb(66, 70, 80); + padding-block-end: 4px; + padding-block-start: 4px; + padding-inline-end: 8px; + padding-inline-start: 2px; + font-size: 14px; } td { - @apply p-4 text-gray-600; + @apply text-gray-600; + padding: 9px 19px 10px 1px; } -tr:nth-child(even) { +/* tr:nth-child(even) { @apply bg-gray-50; -} +} */ /* Status Colors */ .completed { @@ -53,10 +74,33 @@ tr:nth-child(even) { vertical-align: top !important; } +.experiment-details-tabs div button { + color: #474545; + font-weight: bold; +} + .experiment-details-tabs div button[data-state]:not([data-state='active']) { - border : 1px solid lightgray; + border-radius: 0px; + border-right: 1px solid rgb(144, 150, 144); + margin-bottom: 10px; + cursor: pointer; +} +.experiment-details-tabs div button[data-state]:not([data-state='active']):hover { + color: var(--aws-secondary-color); +} +.experiment-details-tabs div button[data-state='active'] { + cursor: pointer; + color: var(--aws-secondary-color); + margin-bottom: 10px; cursor: pointer; - margin:2px; + +} +.experiment-details-tabs div button:last-child { + border: none; +} + +.experiment-details-tabs div button[data-state]:not([data-state='active']):last-child { + border-right: none; } @@ -65,7 +109,210 @@ div.divide-y.divide-\[var\(--ui-border\)\].scroll-py-1 div[role='group'] { overflow-y:scroll; } -div.divide-y.divide-\[var\(--ui-border\)\].scroll-py-1 div[role='group'] { - max-height: 165px; - overflow-y:scroll; + +@theme { + --font-family: 'Amazon Ember', sans-serif; + --aws-primary-color: #ff9900; + --aws-secondary-color: #006ce0; + --aws-tertiary-color: #000000; + + --aws-primary-hover-color: #ec7211; + --aws-secondary-hover-color: #f0fbff; + --aws-secondary-hover-font-color: #002b66; + + + --aws-font-grey-color: #0f141a; + --aws-font-blue-color: #396ee0; + --aws-info-alert-color: #f9f9fa; + + --aws-navbar-bg-color: #161d26; + + --aws-info-tooltip-color: #002b66; + + --aws-success-color: #00802f; + --aws-pending-color: #006ce0; + --aws-error-color: #db0000; + --aws-not-started-color: #656871; +} + +.primary-color { + color: var(--aws-primary-color); +} + +.secondary-color { + color: var(--aws-secondary-color); +} + +.primary-btn { + background-color: var(--aws-primary-color); + color: var(--aws-font-grey-color); + border-radius: 0.25rem; + border-color: var(--aws-primary-color); + border-end-start-radius: 20px; + border-end-end-radius: 20px; + border-start-start-radius: 20px; + border-start-end-radius: 20px; + font-weight: 700; + font-size: 14px; + line-height: 22px; +} + +.primary-btn:hover { + background-color: var(--aws-primary-hover-color); +} + +.secondary-btn { + background-color: transparent; + color: var(--aws-font-blue-color); + border-radius: 0.25rem; + border-color: var(--aws-secondary-color); + border-width: 2px; + border-end-start-radius: 20px; + border-end-end-radius: 20px; + border-start-start-radius: 20px; + border-start-end-radius: 20px; + font-weight: 700; + font-size: 14px; + line-height: 22px; + +} + +.secondary-btn:hover { + background-color: var(--aws-secondary-hover-color); + color: var(--aws-secondary-hover-font-color); + border-color: var(--aws-secondary-hover-font-color); +} + +.navbar { + background-color: var(--aws-navbar-bg-color); +} + +.info-tooltip { + cursor: pointer; + font-size: 12px; + font-weight: 700; + line-height: 16px; + color: var(--aws-secondary-color); +} + +.info-tooltip:hover { + color: var(--aws-info-tooltip-color); + +} + +.tooltip-title { + font-size: 18px; + color: rgb(15, 20, 26); + font-weight: 700; + line-height: 24px; + margin-left: 36px; +} + +.tooltip-description { + font-size: 14px; + color: rgb(66, 70, 80); + line-height: 20px; + margin-left: 36px; +} + +.info-alert { + background-color: var(--aws-secondary-color); + color: var(--aws-info-alert-color); + border-radius: 12px; +} + +.info-error { + background-color: var(--aws-error-color); + color: var(--aws-info-alert-color); + border-radius: 12px; +} + +.external-link { + color: var(--aws-secondary-color); + text-decoration: underline; +} + +.external-link:hover { + color: var(--aws-secondary-hover-font-color); +} + +.primary-modal-with-header > div:first-of-type { + border-bottom: unset; +} + +.secondary-breadcrumb a{ + color: var(--aws-secondary-color); + text-decoration: underline; + font-size: 14px; + line-height: 20px; +} + +.secondary-breadcrumb a.cursor-not-allowed { + color: var(--aws-font-grey-color); + text-decoration: none; +} + +.not-started-badge { + color: var(--aws-not-started-color); +} + +.pending-badge { + color: var(--aws-pending-color); +} + +.success-badge { + color: var(--aws-success-color); +} + +.error-badge { + color: var(--aws-error-color); +} +input, .primary-dropdown { + height: 32px !important; + border-radius: 8px !important; +} + +.custom-options-group { + + border-bottom: 1px solid lightgray; + margin-bottom: 0px !important; + padding: 2px; + +} + +.custom-options-group:last-child { + border-bottom: none; +} + +.custom-options-group:hover { + border-radius: 6px !important; + outline-style: solid; + outline-color: lightgrey; + outline-width: 2px; + border: none; +} + +.custom-tab-list-group { + border-bottom:2px solid lightgray; + border-color : lightgray; +} +.custom-tab-list-group .custom-tabs-trigger{ + border-radius: 0px; + border-right: 1px solid rgb(144, 150, 144); + + margin-bottom: 10px; +} + +.custom-tab-list-group .custom-tab-indicator { + height: 5px !important; + background-color: var(--aws-secondary-color); +} + +.secondery-color { + background-color: var(--aws-secondary-color) !important; + border-color: var(--aws-secondary-color) !important; +} + +.github-link { + height: 32px !important; } \ No newline at end of file diff --git a/ui/app/components/Breadcumb.vue b/ui/app/components/Breadcumb.vue index d207289a..bdd4a1af 100644 --- a/ui/app/components/Breadcumb.vue +++ b/ui/app/components/Breadcumb.vue @@ -43,7 +43,6 @@ const breadcrumbItems = computed((): BreadcrumbItem[] => { const items: BreadcrumbItem[] = [ { label: "Home", - icon: "i-heroicons-home", to: "/", }, ]; @@ -76,6 +75,6 @@ const breadcrumbItems = computed((): BreadcrumbItem[] => { diff --git a/ui/app/components/DownloadResultsButton.vue b/ui/app/components/DownloadResultsButton.vue index 84d36c3e..64debf91 100644 --- a/ui/app/components/DownloadResultsButton.vue +++ b/ui/app/components/DownloadResultsButton.vue @@ -49,5 +49,6 @@ const downloadResults = () => { :label="buttonLabel" icon="i-lucide-download" @click="downloadResults" + class="primary-btn" /> \ No newline at end of file diff --git a/ui/app/components/FetchKbModels.vue b/ui/app/components/FetchKbModels.vue index 890fde60..f33b4647 100644 --- a/ui/app/components/FetchKbModels.vue +++ b/ui/app/components/FetchKbModels.vue @@ -47,11 +47,12 @@ onMounted(() => {
- +

Please select region first

- + +const { $emit } = useNuxtApp() + const props = defineProps<{ fieldName: keyof typeof useTooltipInfo }>() @@ -8,12 +10,16 @@ const setTooltipInfo = computed(() => { return useTooltipInfo[props.fieldName]; }) +const emitTooltip = () => { + $emit('showTooltip', {tooltip: setTooltipInfo, fieldName: props.fieldName}) +} + diff --git a/ui/app/components/File/Upload.vue b/ui/app/components/File/Upload.vue index 4e50eb5e..f3299360 100644 --- a/ui/app/components/File/Upload.vue +++ b/ui/app/components/File/Upload.vue @@ -97,6 +97,6 @@ onChange(async (files) => { diff --git a/ui/app/components/File/UploadKb.vue b/ui/app/components/File/UploadKb.vue index 36cd55d2..4b811e44 100644 --- a/ui/app/components/File/UploadKb.vue +++ b/ui/app/components/File/UploadKb.vue @@ -43,7 +43,7 @@ const onFileData = (data: any) => { - +