diff --git a/.gitignore b/.gitignore
index 9b9ca8f4a..9e300b9e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -607,6 +607,9 @@ $RECYCLE.BIN/
backend/plugins/authentication/*
!backend/plugins/authentication/auth_sample
+# Processor Plugins
+backend/plugins/processor/*
+
# Tool registry
unstract/tool-registry/src/unstract/tool_registry/*.json
unstract/tool-registry/tests/*.yaml
@@ -627,5 +630,3 @@ tools/*/sdks/
tools/*/data_dir/
docker/workflow_data/
-
-
diff --git a/backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py b/backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py
new file mode 100644
index 000000000..c0631e723
--- /dev/null
+++ b/backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py
@@ -0,0 +1,27 @@
+# Generated by Django 4.2.1 on 2024-02-28 09:03
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("adapter_processor", "0004_alter_adapterinstance_adapter_type"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="adapterinstance",
+ name="adapter_type",
+ field=models.CharField(
+ choices=[
+ ("UNKNOWN", "UNKNOWN"),
+ ("LLM", "LLM"),
+ ("EMBEDDING", "EMBEDDING"),
+ ("VECTOR_DB", "VECTOR_DB"),
+ ("OCR", "OCR"),
+ ("X2TEXT", "X2TEXT"),
+ ],
+ db_comment="Type of adapter LLM/EMBEDDING/VECTOR_DB",
+ ),
+ ),
+ ]
diff --git a/backend/file_management/file_management_helper.py b/backend/file_management/file_management_helper.py
index a5808f9a5..5c834d852 100644
--- a/backend/file_management/file_management_helper.py
+++ b/backend/file_management/file_management_helper.py
@@ -195,8 +195,8 @@ def fetch_file_contents(
elif file_content_type == "text/plain":
with fs.open(file_path, "r") as file:
FileManagerHelper.logger.info(f"Reading text file: {file_path}")
- encoded_string = base64.b64encode(file.read())
- return encoded_string
+ text_content = file.read()
+ return text_content
else:
raise InvalidFileType
@@ -246,9 +246,13 @@ def handle_sub_directory_for_tenants(
raise OrgIdNotValid()
base_path = Path(settings.PROMPT_STUDIO_FILE_PATH)
file_path: Path = base_path / org_id / user_id / tool_id
+ extract_file_path: Path = Path(file_path, "extract")
+ summarize_file_path: Path = Path(file_path, "summarize")
if is_create:
try:
os.makedirs(file_path, exist_ok=True)
+ os.makedirs(extract_file_path, exist_ok=True)
+ os.makedirs(summarize_file_path, exist_ok=True)
except OSError as e:
FileManagerHelper.logger.error(
f"Error while creating {file_path}: {e}"
diff --git a/backend/file_management/views.py b/backend/file_management/views.py
index a8756595f..06bb74977 100644
--- a/backend/file_management/views.py
+++ b/backend/file_management/views.py
@@ -121,7 +121,6 @@ def upload(self, request: HttpRequest) -> Response:
@action(detail=True, methods=["post"])
def upload_for_ide(self, request: HttpRequest) -> Response:
- print(request.data)
serializer = FileUploadIdeSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
uploaded_files: Any = serializer.validated_data.get("file")
diff --git a/backend/pdm.lock b/backend/pdm.lock
index da10551b7..9823da806 100644
--- a/backend/pdm.lock
+++ b/backend/pdm.lock
@@ -5,7 +5,7 @@
groups = ["default", "test", "deploy"]
strategy = ["cross_platform", "inherit_metadata"]
lock_version = "4.4.1"
-content_hash = "sha256:73a791002f5f7bab1afe9eaee07e8fc946774da3448f9daabedbccfc9be4f202"
+content_hash = "sha256:27911cc8da87345bc67ffc964493ec9434a770ed2e380403ef87094e8a55e409"
[[package]]
name = "aiobotocore"
@@ -4667,7 +4667,7 @@ dependencies = [
[[package]]
name = "unstract-sdk"
-version = "0.11.2"
+version = "0.12.1"
requires_python = "<3.11.1,>=3.9"
summary = "A framework for writing Unstract Tools/Apps"
groups = ["default"]
@@ -4684,8 +4684,8 @@ dependencies = [
"unstract-adapters~=0.2.2",
]
files = [
- {file = "unstract_sdk-0.11.2-py3-none-any.whl", hash = "sha256:bcab064d87dc2985e5a4340e438a8ef71ef165b3e5078aba0ed2da3bda77f9e0"},
- {file = "unstract_sdk-0.11.2.tar.gz", hash = "sha256:0d6e3e00c9bbc6e569cc79922a2a45a54870c482cfa61287ea89a22ebd7fc606"},
+ {file = "unstract_sdk-0.12.1-py3-none-any.whl", hash = "sha256:960d8a7b92a1ad32894650edd9254aaf7c3f7555053991b3860e55ab684624a8"},
+ {file = "unstract_sdk-0.12.1.tar.gz", hash = "sha256:06b1a7bf17948d548137502127458d83046c2e0b596ff326a292dbe94041a13a"},
]
[[package]]
@@ -4700,7 +4700,7 @@ dependencies = [
"docker~=6.1.3",
"jsonschema~=4.18.2",
"unstract-adapters~=0.2.1",
- "unstract-tool-sandbox @ file:///home/gayathrivijayakumar/code/Unstract/unstract/unstract/tool-registry/../tool-sandbox",
+ "unstract-tool-sandbox @ file:///home/ghost/Documents/zipstack/unstract-oss/unstract/tool-registry/../tool-sandbox",
]
[[package]]
diff --git a/backend/plugins/processor/.gitkeep b/backend/plugins/processor/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/backend/prompt_studio/processor_loader.py b/backend/prompt_studio/processor_loader.py
new file mode 100644
index 000000000..28d12f8d9
--- /dev/null
+++ b/backend/prompt_studio/processor_loader.py
@@ -0,0 +1,76 @@
+import logging
+import os
+from importlib import import_module
+from typing import Any
+
+from django.apps import apps
+
+logger = logging.getLogger(__name__)
+
+
+class ProcessorConfig:
+ """Loader config for processor plugins."""
+
+ PLUGINS_APP = "plugins"
+ PLUGIN_DIR = "processor"
+ MODULE = "module"
+ METADATA = "metadata"
+ METADATA_NAME = "name"
+ METADATA_SERVICE_CLASS = "service_class"
+ METADATA_IS_ACTIVE = "is_active"
+
+
+def load_plugins() -> list[Any]:
+ """Iterate through the processor plugins and register them."""
+ plugins_app = apps.get_app_config(ProcessorConfig.PLUGINS_APP)
+ package_path = plugins_app.module.__package__
+ processor_dir = os.path.join(plugins_app.path, ProcessorConfig.PLUGIN_DIR)
+ processor_package_path = f"{package_path}.{ProcessorConfig.PLUGIN_DIR}"
+ processor_plugins: list[Any] = []
+
+ for item in os.listdir(processor_dir):
+ # Loads a plugin if it is in a directory.
+ if os.path.isdir(os.path.join(processor_dir, item)):
+ processor_module_name = item
+ # Loads a plugin if it is a shared library.
+ # Module name is extracted from shared library name.
+ # `processor.platform_architecture.so` will be file name and
+ # `processor` will be the module name.
+ elif item.endswith(".so"):
+ processor_module_name = item.split(".")[0]
+ else:
+ continue
+ try:
+ full_module_path = (
+ f"{processor_package_path}.{processor_module_name}"
+ )
+ module = import_module(full_module_path)
+ metadata = getattr(module, ProcessorConfig.METADATA, {})
+
+ if metadata.get(ProcessorConfig.METADATA_IS_ACTIVE, False):
+ processor_plugins.append(
+ {
+ ProcessorConfig.MODULE: module,
+ ProcessorConfig.METADATA: module.metadata,
+ }
+ )
+ logger.info(
+ "Loaded processor plugin: %s, is_active: %s",
+ module.metadata[ProcessorConfig.METADATA_NAME],
+ module.metadata[ProcessorConfig.METADATA_IS_ACTIVE],
+ )
+ else:
+ logger.info(
+ "Processor plugin %s is not active.",
+ processor_module_name,
+ )
+ except ModuleNotFoundError as exception:
+ logger.error(
+ "Error while importing processor plugin: %s",
+ exception,
+ )
+
+ if len(processor_plugins) == 0:
+ logger.info("No processor plugins found.")
+
+ return processor_plugins
diff --git a/backend/prompt_studio/prompt_studio_core/constants.py b/backend/prompt_studio/prompt_studio_core/constants.py
index df5be97e9..a848828af 100644
--- a/backend/prompt_studio/prompt_studio_core/constants.py
+++ b/backend/prompt_studio/prompt_studio_core/constants.py
@@ -73,6 +73,8 @@ class ToolStudioPromptKeys:
EVAL_SETTINGS_EVALUATE = "evaluate"
EVAL_SETTINGS_MONITOR_LLM = "monitor_llm"
EVAL_SETTINGS_EXCLUDE_FAILED = "exclude_failed"
+ SUMMARIZE = "summarize"
+ SUMMARIZED_RESULT = "summarized_result"
class LogLevels:
diff --git a/backend/prompt_studio/prompt_studio_core/migrations/0004_customtool_summarize_as_source_and_more.py b/backend/prompt_studio/prompt_studio_core/migrations/0004_customtool_summarize_as_source_and_more.py
new file mode 100644
index 000000000..125ca3b2b
--- /dev/null
+++ b/backend/prompt_studio/prompt_studio_core/migrations/0004_customtool_summarize_as_source_and_more.py
@@ -0,0 +1,42 @@
+# Generated by Django 4.2.1 on 2024-02-27 05:43
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("prompt_profile_manager", "0006_alter_profilemanager_x2text"),
+ ("prompt_studio_core", "0003_merge_20240125_1501"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="customtool",
+ name="summarize_as_source",
+ field=models.BooleanField(default=True),
+ ),
+ migrations.AddField(
+ model_name="customtool",
+ name="summarize_context",
+ field=models.BooleanField(default=True),
+ ),
+ migrations.AddField(
+ model_name="customtool",
+ name="summarize_llm_profile",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="summarize_llm_profile",
+ to="prompt_profile_manager.profilemanager",
+ ),
+ ),
+ migrations.AddField(
+ model_name="customtool",
+ name="summarize_prompt",
+ field=models.TextField(
+ blank=True, db_comment="Field to store the summarize prompt"
+ ),
+ ),
+ ]
diff --git a/backend/prompt_studio/prompt_studio_core/migrations/0005_alter_customtool_default_profile_and_more.py b/backend/prompt_studio/prompt_studio_core/migrations/0005_alter_customtool_default_profile_and_more.py
new file mode 100644
index 000000000..f8c53210a
--- /dev/null
+++ b/backend/prompt_studio/prompt_studio_core/migrations/0005_alter_customtool_default_profile_and_more.py
@@ -0,0 +1,53 @@
+# Generated by Django 4.2.1 on 2024-02-28 09:03
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("prompt_profile_manager", "0006_alter_profilemanager_x2text"),
+ ("prompt_studio_core", "0004_customtool_summarize_as_source_and_more"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="customtool",
+ name="default_profile",
+ field=models.ForeignKey(
+ blank=True,
+ db_comment="Default LLM Profile used in prompt",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="default_profile",
+ to="prompt_profile_manager.profilemanager",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="customtool",
+ name="summarize_as_source",
+ field=models.BooleanField(
+ db_comment="Flag to use summarized content as source",
+ default=True,
+ ),
+ ),
+ migrations.AlterField(
+ model_name="customtool",
+ name="summarize_context",
+ field=models.BooleanField(
+ db_comment="Flag to summarize content", default=True
+ ),
+ ),
+ migrations.AlterField(
+ model_name="customtool",
+ name="summarize_llm_profile",
+ field=models.ForeignKey(
+ blank=True,
+ db_comment="LLM Profile used for summarize",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="summarize_llm_profile",
+ to="prompt_profile_manager.profilemanager",
+ ),
+ ),
+ ]
diff --git a/backend/prompt_studio/prompt_studio_core/models.py b/backend/prompt_studio/prompt_studio_core/models.py
index 4b8128461..82d41948f 100644
--- a/backend/prompt_studio/prompt_studio_core/models.py
+++ b/backend/prompt_studio/prompt_studio_core/models.py
@@ -44,6 +44,26 @@ class CustomTool(BaseModel):
related_name="default_profile",
null=True,
blank=True,
+ db_comment="Default LLM Profile used in prompt",
+ )
+ summarize_llm_profile = models.ForeignKey(
+ ProfileManager,
+ on_delete=models.SET_NULL,
+ related_name="summarize_llm_profile",
+ null=True,
+ blank=True,
+ db_comment="LLM Profile used for summarize",
+ )
+ summarize_context = models.BooleanField(
+ default=True, db_comment="Flag to summarize content"
+ )
+ summarize_as_source = models.BooleanField(
+ default=True, db_comment="Flag to use summarized content as source"
+ )
+ summarize_prompt = models.TextField(
+ blank=True,
+ db_comment="Field to store the summarize prompt",
+ unique=False,
)
prompt_grammer = models.JSONField(
null=True, blank=True, db_comment="Synonymous words used in prompt"
diff --git a/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py b/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py
index a2a4165a1..33bc189ce 100644
--- a/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py
+++ b/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py
@@ -2,7 +2,7 @@
import logging
import os
from pathlib import Path
-from typing import Any, Optional
+from typing import Any
from django.conf import settings
from file_management.file_management_helper import FileManagerHelper
@@ -16,7 +16,6 @@
AnswerFetchError,
DefaultProfileError,
IndexingError,
- OutputSaveError,
PromptNotValid,
ToolNotValid,
)
@@ -82,29 +81,39 @@ def fetch_prompt_from_tool(tool_id: str) -> list[ToolStudioPrompt]:
@staticmethod
def index_document(
- tool_id: str, file_name: str, org_id: str, user_id: str
+ tool_id: str,
+ file_name: str,
+ org_id: str,
+ user_id: str,
+ is_summary: bool = False,
) -> Any:
"""Method to index a document.
Args:
- tool_id (str):Id of the tool
+ tool_id (str): Id of the tool
file_name (str): File to parse
+ org_id (str): The ID of the organization to which the user belongs.
+ user_id (str): The ID of the user who uploaded the document.
+ is_summary (bool, optional): Whether the document is a summary
+ or not. Defaults to False.
Raises:
ToolNotValid
IndexingError
"""
tool: CustomTool = CustomTool.objects.get(pk=tool_id)
- default_profile: Optional[ProfileManager] = tool.default_profile
+ if is_summary:
+ default_profile = tool.summarize_llm_profile
+ file_path = file_name
+ else:
+ default_profile = tool.default_profile
+ file_path = FileManagerHelper.handle_sub_directory_for_tenants(
+ org_id, is_create=False, user_id=user_id, tool_id=tool_id
+ )
+ file_path = str(Path(file_path) / file_name)
+
if not default_profile:
raise DefaultProfileError()
- file_path = FileManagerHelper.handle_sub_directory_for_tenants(
- org_id=org_id,
- user_id=user_id,
- tool_id=tool_id,
- is_create=False,
- )
- file_path = str(Path(file_path) / file_name)
stream_log.publish(
tool_id,
stream_log.log(
@@ -134,6 +143,7 @@ def index_document(
tool_id=tool_id,
file_name=file_path,
org_id=org_id,
+ is_summary=is_summary,
)
logger.info(f"Indexing done sucessfully for {file_name}")
stream_log.publish(
@@ -182,6 +192,15 @@ def prompt_responder(
prompts: list[ToolStudioPrompt] = []
prompts.append(prompt_instance)
tool: CustomTool = prompt_instance.tool_id
+
+ if tool.summarize_as_source:
+ directory, filename = os.path.split(file_path)
+ file_path = os.path.join(
+ directory,
+ TSPKeys.SUMMARIZE,
+ os.path.splitext(filename)[0] + ".txt",
+ )
+
stream_log.publish(
tool.tool_id,
stream_log.log(
@@ -277,6 +296,7 @@ def _fetch_response(
file_name=path,
tool_id=str(tool.tool_id),
org_id=org_id,
+ is_summary=tool.summarize_as_source,
)
output: dict[str, Any] = {}
@@ -348,19 +368,6 @@ def _fetch_response(
if answer["status"] == "ERROR":
raise AnswerFetchError()
output_response = json.loads(answer["structure_output"])
-
- # persist output
- try:
- for key in output_response:
- if TSPKeys.EVAL_RESULT_DELIM not in key:
- persisted_prompt = ToolStudioPrompt.objects.get(
- prompt_key=key
- )
- persisted_prompt.output = output_response[key] or ""
- persisted_prompt.save()
- except Exception as e:
- logger.error(f"Exception while saving prompt output: {e}")
- raise OutputSaveError()
return output_response
@staticmethod
@@ -369,6 +376,7 @@ def dynamic_indexer(
tool_id: str,
file_name: str,
org_id: str,
+ is_summary: bool = False,
) -> str:
try:
util = PromptIdeBaseTool(log_level=LogLevel.INFO, org_id=org_id)
@@ -380,6 +388,12 @@ def dynamic_indexer(
vector_db = str(profile_manager.vector_store.id)
x2text_adapter = str(profile_manager.x2text.id)
file_hash = ToolUtils.get_hash_from_file(file_path=file_name)
+ extract_file_path = None
+ if not is_summary:
+ directory, filename = os.path.split(file_name)
+ extract_file_path: str = os.path.join(
+ directory, "extract", os.path.splitext(filename)[0] + ".txt"
+ )
return str(
tool_index.index_file(
tool_id=tool_id,
@@ -391,5 +405,6 @@ def dynamic_indexer(
chunk_size=profile_manager.chunk_size,
chunk_overlap=profile_manager.chunk_overlap,
reindex=profile_manager.reindex,
+ output_file_path=extract_file_path,
)
)
diff --git a/backend/prompt_studio/prompt_studio_core/views.py b/backend/prompt_studio/prompt_studio_core/views.py
index c26844309..5109782d3 100644
--- a/backend/prompt_studio/prompt_studio_core/views.py
+++ b/backend/prompt_studio/prompt_studio_core/views.py
@@ -6,6 +6,7 @@
from django.db.models import QuerySet
from django.http import HttpRequest
from permissions.permission import IsOwner
+from prompt_studio.processor_loader import ProcessorConfig, load_plugins
from prompt_studio.prompt_studio.exceptions import FilenameMissingError
from prompt_studio.prompt_studio_core.constants import (
ToolStudioErrors,
@@ -42,6 +43,8 @@ class PromptStudioCoreView(viewsets.ModelViewSet):
permission_classes = [IsOwner]
serializer_class = CustomToolSerializer
+ processor_plugins = load_plugins()
+
def get_queryset(self) -> Optional[QuerySet]:
filter_args = FilterHelper.build_filter_args(
self.request,
@@ -141,6 +144,18 @@ def index_document(self, request: HttpRequest) -> Response:
org_id=request.org_id,
user_id=request.user.user_id,
)
+
+ for processor_plugin in self.processor_plugins:
+ cls = processor_plugin[ProcessorConfig.METADATA][
+ ProcessorConfig.METADATA_SERVICE_CLASS
+ ]
+ cls.process(
+ tool_id=tool_id,
+ file_name=file_name,
+ org_id=request.org_id,
+ user_id=request.user.user_id,
+ )
+
if unique_id:
return Response(
{"message": "Document indexed successfully."},
diff --git a/backend/prompt_studio/prompt_studio_output_manager/migrations/0007_promptstudiooutputmanager_eval_metrics.py b/backend/prompt_studio/prompt_studio_output_manager/migrations/0007_promptstudiooutputmanager_eval_metrics.py
new file mode 100644
index 000000000..b57a9bc6a
--- /dev/null
+++ b/backend/prompt_studio/prompt_studio_output_manager/migrations/0007_promptstudiooutputmanager_eval_metrics.py
@@ -0,0 +1,24 @@
+# Generated by Django 4.2.1 on 2024-02-27 05:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ (
+ "prompt_studio_output_manager",
+ "0006_alter_promptstudiooutputmanager_output",
+ ),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="promptstudiooutputmanager",
+ name="eval_metrics",
+ field=models.JSONField(
+ db_column="eval_metrics",
+ db_comment="Field to store the evaluation metrics",
+ default=list,
+ ),
+ ),
+ ]
diff --git a/backend/prompt_studio/prompt_studio_output_manager/models.py b/backend/prompt_studio/prompt_studio_output_manager/models.py
index 3392bd5a2..1e12d7cd2 100644
--- a/backend/prompt_studio/prompt_studio_output_manager/models.py
+++ b/backend/prompt_studio/prompt_studio_output_manager/models.py
@@ -26,7 +26,13 @@ class PromptStudioOutputManager(BaseModel):
db_comment="Field to store the document name",
editable=True,
)
-
+ eval_metrics = models.JSONField(
+ db_column="eval_metrics",
+ null=False,
+ blank=False,
+ default=list,
+ db_comment="Field to store the evaluation metrics",
+ )
tool_id = models.ForeignKey(
CustomTool,
on_delete=models.SET_NULL,
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
index 9494a1fda..faf134db2 100644
--- a/backend/pyproject.toml
+++ b/backend/pyproject.toml
@@ -30,7 +30,7 @@ dependencies = [
"python-socketio==5.9.0", # For log_events
"social-auth-app-django==5.3.0", # For OAuth
"social-auth-core==4.4.2", # For OAuth
- "unstract-sdk~=0.11.2",
+ "unstract-sdk~=0.12.1",
"unstract-adapters~=0.2.2",
# ! IMPORTANT!
# Indirect local dependencies usually need to be added in their own projects
diff --git a/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx b/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx
index da3db4600..5340b4bfa 100644
--- a/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx
+++ b/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx
@@ -1,4 +1,4 @@
-import { Button, Segmented, Space } from "antd";
+import { Segmented } from "antd";
import jsYaml from "js-yaml";
import Prism from "prismjs";
import "prismjs/components/prism-json";
@@ -6,6 +6,7 @@ import "prismjs/plugins/line-numbers/prism-line-numbers.css";
import "prismjs/plugins/line-numbers/prism-line-numbers.js";
import "prismjs/themes/prism.css";
import { useEffect, useState } from "react";
+import PropTypes from "prop-types";
import { handleException, promptType } from "../../../helpers/GetStaticData";
import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate";
@@ -20,27 +21,31 @@ const outputTypes = {
yaml: "YAML",
};
-function CombinedOutput() {
+function CombinedOutput({ doc, setFilledFields }) {
const [combinedOutput, setCombinedOutput] = useState({});
const [yamlData, setYamlData] = useState(null);
const [selectedOutputType, setSelectedOutputType] = useState(
outputTypes.json
);
const [isOutputLoading, setIsOutputLoading] = useState(false);
- const { details, selectedDoc } = useCustomToolStore();
+ const { details } = useCustomToolStore();
const { sessionDetails } = useSessionStore();
const { setAlertDetails } = useAlertStore();
const axiosPrivate = useAxiosPrivate();
useEffect(() => {
- if (!selectedDoc) {
+ if (!doc) {
return;
}
+ let filledFields = 0;
setIsOutputLoading(true);
handleOutputApiRequest()
.then((res) => {
const data = res?.data || [];
+ data.sort((a, b) => {
+ return new Date(b.modified_at) - new Date(a.modified_at);
+ });
const prompts = details?.prompts;
const output = {};
prompts.forEach((item) => {
@@ -59,10 +64,18 @@ function CombinedOutput() {
return;
}
- output[item?.prompt_key] = outputDetails?.output || "";
+ output[item?.prompt_key] = JSON.parse(outputDetails?.output || "");
+
+ if (outputDetails?.output?.length > 0) {
+ filledFields++;
+ }
});
setCombinedOutput(output);
+ if (setFilledFields) {
+ setFilledFields(filledFields);
+ }
+
const yamlDump = jsYaml.dump(output);
setYamlData(yamlDump);
})
@@ -74,7 +87,7 @@ function CombinedOutput() {
.finally(() => {
setIsOutputLoading(false);
});
- }, [selectedDoc]);
+ }, [doc]);
useEffect(() => {
Prism.highlightAll();
@@ -87,7 +100,7 @@ function CombinedOutput() {
const handleOutputApiRequest = async () => {
const requestOptions = {
method: "GET",
- url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/?tool_id=${details?.tool_id}&doc_name=${selectedDoc}`,
+ url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/?tool_id=${details?.tool_id}&doc_name=${doc}`,
headers: {
"X-CSRFToken": sessionDetails?.csrfToken,
},
@@ -107,11 +120,6 @@ function CombinedOutput() {
return (
-
-
-
-
-
{
+ const fileNameTxt = removeFileExtension(selectedDoc);
+ const files = [
+ {
+ fileName: selectedDoc,
+ viewType: viewTypes.pdf,
+ },
+ {
+ fileName: `extract/${fileNameTxt}.txt`,
+ viewType: viewTypes.extract,
+ },
+ {
+ fileName: `summarize/${fileNameTxt}.txt`,
+ viewType: viewTypes.summarize,
+ },
+ ];
+
+ setFileUrl("");
+ setExtractTxt("");
+ setSummaryTxt("");
+ files.forEach((item) => {
+ handleFetchContent(item);
+ });
+ }, [selectedDoc]);
+
+ const handleFetchContent = (fileDetails) => {
+ if (!selectedDoc) {
+ setFileUrl("");
+ setExtractTxt("");
+ setSummaryTxt("");
+ return;
+ }
+
+ const requestOptions = {
+ method: "GET",
+ url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/file/fetch_contents?file_name=${fileDetails?.fileName}&tool_id=${details?.tool_id}`,
+ };
+
+ handleLoadingStateUpdate(fileDetails?.viewType, true);
+ axiosPrivate(requestOptions)
+ .then((res) => {
+ const data = res?.data?.data;
+ if (fileDetails?.viewType === viewTypes.pdf) {
+ const base64String = data || "";
+ const blob = base64toBlob(base64String);
+ setFileUrl(URL.createObjectURL(blob));
+ return;
+ }
+
+ if (fileDetails?.viewType === viewTypes?.extract) {
+ setExtractTxt(data);
+ }
+
+ if (fileDetails?.viewType === viewTypes.summarize) {
+ setSummaryTxt(data);
+ }
+ })
+ .catch((err) => {})
+ .finally(() => {
+ handleLoadingStateUpdate(fileDetails?.viewType, false);
+ });
+ };
+
+ const handleLoadingStateUpdate = (viewType, value) => {
+ if (viewType === viewTypes.pdf) {
+ setIsDocLoading(value);
+ }
+
+ if (viewType === viewTypes.extract) {
+ setIsExtractLoading(value);
+ }
+
+ if (viewType === viewTypes.summarize) {
+ setIsSummaryLoading(value);
+ }
+ };
+
+ const handleActiveKeyChange = (key) => {
+ setActiveKey(key);
+ };
useEffect(() => {
const index = [...listOfDocs].findIndex((item) => item === selectedDoc);
@@ -49,42 +168,82 @@ function DocumentManager({ generateIndex, handleUpdateTool, handleDocChange }) {
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ {activeKey === "1" && (
+
0}
+ setOpenManageDocsModal={setOpenManageDocsModal}
+ >
+
+
+ )}
+ {activeKey === "2" && (
+
0}
+ setOpenManageDocsModal={setOpenManageDocsModal}
+ >
+
+
+ )}
+ {activeKey === "3" && (
+
0}
+ setOpenManageDocsModal={setOpenManageDocsModal}
+ >
+
+
+ )}
{
const promptsAndNotes = details?.prompts || [];
let name = "";
@@ -93,21 +94,23 @@ function DocumentParser({
promptId,
promptStudioUpdateStatus.isUpdating
);
- axiosPrivate(requestOptions)
+
+ return axiosPrivate(requestOptions)
.then((res) => {
const data = res?.data;
const modifiedDetails = { ...details };
const modifiedPrompts = [...(modifiedDetails?.prompts || [])].map(
(item) => {
if (item?.prompt_id === data?.prompt_id) {
- data.evalMetrics = item?.evalMetrics || [];
return data;
}
return item;
}
);
modifiedDetails["prompts"] = modifiedPrompts;
- updateCustomTool({ details: modifiedDetails });
+ if (!isPromptUpdate) {
+ updateCustomTool({ details: modifiedDetails });
+ }
handleUpdateStatus(
isUpdateStatus,
promptId,
diff --git a/frontend/src/components/custom-tools/document-viewer/DocumentViewer.jsx b/frontend/src/components/custom-tools/document-viewer/DocumentViewer.jsx
new file mode 100644
index 000000000..aabd40c63
--- /dev/null
+++ b/frontend/src/components/custom-tools/document-viewer/DocumentViewer.jsx
@@ -0,0 +1,46 @@
+import PropTypes from "prop-types";
+import { SpinnerLoader } from "../../widgets/spinner-loader/SpinnerLoader";
+import { EmptyState } from "../../widgets/empty-state/EmptyState";
+import { Typography } from "antd";
+
+function DocumentViewer({
+ children,
+ doc,
+ isLoading,
+ isContentAvailable,
+ setOpenManageDocsModal,
+}) {
+ if (isLoading) {
+ return ;
+ }
+
+ if (!doc) {
+ return (
+ setOpenManageDocsModal(true)}
+ />
+ );
+ }
+
+ if (!isContentAvailable) {
+ return (
+
+ Failed to load the document
+
+ );
+ }
+
+ return <>{children}>;
+}
+
+DocumentViewer.propTypes = {
+ children: PropTypes.any.isRequired,
+ doc: PropTypes.string,
+ isLoading: PropTypes.bool.isRequired,
+ isContentAvailable: PropTypes.bool.isRequired,
+ setOpenManageDocsModal: PropTypes.func,
+};
+
+export { DocumentViewer };
diff --git a/frontend/src/components/custom-tools/editable-text/EditableText.jsx b/frontend/src/components/custom-tools/editable-text/EditableText.jsx
index ac5b14b00..6ba422c94 100644
--- a/frontend/src/components/custom-tools/editable-text/EditableText.jsx
+++ b/frontend/src/components/custom-tools/editable-text/EditableText.jsx
@@ -15,6 +15,8 @@ function EditableText({
isTextarea,
}) {
const [text, setText] = useState("");
+ const name = isTextarea ? "prompt" : "prompt_key";
+ const [triggerHandleChange, setTriggerHandleChange] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const divRef = useRef(null);
const { disableLlmOrDocChange } = useCustomToolStore();
@@ -45,11 +47,19 @@ function EditableText({
const onSearchDebounce = useCallback(
debounce((event) => {
- handleChange(event, promptId, false, true);
+ setTriggerHandleChange(true);
}, 1000),
[]
);
+ useEffect(() => {
+ if (!triggerHandleChange) {
+ return;
+ }
+ handleChange(text, promptId, name, true, true);
+ setTriggerHandleChange(false);
+ }, [triggerHandleChange]);
+
const handleClickOutside = (event) => {
if (divRef.current && !divRef.current.contains(event.target)) {
// Clicked outside the div
@@ -64,7 +74,7 @@ function EditableText({
value={text}
onChange={handleTextChange}
placeholder="Enter Prompt"
- name="prompt"
+ name={name}
size="small"
style={{ backgroundColor: "transparent" }}
variant={`${!isEditing && !isHovered ? "borderless" : "outlined"}`}
@@ -84,7 +94,7 @@ function EditableText({
value={text}
onChange={handleTextChange}
placeholder="Enter Key"
- name="prompt_key"
+ name={name}
size="small"
style={{ backgroundColor: "transparent" }}
variant={`${!isEditing && !isHovered ? "borderless" : "outlined"}`}
diff --git a/frontend/src/components/custom-tools/header/Header.jsx b/frontend/src/components/custom-tools/header/Header.jsx
index 498512cd8..355e7de10 100644
--- a/frontend/src/components/custom-tools/header/Header.jsx
+++ b/frontend/src/components/custom-tools/header/Header.jsx
@@ -4,13 +4,12 @@ import {
DiffOutlined,
EditOutlined,
ExportOutlined,
- FilePdfOutlined,
FileTextOutlined,
MessageOutlined,
} from "@ant-design/icons";
import { Button, Tooltip, Typography } from "antd";
import PropTypes from "prop-types";
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import "./Header.css";
@@ -21,26 +20,34 @@ import { useCustomToolStore } from "../../../store/custom-tool-store";
import { useSessionStore } from "../../../store/session-store";
import { CustomButton } from "../../widgets/custom-button/CustomButton";
import { PreAndPostAmbleModal } from "../pre-and-post-amble-modal/PreAndPostAmbleModal";
+import { SelectLlmProfileModal } from "../select-llm-profile-modal/SelectLlmProfileModal";
function Header({
setOpenCusSynonymsModal,
- setOpenManageDocsModal,
setOpenManageLlmModal,
handleUpdateTool,
}) {
const [openPreOrPostAmbleModal, setOpenPreOrPostAmbleModal] = useState(false);
+ const [openSummLlmProfileModal, setOpenSummLlmProfileModal] = useState(false);
const [preOrPostAmble, setPreOrPostAmble] = useState("");
const [isExportLoading, setIsExportLoading] = useState(false);
- const { details } = useCustomToolStore();
+ const [summarizeLlmBtnText, setSummarizeLlmBtnText] = useState(null);
+ const [llmItems, setLlmItems] = useState([]);
+ const { details, llmProfiles } = useCustomToolStore();
const { sessionDetails } = useSessionStore();
const { setAlertDetails } = useAlertStore();
const axiosPrivate = useAxiosPrivate();
const navigate = useNavigate();
+ useEffect(() => {
+ getLlmProfilesDropdown();
+ }, []);
+
const handleOpenPreOrPostAmbleModal = (type) => {
setOpenPreOrPostAmbleModal(true);
setPreOrPostAmble(type);
};
+
const handleClosePreOrPostAmbleModal = () => {
setOpenPreOrPostAmbleModal(false);
setPreOrPostAmble("");
@@ -68,6 +75,16 @@ function Header({
});
};
+ const getLlmProfilesDropdown = () => {
+ const items = [...llmProfiles].map((item) => {
+ return {
+ value: item?.profile_id,
+ label: item?.profile_name,
+ };
+ });
+ setLlmItems(items);
+ };
+
return (
@@ -89,25 +106,31 @@ function Header({
-
-
-
+
-
-
-
+
-
-
-
+
@@ -143,13 +166,19 @@ function Header({
type={preOrPostAmble}
handleUpdateTool={handleUpdateTool}
/>
+
);
}
Header.propTypes = {
setOpenCusSynonymsModal: PropTypes.func.isRequired,
- setOpenManageDocsModal: PropTypes.func.isRequired,
setOpenManageLlmModal: PropTypes.func.isRequired,
handleUpdateTool: PropTypes.func.isRequired,
};
diff --git a/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx b/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx
index 7630be445..87e97a980 100644
--- a/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx
+++ b/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx
@@ -94,7 +94,27 @@ function ManageDocsModal({
setRows(newRows);
}, [listOfDocs, selectedDoc, disableLlmOrDocChange]);
- const handleUploadChange = (info) => {
+ const beforeUpload = (file) => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = () => {
+ const fileName = file.name;
+ const fileAlreadyExists = [...listOfDocs].includes(fileName);
+ if (!fileAlreadyExists) {
+ resolve(file);
+ } else {
+ setAlertDetails({
+ type: "error",
+ content: "File name already exists",
+ });
+ reject(new Error("File name already exists"));
+ }
+ };
+ });
+ };
+
+ const handleUploadChange = async (info) => {
if (info.file.status === "uploading") {
setIsUploading(true);
}
@@ -109,14 +129,14 @@ function ManageDocsModal({
const docName = info?.file?.name;
const newListOfDocs = [...listOfDocs];
newListOfDocs.push(docName);
+ setOpen(false);
+ await generateIndex(info?.file?.name);
const body = {
selectedDoc: docName,
listOfDocs: newListOfDocs,
};
updateCustomTool(body);
handleUpdateTool({ output: docName });
- setOpen(false);
- generateIndex(info?.file?.name);
} else if (info.file.status === "error") {
setIsUploading(false);
setAlertDetails({
@@ -175,6 +195,8 @@ function ManageDocsModal({
onChange={handleUploadChange}
disabled={isUploading || !defaultLlmProfile}
showUploadList={false}
+ accept=".pdf"
+ beforeUpload={beforeUpload}
>
+ {updateStatus?.promptId === details?.prompt_id && (
+ <>
+ {updateStatus?.status ===
+ promptStudioUpdateStatus.isUpdating && (
+
}
+ color="processing"
+ className="display-flex-align-center"
+ >
+ Updating
+
+ )}
+ {updateStatus?.status === promptStudioUpdateStatus.done && (
+
}
+ color="success"
+ className="display-flex-align-center"
+ >
+ Done
+
+ )}
+ >
+ )}
- View in Output Analyzer
+ navigate("outputAnalyzer")}>
+ View in Output Analyzer
+
diff --git a/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx b/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx
index 5fd9f9201..4ee872189 100644
--- a/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx
+++ b/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx
@@ -1,87 +1,11 @@
import { Viewer, Worker } from "@react-pdf-viewer/core";
import { defaultLayoutPlugin } from "@react-pdf-viewer/default-layout";
import { pageNavigationPlugin } from "@react-pdf-viewer/page-navigation";
-import { Typography } from "antd";
import PropTypes from "prop-types";
-import { useEffect, useState } from "react";
-import { handleException } from "../../../helpers/GetStaticData";
-import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate";
-import { useAlertStore } from "../../../store/alert-store";
-import { useCustomToolStore } from "../../../store/custom-tool-store";
-import { useSessionStore } from "../../../store/session-store";
-import { EmptyState } from "../../widgets/empty-state/EmptyState";
-import { SpinnerLoader } from "../../widgets/spinner-loader/SpinnerLoader";
-
-function PdfViewer({ setOpenManageDocsModal }) {
- const [fileUrl, setFileUrl] = useState("");
- const [isLoading, setIsLoading] = useState(false);
+function PdfViewer({ fileUrl }) {
const newPlugin = defaultLayoutPlugin();
const pageNavigationPluginInstance = pageNavigationPlugin();
- const { sessionDetails } = useSessionStore();
- const { setAlertDetails } = useAlertStore();
- const { selectedDoc, details } = useCustomToolStore();
- const axiosPrivate = useAxiosPrivate();
-
- const base64toBlob = (data) => {
- const bytes = atob(data);
- let length = bytes.length;
- const out = new Uint8Array(length);
-
- while (length--) {
- out[length] = bytes.charCodeAt(length);
- }
-
- return new Blob([out], { type: "application/pdf" });
- };
-
- useEffect(() => {
- if (!selectedDoc) {
- setFileUrl("");
- return;
- }
-
- const requestOptions = {
- method: "GET",
- url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/file/fetch_contents?file_name=${selectedDoc}&tool_id=${details?.tool_id}`,
- };
-
- setIsLoading(true);
- axiosPrivate(requestOptions)
- .then((res) => {
- const base64String = res?.data?.data || "";
- const blob = base64toBlob(base64String);
- setFileUrl(URL.createObjectURL(blob));
- })
- .catch((err) => {
- setAlertDetails(handleException(err, "Failed to load the document"));
- })
- .finally(() => {
- setIsLoading(false);
- });
- }, [selectedDoc]);
-
- if (isLoading) {
- return
;
- }
-
- if (!selectedDoc) {
- return (
-
setOpenManageDocsModal(true)}
- />
- );
- }
-
- if (!fileUrl) {
- return (
-
- Failed to load the document
-
- );
- }
return (
@@ -96,7 +20,7 @@ function PdfViewer({ setOpenManageDocsModal }) {
}
PdfViewer.propTypes = {
- setOpenManageDocsModal: PropTypes.func.isRequired,
+ fileUrl: PropTypes.any,
};
export { PdfViewer };
diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx b/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx
index 9e5eebcb5..4b6af7cbc 100644
--- a/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx
+++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx
@@ -4,6 +4,7 @@ import {
DeleteOutlined,
EditOutlined,
LeftOutlined,
+ LoadingOutlined,
PlayCircleOutlined,
RightOutlined,
SearchOutlined,
@@ -30,6 +31,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { AssertionIcon } from "../../../assets";
import {
+ displayPromptResult,
handleException,
promptStudioUpdateStatus,
} from "../../../helpers/GetStaticData";
@@ -63,6 +65,7 @@ function PromptCard({
const [result, setResult] = useState({
promptOutputId: null,
output: "",
+ evalMetrics: [],
});
const [outputIds, setOutputIds] = useState([]);
const [coverage, setCoverage] = useState(0);
@@ -75,7 +78,6 @@ function PromptCard({
llmProfiles,
selectedDoc,
listOfDocs,
- evalMetrics,
updateCustomTool,
details,
disableLlmOrDocChange,
@@ -174,10 +176,6 @@ function PromptCard({
[]
);
- const isJson = (text) => {
- return typeof text === "object";
- };
-
const handlePageLeft = () => {
if (page <= 1) {
return;
@@ -214,24 +212,16 @@ function PromptCard({
return sortedMetrics;
};
+ const handleTypeChange = (value) => {
+ handleChange(value, promptDetails?.prompt_id, "enforce_type", true).then(
+ () => {
+ handleRun();
+ }
+ );
+ };
+
// Generate the result for the currently selected document
const handleRun = () => {
- if (!promptDetails?.prompt_key) {
- setAlertDetails({
- type: "error",
- content: "Prompt key is not set",
- });
- return;
- }
-
- if (!promptDetails?.prompt) {
- setAlertDetails({
- type: "error",
- content: "Prompt cannot be empty",
- });
- return;
- }
-
if (!promptDetails?.profile_manager?.length) {
setAlertDetails({
type: "error",
@@ -251,41 +241,39 @@ function PromptCard({
setIsRunLoading(true);
setIsCoverageLoading(true);
setCoverage(0);
+ setCoverageTotal(0);
+
+ let method = "POST";
+ let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/`;
+ if (result?.promptOutputId) {
+ method = "PATCH";
+ url += `${result?.promptOutputId}/`;
+ }
handleRunApiRequest(selectedDoc)
.then((res) => {
const data = res?.data;
const value = data[promptDetails?.prompt_key];
- let method = "POST";
- let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/`;
- if (result?.promptOutputId) {
- method = "PATCH";
- url += `${result?.promptOutputId}/`;
- }
- handleUpdateOutput(value, selectedDoc, method, url);
-
- if (value?.length > 0) {
+ if (value !== null && String(value)?.length > 0) {
setCoverage((prev) => prev + 1);
- } else {
- setAlertDetails({
- type: "error",
- content: `Failed to generate output for ${selectedDoc}`,
- });
}
- handleCoverage();
- const modifiedEvalMetrics = { ...evalMetrics };
- modifiedEvalMetrics[`${promptDetails?.prompt_id}`] =
- sortEvalMetricsByType(
- data[`${promptDetails?.prompt_key}__evaluation`] || []
- );
- updateCustomTool({ evalMetrics: modifiedEvalMetrics });
+ // Handle Eval
+ let evalMetrics = [];
+ if (promptDetails?.evaluate) {
+ evalMetrics = data[`${promptDetails?.prompt_key}__evaluation`] || [];
+ }
+ handleUpdateOutput(value, selectedDoc, evalMetrics, method, url);
})
.catch((err) => {
- setIsCoverageLoading(false);
- setAlertDetails(handleException(err));
+ handleUpdateOutput(null, selectedDoc, [], method, url);
+ setAlertDetails(
+ handleException(err, `Failed to generate output for ${selectedDoc}`)
+ );
})
.finally(() => {
setIsRunLoading(false);
+ setCoverageTotal((prev) => prev + 1);
+ handleCoverage();
});
};
@@ -300,14 +288,32 @@ function PromptCard({
return;
}
- setCoverageTotal(1);
listOfDocsToProcess.forEach((item) => {
+ let method = "POST";
+ let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/`;
+ const outputId = outputIds.find((output) => output?.docName === item);
+ if (outputId?.promptOutputId?.length) {
+ method = "PATCH";
+ url += `${outputId?.promptOutputId}/`;
+ }
handleRunApiRequest(item)
.then((res) => {
const data = res?.data;
- handleCoverageData(data, item);
+ const outputValue = data[promptDetails?.prompt_key];
+ if (outputValue !== null && String(outputValue)?.length > 0) {
+ setCoverage((prev) => prev + 1);
+ }
+
+ // Handle Eval
+ let evalMetrics = [];
+ if (promptDetails?.evaluate) {
+ evalMetrics =
+ data[`${promptDetails?.prompt_key}__evaluation`] || [];
+ }
+ handleUpdateOutput(outputValue, item, evalMetrics, method, url);
})
.catch((err) => {
+ handleUpdateOutput(null, item, [], method, url);
setAlertDetails(
handleException(err, `Failed to generate output for ${item}`)
);
@@ -344,34 +350,20 @@ function PromptCard({
});
};
- const handleCoverageData = (data, docName) => {
- const outputValue = data[promptDetails?.prompt_key];
- let method = "POST";
- let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/`;
- const outputId = outputIds.find((output) => output?.docName === docName);
- if (outputId?.promptOutputId?.length) {
- method = "PATCH";
- url += `${outputId?.promptOutputId}/`;
- }
- handleUpdateOutput(outputValue, docName, method, url);
-
- if (outputValue?.length > 0) {
- setCoverage((prev) => prev + 1);
- } else {
- setAlertDetails({
- type: "error",
- content: `Failed to generate output for ${docName}`,
- });
- }
- };
-
- const handleUpdateOutput = (outputValue, docName, method, url) => {
+ const handleUpdateOutput = (
+ outputValue,
+ docName,
+ evalMetrics,
+ method,
+ url
+ ) => {
const body = {
- output: outputValue,
+ output: outputValue !== null ? JSON.stringify(outputValue) : null,
tool_id: details?.tool_id,
prompt_id: promptDetails?.prompt_id,
profile_manager: promptDetails?.profile_manager,
doc_name: docName,
+ eval_metrics: evalMetrics,
};
const requestOptions = {
@@ -386,14 +378,24 @@ function PromptCard({
axiosPrivate(requestOptions)
.then((res) => {
- if (docName !== selectedDoc) {
- return;
- }
const data = res?.data;
- setResult({
- promptOutputId: data?.prompt_output_id || null,
- output: data?.output || "",
- });
+ const promptOutputId = data?.prompt_output_id || null;
+ if (docName === selectedDoc) {
+ setResult({
+ promptOutputId: promptOutputId,
+ output: data?.output,
+ evalMetrics: sortEvalMetricsByType(data?.eval_metrics || []),
+ });
+ }
+
+ const isOutputIdAvailable = outputIds.find(
+ (item) => item?.promptOutputId === promptOutputId
+ );
+ if (!isOutputIdAvailable) {
+ const listOfOutputIds = [...outputIds];
+ listOfOutputIds.push({ promptOutputId, docName });
+ setOutputIds(listOfOutputIds);
+ }
})
.catch((err) => {
setAlertDetails(handleException(err, "Failed to persist the result"));
@@ -405,19 +407,19 @@ function PromptCard({
setResult({
promptOutputId: null,
output: "",
+ evalMetrics: [],
});
return;
}
setIsRunLoading(true);
handleOutputApiRequest(true)
- .then((res) => {
- const data = res?.data;
-
+ .then((data) => {
if (!data || data?.length === 0) {
setResult({
promptOutputId: null,
output: "",
+ evalMetrics: [],
});
return;
}
@@ -425,7 +427,8 @@ function PromptCard({
const outputResult = data[0];
setResult({
promptOutputId: outputResult?.prompt_output_id,
- output: outputResult?.output || "",
+ output: outputResult?.output,
+ evalMetrics: sortEvalMetricsByType(outputResult?.eval_metrics || []),
});
})
.catch((err) => {
@@ -443,11 +446,7 @@ function PromptCard({
setCoverage(0);
handleOutputApiRequest()
- .then((res) => {
- const data = res?.data;
- data.sort((a, b) => {
- return new Date(b.created_at) - new Date(a.created_at);
- });
+ .then((data) => {
handleGetCoverageData(data);
})
.catch((err) => {
@@ -470,7 +469,13 @@ function PromptCard({
};
return axiosPrivate(requestOptions)
- .then((res) => res)
+ .then((res) => {
+ const data = res?.data;
+ data.sort((a, b) => {
+ return new Date(b.modified_at) - new Date(a.modified_at);
+ });
+ return data;
+ })
.catch((err) => {
throw err;
});
@@ -488,7 +493,7 @@ function PromptCard({
}
if (
- item?.output?.length > 0 &&
+ item?.output !== undefined &&
[...listOfDocs].includes(item?.doc_name)
) {
ids.push({
@@ -581,6 +586,15 @@ function PromptCard({
/>
+ {isCoverageLoading && (
+
}
+ color="processing"
+ className="display-flex-align-center"
+ >
+ Generating Response
+
+ )}
{updateStatus?.promptId === promptDetails?.prompt_id && (
<>
{updateStatus?.status ===
@@ -680,8 +694,10 @@ function PromptCard({
0) &&
- "prompt-card-comp-layout-border"
+ !(
+ isRunLoading ||
+ (result?.output !== undefined && outputIds?.length > 0)
+ ) && "prompt-card-comp-layout-border"
}`}
>
@@ -708,6 +724,7 @@ function PromptCard({
icon={}
loading={isCoverageLoading}
onClick={() => setOpenOutputForDoc(true)}
+ disabled={outputIds?.length === 0}
>
Coverage: {coverage} of {listOfDocs?.length || 0} docs
@@ -725,14 +742,7 @@ function PromptCard({
disabled={disableLlmOrDocChange.includes(
promptDetails?.prompt_id
)}
- onChange={(value) =>
- handleChange(
- value,
- promptDetails?.prompt_id,
- "enforce_type",
- true
- )
- }
+ onChange={(value) => handleTypeChange(value)}
/>
@@ -781,18 +791,17 @@ function PromptCard({
- {evalMetrics?.[`${promptDetails?.prompt_id}`] && (
+ {result?.evalMetrics?.length > 0 && (
- {evalMetrics?.[`${promptDetails?.prompt_id}`].map(
- (evalMetric) => (
-
- )
- )}
+ {result?.evalMetrics.map((evalMetric) => (
+
+ ))}
)}
>
- {(isRunLoading || result?.output?.length > 0) && (
+ {(isRunLoading ||
+ (result?.output !== undefined && outputIds?.length > 0)) && (
<>
@@ -800,11 +809,7 @@ function PromptCard({
} />
) : (
-
- {isJson(result?.output)
- ? JSON.stringify(result?.output, null, 4)
- : result?.output}
-
+ {displayPromptResult(result?.output)}
)}
@@ -816,7 +821,6 @@ function PromptCard({
setOpen={setOpenEval}
promptDetails={promptDetails}
handleChange={handleChange}
- handleRun={handleRun}
/>
{
+ setIsContext(details?.summarize_context);
+ setIsSource(details?.summarize_as_source);
+ }, []);
+
+ useEffect(() => {
+ if (!selectedLlm) {
+ setBtnText("");
+
+ // If the LLM is not selected, the context needs to be set to false and disabled in the UI
+ if (isContext) {
+ handleLlmProfileChange(fieldNames.SUMMARIZE_CONTEXT, false);
+ }
+ return;
+ }
+
+ const llmItem = [...llmItems].find((item) => item?.value === selectedLlm);
+ setBtnText(llmItem?.label || "");
+ }, [selectedLlm]);
+
+ useEffect(() => {
+ if (llmItems?.length) {
+ setSelectedLlm(details?.summarize_llm_profile);
+ }
+ }, [llmItems]);
+
+ const handleStateUpdate = (fieldName, value) => {
+ if (fieldName === fieldNames.SUMMARIZE_LLM_PROFILE) {
+ setSelectedLlm(value);
+ }
+
+ if (fieldName === fieldNames.SUMMARIZE_PROMPT) {
+ setPrompt(value);
+ }
+
+ if (fieldName === fieldNames.SUMMARIZE_CONTEXT) {
+ setIsContext(value);
+ }
+
+ if (fieldName === fieldNames.SUMMARIZE_AS_SOURCE) {
+ setIsSource(value);
+ }
+ };
+
+ const handleLlmProfileChange = (fieldName, value) => {
+ handleStateUpdate(fieldName, value);
+ const body = {
+ [fieldName]: value,
+ };
+
+ if (fieldName === fieldNames.SUMMARIZE_CONTEXT && !value) {
+ body[fieldNames.SUMMARIZE_AS_SOURCE] = false;
+ handleStateUpdate(fieldNames.SUMMARIZE_AS_SOURCE, false);
+ }
+
+ handleUpdateTool(body)
+ .then(() => {
+ setAlertDetails({
+ type: "success",
+ content: "Successfully updated the LLM profile",
+ });
+ })
+ .catch((err) => {
+ setAlertDetails(
+ handleException(err, "Failed to update the LLM profile")
+ );
+ });
+ };
+
+ const onSearchDebounce = useCallback(
+ debounce((event) => {
+ handleLlmProfileChange(event.target.name, event.target.value);
+ }, 1000),
+ []
+ );
+
+ return (
+ setOpen(null)}
+ maskClosable={false}
+ centered
+ footer={null}
+ >
+
+
+
+ Summarize Manager
+
+
+
+
+
+
+
+
+
+
+ handleLlmProfileChange(fieldNames.SUMMARIZE_CONTEXT, value)
+ }
+ />
+ Summarize Context
+
+
+
+
+
+
+ handleLlmProfileChange(
+ fieldNames.SUMMARIZE_AS_SOURCE,
+ e.target.checked
+ )
+ }
+ />
+ Use summarize context as source
+
+
+
+
+
+ );
+}
+
+SelectLlmProfileModal.propTypes = {
+ open: PropTypes.bool.isRequired,
+ setOpen: PropTypes.func.isRequired,
+ llmItems: PropTypes.array.isRequired,
+ setBtnText: PropTypes.func.isRequired,
+ handleUpdateTool: PropTypes.func.isRequired,
+};
+
+export { SelectLlmProfileModal };
diff --git a/frontend/src/components/custom-tools/text-viewer/TextViewer.jsx b/frontend/src/components/custom-tools/text-viewer/TextViewer.jsx
new file mode 100644
index 000000000..a68fd006f
--- /dev/null
+++ b/frontend/src/components/custom-tools/text-viewer/TextViewer.jsx
@@ -0,0 +1,24 @@
+import { Typography } from "antd";
+import PropTypes from "prop-types";
+import React from "react";
+
+function TextViewer({ text }) {
+ return (
+
+
+ {text.split("\n").map((line) => (
+
+ {line}
+
+
+ ))}
+
+
+ );
+}
+
+TextViewer.propTypes = {
+ text: PropTypes.string,
+};
+
+export { TextViewer };
diff --git a/frontend/src/components/custom-tools/tool-ide/ToolIde.jsx b/frontend/src/components/custom-tools/tool-ide/ToolIde.jsx
index a7bf58a8a..bc9a22347 100644
--- a/frontend/src/components/custom-tools/tool-ide/ToolIde.jsx
+++ b/frontend/src/components/custom-tools/tool-ide/ToolIde.jsx
@@ -1,6 +1,6 @@
import { FullscreenExitOutlined, FullscreenOutlined } from "@ant-design/icons";
import { Col, Collapse, Modal, Row } from "antd";
-import { useEffect, useState } from "react";
+import { useState } from "react";
import { handleException } from "../../../helpers/GetStaticData";
import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate";
@@ -14,7 +14,6 @@ import { DisplayLogs } from "../display-logs/DisplayLogs";
import { DocumentManager } from "../document-manager/DocumentManager";
import { GenerateIndex } from "../generate-index/GenerateIndex";
import { Header } from "../header/Header";
-import { ManageDocsModal } from "../manage-docs-modal/ManageDocsModal";
import { ManageLlmProfilesModal } from "../manage-llm-profiles-modal/ManageLlmProfilesModal";
import { ToolsMain } from "../tools-main/ToolsMain";
import "./ToolIde.css";
@@ -23,19 +22,14 @@ function ToolIde() {
const [showLogsModal, setShowLogsModal] = useState(false);
const [activeKey, setActiveKey] = useState([]);
const [openCusSynonymsModal, setOpenCusSynonymsModal] = useState(false);
- const [openManageDocsModal, setOpenManageDocsModal] = useState(false);
const [openManageLlmModal, setOpenManageLlmModal] = useState(false);
const [openAddLlmModal, setOpenAddLlmModal] = useState(false);
const [editLlmProfileId, setEditLlmProfileId] = useState(null);
const [isGenerateIndexOpen, setIsGenerateIndexOpen] = useState(false);
const [isGeneratingIndex, setIsGeneratingIndex] = useState(false);
const [generateIndexResult, setGenerateIndexResult] = useState("");
- const {
- details,
- updateCustomTool,
- setDefaultCustomTool,
- disableLlmOrDocChange,
- } = useCustomToolStore();
+ const { details, updateCustomTool, disableLlmOrDocChange, selectedDoc } =
+ useCustomToolStore();
const { sessionDetails } = useSessionStore();
const { setAlertDetails } = useAlertStore();
const axiosPrivate = useAxiosPrivate();
@@ -72,12 +66,6 @@ function ToolIde() {
},
];
- useEffect(() => {
- return () => {
- setDefaultCustomTool();
- };
- }, []);
-
const handleCollapse = (keys) => {
setActiveKey(keys);
};
@@ -89,7 +77,7 @@ function ToolIde() {
setIsGenerateIndexOpen(isOpen);
};
- const generateIndex = (fileName) => {
+ const generateIndex = async (fileName) => {
setIsGenerateIndexOpen(true);
setIsGeneratingIndex(true);
@@ -107,7 +95,7 @@ function ToolIde() {
data: body,
};
- axiosPrivate(requestOptions)
+ return axiosPrivate(requestOptions)
.then(() => {
setGenerateIndexResult("SUCCESS");
})
@@ -149,18 +137,28 @@ function ToolIde() {
return;
}
+ const prevSelectedDoc = selectedDoc;
+ const data = {
+ selectedDoc: docName,
+ };
+ updateCustomTool(data);
+
const body = {
output: docName,
};
handleUpdateTool(body)
- .then((res) => {
- const data = {
- selectedDoc: docName,
- };
- updateCustomTool(data);
+ .then(() => {
+ setAlertDetails({
+ type: "success",
+ content: "Document changed successfully",
+ });
})
.catch((err) => {
+ const revertSelectedDoc = {
+ selectedDoc: prevSelectedDoc,
+ };
+ updateCustomTool(revertSelectedDoc);
setAlertDetails(handleException(err, "Failed to select the document"));
});
};
@@ -170,7 +168,6 @@ function ToolIde() {
@@ -222,13 +219,6 @@ function ToolIde() {
open={openCusSynonymsModal}
setOpen={setOpenCusSynonymsModal}
/>
-
)}
- {activeKey === "2" && }
+ {activeKey === "2" && }
diff --git a/frontend/src/components/helpers/custom-tools/CustomToolsHelper.js b/frontend/src/components/helpers/custom-tools/CustomToolsHelper.js
index 8f5f1dd92..ec3c3d5ac 100644
--- a/frontend/src/components/helpers/custom-tools/CustomToolsHelper.js
+++ b/frontend/src/components/helpers/custom-tools/CustomToolsHelper.js
@@ -14,7 +14,7 @@ function CustomToolsHelper() {
const [logId, setLogId] = useState(null);
const { id } = useParams();
const { sessionDetails } = useSessionStore();
- const { updateCustomTool } = useCustomToolStore();
+ const { updateCustomTool, setDefaultCustomTool } = useCustomToolStore();
const { setAlertDetails } = useAlertStore();
const navigate = useNavigate();
const axiosPrivate = useAxiosPrivate();
@@ -49,7 +49,11 @@ function CustomToolsHelper() {
return handleApiRequest(reqOpsDocs);
})
.then((res) => {
- const data = res?.data?.map((item) => item?.name);
+ const data = (res?.data || [])
+ ?.filter(
+ (item) => item?.name !== "extract" && item?.name !== "summarize"
+ )
+ ?.map((item) => item?.name);
updatedCusTool["listOfDocs"] = data;
const reqOpsDropdownItems = {
@@ -83,6 +87,12 @@ function CustomToolsHelper() {
});
}, [id]);
+ useEffect(() => {
+ return () => {
+ setDefaultCustomTool();
+ };
+ }, []);
+
const handleApiRequest = async (requestOptions) => {
return axiosPrivate(requestOptions)
.then((res) => res)
diff --git a/frontend/src/helpers/GetStaticData.js b/frontend/src/helpers/GetStaticData.js
index 996ed44bc..be060d76a 100644
--- a/frontend/src/helpers/GetStaticData.js
+++ b/frontend/src/helpers/GetStaticData.js
@@ -244,9 +244,13 @@ const getTimeForLogs = () => {
const handleException = (err, errMessage) => {
if (err?.response?.data?.type === "validation_error") {
// Handle validation errors
- } else if (
- ["client_error", "server_error"].includes(err?.response?.data?.type)
- ) {
+ return {
+ type: "error",
+ content: errMessage || "Something went wrong",
+ };
+ }
+
+ if (["client_error", "server_error"].includes(err?.response?.data?.type)) {
// Handle client_error, server_error
return {
type: "error",
@@ -255,11 +259,62 @@ const handleException = (err, errMessage) => {
errMessage ||
"Something went wrong",
};
- } else {
- return {
- type: "error",
- content: err?.message,
- };
+ }
+
+ return {
+ type: "error",
+ content: err?.message || errMessage,
+ };
+};
+
+const base64toBlob = (data) => {
+ const bytes = atob(data);
+ let length = bytes.length;
+ const out = new Uint8Array(length);
+
+ while (length--) {
+ out[length] = bytes.charCodeAt(length);
+ }
+
+ return new Blob([out], { type: "application/pdf" });
+};
+
+const removeFileExtension = (fileName) => {
+ if (!fileName) {
+ return "";
+ }
+ const fileNameSplit = fileName.split(".");
+ const fileNameSplitLength = fileNameSplit.length;
+ const modFileName = fileNameSplit.slice(0, fileNameSplitLength - 1);
+ return modFileName.join(".");
+};
+
+const isJson = (text) => {
+ try {
+ if (typeof text === "object") {
+ return true;
+ }
+
+ if (typeof text === "string") {
+ const json = JSON.parse(text);
+ return typeof json === "object";
+ }
+ return false;
+ } catch (err) {
+ return false;
+ }
+};
+
+const displayPromptResult = (output) => {
+ try {
+ if (isJson(output)) {
+ return JSON.stringify(JSON.parse(output), null, 4);
+ }
+
+ const outputParsed = JSON.parse(output);
+ return outputParsed;
+ } catch (err) {
+ return output;
}
};
@@ -287,4 +342,8 @@ export {
toolIdeOutput,
wfExecutionTypes,
workflowStatus,
+ base64toBlob,
+ removeFileExtension,
+ isJson,
+ displayPromptResult,
};
diff --git a/frontend/src/index.css b/frontend/src/index.css
index d64e54b4d..471addb68 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,8 +1,10 @@
@import "variables.css";
-html, #root {
+html,
+#root {
height: 100%;
}
+
body {
font-family: var(--font-family) !important;
-webkit-font-smoothing: antialiased;
@@ -20,6 +22,7 @@ body {
align-items: center;
justify-content: center;
}
+
/* Custom scroll bar css starts */
::-webkit-scrollbar {
width: 3px;
@@ -47,6 +50,7 @@ body {
::-webkit-scrollbar-corner {
background: none;
}
+
/* Custom scroll bar css ends */
/* Justify content to the right */
@@ -71,6 +75,14 @@ body {
font-size: 12px;
}
+.font-size-16 {
+ font-size: 16px;
+}
+
+.font-weight-600 {
+ font-weight: 600px;
+}
+
.display-flex-center-h-and-v {
height: 100%;
display: flex;
@@ -101,6 +113,10 @@ body {
width: 100%;
}
+.height-100 {
+ height: 100%;
+}
+
.cur-pointer {
cursor: pointer;
}
@@ -108,4 +124,4 @@ body {
.modal-header {
font-size: 16px;
font-weight: bold;
-}
+}
\ No newline at end of file
diff --git a/frontend/src/pages/OutputAnalyzerPage.jsx b/frontend/src/pages/OutputAnalyzerPage.jsx
new file mode 100644
index 000000000..af89f315d
--- /dev/null
+++ b/frontend/src/pages/OutputAnalyzerPage.jsx
@@ -0,0 +1,7 @@
+import { OutputAnalyzerList } from "../components/custom-tools/output-analyzer-list/OutputAnalyzerList";
+
+function OutputAnalyzerPage() {
+ return ;
+}
+
+export { OutputAnalyzerPage };
diff --git a/frontend/src/routes/Router.jsx b/frontend/src/routes/Router.jsx
index f52102ba2..68635eb51 100644
--- a/frontend/src/routes/Router.jsx
+++ b/frontend/src/routes/Router.jsx
@@ -27,6 +27,7 @@ import { ToolIdePage } from "../pages/ToolIdePage.jsx";
import { ToolsSettingsPage } from "../pages/ToolsSettingsPage.jsx";
import { UsersPage } from "../pages/UsersPage.jsx";
import { WorkflowsPage } from "../pages/WorkflowsPage.jsx";
+import { OutputAnalyzerPage } from "../pages/OutputAnalyzerPage.jsx";
function Router() {
return (
@@ -68,6 +69,10 @@ function Router() {
} />
}>
} />
+ }
+ />