mirror of
https://github.com/langgenius/dify.git
synced 2026-01-31 08:57:01 +08:00
Compare commits
31 Commits
fix/agent-
...
feat/knowl
| Author | SHA1 | Date | |
|---|---|---|---|
| 3107ec878a | |||
| 7d5fcfef4c | |||
| e862ab0def | |||
| 15f80a72b8 | |||
| 1a7de23864 | |||
| 10fccd2b3f | |||
| b568947e00 | |||
| 7dcbb75839 | |||
| ffdfcdd4a4 | |||
| f4604bf6d0 | |||
| 3a72b76c32 | |||
| 49dd77e219 | |||
| b9f223d9d4 | |||
| 3c4da03575 | |||
| 7692476097 | |||
| 428438eeca | |||
| b7c546f2ad | |||
| 0ed892a747 | |||
| 5e2bd407a8 | |||
| a4668e0ffc | |||
| 1ca79ea729 | |||
| ebb6de5f52 | |||
| 2adc704463 | |||
| b74f1b3c07 | |||
| f60e650400 | |||
| 83d0142641 | |||
| 56c7f49625 | |||
| 7c1d842cfe | |||
| 2ea3b64a45 | |||
| 824f8d8994 | |||
| 31c17e6378 |
14
.github/workflows/build-push.yml
vendored
14
.github/workflows/build-push.yml
vendored
@ -6,7 +6,6 @@ on:
|
||||
- "main"
|
||||
- "deploy/dev"
|
||||
- "plugins/beta"
|
||||
- "dev/plugin-deploy"
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
@ -141,16 +140,3 @@ jobs:
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env[matrix.image_name_env] }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
- name: print context var
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: deploy pod in plugin env
|
||||
if: github.ref == 'refs/heads/dev/plugin-deploy'
|
||||
env:
|
||||
IMAGEHASH: ${{ github.sha }}
|
||||
APICMD: "${{ secrets.PLUGIN_CD_API_CURL }}"
|
||||
WEBCMD: "${{ secrets.PLUGIN_CD_WEB_CURL }}"
|
||||
run: |
|
||||
bash -c "${APICMD/yourNewVersion/$IMAGEHASH}"
|
||||
bash -c "${WEBCMD/yourNewVersion/$IMAGEHASH}"
|
||||
|
||||
23
.github/workflows/deploy-plugin-dev.yml
vendored
23
.github/workflows/deploy-plugin-dev.yml
vendored
@ -1,23 +0,0 @@
|
||||
name: Deploy Plugin Dev
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Build and Push API & Web"]
|
||||
branches:
|
||||
- "dev/plugin-deploy"
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
steps:
|
||||
- name: Deploy to server
|
||||
uses: appleboy/ssh-action@v0.1.8
|
||||
with:
|
||||
host: ${{ secrets.SSH_HOST }}
|
||||
username: ${{ secrets.SSH_USER }}
|
||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
script: "echo 123"
|
||||
@ -384,6 +384,7 @@ class AdvancedChatAppGenerateTaskPipeline:
|
||||
task_id=self._application_generate_entity.task_id,
|
||||
workflow_node_execution=workflow_node_execution,
|
||||
)
|
||||
session.commit()
|
||||
|
||||
if node_finish_resp:
|
||||
yield node_finish_resp
|
||||
|
||||
@ -387,7 +387,6 @@ class WorkflowBasedAppRunner(AppRunner):
|
||||
status=event.status,
|
||||
data=event.data,
|
||||
metadata=event.metadata,
|
||||
node_id=event.node_id,
|
||||
)
|
||||
)
|
||||
elif isinstance(event, ParallelBranchRunStartedEvent):
|
||||
|
||||
@ -331,7 +331,6 @@ class QueueAgentLogEvent(AppQueueEvent):
|
||||
status: str
|
||||
data: Mapping[str, Any]
|
||||
metadata: Optional[Mapping[str, Any]] = None
|
||||
node_id: str
|
||||
|
||||
|
||||
class QueueNodeRetryEvent(QueueNodeStartedEvent):
|
||||
|
||||
@ -719,7 +719,6 @@ class AgentLogStreamResponse(StreamResponse):
|
||||
status: str
|
||||
data: Mapping[str, Any]
|
||||
metadata: Optional[Mapping[str, Any]] = None
|
||||
node_id: str
|
||||
|
||||
event: StreamEvent = StreamEvent.AGENT_LOG
|
||||
data: Data
|
||||
|
||||
@ -864,6 +864,5 @@ class WorkflowCycleManage:
|
||||
status=event.status,
|
||||
data=event.data,
|
||||
metadata=event.metadata,
|
||||
node_id=event.node_id,
|
||||
),
|
||||
)
|
||||
|
||||
@ -65,8 +65,7 @@ def make_request(method, url, max_retries=SSRF_DEFAULT_MAX_RETRIES, **kwargs):
|
||||
retries += 1
|
||||
if retries <= max_retries:
|
||||
time.sleep(BACKOFF_FACTOR * (2 ** (retries - 1)))
|
||||
raise MaxRetriesExceededError(
|
||||
f"Reached maximum retries ({max_retries}) for URL {url}")
|
||||
raise MaxRetriesExceededError(f"Reached maximum retries ({max_retries}) for URL {url}")
|
||||
|
||||
|
||||
def get(url, max_retries=SSRF_DEFAULT_MAX_RETRIES, **kwargs):
|
||||
|
||||
@ -20,6 +20,7 @@ from core.model_runtime.model_providers.__base.text_embedding_model import TextE
|
||||
from core.model_runtime.model_providers.__base.tts_model import TTSModel
|
||||
from core.model_runtime.schema_validators.model_credential_schema_validator import ModelCredentialSchemaValidator
|
||||
from core.model_runtime.schema_validators.provider_credential_schema_validator import ProviderCredentialSchemaValidator
|
||||
from core.plugin.entities.plugin import ModelProviderID
|
||||
from core.plugin.entities.plugin_daemon import PluginModelProviderEntity
|
||||
from core.plugin.manager.asset import PluginAssetManager
|
||||
from core.plugin.manager.model import PluginModelManager
|
||||
@ -112,6 +113,9 @@ class ModelProviderFactory:
|
||||
:param provider: provider name
|
||||
:return: provider schema
|
||||
"""
|
||||
if "/" not in provider:
|
||||
provider = str(ModelProviderID(provider))
|
||||
|
||||
# fetch plugin model providers
|
||||
plugin_model_provider_entities = self.get_plugin_model_providers()
|
||||
|
||||
@ -363,4 +367,4 @@ class ModelProviderFactory:
|
||||
plugin_id = "/".join(provider.split("/")[:-1])
|
||||
provider_name = provider.split("/")[-1]
|
||||
|
||||
return plugin_id, provider_name
|
||||
return str(plugin_id), provider_name
|
||||
|
||||
@ -169,6 +169,21 @@ class GenericProviderID:
|
||||
return f"{self.organization}/{self.plugin_name}"
|
||||
|
||||
|
||||
class ModelProviderID(GenericProviderID):
|
||||
def __init__(self, value: str, is_hardcoded: bool = False) -> None:
|
||||
super().__init__(value, is_hardcoded)
|
||||
if self.organization == "langgenius" and self.provider_name == "google":
|
||||
self.provider_name = "gemini"
|
||||
|
||||
|
||||
class ToolProviderID(GenericProviderID):
|
||||
def __init__(self, value: str, is_hardcoded: bool = False) -> None:
|
||||
super().__init__(value, is_hardcoded)
|
||||
if self.organization == "langgenius":
|
||||
if self.provider_name in ["jina", "siliconflow"]:
|
||||
self.provider_name = f"{self.provider_name}_tool"
|
||||
|
||||
|
||||
class PluginDependency(BaseModel):
|
||||
class Type(enum.StrEnum):
|
||||
Github = PluginInstallationSource.Github.value
|
||||
@ -197,9 +212,3 @@ class PluginDependency(BaseModel):
|
||||
|
||||
type: Type
|
||||
value: Github | Marketplace | Package
|
||||
current_identifier: Optional[str] = None
|
||||
|
||||
|
||||
class MissingPluginDependency(BaseModel):
|
||||
plugin_unique_identifier: str
|
||||
current_identifier: Optional[str] = None
|
||||
|
||||
@ -3,7 +3,6 @@ from collections.abc import Sequence
|
||||
from core.plugin.entities.bundle import PluginBundleDependency
|
||||
from core.plugin.entities.plugin import (
|
||||
GenericProviderID,
|
||||
MissingPluginDependency,
|
||||
PluginDeclaration,
|
||||
PluginEntity,
|
||||
PluginInstallation,
|
||||
@ -176,16 +175,14 @@ class PluginInstallationManager(BasePluginManager):
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
def fetch_missing_dependencies(
|
||||
self, tenant_id: str, plugin_unique_identifiers: list[str]
|
||||
) -> list[MissingPluginDependency]:
|
||||
def fetch_missing_dependencies(self, tenant_id: str, plugin_unique_identifiers: list[str]) -> list[str]:
|
||||
"""
|
||||
Fetch missing dependencies
|
||||
"""
|
||||
return self._request_with_plugin_daemon_response(
|
||||
"POST",
|
||||
f"plugin/{tenant_id}/management/installation/missing",
|
||||
list[MissingPluginDependency],
|
||||
list[str],
|
||||
data={"plugin_unique_identifiers": plugin_unique_identifiers},
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
@ -160,8 +160,8 @@ class ToolManager:
|
||||
"""
|
||||
get the tool runtime
|
||||
|
||||
:param provider_type: the type of the provider
|
||||
:param provider_name: the name of the provider
|
||||
:param provider_type: the type of the provider
|
||||
:param provider_name: the name of the provider
|
||||
:param tool_name: the name of the tool
|
||||
|
||||
:return: the tool
|
||||
|
||||
@ -207,7 +207,6 @@ class AgentLogEvent(BaseAgentEvent):
|
||||
status: str = Field(..., description="status")
|
||||
data: Mapping[str, Any] = Field(..., description="data")
|
||||
metadata: Optional[Mapping[str, Any]] = Field(default=None, description="metadata")
|
||||
node_id: str = Field(..., description="agent node id")
|
||||
|
||||
|
||||
InNodeEvent = BaseNodeEvent | BaseParallelBranchEvent | BaseIterationEvent | BaseAgentEvent
|
||||
|
||||
@ -18,7 +18,6 @@ from core.workflow.entities.node_entities import AgentNodeStrategyInit, NodeRunM
|
||||
from core.workflow.entities.variable_pool import VariablePool, VariableValue
|
||||
from core.workflow.graph_engine.condition_handlers.condition_manager import ConditionManager
|
||||
from core.workflow.graph_engine.entities.event import (
|
||||
BaseAgentEvent,
|
||||
BaseIterationEvent,
|
||||
GraphEngineEvent,
|
||||
GraphRunFailedEvent,
|
||||
@ -502,7 +501,7 @@ class GraphEngine:
|
||||
break
|
||||
|
||||
yield event
|
||||
if not isinstance(event, BaseAgentEvent) and event.parallel_id == parallel_id:
|
||||
if event.parallel_id == parallel_id:
|
||||
if isinstance(event, ParallelBranchRunSucceededEvent):
|
||||
succeeded_count += 1
|
||||
if succeeded_count == len(futures):
|
||||
|
||||
@ -8,12 +8,12 @@ from core.model_manager import ModelManager
|
||||
from core.model_runtime.entities.model_entities import ModelType
|
||||
from core.plugin.manager.exc import PluginDaemonClientSideError
|
||||
from core.plugin.manager.plugin import PluginInstallationManager
|
||||
from core.tools.entities.tool_entities import ToolParameter, ToolProviderType
|
||||
from core.tools.entities.tool_entities import ToolProviderType
|
||||
from core.tools.tool_manager import ToolManager
|
||||
from core.workflow.entities.node_entities import NodeRunResult
|
||||
from core.workflow.entities.variable_pool import VariablePool
|
||||
from core.workflow.enums import SystemVariableKey
|
||||
from core.workflow.nodes.agent.entities import AgentNodeData, ParamsAutoGenerated
|
||||
from core.workflow.nodes.agent.entities import AgentNodeData
|
||||
from core.workflow.nodes.base.entities import BaseNodeData
|
||||
from core.workflow.nodes.enums import NodeType
|
||||
from core.workflow.nodes.event.event import RunCompletedEvent
|
||||
@ -156,37 +156,16 @@ class AgentNode(ToolNode):
|
||||
value = cast(list[dict[str, Any]], value)
|
||||
value = [tool for tool in value if tool.get("enabled", False)]
|
||||
|
||||
for tool in value:
|
||||
if "schemas" in tool:
|
||||
tool.pop("schemas")
|
||||
parameters = tool.get("parameters", {})
|
||||
if all(isinstance(v, dict) for _, v in parameters.items()):
|
||||
params = {}
|
||||
for key, param in parameters.items():
|
||||
if param.get("auto", ParamsAutoGenerated.OPEN.value) == ParamsAutoGenerated.CLOSE.value:
|
||||
params[key] = param.get("value", {}).get("value", "")
|
||||
else:
|
||||
params[key] = None
|
||||
parameters = params
|
||||
tool["settings"] = {k: v.get("value", None) for k, v in tool.get("settings", {}).items()}
|
||||
tool["parameters"] = parameters
|
||||
|
||||
if not for_log:
|
||||
if parameter.type == "array[tools]":
|
||||
value = cast(list[dict[str, Any]], value)
|
||||
tool_value = []
|
||||
for tool in value:
|
||||
provider_type = ToolProviderType(tool.get("type", ToolProviderType.BUILT_IN.value))
|
||||
setting_params = tool.get("settings", {})
|
||||
parameters = tool.get("parameters", {})
|
||||
manual_input_params = [key for key, value in parameters.items() if value is not None]
|
||||
|
||||
parameters = {**parameters, **setting_params}
|
||||
entity = AgentToolEntity(
|
||||
provider_id=tool.get("provider_name", ""),
|
||||
provider_type=provider_type,
|
||||
provider_type=ToolProviderType.BUILT_IN,
|
||||
tool_name=tool.get("tool_name", ""),
|
||||
tool_parameters=parameters,
|
||||
tool_parameters=tool.get("parameters", {}),
|
||||
plugin_unique_identifier=tool.get("plugin_unique_identifier", None),
|
||||
)
|
||||
|
||||
@ -199,27 +178,13 @@ class AgentNode(ToolNode):
|
||||
tool_runtime.entity.description.llm = (
|
||||
extra.get("descrption", "") or tool_runtime.entity.description.llm
|
||||
)
|
||||
for params in tool_runtime.entity.parameters:
|
||||
params.form = (
|
||||
ToolParameter.ToolParameterForm.FORM
|
||||
if params.name in manual_input_params
|
||||
else params.form
|
||||
)
|
||||
if tool_runtime.entity.parameters:
|
||||
manual_input_value = {
|
||||
key: value for key, value in parameters.items() if key in manual_input_params
|
||||
|
||||
tool_value.append(
|
||||
{
|
||||
**tool_runtime.entity.model_dump(mode="json"),
|
||||
"runtime_parameters": tool_runtime.runtime.runtime_parameters,
|
||||
}
|
||||
runtime_parameters = {
|
||||
**tool_runtime.runtime.runtime_parameters,
|
||||
**manual_input_value,
|
||||
}
|
||||
tool_value.append(
|
||||
{
|
||||
**tool_runtime.entity.model_dump(mode="json"),
|
||||
"runtime_parameters": runtime_parameters,
|
||||
"provider_type": provider_type.value,
|
||||
}
|
||||
)
|
||||
)
|
||||
value = tool_value
|
||||
if parameter.type == "model-selector":
|
||||
value = cast(dict[str, Any], value)
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Literal, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
@ -17,8 +16,3 @@ class AgentNodeData(BaseNodeData):
|
||||
type: Literal["mixed", "variable", "constant"]
|
||||
|
||||
agent_parameters: dict[str, AgentInput]
|
||||
|
||||
|
||||
class ParamsAutoGenerated(Enum):
|
||||
CLOSE = 0
|
||||
OPEN = 1
|
||||
|
||||
@ -64,7 +64,7 @@ class EndStreamGeneratorRouter:
|
||||
node_type = node.get("data", {}).get("type")
|
||||
if (
|
||||
variable_selector.value_selector not in value_selectors
|
||||
and (node_type in (NodeType.LLM.value, NodeType.AGENT.value))
|
||||
and node_type == NodeType.LLM.value
|
||||
and variable_selector.value_selector[1] == "text"
|
||||
):
|
||||
value_selectors.append(list(variable_selector.value_selector))
|
||||
|
||||
@ -44,13 +44,11 @@ class QuestionClassifierNode(LLMNode):
|
||||
variable_pool = self.graph_runtime_state.variable_pool
|
||||
|
||||
# extract variables
|
||||
variable = variable_pool.get(
|
||||
node_data.query_variable_selector) if node_data.query_variable_selector else None
|
||||
variable = variable_pool.get(node_data.query_variable_selector) if node_data.query_variable_selector else None
|
||||
query = variable.value if variable else None
|
||||
variables = {"query": query}
|
||||
# fetch model config
|
||||
model_instance, model_config = self._fetch_model_config(
|
||||
node_data.model)
|
||||
model_instance, model_config = self._fetch_model_config(node_data.model)
|
||||
# fetch memory
|
||||
memory = self._fetch_memory(
|
||||
node_data_memory=node_data.memory,
|
||||
@ -58,8 +56,7 @@ class QuestionClassifierNode(LLMNode):
|
||||
)
|
||||
# fetch instruction
|
||||
node_data.instruction = node_data.instruction or ""
|
||||
node_data.instruction = variable_pool.convert_template(
|
||||
node_data.instruction).text
|
||||
node_data.instruction = variable_pool.convert_template(node_data.instruction).text
|
||||
|
||||
files = (
|
||||
self._fetch_files(
|
||||
@ -181,15 +178,12 @@ class QuestionClassifierNode(LLMNode):
|
||||
variable_mapping = {"query": node_data.query_variable_selector}
|
||||
variable_selectors = []
|
||||
if node_data.instruction:
|
||||
variable_template_parser = VariableTemplateParser(
|
||||
template=node_data.instruction)
|
||||
variable_selectors.extend(
|
||||
variable_template_parser.extract_variable_selectors())
|
||||
variable_template_parser = VariableTemplateParser(template=node_data.instruction)
|
||||
variable_selectors.extend(variable_template_parser.extract_variable_selectors())
|
||||
for variable_selector in variable_selectors:
|
||||
variable_mapping[variable_selector.variable] = variable_selector.value_selector
|
||||
|
||||
variable_mapping = {node_id + "." + key: value for key,
|
||||
value in variable_mapping.items()}
|
||||
variable_mapping = {node_id + "." + key: value for key, value in variable_mapping.items()}
|
||||
|
||||
return variable_mapping
|
||||
|
||||
@ -210,8 +204,7 @@ class QuestionClassifierNode(LLMNode):
|
||||
context: Optional[str],
|
||||
) -> int:
|
||||
prompt_transform = AdvancedPromptTransform(with_variable_tmpl=True)
|
||||
prompt_template = self._get_prompt_template(
|
||||
node_data, query, None, 2000)
|
||||
prompt_template = self._get_prompt_template(node_data, query, None, 2000)
|
||||
prompt_messages = prompt_transform.get_prompt(
|
||||
prompt_template=prompt_template,
|
||||
inputs={},
|
||||
@ -224,15 +217,13 @@ class QuestionClassifierNode(LLMNode):
|
||||
)
|
||||
rest_tokens = 2000
|
||||
|
||||
model_context_tokens = model_config.model_schema.model_properties.get(
|
||||
ModelPropertyKey.CONTEXT_SIZE)
|
||||
model_context_tokens = model_config.model_schema.model_properties.get(ModelPropertyKey.CONTEXT_SIZE)
|
||||
if model_context_tokens:
|
||||
model_instance = ModelInstance(
|
||||
provider_model_bundle=model_config.provider_model_bundle, model=model_config.model
|
||||
)
|
||||
|
||||
curr_message_tokens = model_instance.get_llm_num_tokens(
|
||||
prompt_messages)
|
||||
curr_message_tokens = model_instance.get_llm_num_tokens(prompt_messages)
|
||||
|
||||
max_tokens = 0
|
||||
for parameter_rule in model_config.model_schema.parameter_rules:
|
||||
@ -273,8 +264,7 @@ class QuestionClassifierNode(LLMNode):
|
||||
prompt_messages: list[LLMNodeChatModelMessage] = []
|
||||
if model_mode == ModelMode.CHAT:
|
||||
system_prompt_messages = LLMNodeChatModelMessage(
|
||||
role=PromptMessageRole.SYSTEM, text=QUESTION_CLASSIFIER_SYSTEM_PROMPT.format(
|
||||
histories=memory_str)
|
||||
role=PromptMessageRole.SYSTEM, text=QUESTION_CLASSIFIER_SYSTEM_PROMPT.format(histories=memory_str)
|
||||
)
|
||||
prompt_messages.append(system_prompt_messages)
|
||||
user_prompt_message_1 = LLMNodeChatModelMessage(
|
||||
@ -315,5 +305,4 @@ class QuestionClassifierNode(LLMNode):
|
||||
)
|
||||
|
||||
else:
|
||||
raise InvalidModelTypeError(
|
||||
f"Model mode {model_mode} not support.")
|
||||
raise InvalidModelTypeError(f"Model mode {model_mode} not support.")
|
||||
|
||||
@ -338,7 +338,6 @@ class ToolNode(BaseNode[ToolNodeData]):
|
||||
data=message.message.data,
|
||||
label=message.message.label,
|
||||
metadata=message.message.metadata,
|
||||
node_id=self.node_id,
|
||||
)
|
||||
|
||||
# check if the agent log is already in the list
|
||||
|
||||
@ -198,7 +198,7 @@ app_site_fields = {
|
||||
"use_icon_as_answer_icon": fields.Boolean,
|
||||
}
|
||||
|
||||
leaked_dependency_fields = {"type": fields.String, "value": fields.Raw, "current_identifier": fields.String}
|
||||
leaked_dependency_fields = {"type": fields.String, "value": fields.Raw}
|
||||
|
||||
app_import_fields = {
|
||||
"id": fields.String,
|
||||
|
||||
@ -748,7 +748,7 @@ class DatasetKeywordTable(db.Model): # type: ignore[name-defined]
|
||||
if keyword_table_text:
|
||||
return json.loads(keyword_table_text.decode("utf-8"), cls=SetDecoder)
|
||||
return None
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logging.exception(f"Failed to load keyword table from file: {file_key}")
|
||||
return None
|
||||
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy.orm import Mapped
|
||||
|
||||
from extensions.ext_database import db
|
||||
from models.base import Base
|
||||
|
||||
from .types import StringUUID
|
||||
|
||||
|
||||
class StagingAccountWhitelist(Base):
|
||||
__tablename__ = "staging_account_whitelists"
|
||||
|
||||
__table_args__ = (
|
||||
db.PrimaryKeyConstraint("id", name="staging_account_whitelist_pkey"),
|
||||
db.Index("account_email_idx", "email"),
|
||||
)
|
||||
|
||||
id: Mapped[str] = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
|
||||
email: Mapped[str] = db.Column(db.String(255), nullable=False)
|
||||
disabled: Mapped[bool] = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
|
||||
created_at: Mapped[datetime] = db.Column(
|
||||
db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)")
|
||||
)
|
||||
updated_at: Mapped[datetime] = db.Column(
|
||||
db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)")
|
||||
)
|
||||
@ -33,7 +33,6 @@ from models.account import (
|
||||
TenantStatus,
|
||||
)
|
||||
from models.model import DifySetup
|
||||
from models.staging import StagingAccountWhitelist
|
||||
from services.billing_service import BillingService
|
||||
from services.errors.account import (
|
||||
AccountAlreadyInTenantError,
|
||||
@ -349,9 +348,6 @@ class AccountService:
|
||||
|
||||
@staticmethod
|
||||
def login(account: Account, *, ip_address: Optional[str] = None) -> TokenPair:
|
||||
if not AccountService.verify_account_whitelist(account.email):
|
||||
raise ValueError("Account is not whitelisted")
|
||||
|
||||
if ip_address:
|
||||
AccountService.update_login_info(account=account, ip_address=ip_address)
|
||||
|
||||
@ -383,9 +379,6 @@ class AccountService:
|
||||
if not account:
|
||||
raise ValueError("Invalid account")
|
||||
|
||||
if not AccountService.verify_account_whitelist(account.email):
|
||||
raise ValueError("Account is not whitelisted")
|
||||
|
||||
# Generate new access token and refresh token
|
||||
new_access_token = AccountService.get_account_jwt_token(account)
|
||||
new_refresh_token = _generate_refresh_token()
|
||||
@ -395,16 +388,6 @@ class AccountService:
|
||||
|
||||
return TokenPair(access_token=new_access_token, refresh_token=new_refresh_token)
|
||||
|
||||
@staticmethod
|
||||
def verify_account_whitelist(email: str) -> bool:
|
||||
with Session(db.engine) as session:
|
||||
return (
|
||||
session.query(StagingAccountWhitelist)
|
||||
.filter(StagingAccountWhitelist.email == email, StagingAccountWhitelist.disabled == False)
|
||||
.first()
|
||||
is not None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def load_logged_in_account(*, account_id: str):
|
||||
return AccountService.load_user(account_id)
|
||||
@ -449,13 +432,9 @@ class AccountService:
|
||||
def send_email_code_login_email(
|
||||
cls, account: Optional[Account] = None, email: Optional[str] = None, language: Optional[str] = "en-US"
|
||||
):
|
||||
if email:
|
||||
if not AccountService.verify_account_whitelist(email):
|
||||
raise ValueError("Account is not whitelisted")
|
||||
elif account:
|
||||
if not AccountService.verify_account_whitelist(account.email):
|
||||
raise ValueError("Account is not whitelisted")
|
||||
|
||||
email = account.email if account else email
|
||||
if email is None:
|
||||
raise ValueError("Email must be provided.")
|
||||
if cls.email_code_login_rate_limiter.is_rate_limited(email):
|
||||
from controllers.console.auth.error import EmailCodeLoginRateLimitExceededError
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import logging
|
||||
import uuid
|
||||
from collections.abc import Mapping
|
||||
from enum import StrEnum
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
@ -257,16 +256,6 @@ class AppDslService:
|
||||
check_dependencies_pending_data = None
|
||||
if dependencies:
|
||||
check_dependencies_pending_data = [PluginDependency.model_validate(d) for d in dependencies]
|
||||
elif imported_version <= "0.1.5":
|
||||
if "workflow" in data:
|
||||
graph = data.get("workflow", {}).get("graph", {})
|
||||
dependencies_list = self._extract_dependencies_from_workflow_graph(graph)
|
||||
else:
|
||||
dependencies_list = self._extract_dependencies_from_model_config(data.get("model_config", {}))
|
||||
|
||||
check_dependencies_pending_data = DependenciesAnalysisService.generate_latest_dependencies(
|
||||
dependencies_list
|
||||
)
|
||||
|
||||
# Create or update app
|
||||
app = self._create_or_update_app(
|
||||
@ -569,7 +558,7 @@ class AppDslService:
|
||||
raise ValueError("Missing app configuration, please check.")
|
||||
|
||||
export_data["model_config"] = app_model_config.to_dict()
|
||||
dependencies = cls._extract_dependencies_from_model_config(app_model_config.to_dict())
|
||||
dependencies = cls._extract_dependencies_from_model_config(app_model_config)
|
||||
export_data["dependencies"] = [
|
||||
jsonable_encoder(d.model_dump())
|
||||
for d in DependenciesAnalysisService.generate_dependencies(
|
||||
@ -585,16 +574,6 @@ class AppDslService:
|
||||
:return: dependencies list format like ["langgenius/google"]
|
||||
"""
|
||||
graph = workflow.graph_dict
|
||||
dependencies = cls._extract_dependencies_from_workflow_graph(graph)
|
||||
return dependencies
|
||||
|
||||
@classmethod
|
||||
def _extract_dependencies_from_workflow_graph(cls, graph: Mapping) -> list[str]:
|
||||
"""
|
||||
Extract dependencies from workflow graph
|
||||
:param graph: Workflow graph
|
||||
:return: dependencies list format like ["langgenius/google"]
|
||||
"""
|
||||
dependencies = []
|
||||
for node in graph.get("nodes", []):
|
||||
try:
|
||||
@ -668,24 +647,24 @@ class AppDslService:
|
||||
return dependencies
|
||||
|
||||
@classmethod
|
||||
def _extract_dependencies_from_model_config(cls, model_config: Mapping) -> list[str]:
|
||||
def _extract_dependencies_from_model_config(cls, model_config: AppModelConfig) -> list[str]:
|
||||
"""
|
||||
Extract dependencies from model config
|
||||
:param model_config: model config dict
|
||||
:return: dependencies list format like ["langgenius/google"]
|
||||
:param model_config: AppModelConfig instance
|
||||
:return: dependencies list format like ["langgenius/google:1.0.0@abcdef1234567890"]
|
||||
"""
|
||||
dependencies = []
|
||||
|
||||
try:
|
||||
# completion model
|
||||
model_dict = model_config.get("model", {})
|
||||
model_dict = model_config.model_dict
|
||||
if model_dict:
|
||||
dependencies.append(
|
||||
DependenciesAnalysisService.analyze_model_provider_dependency(model_dict.get("provider", ""))
|
||||
)
|
||||
|
||||
# reranking model
|
||||
dataset_configs = model_config.get("dataset_configs", {})
|
||||
dataset_configs = model_config.dataset_configs_dict
|
||||
if dataset_configs:
|
||||
for dataset_config in dataset_configs.get("datasets", {}).get("datasets", []):
|
||||
if dataset_config.get("reranking_model"):
|
||||
@ -698,7 +677,7 @@ class AppDslService:
|
||||
)
|
||||
|
||||
# tools
|
||||
agent_configs = model_config.get("agent_mode", {})
|
||||
agent_configs = model_config.agent_mode_dict
|
||||
if agent_configs:
|
||||
for agent_config in agent_configs.get("tools", []):
|
||||
dependencies.append(
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
from core.helper import marketplace
|
||||
from core.plugin.entities.plugin import GenericProviderID, PluginDependency, PluginInstallationSource
|
||||
from core.plugin.manager.plugin import PluginInstallationManager
|
||||
|
||||
@ -13,8 +12,6 @@ class DependenciesAnalysisService:
|
||||
"""
|
||||
try:
|
||||
tool_provider_id = GenericProviderID(tool_id)
|
||||
if tool_id in ["jina", "siliconflow"]:
|
||||
tool_provider_id.plugin_name = tool_provider_id.plugin_name + "_tool"
|
||||
return tool_provider_id.plugin_id
|
||||
except Exception as e:
|
||||
raise e
|
||||
@ -28,9 +25,6 @@ class DependenciesAnalysisService:
|
||||
"""
|
||||
try:
|
||||
generic_provider_id = GenericProviderID(model_provider_id)
|
||||
if model_provider_id == "google":
|
||||
generic_provider_id.plugin_name = "gemini"
|
||||
|
||||
return generic_provider_id.plugin_id
|
||||
except Exception as e:
|
||||
raise e
|
||||
@ -45,22 +39,15 @@ class DependenciesAnalysisService:
|
||||
required_plugin_unique_identifiers.append(dependency.value.plugin_unique_identifier)
|
||||
|
||||
manager = PluginInstallationManager()
|
||||
|
||||
# get leaked dependencies
|
||||
missing_plugins = manager.fetch_missing_dependencies(tenant_id, required_plugin_unique_identifiers)
|
||||
missing_plugin_unique_identifiers = {plugin.plugin_unique_identifier: plugin for plugin in missing_plugins}
|
||||
missing_plugin_unique_identifiers = manager.fetch_missing_dependencies(
|
||||
tenant_id, required_plugin_unique_identifiers
|
||||
)
|
||||
|
||||
leaked_dependencies = []
|
||||
for dependency in dependencies:
|
||||
unique_identifier = dependency.value.plugin_unique_identifier
|
||||
if unique_identifier in missing_plugin_unique_identifiers:
|
||||
leaked_dependencies.append(
|
||||
PluginDependency(
|
||||
type=dependency.type,
|
||||
value=dependency.value,
|
||||
current_identifier=missing_plugin_unique_identifiers[unique_identifier].current_identifier,
|
||||
)
|
||||
)
|
||||
leaked_dependencies.append(dependency)
|
||||
|
||||
return leaked_dependencies
|
||||
|
||||
@ -111,18 +98,3 @@ class DependenciesAnalysisService:
|
||||
raise ValueError(f"Unknown plugin source: {plugin.source}")
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def generate_latest_dependencies(cls, dependencies: list[str]) -> list[PluginDependency]:
|
||||
"""
|
||||
Generate the latest version of dependencies
|
||||
"""
|
||||
dependencies = list(set(dependencies))
|
||||
deps = marketplace.batch_fetch_plugin_manifests(dependencies)
|
||||
return [
|
||||
PluginDependency(
|
||||
type=PluginDependency.Type.Marketplace,
|
||||
value=PluginDependency.Marketplace(marketplace_plugin_unique_identifier=dep.latest_package_identifier),
|
||||
)
|
||||
for dep in deps
|
||||
]
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Mapping, Sequence
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
@ -418,8 +417,6 @@ class PluginMigration:
|
||||
|
||||
logger.info("Uninstall plugins")
|
||||
|
||||
sys.exit(-1)
|
||||
|
||||
# get installation
|
||||
try:
|
||||
installation = manager.list_plugins(fake_tenant_id)
|
||||
|
||||
@ -7,7 +7,7 @@ from sqlalchemy.orm import Session
|
||||
from configs import dify_config
|
||||
from core.helper.position_helper import is_filtered
|
||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from core.plugin.entities.plugin import GenericProviderID
|
||||
from core.plugin.entities.plugin import GenericProviderID, ToolProviderID
|
||||
from core.plugin.manager.exc import PluginDaemonClientSideError
|
||||
from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort
|
||||
from core.tools.entities.api_entities import ToolApiEntity, ToolProviderApiEntity
|
||||
@ -240,10 +240,7 @@ class BuiltinToolManageService:
|
||||
|
||||
# rewrite db_providers
|
||||
for db_provider in db_providers:
|
||||
try:
|
||||
GenericProviderID(db_provider.provider)
|
||||
except Exception:
|
||||
db_provider.provider = f"langgenius/{db_provider.provider}/{db_provider.provider}"
|
||||
db_provider.provider = str(ToolProviderID(db_provider.provider))
|
||||
|
||||
# find provider
|
||||
def find_provider(provider):
|
||||
@ -258,7 +255,7 @@ class BuiltinToolManageService:
|
||||
include_set=dify_config.POSITION_TOOL_INCLUDES_SET, # type: ignore
|
||||
exclude_set=dify_config.POSITION_TOOL_EXCLUDES_SET, # type: ignore
|
||||
data=provider_controller,
|
||||
name_func=lambda x: x.entity.identity.name,
|
||||
name_func=lambda x: x.identity.name,
|
||||
):
|
||||
continue
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ class ToolTransformService:
|
||||
@staticmethod
|
||||
def repack_provider(tenant_id: str, provider: Union[dict, ToolProviderApiEntity]):
|
||||
"""
|
||||
repack provider
|
||||
repack provider
|
||||
|
||||
:param provider: the provider dict
|
||||
"""
|
||||
|
||||
@ -68,8 +68,7 @@ def test_executor_with_json_body_and_object_variable():
|
||||
system_variables={},
|
||||
user_inputs={},
|
||||
)
|
||||
variable_pool.add(["pre_node_id", "object"], {
|
||||
"name": "John Doe", "age": 30, "email": "john@example.com"})
|
||||
variable_pool.add(["pre_node_id", "object"], {"name": "John Doe", "age": 30, "email": "john@example.com"})
|
||||
|
||||
# Prepare the node data
|
||||
node_data = HttpRequestNodeData(
|
||||
@ -124,8 +123,7 @@ def test_executor_with_json_body_and_nested_object_variable():
|
||||
system_variables={},
|
||||
user_inputs={},
|
||||
)
|
||||
variable_pool.add(["pre_node_id", "object"], {
|
||||
"name": "John Doe", "age": 30, "email": "john@example.com"})
|
||||
variable_pool.add(["pre_node_id", "object"], {"name": "John Doe", "age": 30, "email": "john@example.com"})
|
||||
|
||||
# Prepare the node data
|
||||
node_data = HttpRequestNodeData(
|
||||
|
||||
@ -18,14 +18,6 @@ from models.enums import UserFrom
|
||||
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
|
||||
|
||||
|
||||
def test_plain_text_to_dict():
|
||||
assert _plain_text_to_dict("aa\n cc:") == {"aa": "", "cc": ""}
|
||||
assert _plain_text_to_dict("aa:bb\n cc:dd") == {"aa": "bb", "cc": "dd"}
|
||||
assert _plain_text_to_dict("aa:bb\n cc:dd\n") == {"aa": "bb", "cc": "dd"}
|
||||
assert _plain_text_to_dict("aa:bb\n\n cc : dd\n\n") == {
|
||||
"aa": "bb", "cc": "dd"}
|
||||
|
||||
|
||||
def test_http_request_node_binary_file(monkeypatch):
|
||||
data = HttpRequestNodeData(
|
||||
title="test",
|
||||
@ -191,8 +183,7 @@ def test_http_request_node_form_with_file(monkeypatch):
|
||||
|
||||
def attr_checker(*args, **kwargs):
|
||||
assert kwargs["data"] == {"name": "test"}
|
||||
assert kwargs["files"] == {
|
||||
"file": (None, b"test", "application/octet-stream")}
|
||||
assert kwargs["files"] == {"file": (None, b"test", "application/octet-stream")}
|
||||
return httpx.Response(200, content=b"")
|
||||
|
||||
monkeypatch.setattr(
|
||||
|
||||
@ -464,8 +464,6 @@ services:
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
APP_API_URL: ${APP_API_URL:-}
|
||||
MARKETPLACE_API_URL: ${MARKETPLACE_API_URL:-}
|
||||
MARKETPLACE_URL: ${MARKETPLACE_URL:-}
|
||||
SENTRY_DSN: ${WEB_SENTRY_DSN:-}
|
||||
NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
|
||||
TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000}
|
||||
|
||||
@ -20,7 +20,9 @@ import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label'
|
||||
|
||||
// import DatasetMetadataDrawer from '@/app/components/datasets/metadata/dataset-metadata-drawer'
|
||||
// import MetaDataDocument from '@/app/components/datasets/metadata/metadata-document'
|
||||
import EditMetadataBatchModal from '@/app/components/datasets/metadata/edit-metadata-batch/modal'
|
||||
// Services
|
||||
import { fetchDatasetApiBaseUrl } from '@/service/datasets'
|
||||
|
||||
@ -29,6 +31,7 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
||||
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useExternalApiPanel } from '@/context/external-api-panel-context'
|
||||
import { DataType } from '@/app/components/datasets/metadata/types'
|
||||
|
||||
const Container = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -81,8 +84,54 @@ const Container = () => {
|
||||
return router.replace('/apps')
|
||||
}, [currentWorkspace, router])
|
||||
|
||||
const [isBuiltInEnabled, setIsBuiltInEnabled] = useState(false)
|
||||
const [userMetadata, setUserMetadata] = useState([
|
||||
{ id: '1', name: 'name1', type: DataType.string, valueLength: 1 },
|
||||
{ id: '2', name: 'name2', type: DataType.number, valueLength: 2 },
|
||||
{ id: '3', name: 'name3', type: DataType.time, valueLength: 3 },
|
||||
])
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className='grow relative flex flex-col bg-background-body overflow-y-auto scroll-container'>
|
||||
<div className='flex justify-end mt-[300px] mr-[100px]'>
|
||||
{/* <MetaDataDocument /> */}
|
||||
{/* <SelectMetadataModal trigger={<Button className='w-[200px]'>select</Button>} onSave={(data) => { console.log(data) }} />
|
||||
<CreateModal trigger={<Button className='w-[200px]'>add</Button>} hasBack onSave={(data) => { console.log(data) }} />
|
||||
<Button className='flex w-[200px]' size="medium" onClick={() => setShowExternalApiPanel(true)}>
|
||||
Metadata
|
||||
</Button> */}
|
||||
{/* <DatasetMetadataDrawer
|
||||
userMetadata={userMetadata}
|
||||
onChange={setUserMetadata}
|
||||
builtInMetadata={[
|
||||
{ id: '1', name: 'name1', type: DataType.string, valueLength: 1 },
|
||||
{ id: '2', name: 'name2', type: DataType.number, valueLength: 2 },
|
||||
{ id: '3', name: 'name3', type: DataType.time, valueLength: 3 },
|
||||
]}
|
||||
isBuiltInEnabled={isBuiltInEnabled}
|
||||
onIsBuiltInEnabledChange={setIsBuiltInEnabled}
|
||||
onClose={() => { }}
|
||||
/> */}
|
||||
<EditMetadataBatchModal
|
||||
documentNum={20}
|
||||
list={[
|
||||
{
|
||||
id: '1', name: 'name1', type: DataType.string, value: 'aaa',
|
||||
},
|
||||
{
|
||||
id: '2', name: 'name2', type: DataType.number, value: 'ccc', isMultipleValue: true, isUpdated: true,
|
||||
},
|
||||
{
|
||||
id: '2.1', name: 'num v', type: DataType.number, value: 10,
|
||||
},
|
||||
{
|
||||
id: '3', name: 'name3', type: DataType.time, value: '', isUpdated: true, // updateType: UpdateType.delete,
|
||||
},
|
||||
]}
|
||||
onHide={() => { }}
|
||||
onChange={(list, newList, isApplyToAllSelectDocument) => { console.log(list, newList, isApplyToAllSelectDocument) }}
|
||||
/>
|
||||
</div>
|
||||
<div className='sticky top-0 flex justify-between pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'>
|
||||
<TabSliderNew
|
||||
value={activeTab}
|
||||
|
||||
@ -8,7 +8,7 @@ import { logout } from '@/service/common'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
export type IAppSelector = {
|
||||
export interface IAppSelector {
|
||||
isMobile: boolean
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ import { useChatContext } from '@/app/components/base/chat/chat/context'
|
||||
|
||||
const MAX_DEPTH = 3
|
||||
|
||||
export type IGenerationItemProps = {
|
||||
export interface IGenerationItemProps {
|
||||
isWorkflow?: boolean
|
||||
workflowProcessData?: WorkflowProcess
|
||||
className?: string
|
||||
|
||||
@ -53,15 +53,18 @@ export default function Drawer({
|
||||
/>
|
||||
<div className={cn('relative z-50 flex flex-col justify-between bg-components-panel-bg w-full max-w-sm p-6 overflow-hidden text-left align-middle shadow-xl', panelClassname)}>
|
||||
<>
|
||||
{title && <Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-text-primary"
|
||||
>
|
||||
{title}
|
||||
</Dialog.Title>}
|
||||
{showClose && <Dialog.Title className="flex items-center mb-4" as="div">
|
||||
<XMarkIcon className='w-4 h-4 text-text-tertiary' onClick={onClose} />
|
||||
</Dialog.Title>}
|
||||
<div className='flex justify-between'>
|
||||
{title && <Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-text-primary"
|
||||
>
|
||||
{title}
|
||||
</Dialog.Title>}
|
||||
{showClose && <Dialog.Title className="flex items-center mb-4" as="div">
|
||||
<XMarkIcon className='w-4 h-4 text-text-tertiary cursor-pointer' onClick={onClose} />
|
||||
</Dialog.Title>}
|
||||
</div>
|
||||
|
||||
{description && <Dialog.Description className='text-text-tertiary text-xs font-normal mt-2'>{description}</Dialog.Description>}
|
||||
{children}
|
||||
</>
|
||||
|
||||
@ -12,10 +12,13 @@ export type InputNumberProps = {
|
||||
max?: number
|
||||
min?: number
|
||||
defaultValue?: number
|
||||
wrapClassName?: string
|
||||
controlWrapClassName?: string
|
||||
controlClassName?: string
|
||||
} & Omit<InputProps, 'value' | 'onChange' | 'size' | 'min' | 'max' | 'defaultValue'>
|
||||
|
||||
export const InputNumber: FC<InputNumberProps> = (props) => {
|
||||
const { unit, className, onChange, amount = 1, value, size = 'md', max, min, defaultValue, ...rest } = props
|
||||
const { unit, className, onChange, amount = 1, value, size = 'md', max, min, defaultValue, wrapClassName, controlWrapClassName, controlClassName, ...rest } = props
|
||||
|
||||
const isValidValue = (v: number) => {
|
||||
if (max && v > max)
|
||||
@ -46,7 +49,7 @@ export const InputNumber: FC<InputNumberProps> = (props) => {
|
||||
onChange(newValue)
|
||||
}
|
||||
|
||||
return <div className='flex'>
|
||||
return <div className={classNames('flex', wrapClassName)}>
|
||||
<Input {...rest}
|
||||
// disable default controller
|
||||
type='text'
|
||||
@ -68,16 +71,18 @@ export const InputNumber: FC<InputNumberProps> = (props) => {
|
||||
}}
|
||||
unit={unit}
|
||||
/>
|
||||
<div className='flex flex-col bg-components-input-bg-normal rounded-r-md border-l border-divider-subtle text-text-tertiary focus:shadow-xs'>
|
||||
<div className={classNames('flex flex-col bg-components-input-bg-normal rounded-r-md border-l border-divider-subtle text-text-tertiary focus:shadow-xs', controlWrapClassName)}>
|
||||
<button onClick={inc} className={classNames(
|
||||
size === 'sm' ? 'pt-1' : 'pt-1.5',
|
||||
'px-1.5 hover:bg-components-input-bg-hover',
|
||||
controlClassName,
|
||||
)}>
|
||||
<RiArrowUpSLine className='size-3' />
|
||||
</button>
|
||||
<button onClick={dec} className={classNames(
|
||||
size === 'sm' ? 'pb-1' : 'pb-1.5',
|
||||
'px-1.5 hover:bg-components-input-bg-hover',
|
||||
controlClassName,
|
||||
)}>
|
||||
<RiArrowDownSLine className='size-3' />
|
||||
</button>
|
||||
|
||||
58
web/app/components/base/modal-like-wrap/index.tsx
Normal file
58
web/app/components/base/modal-like-wrap/index.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '../button'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
className?: string
|
||||
beforeHeader?: React.ReactNode
|
||||
onClose: () => void
|
||||
hideCloseBtn?: boolean
|
||||
onConfirm: () => void
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const ModalLikeWrap: FC<Props> = ({
|
||||
title,
|
||||
className,
|
||||
beforeHeader,
|
||||
children,
|
||||
onClose,
|
||||
hideCloseBtn,
|
||||
onConfirm,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className={cn('w-[320px] px-3 pt-3.5 pb-4 bg-components-panel-bg shadow-xl rounded-2xl border-[0.5px] border-components-panel-border', className)}>
|
||||
{beforeHeader || null}
|
||||
<div className='mb-1 flex h-6 items-center justify-between'>
|
||||
<div className='system-xl-semibold text-text-primary'>{title}</div>
|
||||
{!hideCloseBtn && (
|
||||
<div
|
||||
className='p-1.5 text-text-tertiary cursor-pointer'
|
||||
onClick={onClose}
|
||||
>
|
||||
<RiCloseLine className='size-4' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='mt-2'>{children}</div>
|
||||
<div className='mt-4 flex justify-end'>
|
||||
<Button
|
||||
className='mr-2'
|
||||
onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button
|
||||
onClick={onConfirm}
|
||||
variant='primary'
|
||||
>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ModalLikeWrap)
|
||||
@ -12,7 +12,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const maxTopK = (() => {
|
||||
const configValue = Number.parseInt(globalThis.document?.body?.getAttribute('data-public-top-k-max-value') || '', 10)
|
||||
const configValue = parseInt(globalThis.document?.body?.getAttribute('data-public-top-k-max-value') || '', 10)
|
||||
if (configValue && !isNaN(configValue))
|
||||
return configValue
|
||||
return 10
|
||||
@ -33,7 +33,7 @@ const TopKItem: FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const handleParamChange = (key: string, value: number) => {
|
||||
let notOutRangeValue = Number.parseFloat(value.toFixed(2))
|
||||
let notOutRangeValue = parseFloat(value.toFixed(2))
|
||||
notOutRangeValue = Math.max(VALUE_LIMIT.min, notOutRangeValue)
|
||||
notOutRangeValue = Math.min(VALUE_LIMIT.max, notOutRangeValue)
|
||||
onChange(key, notOutRangeValue)
|
||||
|
||||
@ -51,11 +51,12 @@ const Toast = ({
|
||||
'top-0',
|
||||
'right-0',
|
||||
)}>
|
||||
<div className={`absolute inset-0 opacity-40 ${(type === 'success' && 'bg-toast-success-bg')
|
||||
<div className={`absolute inset-0 opacity-40 ${
|
||||
(type === 'success' && 'bg-toast-success-bg')
|
||||
|| (type === 'warning' && 'bg-toast-warning-bg')
|
||||
|| (type === 'error' && 'bg-toast-error-bg')
|
||||
|| (type === 'info' && 'bg-toast-info-bg')
|
||||
}`}
|
||||
}`}
|
||||
/>
|
||||
<div className={`flex ${size === 'md' ? 'gap-1' : 'gap-0.5'}`}>
|
||||
<div className={`flex justify-center items-center ${size === 'md' ? 'p-0.5' : 'p-1'}`}>
|
||||
@ -64,11 +65,8 @@ const Toast = ({
|
||||
{type === 'warning' && <RiAlertFill className={`${size === 'md' ? 'w-5 h-5' : 'w-4 h-4'} text-text-warning-secondary`} aria-hidden="true" />}
|
||||
{type === 'info' && <RiInformation2Fill className={`${size === 'md' ? 'w-5 h-5' : 'w-4 h-4'} text-text-accent`} aria-hidden="true" />}
|
||||
</div>
|
||||
<div className={`flex py-1 ${size === 'md' ? 'px-1' : 'px-0.5'} flex-col items-start gap-1 grow z-10`}>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='text-text-primary system-sm-semibold'>{message}</div>
|
||||
{customComponent}
|
||||
</div>
|
||||
<div className={`flex py-1 ${size === 'md' ? 'px-1' : 'px-0.5'} flex-col items-start gap-1 grow`}>
|
||||
<div className='text-text-primary system-sm-semibold'>{message}</div>
|
||||
{children && <div className='text-text-secondary system-xs-regular'>
|
||||
{children}
|
||||
</div>
|
||||
@ -80,7 +78,7 @@ const Toast = ({
|
||||
</ActionButton>)
|
||||
}
|
||||
</div>
|
||||
</div >
|
||||
</div>
|
||||
}
|
||||
|
||||
export const ToastProvider = ({
|
||||
@ -132,7 +130,7 @@ Toast.notify = ({
|
||||
|
||||
root.render(
|
||||
<ToastContext.Provider value={{
|
||||
notify: () => { },
|
||||
notify: () => {},
|
||||
close: () => {
|
||||
if (holder) {
|
||||
root.unmount()
|
||||
|
||||
@ -39,7 +39,7 @@ export const DelimiterInput: FC<InputProps & { tooltip?: string }> = (props) =>
|
||||
}
|
||||
|
||||
export const MaxLengthInput: FC<InputNumberProps> = (props) => {
|
||||
const maxValue = Number.parseInt(globalThis.document?.body?.getAttribute('data-public-indexing-max-segmentation-tokens-length') || '4000', 10)
|
||||
const maxValue = parseInt(globalThis.document?.body?.getAttribute('data-public-indexing-max-segmentation-tokens-length') || '4000', 10)
|
||||
|
||||
const { t } = useTranslation()
|
||||
return <FormField label={<div className='system-sm-semibold mb-1'>
|
||||
|
||||
@ -80,7 +80,7 @@ export const useSegmentListContext = (selector: (value: SegmentListContextValue)
|
||||
return useContextSelector(SegmentListContext, selector)
|
||||
}
|
||||
|
||||
type ICompletedProps = {
|
||||
interface ICompletedProps {
|
||||
embeddingAvailable: boolean
|
||||
showNewSegmentModal: boolean
|
||||
onNewSegmentModalChange: (state: boolean) => void
|
||||
|
||||
@ -106,8 +106,8 @@ const ModifyRetrievalModal: FC<Props> = ({
|
||||
borderColor: 'rgba(0, 0, 0, 0.05)',
|
||||
}}
|
||||
>
|
||||
<Button className='mr-2 shrink-0' onClick={onHide}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' className='shrink-0' onClick={handleSave} >{t('common.operation.save')}</Button>
|
||||
<Button className='mr-2 flex-shrink-0' onClick={onHide}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' className='flex-shrink-0' onClick={handleSave} >{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -15,7 +15,7 @@ import { asyncRunSafe } from '@/utils'
|
||||
import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app'
|
||||
import promptS from '@/app/components/app/configuration/config-prompt/style.module.css'
|
||||
|
||||
type TextAreaWithButtonIProps = {
|
||||
interface TextAreaWithButtonIProps {
|
||||
datasetId: string
|
||||
onUpdateList: () => void
|
||||
setHitResult: (res: HitTestingResponse) => void
|
||||
|
||||
31
web/app/components/datasets/metadata/add-metadata-button.tsx
Normal file
31
web/app/components/datasets/metadata/add-metadata-button.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import Button from '../../base/button'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const AddedMetadataButton: FC<Props> = ({
|
||||
className,
|
||||
onClick,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Button
|
||||
className={cn('w-full flex items-center', className)}
|
||||
size='small'
|
||||
variant='tertiary'
|
||||
onClick={onClick}
|
||||
>
|
||||
<RiAddLine className='mr-1 size-3.5' />
|
||||
<div>{t('dataset.metadata.addMetadata')}</div>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
export default React.memo(AddedMetadataButton)
|
||||
87
web/app/components/datasets/metadata/create-content.tsx
Normal file
87
web/app/components/datasets/metadata/create-content.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { DataType } from './types'
|
||||
import ModalLikeWrap from '../../base/modal-like-wrap'
|
||||
import Field from './field'
|
||||
import OptionCard from '../../workflow/nodes/_base/components/option-card'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { RiArrowLeftLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const i18nPrefix = 'dataset.metadata.createMetadata'
|
||||
|
||||
export type Props = {
|
||||
onSave: (data: any) => void
|
||||
hasBack?: boolean
|
||||
onBack?: () => void
|
||||
}
|
||||
|
||||
const CreateContent: FC<Props> = ({
|
||||
hasBack,
|
||||
onBack,
|
||||
onSave,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [type, setType] = useState(DataType.string)
|
||||
|
||||
const handleTypeChange = useCallback((newType: DataType) => {
|
||||
return () => setType(newType)
|
||||
}, [setType])
|
||||
const [name, setName] = useState('')
|
||||
const handleNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setName(e.target.value)
|
||||
}, [setName])
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
onSave({
|
||||
type,
|
||||
name,
|
||||
})
|
||||
}, [onSave, type, name])
|
||||
|
||||
return (
|
||||
<ModalLikeWrap
|
||||
title={t(`${i18nPrefix}.title`)}
|
||||
onClose={() => { }}
|
||||
onConfirm={handleSave}
|
||||
hideCloseBtn={hasBack}
|
||||
beforeHeader={hasBack && (
|
||||
<div className='relative left-[-4px] mb-1 flex items-center py-1 space-x-1 text-text-accent cursor-pointer' onClick={onBack}>
|
||||
<RiArrowLeftLine className='size-4' />
|
||||
<div className='system-xs-semibold-uppercase'>{t(`${i18nPrefix}.back`)}</div>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div className='space-y-3'>
|
||||
<Field label={t(`${i18nPrefix}.type`)}>
|
||||
<div className='grid grid-cols-3 gap-2'>
|
||||
<OptionCard
|
||||
title='String'
|
||||
selected={type === DataType.string}
|
||||
onSelect={handleTypeChange(DataType.string)}
|
||||
/>
|
||||
<OptionCard
|
||||
title='Number'
|
||||
selected={type === DataType.number}
|
||||
onSelect={handleTypeChange(DataType.number)}
|
||||
/>
|
||||
<OptionCard
|
||||
title='Time'
|
||||
selected={type === DataType.time}
|
||||
onSelect={handleTypeChange(DataType.time)}
|
||||
/>
|
||||
</div>
|
||||
</Field>
|
||||
<Field label={t(`${i18nPrefix}.name`)}>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={handleNameChange}
|
||||
placeholder={t(`${i18nPrefix}.namePlaceholder`)}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</ModalLikeWrap>
|
||||
)
|
||||
}
|
||||
export default React.memo(CreateContent)
|
||||
@ -0,0 +1,43 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import type { Props as CreateContentProps } from './create-content'
|
||||
import CreateContent from './create-content'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
|
||||
|
||||
type Props = {
|
||||
onSave: (data: any) => void
|
||||
trigger: React.ReactNode
|
||||
popupLeft?: number
|
||||
} & CreateContentProps
|
||||
|
||||
const CreateMetadataModal: FC<Props> = ({
|
||||
trigger,
|
||||
popupLeft = 20,
|
||||
...createContentProps
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='left-start'
|
||||
offset={{
|
||||
mainAxis: popupLeft,
|
||||
crossAxis: -38,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{trigger}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<CreateContent {...createContentProps} />
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem >
|
||||
|
||||
)
|
||||
}
|
||||
export default React.memo(CreateMetadataModal)
|
||||
228
web/app/components/datasets/metadata/dataset-metadata-drawer.tsx
Normal file
228
web/app/components/datasets/metadata/dataset-metadata-drawer.tsx
Normal file
@ -0,0 +1,228 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import type { MetadataItemWithValueLength } from './types'
|
||||
import Drawer from '../../base/drawer'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiAddLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
import { getIcon } from './utils/get-icon'
|
||||
import cn from '@/utils/classnames'
|
||||
import Modal from '../../base/modal'
|
||||
import Field from './field'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import Switch from '../../base/switch'
|
||||
import Tooltip from '../../base/tooltip'
|
||||
import CreateModal from '@/app/components/datasets/metadata/create-metadata-modal'
|
||||
import { useBoolean, useHover } from 'ahooks'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
|
||||
const i18nPrefix = 'dataset.metadata.datasetMetadata'
|
||||
|
||||
type Props = {
|
||||
userMetadata: MetadataItemWithValueLength[]
|
||||
builtInMetadata: MetadataItemWithValueLength[]
|
||||
isBuiltInEnabled: boolean
|
||||
onIsBuiltInEnabledChange: (value: boolean) => void
|
||||
onClose: () => void
|
||||
onChange: (data: MetadataItemWithValueLength[]) => void
|
||||
}
|
||||
|
||||
type ItemProps = {
|
||||
readonly?: boolean
|
||||
disabled?: boolean
|
||||
payload: MetadataItemWithValueLength
|
||||
onRename?: () => void
|
||||
onDelete?: () => void
|
||||
}
|
||||
const Item: FC<ItemProps> = ({
|
||||
readonly,
|
||||
disabled,
|
||||
payload,
|
||||
onRename,
|
||||
onDelete,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const Icon = getIcon(payload.type)
|
||||
|
||||
const handleRename = useCallback(() => {
|
||||
onRename?.()
|
||||
}, [onRename])
|
||||
|
||||
const deleteBtnRef = useRef<HTMLDivElement>(null)
|
||||
const isDeleteHovering = useHover(deleteBtnRef)
|
||||
const [isShowDeleteConfirm, {
|
||||
setTrue: showDeleteConfirm,
|
||||
setFalse: hideDeleteConfirm,
|
||||
}] = useBoolean(false)
|
||||
const handleDelete = useCallback(() => {
|
||||
hideDeleteConfirm()
|
||||
onDelete?.()
|
||||
}, [hideDeleteConfirm, onDelete])
|
||||
|
||||
return (
|
||||
<div
|
||||
key={payload.id}
|
||||
className={cn(
|
||||
!readonly && !disabled && 'group/item hover:shadow-xs cursor-pointer',
|
||||
'border border-components-panel-border-subtle rounded-md bg-components-panel-on-panel-item-bg',
|
||||
isDeleteHovering && 'border border-state-destructive-border bg-state-destructive-hover',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center h-8 px-2 justify-between',
|
||||
disabled && 'opacity-30', // not include border and bg
|
||||
)}
|
||||
>
|
||||
<div className='flex items-center h-full text-text-tertiary space-x-1'>
|
||||
<Icon className='shrink-0 size-4' />
|
||||
<div className='max-w-[250px] truncate system-sm-medium text-text-primary'>{payload.name}</div>
|
||||
<div className='shrink-0 system-xs-regular'>{payload.type}</div>
|
||||
</div>
|
||||
<div className='group-hover/item:hidden ml-2 shrink-0 system-xs-regular text-text-tertiary'>
|
||||
{disabled ? t(`${i18nPrefix}.disabled`) : t(`${i18nPrefix}.values`, { num: payload.valueLength || 0 })}
|
||||
</div>
|
||||
<div className='group-hover/item:flex hidden ml-2 items-center text-text-tertiary space-x-1'>
|
||||
<RiEditLine className='size-4 cursor-pointer' onClick={handleRename} />
|
||||
<div ref={deleteBtnRef} className='hover:text-text-destructive'>
|
||||
<RiDeleteBinLine className='size-4 cursor-pointer' onClick={showDeleteConfirm} />
|
||||
</div>
|
||||
</div>
|
||||
{isShowDeleteConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
type='warning'
|
||||
title={'Confirm to delete'}
|
||||
content={`Are you sure you want to delete the metadata "${payload.name}"?`}
|
||||
onConfirm={handleDelete}
|
||||
onCancel={hideDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DatasetMetadataDrawer: FC<Props> = ({
|
||||
userMetadata,
|
||||
builtInMetadata,
|
||||
isBuiltInEnabled,
|
||||
onIsBuiltInEnabledChange,
|
||||
onClose,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isShowRenameModal, setIsShowRenameModal] = useState(false)
|
||||
const [currPayload, setCurrPayload] = useState<MetadataItemWithValueLength | null>(null)
|
||||
const [templeName, setTempleName] = useState('')
|
||||
const handleRename = useCallback((payload: MetadataItemWithValueLength) => {
|
||||
return () => {
|
||||
setCurrPayload(payload)
|
||||
setTempleName(payload.name)
|
||||
setIsShowRenameModal(true)
|
||||
}
|
||||
}, [setCurrPayload, setIsShowRenameModal])
|
||||
|
||||
const handleAdd = useCallback((data: MetadataItemWithValueLength) => {
|
||||
const nextUserMetadata = produce(userMetadata, (draft) => {
|
||||
draft.push(data)
|
||||
})
|
||||
onChange(nextUserMetadata)
|
||||
}, [userMetadata, onChange])
|
||||
|
||||
const handleRenamed = useCallback(() => {
|
||||
const nextUserMetadata = produce(userMetadata, (draft) => {
|
||||
const index = draft.findIndex(p => p.id === currPayload?.id)
|
||||
if (index !== -1)
|
||||
draft[index].name = templeName!
|
||||
})
|
||||
|
||||
onChange(nextUserMetadata)
|
||||
setIsShowRenameModal(false)
|
||||
}, [currPayload, templeName, userMetadata, onChange])
|
||||
|
||||
const handleDelete = useCallback((payload: MetadataItemWithValueLength) => {
|
||||
return () => {
|
||||
const nextUserMetadata = userMetadata.filter(p => p.id !== payload.id)
|
||||
onChange(nextUserMetadata)
|
||||
}
|
||||
}, [userMetadata, onChange])
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
showClose
|
||||
title={t('dataset.metadata.metadata')}
|
||||
footer={null}
|
||||
panelClassname='px-4 block !max-w-[420px] my-2 rounded-l-2xl'
|
||||
>
|
||||
<div className='system-sm-regular text-text-tertiary'>{t(`${i18nPrefix}.description`)}</div>
|
||||
|
||||
<CreateModal trigger={<Button variant='primary' className='mt-3'>
|
||||
<RiAddLine className='mr-1' />
|
||||
{t(`${i18nPrefix}.addMetaData`)}
|
||||
</Button>} hasBack onSave={handleAdd} />
|
||||
|
||||
<div className='mt-3 space-y-1'>
|
||||
{userMetadata.map(payload => (
|
||||
<Item
|
||||
key={payload.id}
|
||||
payload={payload}
|
||||
onRename={handleRename(payload)}
|
||||
onDelete={handleDelete(payload)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='mt-3 flex h-6 items-center'>
|
||||
<Switch
|
||||
defaultValue={isBuiltInEnabled}
|
||||
onChange={onIsBuiltInEnabledChange}
|
||||
/>
|
||||
<div className='ml-2 mr-0.5 system-sm-semibold text-text-secondary'>{t(`${i18nPrefix}.builtIn`)}</div>
|
||||
<Tooltip popupContent={<div className='max-w-[100px]'>{t(`${i18nPrefix}.builtInDescription`)}</div>} />
|
||||
</div>
|
||||
|
||||
<div className='mt-1 space-y-1'>
|
||||
{builtInMetadata.map(payload => (
|
||||
<Item
|
||||
key={payload.id}
|
||||
readonly
|
||||
disabled={!isBuiltInEnabled}
|
||||
payload={payload}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{isShowRenameModal && (
|
||||
<Modal isShow title={t(`${i18nPrefix}.rename`)} onClose={() => setIsShowRenameModal(false)}>
|
||||
<Field label={t(`${i18nPrefix}.name`)}>
|
||||
<Input
|
||||
value={templeName}
|
||||
onChange={e => setTempleName(e.target.value)}
|
||||
placeholder={t(`${i18nPrefix}.namePlaceholder`)}
|
||||
/>
|
||||
</Field>
|
||||
<div className='mt-4 flex justify-end'>
|
||||
<Button
|
||||
className='mr-2'
|
||||
onClick={() => {
|
||||
setIsShowRenameModal(false)
|
||||
setTempleName(currPayload!.name)
|
||||
}}>{t('common.operation.cancel')}</Button>
|
||||
<Button
|
||||
onClick={handleRenamed}
|
||||
variant='primary'
|
||||
disabled={!templeName}
|
||||
>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
export default React.memo(DatasetMetadataDrawer)
|
||||
@ -0,0 +1,45 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import type { MetadataItemWithEdit } from '../types'
|
||||
import cn from '@/utils/classnames'
|
||||
import Label from './label'
|
||||
import InputCombined from './input-combined'
|
||||
import { RiIndeterminateCircleLine } from '@remixicon/react'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
payload: MetadataItemWithEdit
|
||||
onChange: (value: any) => void
|
||||
onRemove: () => void
|
||||
}
|
||||
|
||||
const AddRow: FC<Props> = ({
|
||||
className,
|
||||
payload,
|
||||
onChange,
|
||||
onRemove,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn('flex h-6 items-center space-x-0.5', className)}>
|
||||
<Label text={payload.name} />
|
||||
<InputCombined
|
||||
type={payload.type}
|
||||
value={payload.value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<div
|
||||
className={
|
||||
cn(
|
||||
'p-1 rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive cursor-pointer',
|
||||
)
|
||||
}
|
||||
onClick={onRemove}
|
||||
>
|
||||
<RiIndeterminateCircleLine className='size-4' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(AddRow)
|
||||
@ -0,0 +1,50 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { type MetadataItemWithEdit, UpdateType } from '../types'
|
||||
import Label from './label'
|
||||
import { RiDeleteBinLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import InputHasSetMultipleValue from './input-has-set-multiple-value'
|
||||
import InputCombined from './input-combined'
|
||||
import EditedBeacon from './edited-beacon'
|
||||
|
||||
type Props = {
|
||||
payload: MetadataItemWithEdit
|
||||
onChange: (payload: MetadataItemWithEdit) => void
|
||||
onRemove: (id: string) => void
|
||||
}
|
||||
|
||||
const EditMetadatabatchItem: FC<Props> = ({
|
||||
payload,
|
||||
onChange,
|
||||
onRemove,
|
||||
}) => {
|
||||
const isUpdated = payload.isUpdated
|
||||
const isDeleted = payload.updateType === UpdateType.delete
|
||||
return (
|
||||
<div className='flex h-6 items-center space-x-0.5'>
|
||||
{isUpdated ? <EditedBeacon onReset={() => { }} /> : <div className='shrink-0 size-4' />}
|
||||
<Label text={payload.name} isDeleted={isDeleted} />
|
||||
{payload.isMultipleValue
|
||||
? <InputHasSetMultipleValue onClear={() => onChange({ ...payload, isMultipleValue: false })} />
|
||||
: <InputCombined
|
||||
type={payload.type}
|
||||
value={payload.value}
|
||||
onChange={v => onChange({ ...payload, value: v as string })
|
||||
} />}
|
||||
|
||||
<div
|
||||
className={
|
||||
cn(
|
||||
'p-1 rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive cursor-pointer',
|
||||
isDeleted && 'cursor-default bg-state-destructive-hover text-text-destructive')
|
||||
}
|
||||
onClick={() => onRemove(payload.id)}
|
||||
>
|
||||
<RiDeleteBinLine className='size-4' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(EditMetadatabatchItem)
|
||||
@ -0,0 +1,36 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useRef } from 'react'
|
||||
import { useHover } from 'ahooks'
|
||||
import { RiResetLeftLine } from '@remixicon/react'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Props = {
|
||||
onReset: () => void
|
||||
}
|
||||
|
||||
const EditedBeacon: FC<Props> = ({
|
||||
onReset,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const ref = useRef(null)
|
||||
const isHovering = useHover(ref)
|
||||
|
||||
return (
|
||||
<div ref={ref} className='size-4 cursor-pointer'>
|
||||
{isHovering ? (
|
||||
<Tooltip popupContent={t('common.operation.reset')}>
|
||||
<div className='flex justify-center items-center size-4 bg-text-accent-secondary rounded-full' onClick={onReset}>
|
||||
<RiResetLeftLine className='size-[10px] text-text-primary-on-surface' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div className='flex items-center justify-center size-4'>
|
||||
<div className='size-1 rounded-full bg-text-accent-secondary'></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(EditedBeacon)
|
||||
@ -0,0 +1,51 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { DataType } from '../types'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { InputNumber } from '@/app/components/base/input-number'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
type: DataType
|
||||
value: any
|
||||
onChange: (value: any) => void
|
||||
}
|
||||
|
||||
const InputCombined: FC<Props> = ({
|
||||
className: configClassName,
|
||||
type,
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
// TODO: configClassName...
|
||||
const className = cn('grow p-0.5 h-6 text-xs')
|
||||
if (type === DataType.time)
|
||||
return <div className='grow text-xs'>Datepicker placeholder</div>
|
||||
|
||||
if (type === DataType.number) {
|
||||
return (
|
||||
<div className='grow text-[0]'>
|
||||
<InputNumber
|
||||
className={cn(className, 'rounded-l-md')}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
size='sm'
|
||||
controlWrapClassName='overflow-hidden'
|
||||
controlClassName='pt-0 pb-0'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Input
|
||||
wrapperClassName={configClassName}
|
||||
className={cn(className, 'rounded-md')}
|
||||
value={value}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
>
|
||||
</Input>
|
||||
)
|
||||
}
|
||||
export default React.memo(InputCombined)
|
||||
@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Props = {
|
||||
onClear: () => void
|
||||
}
|
||||
|
||||
const InputHasSetMultipleValue: FC<Props> = ({
|
||||
onClear,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='grow h-6 p-0.5 rounded-md bg-components-input-bg-normal text-[0]'>
|
||||
<div className='inline-flex rounded-[5px] items-center h-5 pl-1.5 pr-0.5 bg-components-badge-white-to-dark border-[0.5px] border-components-panel-border shadow-xs space-x-0.5'>
|
||||
<div className='system-xs-regular text-text-secondary'>{t('dataset.metadata.batchEditMetadata.multipleValue')}</div>
|
||||
<div className='p-0.5rounded-sm text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer'>
|
||||
<RiCloseLine
|
||||
className='size-3.5 '
|
||||
onClick={onClear}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(InputHasSetMultipleValue)
|
||||
@ -0,0 +1,27 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
isDeleted?: boolean,
|
||||
className?: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
const Label: FC<Props> = ({
|
||||
isDeleted,
|
||||
className,
|
||||
text,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn(
|
||||
'shrink-0 w-[136px] system-xs-medium text-text-tertiary truncate',
|
||||
isDeleted && 'line-through text-text-quaternary',
|
||||
className,
|
||||
)}>
|
||||
{text}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Label)
|
||||
@ -0,0 +1,139 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import Modal from '../../../base/modal'
|
||||
import { DataType, type MetadataItemWithEdit } from '../types'
|
||||
import EditMetadataBatchItem from './edit-row'
|
||||
import AddedMetadataItem from './add-row'
|
||||
import Button from '../../../base/button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Checkbox from '../../../base/checkbox'
|
||||
import Tooltip from '../../../base/tooltip'
|
||||
import SelectMetadataModal from '../select-metadata-modal'
|
||||
import { RiQuestionLine } from '@remixicon/react'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import AddMetadataButton from '../add-metadata-button'
|
||||
|
||||
const i18nPrefix = 'dataset.metadata.batchEditMetadata'
|
||||
|
||||
type Props = {
|
||||
documentNum: number
|
||||
list: MetadataItemWithEdit[]
|
||||
onChange: (list: MetadataItemWithEdit[], addedList: MetadataItemWithEdit[], isApplyToAllSelectDocument: boolean) => void
|
||||
onHide: () => void
|
||||
}
|
||||
|
||||
const EditMetadataBatchModal: FC<Props> = ({
|
||||
documentNum,
|
||||
list,
|
||||
onChange,
|
||||
onHide,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [templeList, setTempleList] = useState<MetadataItemWithEdit[]>(list)
|
||||
const handleTemplesChange = useCallback((payload: MetadataItemWithEdit) => {
|
||||
const newTempleList = templeList.map(i => i.id === payload.id ? payload : i)
|
||||
setTempleList(newTempleList)
|
||||
}, [templeList])
|
||||
const handleTempleItemRemove = useCallback((id: string) => {
|
||||
const newTempleList = templeList.filter(i => i.id !== id)
|
||||
setTempleList(newTempleList)
|
||||
}, [templeList])
|
||||
|
||||
const testAddedList: MetadataItemWithEdit[] = [
|
||||
{
|
||||
id: '1', name: 'name1', type: DataType.string, value: 'aaa',
|
||||
},
|
||||
{
|
||||
id: '2.1', name: 'num v', type: DataType.number, value: 10,
|
||||
},
|
||||
]
|
||||
const [addedList, setAddedList] = useState<MetadataItemWithEdit[]>(testAddedList)
|
||||
const handleAddedListChange = useCallback((payload: MetadataItemWithEdit) => {
|
||||
const newAddedList = addedList.map(i => i.id === payload.id ? payload : i)
|
||||
setAddedList(newAddedList)
|
||||
}, [addedList])
|
||||
const handleAddedItemRemove = useCallback((removeIndex: number) => {
|
||||
return () => {
|
||||
const newAddedList = addedList.filter((i, index) => index !== removeIndex)
|
||||
setAddedList(newAddedList)
|
||||
}
|
||||
}, [addedList])
|
||||
|
||||
const [isApplyToAllSelectDocument, setIsApplyToAllSelectDocument] = useState(false)
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
onChange(templeList, addedList, isApplyToAllSelectDocument)
|
||||
}, [templeList, addedList, isApplyToAllSelectDocument, onChange])
|
||||
return (
|
||||
<Modal
|
||||
title={t(`${i18nPrefix}.editMetadata`)}
|
||||
isShow
|
||||
closable
|
||||
onClose={onHide}
|
||||
className='!max-w-[640px]'
|
||||
>
|
||||
<div className='system-xs-medium text-text-accent'>{t(`${i18nPrefix}.editDocumentsNum`, { num: documentNum })}</div>
|
||||
{/* TODO handle list scroll. There is two list. */}
|
||||
<div className='mt-4 space-y-2'>
|
||||
{templeList.map(item => (
|
||||
<EditMetadataBatchItem
|
||||
key={item.id}
|
||||
payload={item}
|
||||
onChange={handleTemplesChange}
|
||||
onRemove={handleTempleItemRemove}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className='mt-4 pl-[18px]'>
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-2 shrink-0 system-xs-medium-uppercase text-text-tertiary'>{t('dataset.metadata.createMetadata.title')}</div>
|
||||
<Divider bgStyle='gradient' />
|
||||
</div>
|
||||
<div className='mt-2 space-y-2'>
|
||||
{addedList.map((item, i) => (
|
||||
<AddedMetadataItem
|
||||
key={i}
|
||||
payload={item}
|
||||
onChange={handleAddedListChange}
|
||||
onRemove={handleAddedItemRemove(i)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className='mt-3'>
|
||||
<SelectMetadataModal
|
||||
popupPlacement='top-start'
|
||||
popupOffset={{ mainAxis: 4, crossAxis: 0 }}
|
||||
trigger={
|
||||
<AddMetadataButton />
|
||||
}
|
||||
onSave={data => setAddedList([...addedList, data])}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-4 flex items-center justify-between'>
|
||||
<div className='flex items-center select-none'>
|
||||
<Checkbox checked={isApplyToAllSelectDocument} onCheck={() => setIsApplyToAllSelectDocument(!isApplyToAllSelectDocument)} />
|
||||
<div className='ml-2 mr-1 system-xs-medium text-text-secondary'>{t(`${i18nPrefix}.applyToAllSelectDocument`)}</div>
|
||||
<Tooltip popupContent={
|
||||
<div className='max-w-[240px]'>{t(`${i18nPrefix}.applyToAllSelectDocumentTip`)}</div>
|
||||
} >
|
||||
<div className='p-px cursor-pointer'>
|
||||
<RiQuestionLine className='size-3.5 text-text-tertiary' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Button
|
||||
onClick={onHide}>{t('common.operation.cancel')}</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
variant='primary'
|
||||
>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
export default React.memo(EditMetadataBatchModal)
|
||||
23
web/app/components/datasets/metadata/field.tsx
Normal file
23
web/app/components/datasets/metadata/field.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
label: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const Field: FC<Props> = ({
|
||||
className,
|
||||
label,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='py-1 system-sm-semibold text-text-secondary'>{label}</div>
|
||||
<div className='mt-1'>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Field)
|
||||
@ -0,0 +1,26 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
label: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const Field: FC<Props> = ({
|
||||
label,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className='flex items-start space-x-2'>
|
||||
<div className='shrink-0 w-[128px] truncate py-1 items-center text-text-tertiary system-xs-medium'>
|
||||
{label}
|
||||
</div>
|
||||
<div className='shrink-0 w-[244px]'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Field)
|
||||
127
web/app/components/datasets/metadata/metadata-document/index.tsx
Normal file
127
web/app/components/datasets/metadata/metadata-document/index.tsx
Normal file
@ -0,0 +1,127 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { DataType, type MetadataItemWithValue } from '../types'
|
||||
import InfoGroup from './info-group'
|
||||
import NoData from './no-data'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiEditLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
||||
const i18nPrefix = 'dataset.metadata.documentMetadata'
|
||||
|
||||
const MetadataDocument: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [isEdit, setIsEdit] = useState(true)
|
||||
|
||||
const [list, setList] = useState<MetadataItemWithValue[]>([
|
||||
{
|
||||
id: '1',
|
||||
name: 'Doc type',
|
||||
value: 'PDF',
|
||||
type: DataType.string,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Title',
|
||||
value: 'PDF',
|
||||
type: DataType.string,
|
||||
},
|
||||
])
|
||||
const [tempList, setTempList] = useState<MetadataItemWithValue[]>(list)
|
||||
const builtInEnabled = true
|
||||
const builtList = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'OriginalfileNmae',
|
||||
value: 'Steve Jobs The Man Who Thought Different.pdf',
|
||||
type: DataType.string,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Title',
|
||||
value: 'PDF',
|
||||
type: DataType.string,
|
||||
},
|
||||
]
|
||||
const hasData = list.length > 0
|
||||
|
||||
const documentInfoList = builtList
|
||||
const technicalParams = builtList
|
||||
return (
|
||||
<div className='w-[388px] space-y-4'>
|
||||
{!hasData ? (
|
||||
<div className='pl-2'>
|
||||
<InfoGroup
|
||||
title={t('dataset.metadata.metadata')}
|
||||
uppercaseTitle={false}
|
||||
titleTooltip={t(`${i18nPrefix}.metadataToolTip`)}
|
||||
list={isEdit ? tempList : list}
|
||||
headerRight={isEdit ? (
|
||||
<div className='flex space-x-1'>
|
||||
<Button variant='ghost' size='small' onClick={() => {
|
||||
setTempList(list)
|
||||
setIsEdit(false)
|
||||
}}>
|
||||
<div>{t('common.operation.cancel')}</div>
|
||||
</Button>
|
||||
<Button variant='primary' size='small' onClick={() => {
|
||||
setIsEdit(false)
|
||||
setList(tempList)
|
||||
}}>
|
||||
<div>{t('common.operation.save')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button variant='ghost' size='small' onClick={() => {
|
||||
setTempList(list)
|
||||
setIsEdit(true)
|
||||
}}>
|
||||
<RiEditLine className='mr-1 size-3.5 text-text-tertiary cursor-pointer' />
|
||||
<div>{t('common.operation.edit')}</div>
|
||||
</Button>
|
||||
)}
|
||||
isEdit={isEdit}
|
||||
contentClassName='mt-5'
|
||||
onChange={(item) => {
|
||||
const newList = tempList.map(i => (i.name === item.name ? item : i))
|
||||
setList(newList)
|
||||
}}
|
||||
onDelete={(item) => {
|
||||
const newList = tempList.filter(i => i.name !== item.name)
|
||||
setList(newList)
|
||||
}}
|
||||
onAdd={() => {
|
||||
}}
|
||||
/>
|
||||
{builtInEnabled && (
|
||||
<>
|
||||
<Divider className='my-3' bgStyle='gradient' />
|
||||
<InfoGroup
|
||||
noHeader
|
||||
titleTooltip='Built-in metadata is system-generated metadata that is automatically added to the document. You can enable or disable built-in metadata here.'
|
||||
list={builtList}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<NoData onStart={() => { }} />
|
||||
)}
|
||||
|
||||
<InfoGroup
|
||||
className='pl-2'
|
||||
title={t(`${i18nPrefix}.documentInformation`)}
|
||||
list={documentInfoList}
|
||||
/>
|
||||
<InfoGroup
|
||||
className='pl-2'
|
||||
title={t(`${i18nPrefix}.technicalParameters`)}
|
||||
list={technicalParams}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(MetadataDocument)
|
||||
@ -0,0 +1,94 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import type { MetadataItemWithValue } from '../types'
|
||||
import Field from './field'
|
||||
import InputCombined from '../edit-metadata-batch/input-combined'
|
||||
import { RiDeleteBinLine, RiQuestionLine } from '@remixicon/react'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import cn from '@/utils/classnames'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import SelectMetadataModal from '../select-metadata-modal'
|
||||
import AddMetadataButton from '../add-metadata-button'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
noHeader?: boolean
|
||||
title?: string
|
||||
uppercaseTitle?: boolean
|
||||
titleTooltip?: string
|
||||
headerRight?: React.ReactNode
|
||||
contentClassName?: string
|
||||
list: MetadataItemWithValue[]
|
||||
isEdit?: boolean
|
||||
onChange?: (item: MetadataItemWithValue) => void
|
||||
onDelete?: (item: MetadataItemWithValue) => void
|
||||
onAdd?: (item: MetadataItemWithValue) => void
|
||||
}
|
||||
|
||||
const InfoGroup: FC<Props> = ({
|
||||
className,
|
||||
noHeader,
|
||||
title,
|
||||
uppercaseTitle = true,
|
||||
titleTooltip,
|
||||
headerRight,
|
||||
contentClassName,
|
||||
list,
|
||||
isEdit,
|
||||
onChange,
|
||||
onDelete,
|
||||
onAdd,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn('bg-white', className)}>
|
||||
{!noHeader && (
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center space-x-1'>
|
||||
<div className={cn('text-text-secondary', uppercaseTitle ? 'system-xs-semibold-uppercase' : 'system-md-semibold')}>{title}</div>
|
||||
{titleTooltip && (
|
||||
<Tooltip popupContent={<div className='max-w-[240px]'>{titleTooltip}</div>}>
|
||||
<RiQuestionLine className='size-3.5 text-text-tertiary' />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
{headerRight}
|
||||
{/* <div className='flex px-1.5 rounded-md hover:bg-components-button-tertiary-bg-hover items-center h-6 space-x-1 cursor-pointer' onClick={() => setIsEdit(true)}>
|
||||
</div> */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={cn('mt-3 space-y-1', !noHeader && 'mt-0', contentClassName)}>
|
||||
{isEdit && (
|
||||
<div>
|
||||
<SelectMetadataModal
|
||||
trigger={
|
||||
<AddMetadataButton />
|
||||
}
|
||||
onSave={() => { }}
|
||||
/>
|
||||
<Divider className='my-3 ' bgStyle='gradient' />
|
||||
</div>
|
||||
)}
|
||||
{list.map((item, i) => (
|
||||
<Field key={item.id || `${i}`} label={item.name}>
|
||||
{isEdit ? (
|
||||
<div className='flex items-center space-x-0.5'>
|
||||
<InputCombined
|
||||
className='h-6'
|
||||
type={item.type}
|
||||
value={item.value}
|
||||
onChange={value => onChange?.({ ...item, value })}
|
||||
/>
|
||||
<div className='shrink-0 p-1 rounded-md text-text-tertiary hover:text-text-destructive hover:bg-state-destructive-hover cursor-pointer'>
|
||||
<RiDeleteBinLine className='size-4' />
|
||||
</div>
|
||||
</div>
|
||||
) : (<div className='py-1 system-xs-regular text-text-secondary'>{item.value}</div>)}
|
||||
</Field>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(InfoGroup)
|
||||
@ -0,0 +1,27 @@
|
||||
'use client'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiArrowRightLine } from '@remixicon/react'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Props = {
|
||||
onStart: () => void
|
||||
}
|
||||
|
||||
const NoData: FC<Props> = ({
|
||||
onStart,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='p-4 pt-3 rounded-xl bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2'>
|
||||
<div className='text-text-secondary text-xs font-semibold leading-5'>{t('dataset.metadata.metadata')}</div>
|
||||
<div className='mt-1 system-xs-regular text-text-tertiary'>{t('dataset.metadata.documentMetadata.metadataToolTip')}</div>
|
||||
<Button variant='primary' className='mt-2' onClick={onStart}>
|
||||
<div>{t('dataset.metadata.documentMetadata.startLabeling')}</div>
|
||||
<RiArrowRightLine className='ml-1 size-4' />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(NoData)
|
||||
@ -0,0 +1,76 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import type { Props as CreateContentProps } from './create-content'
|
||||
import CreateContent from './create-content'
|
||||
import SelectMetadata from './select-metadata'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
|
||||
import type { MetadataItem } from './types'
|
||||
import type { Placement } from '@floating-ui/react'
|
||||
import { DataType } from './types'
|
||||
|
||||
type Props = {
|
||||
popupPlacement?: Placement
|
||||
popupOffset?: { mainAxis: number, crossAxis: number }
|
||||
onSave: (data: any) => void
|
||||
trigger: React.ReactNode
|
||||
} & CreateContentProps
|
||||
|
||||
enum Step {
|
||||
select = 'select',
|
||||
create = 'create',
|
||||
}
|
||||
|
||||
const testMetadataList: MetadataItem[] = [
|
||||
{ id: '1', name: 'name1', type: DataType.string },
|
||||
{ id: '2', name: 'name2', type: DataType.number },
|
||||
{ id: '3', name: 'name3', type: DataType.time },
|
||||
]
|
||||
|
||||
const SelectMetadataModal: FC<Props> = ({
|
||||
popupPlacement = 'left-start',
|
||||
popupOffset = { mainAxis: -38, crossAxis: 4 },
|
||||
trigger,
|
||||
onSave,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [step, setStep] = useState(Step.select)
|
||||
|
||||
const handleSave = useCallback((data: MetadataItem) => {
|
||||
onSave(data)
|
||||
setOpen(false)
|
||||
}, [onSave])
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement={popupPlacement}
|
||||
offset={popupOffset}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => setOpen(!open)}
|
||||
className='block'
|
||||
>
|
||||
{trigger}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
{step === Step.select ? (
|
||||
<SelectMetadata
|
||||
onSelect={handleSave}
|
||||
list={testMetadataList}
|
||||
onNew={() => setStep(Step.create)}
|
||||
onManage={() => { }}
|
||||
/>
|
||||
) : (
|
||||
<CreateContent
|
||||
onSave={handleSave}
|
||||
hasBack
|
||||
onBack={() => setStep(Step.select)}
|
||||
/>
|
||||
)}
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem >
|
||||
|
||||
)
|
||||
}
|
||||
export default React.memo(SelectMetadataModal)
|
||||
72
web/app/components/datasets/metadata/select-metadata.tsx
Normal file
72
web/app/components/datasets/metadata/select-metadata.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import type { MetadataItem } from './types'
|
||||
import SearchInput from '../../base/search-input'
|
||||
import { RiAddLine, RiArrowRightUpLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getIcon } from './utils/get-icon'
|
||||
|
||||
const i18nPrefix = 'dataset.metadata.selectMetadata'
|
||||
|
||||
type Props = {
|
||||
list: MetadataItem[]
|
||||
onSelect: (data: any) => void
|
||||
onNew: () => void
|
||||
onManage: () => void
|
||||
}
|
||||
|
||||
const SelectMetadata: FC<Props> = ({
|
||||
list,
|
||||
onSelect,
|
||||
onNew,
|
||||
onManage,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [query, setQuery] = useState('')
|
||||
return (
|
||||
<div className='w-[320px] pt-2 pb-0 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg backdrop-blur-[5px]'>
|
||||
<SearchInput
|
||||
className='mx-2'
|
||||
value={query}
|
||||
onChange={setQuery}
|
||||
placeholder={t(`${i18nPrefix}.search`)}
|
||||
/>
|
||||
<div className='mt-2'>
|
||||
{list.map((item) => {
|
||||
const Icon = getIcon(item.type)
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className='mx-1 flex items-center h-6 px-3 justify-between rounded-md hover:bg-state-base-hover cursor-pointer'
|
||||
onClick={() => onSelect(item)}
|
||||
>
|
||||
<div className='w-0 grow flex items-center h-full text-text-secondary'>
|
||||
<Icon className='shrink-0 mr-[5px] size-3.5' />
|
||||
<div className='w-0 grow truncate system-sm-medium'>{item.name}</div>
|
||||
</div>
|
||||
<div className='ml-1 shrink-0 system-xs-regular text-text-tertiary'>
|
||||
{item.type}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className='mt-1 flex justify-between p-1 border-t border-divider-subtle'>
|
||||
<div className='flex items-center h-6 px-3 text-text-secondary rounded-md hover:bg-state-base-hover cursor-pointer space-x-1' onClick={onNew}>
|
||||
<RiAddLine className='size-3.5' />
|
||||
<div className='system-sm-medium'>{t(`${i18nPrefix}.newAction`)}</div>
|
||||
</div>
|
||||
<div className='flex items-center h-6 text-text-secondary '>
|
||||
<div className='mr-[3px] w-px h-3 bg-divider-regular'></div>
|
||||
<div className='flex h-full items-center px-1.5 hover:bg-state-base-hover rounded-md cursor-pointer' onClick={onManage}>
|
||||
<div className='mr-1 system-sm-medium'>{t(`${i18nPrefix}.manageAction`)}</div>
|
||||
<RiArrowRightUpLine className='size-3.5' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(SelectMetadata)
|
||||
28
web/app/components/datasets/metadata/types.ts
Normal file
28
web/app/components/datasets/metadata/types.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export enum DataType {
|
||||
string = 'string',
|
||||
number = 'number',
|
||||
time = 'time',
|
||||
}
|
||||
|
||||
export type MetadataItem = {
|
||||
id: string
|
||||
type: DataType
|
||||
name: string
|
||||
}
|
||||
|
||||
export type MetadataItemWithValue = MetadataItem & {
|
||||
value: string | number
|
||||
}
|
||||
|
||||
export type MetadataItemWithValueLength = MetadataItem & {
|
||||
valueLength: number
|
||||
}
|
||||
export enum UpdateType {
|
||||
changeValue = 'changeValue',
|
||||
delete = 'delete',
|
||||
}
|
||||
export type MetadataItemWithEdit = MetadataItemWithValue & {
|
||||
isMultipleValue?: boolean
|
||||
isUpdated?: boolean
|
||||
updateType?: UpdateType
|
||||
}
|
||||
10
web/app/components/datasets/metadata/utils/get-icon.ts
Normal file
10
web/app/components/datasets/metadata/utils/get-icon.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { DataType } from '../types'
|
||||
import { RiHashtag, RiTextSnippet, RiTimeLine } from '@remixicon/react'
|
||||
|
||||
export const getIcon = (type: DataType) => {
|
||||
return ({
|
||||
[DataType.string]: RiTextSnippet,
|
||||
[DataType.number]: RiHashtag,
|
||||
[DataType.time]: RiTimeLine,
|
||||
}[type] || RiTextSnippet)
|
||||
}
|
||||
@ -16,6 +16,7 @@ const WorkplaceSelector = () => {
|
||||
const { notify } = useContext(ToastContext)
|
||||
const { workspaces } = useWorkspacesContext()
|
||||
const currentWorkspace = workspaces.find(v => v.current)
|
||||
const isFreePlan = plan.type === 'sandbox'
|
||||
const handleSwitchWorkspace = async (tenant_id: string) => {
|
||||
try {
|
||||
if (currentWorkspace?.id === tenant_id)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { FC } from 'react'
|
||||
import type { ModelProvider } from '../declarations'
|
||||
import { useLanguage } from '../hooks'
|
||||
import { Openai } from '@/app/components/base/icons/src/vender/other'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { Openai } from '@/app/components/base/icons/src/vender/other'
|
||||
import { AnthropicDark, AnthropicLight } from '@/app/components/base/icons/src/public/llm'
|
||||
import { renderI18nObject } from '@/hooks/use-i18n'
|
||||
import { Theme } from '@/types/app'
|
||||
|
||||
@ -1,160 +0,0 @@
|
||||
import type { PluginDeclaration } from '../types'
|
||||
import { PluginType } from '../types'
|
||||
|
||||
export const toolNeko: PluginDeclaration = {
|
||||
plugin_unique_identifier: 'xxxxxx',
|
||||
version: '0.0.1',
|
||||
author: 'langgenius',
|
||||
name: 'neko',
|
||||
description: {
|
||||
en_US: 'Neko is a cute cat.',
|
||||
zh_Hans: '这是一只可爱的小猫。',
|
||||
pt_BR: 'Neko is a cute cat.',
|
||||
ja_JP: 'Neko is a cute cat.',
|
||||
},
|
||||
icon: '241e5209ecc8b5ce6b7a29a8e50388e9c75b89c3047c6ecd8e552f26de758883.svg',
|
||||
label: {
|
||||
en_US: 'Neko',
|
||||
zh_Hans: 'Neko',
|
||||
pt_BR: 'Neko',
|
||||
ja_JP: 'Neko',
|
||||
},
|
||||
category: 'extension' as any,
|
||||
created_at: '2024-07-12T08:03:44.658609Z',
|
||||
resource: {
|
||||
memory: 1048576,
|
||||
permission: {
|
||||
tool: {
|
||||
enabled: true,
|
||||
},
|
||||
model: {
|
||||
enabled: true,
|
||||
llm: true,
|
||||
text_embedding: false,
|
||||
rerank: false,
|
||||
tts: false,
|
||||
speech2text: false,
|
||||
moderation: false,
|
||||
},
|
||||
node: null,
|
||||
endpoint: {
|
||||
enabled: true,
|
||||
},
|
||||
storage: {
|
||||
enabled: true,
|
||||
size: 1048576,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tools: null,
|
||||
models: null,
|
||||
endpoints: [
|
||||
'provider/neko.yaml',
|
||||
],
|
||||
},
|
||||
tags: [],
|
||||
verified: false,
|
||||
tool: null,
|
||||
model: null,
|
||||
endpoint: null,
|
||||
}
|
||||
|
||||
export const toolNotion = {
|
||||
type: PluginType.tool,
|
||||
org: 'Notion',
|
||||
name: 'notion page search',
|
||||
version: '1.2.0',
|
||||
latest_version: '1.3.0',
|
||||
icon: 'https://via.placeholder.com/150',
|
||||
label: {
|
||||
'en-US': 'Notion Page Search',
|
||||
'zh-Hans': 'Notion 页面搜索',
|
||||
},
|
||||
brief: {
|
||||
'en-US': 'Description: Search Notion pages and open visited ones faster. No admin access required.More and more info...More and more info...More and more info...',
|
||||
'zh-Hans': '搜索 Notion 页面并更快地打开已访问的页面。无需管理员访问权限。More and more info...More and more info...More and more info...',
|
||||
},
|
||||
}
|
||||
|
||||
export const toolNotionManifest: PluginDeclaration = {
|
||||
version: '1.2.0',
|
||||
author: 'Notion',
|
||||
icon: 'https://via.placeholder.com/150',
|
||||
name: 'notion page search',
|
||||
category: PluginType.tool,
|
||||
label: {
|
||||
'en-US': 'Notion Page Search',
|
||||
'zh-Hans': 'Notion 页面搜索',
|
||||
},
|
||||
description: {
|
||||
'en-US': 'Description: Search Notion pages and open visited ones faster. No admin access required.More and more info...More and more info...More and more info...',
|
||||
'zh-Hans': '搜索 Notion 页面并更快地打开已访问的页面。无需管理员访问权限。More and more info...More and more info...More and more info...',
|
||||
},
|
||||
created_at: '2022-01-01',
|
||||
resource: {},
|
||||
plugins: {},
|
||||
verified: true,
|
||||
endpoint: {
|
||||
settings: [],
|
||||
endpoints: [],
|
||||
},
|
||||
tool: {
|
||||
} as any,
|
||||
model: {},
|
||||
}
|
||||
|
||||
export const extensionDallE = {
|
||||
type: PluginType.extension,
|
||||
org: 'OpenAI',
|
||||
name: 'DALL-E',
|
||||
version: '1.1.0',
|
||||
latest_version: '1.2.0',
|
||||
install_count: 1234,
|
||||
icon: 'https://via.placeholder.com/150',
|
||||
label: {
|
||||
'en-US': 'DALL-E',
|
||||
'zh-Hans': 'DALL-E',
|
||||
},
|
||||
brief: {
|
||||
'en-US': 'Description: A simple plugin to use OpenAI DALL-E model.',
|
||||
'zh-Hans': '一个使用 OpenAI DALL-E 模型的简单插件。',
|
||||
},
|
||||
}
|
||||
|
||||
export const modelGPT4 = {
|
||||
type: PluginType.model,
|
||||
org: 'OpenAI',
|
||||
name: 'GPT-4',
|
||||
version: '1.0.0',
|
||||
latest_version: '1.0.0',
|
||||
install_count: 99999,
|
||||
icon: 'https://via.placeholder.com/150',
|
||||
label: {
|
||||
'en-US': 'GPT-4',
|
||||
'zh-Hans': 'GPT-4',
|
||||
},
|
||||
brief: {
|
||||
'en-US': 'Description: A simple plugin to use OpenAI GPT-4 model.',
|
||||
'zh-Hans': '一个使用 OpenAI GPT-4 模型的简单插件。',
|
||||
},
|
||||
}
|
||||
|
||||
export const customTool = {
|
||||
type: PluginType.tool,
|
||||
name: 'notion page search',
|
||||
version: '1.2.0',
|
||||
latest_version: '1.3.0',
|
||||
icon: {
|
||||
content: '🕵️',
|
||||
background: '#FEF7C3',
|
||||
},
|
||||
label: {
|
||||
'en-US': 'Notion Page Search',
|
||||
'zh-Hans': 'Notion 页面搜索',
|
||||
},
|
||||
brief: {
|
||||
'en-US': 'Description: Search Notion pages and open visited ones faster. No admin access required.More and more info...More and more info...More and more info...',
|
||||
'zh-Hans': '搜索 Notion 页面并更快地打开已访问的页面。无需管理员访问权限。More and more info...More and more info...More and more info...',
|
||||
},
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import { useUpdateModelProviders } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import { useInvalidateAllBuiltInTools, useInvalidateAllToolProviders } from '@/service/use-tools'
|
||||
@ -8,7 +9,9 @@ import { PluginType } from '../../types'
|
||||
|
||||
const useRefreshPluginList = () => {
|
||||
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
|
||||
const updateModelProviders = useUpdateModelProviders()
|
||||
const { mutate: refetchLLMModelList } = useModelList(ModelTypeEnum.textGeneration)
|
||||
const { mutate: refetchEmbeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)
|
||||
const { mutate: refetchRerankModelList } = useModelList(ModelTypeEnum.rerank)
|
||||
const { refreshModelProviders } = useProviderContext()
|
||||
|
||||
const invalidateAllToolProviders = useInvalidateAllToolProviders()
|
||||
@ -31,8 +34,10 @@ const useRefreshPluginList = () => {
|
||||
|
||||
// model select
|
||||
if (PluginType.model.includes(manifest.category) || refreshAllType) {
|
||||
updateModelProviders()
|
||||
refreshModelProviders()
|
||||
refetchLLMModelList()
|
||||
refetchEmbeddingModelList()
|
||||
refetchRerankModelList()
|
||||
}
|
||||
|
||||
// agent select
|
||||
|
||||
@ -87,10 +87,13 @@ const InstallByDSLList: FC<Props> = ({
|
||||
const failedIndex: number[] = []
|
||||
const nextPlugins = produce(pluginsRef.current, (draft) => {
|
||||
marketPlaceInDSLIndex.forEach((index, i) => {
|
||||
if (payloads[i])
|
||||
draft[index] = payloads[i]
|
||||
else
|
||||
failedIndex.push(index)
|
||||
if (payloads[i]) {
|
||||
draft[index] = {
|
||||
...payloads[i],
|
||||
version: payloads[i].version || payloads[i].latest_version,
|
||||
}
|
||||
}
|
||||
else { failedIndex.push(index) }
|
||||
})
|
||||
})
|
||||
setPlugins(nextPlugins)
|
||||
@ -192,8 +195,8 @@ const InstallByDSLList: FC<Props> = ({
|
||||
key={index}
|
||||
checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
|
||||
onCheckedChange={handleSelect(index)}
|
||||
payload={plugins[index] as Plugin}
|
||||
version={(d as GitHubItemAndMarketPlaceDependency).value.version!}
|
||||
payload={plugin}
|
||||
version={(d as GitHubItemAndMarketPlaceDependency).value.version! || plugin?.version || ''}
|
||||
versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -136,9 +136,10 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.uploadFailed }))
|
||||
}, [])
|
||||
|
||||
const handleInstalled = useCallback(() => {
|
||||
const handleInstalled = useCallback((notRefresh?: boolean) => {
|
||||
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed }))
|
||||
refreshPluginList(manifest)
|
||||
if (!notRefresh)
|
||||
refreshPluginList(manifest)
|
||||
setIsInstalling(false)
|
||||
onSuccess()
|
||||
}, [manifest, onSuccess, refreshPluginList, setIsInstalling])
|
||||
|
||||
@ -24,7 +24,7 @@ type LoadedProps = {
|
||||
selectedPackage: string
|
||||
onBack: () => void
|
||||
onStartToInstall?: () => void
|
||||
onInstalled: () => void
|
||||
onInstalled: (notRefresh?: boolean) => void
|
||||
onFailed: (message?: string) => void
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ const Loaded: React.FC<LoadedProps> = ({
|
||||
|
||||
const [isInstalling, setIsInstalling] = React.useState(false)
|
||||
const { mutateAsync: installPackageFromGitHub } = useInstallPackageFromGitHub()
|
||||
const { handleRefetch } = usePluginTaskList()
|
||||
const { handleRefetch } = usePluginTaskList(payload.category)
|
||||
const { check } = checkTaskStatus()
|
||||
|
||||
useEffect(() => {
|
||||
@ -127,7 +127,7 @@ const Loaded: React.FC<LoadedProps> = ({
|
||||
onFailed(error)
|
||||
return
|
||||
}
|
||||
onInstalled()
|
||||
onInstalled(true)
|
||||
}
|
||||
catch (e) {
|
||||
if (typeof e === 'string') {
|
||||
|
||||
@ -32,9 +32,10 @@ const ReadyToInstall: FC<Props> = ({
|
||||
}) => {
|
||||
const { refreshPluginList } = useRefreshPluginList()
|
||||
|
||||
const handleInstalled = useCallback(() => {
|
||||
const handleInstalled = useCallback((notRefresh?: boolean) => {
|
||||
onStepChange(InstallStep.installed)
|
||||
refreshPluginList(manifest)
|
||||
if (!notRefresh)
|
||||
refreshPluginList(manifest)
|
||||
setIsInstalling(false)
|
||||
}, [manifest, onStepChange, refreshPluginList, setIsInstalling])
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ type Props = {
|
||||
payload: PluginDeclaration
|
||||
onCancel: () => void
|
||||
onStartToInstall?: () => void
|
||||
onInstalled: () => void
|
||||
onInstalled: (notRefresh?: boolean) => void
|
||||
onFailed: (message?: string) => void
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ const Installed: FC<Props> = ({
|
||||
onCancel()
|
||||
}
|
||||
|
||||
const { handleRefetch } = usePluginTaskList()
|
||||
const { handleRefetch } = usePluginTaskList(payload.category)
|
||||
const handleInstall = async () => {
|
||||
if (isInstalling) return
|
||||
setIsInstalling(true)
|
||||
@ -92,7 +92,7 @@ const Installed: FC<Props> = ({
|
||||
onFailed(error)
|
||||
return
|
||||
}
|
||||
onInstalled()
|
||||
onInstalled(true)
|
||||
}
|
||||
catch (e) {
|
||||
if (typeof e === 'string') {
|
||||
|
||||
@ -54,9 +54,10 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
|
||||
return t(`${i18nPrefix}.installPlugin`)
|
||||
}, [isBundle, step, t])
|
||||
|
||||
const handleInstalled = useCallback(() => {
|
||||
const handleInstalled = useCallback((notRefresh?: boolean) => {
|
||||
setStep(InstallStep.installed)
|
||||
refreshPluginList(manifest)
|
||||
if (!notRefresh)
|
||||
refreshPluginList(manifest)
|
||||
setIsInstalling(false)
|
||||
}, [manifest, refreshPluginList, setIsInstalling])
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ type Props = {
|
||||
payload: PluginManifestInMarket | Plugin
|
||||
onCancel: () => void
|
||||
onStartToInstall?: () => void
|
||||
onInstalled: () => void
|
||||
onInstalled: (notRefresh?: boolean) => void
|
||||
onFailed: (message?: string) => void
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ const Installed: FC<Props> = ({
|
||||
check,
|
||||
stop,
|
||||
} = checkTaskStatus()
|
||||
const { handleRefetch } = usePluginTaskList()
|
||||
const { handleRefetch } = usePluginTaskList(payload.category)
|
||||
|
||||
useEffect(() => {
|
||||
if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier)
|
||||
@ -106,7 +106,7 @@ const Installed: FC<Props> = ({
|
||||
onFailed(error)
|
||||
return
|
||||
}
|
||||
onInstalled()
|
||||
onInstalled(true)
|
||||
}
|
||||
catch (e) {
|
||||
if (typeof e === 'string') {
|
||||
|
||||
@ -63,7 +63,7 @@ const Description = async ({
|
||||
</>
|
||||
)
|
||||
}
|
||||
</h2 >
|
||||
</h2>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -113,6 +113,7 @@ const DetailHeader = ({
|
||||
},
|
||||
payload: {
|
||||
type: PluginSource.github,
|
||||
category: detail.declaration.category,
|
||||
github: {
|
||||
originalPackageInfo: {
|
||||
id: detail.plugin_unique_identifier,
|
||||
@ -287,6 +288,7 @@ const DetailHeader = ({
|
||||
isShowUpdateModal && (
|
||||
<UpdateFromMarketplace
|
||||
payload={{
|
||||
category: detail.declaration.category,
|
||||
originalPackageInfo: {
|
||||
id: detail.plugin_unique_identifier,
|
||||
payload: detail.declaration,
|
||||
|
||||
@ -14,6 +14,7 @@ import { useGitHubReleases } from '../install-plugin/hooks'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import type { PluginType } from '@/app/components/plugins/types'
|
||||
|
||||
const i18nPrefix = 'plugin.action'
|
||||
|
||||
@ -22,6 +23,7 @@ type Props = {
|
||||
installationId: string
|
||||
pluginUniqueIdentifier: string
|
||||
pluginName: string
|
||||
category: PluginType
|
||||
usedInApps: number
|
||||
isShowFetchNewVersion: boolean
|
||||
isShowInfo: boolean
|
||||
@ -34,6 +36,7 @@ const Action: FC<Props> = ({
|
||||
installationId,
|
||||
pluginUniqueIdentifier,
|
||||
pluginName,
|
||||
category,
|
||||
isShowFetchNewVersion,
|
||||
isShowInfo,
|
||||
isShowDelete,
|
||||
@ -67,6 +70,7 @@ const Action: FC<Props> = ({
|
||||
},
|
||||
payload: {
|
||||
type: PluginSource.github,
|
||||
category,
|
||||
github: {
|
||||
originalPackageInfo: {
|
||||
id: pluginUniqueIdentifier,
|
||||
@ -94,7 +98,7 @@ const Action: FC<Props> = ({
|
||||
hideDeleteConfirm()
|
||||
onDelete()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [installationId, onDelete])
|
||||
return (
|
||||
<div className='flex space-x-1'>
|
||||
|
||||
@ -20,11 +20,9 @@ import Title from '../card/base/title'
|
||||
import Action from './action'
|
||||
import cn from '@/utils/classnames'
|
||||
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import { useInvalidateAllBuiltInTools, useInvalidateAllToolProviders } from '@/service/use-tools'
|
||||
import { useSingleCategories } from '../hooks'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@ -39,10 +37,7 @@ const PluginItem: FC<Props> = ({
|
||||
const { categoriesMap } = useSingleCategories()
|
||||
const currentPluginID = usePluginPageContext(v => v.currentPluginID)
|
||||
const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID)
|
||||
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
|
||||
const invalidateAllToolProviders = useInvalidateAllToolProviders()
|
||||
const invalidateAllBuiltinTools = useInvalidateAllBuiltInTools()
|
||||
const { refreshModelProviders } = useProviderContext()
|
||||
const { refreshPluginList } = useRefreshPluginList()
|
||||
|
||||
const {
|
||||
source,
|
||||
@ -60,13 +55,7 @@ const PluginItem: FC<Props> = ({
|
||||
}, [source, author])
|
||||
|
||||
const handleDelete = () => {
|
||||
invalidateInstalledPluginList()
|
||||
if (PluginType.model.includes(category))
|
||||
refreshModelProviders()
|
||||
if (PluginType.tool.includes(category)) {
|
||||
invalidateAllToolProviders()
|
||||
invalidateAllBuiltinTools()
|
||||
}
|
||||
refreshPluginList({ category } as any)
|
||||
}
|
||||
const getValueFromI18nObject = useRenderI18nObject()
|
||||
const title = getValueFromI18nObject(label)
|
||||
@ -116,6 +105,7 @@ const PluginItem: FC<Props> = ({
|
||||
isShowDelete
|
||||
meta={meta}
|
||||
onDelete={handleDelete}
|
||||
category={category}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -29,8 +29,8 @@ const DebugInfo: FC = () => {
|
||||
popupContent={
|
||||
<>
|
||||
<div className='flex items-center gap-1 self-stretch'>
|
||||
<span className='flex flex-col justify-center items-start flex-grow flex-shrink-0 basis-0 text-text-secondary system-sm-semibold'>{t(`${i18nPrefix}.title`)}</span>
|
||||
<a href='' target='_blank' className='flex items-center gap-0.5 text-text-accent-light-mode-only cursor-pointer'>
|
||||
<span className='flex flex-col justify-center items-start grow shrink-0 basis-0 text-text-secondary system-sm-semibold'>{t(`${i18nPrefix}.title`)}</span>
|
||||
<a href='https://docs.dify.ai/plugins/quick-start/develop-plugins/debug-plugin' target='_blank' className='flex items-center gap-0.5 text-text-accent-light-mode-only cursor-pointer'>
|
||||
<span className='system-xs-medium'>{t(`${i18nPrefix}.viewDocs`)}</span>
|
||||
<RiArrowRightUpLine className='w-3 h-3' />
|
||||
</a>
|
||||
|
||||
@ -151,6 +151,7 @@ export type Permissions = {
|
||||
}
|
||||
|
||||
export type UpdateFromMarketPlacePayload = {
|
||||
category: PluginType
|
||||
originalPackageInfo: {
|
||||
id: string
|
||||
payload: PluginDeclaration
|
||||
@ -173,6 +174,7 @@ export type UpdateFromGitHubPayload = {
|
||||
|
||||
export type UpdatePluginPayload = {
|
||||
type: PluginSource
|
||||
category: PluginType
|
||||
marketPlace?: UpdateFromMarketPlacePayload
|
||||
github?: UpdateFromGitHubPayload
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ const UpdatePluginModal: FC<Props> = ({
|
||||
}
|
||||
|
||||
const [uploadStep, setUploadStep] = useState<UploadStep>(UploadStep.notStarted)
|
||||
const { handleRefetch } = usePluginTaskList()
|
||||
const { handleRefetch } = usePluginTaskList(payload.category)
|
||||
|
||||
const configBtnText = useMemo(() => {
|
||||
return ({
|
||||
|
||||
@ -321,6 +321,7 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
setControlSend(Date.now())
|
||||
// clear run once task status
|
||||
setControlStopResponding(Date.now())
|
||||
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
showResSidebar()
|
||||
}
|
||||
@ -400,6 +401,7 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
transfer_methods: file_upload.allowed_file_upload_methods || file_upload.allowed_upload_methods,
|
||||
// legacy of image upload compatible
|
||||
image_file_size_limit: appParams?.system_parameters?.image_file_size_limit,
|
||||
fileUploadConfig: appParams?.system_parameters,
|
||||
})
|
||||
const prompt_variables = userInputsFormToPromptVariables(user_input_form)
|
||||
setPromptConfig({
|
||||
|
||||
@ -69,7 +69,7 @@ const ProviderList = () => {
|
||||
className='relative flex flex-col overflow-y-auto bg-background-body grow'
|
||||
>
|
||||
<div className={cn(
|
||||
'sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] z-20 flex-wrap gap-y-2',
|
||||
'sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-background-body z-20 flex-wrap gap-y-2',
|
||||
currentProvider && 'pr-6',
|
||||
)}>
|
||||
<TabSliderNew
|
||||
|
||||
@ -27,6 +27,7 @@ import SearchBox from '@/app/components/plugins/marketplace/search-box'
|
||||
import {
|
||||
Plus02,
|
||||
} from '@/app/components/base/icons/src/vender/line/general'
|
||||
import classNames from '@/utils/classnames'
|
||||
|
||||
type NodeSelectorProps = {
|
||||
open?: boolean
|
||||
|
||||
@ -162,7 +162,7 @@ export const useWorkflowRun = () => {
|
||||
else
|
||||
ttsUrl = `/apps/${params.appId}/text-to-audio`
|
||||
}
|
||||
const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', (_: any): any => { })
|
||||
const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', (_: any): any => {})
|
||||
|
||||
ssePost(
|
||||
url,
|
||||
|
||||
@ -61,7 +61,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
showMessageLogModal: state.showMessageLogModal,
|
||||
})))
|
||||
const showSingleRunPanel = useStore(s => s.showSingleRunPanel)
|
||||
const panelWidth = localStorage.getItem('workflow-node-panel-width') ? Number.parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 420
|
||||
const panelWidth = localStorage.getItem('workflow-node-panel-width') ? parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 420
|
||||
const {
|
||||
setPanelWidth,
|
||||
} = useWorkflow()
|
||||
|
||||
@ -168,6 +168,46 @@ const translation = {
|
||||
preprocessDocument: '{{num}} Preprocess Documents',
|
||||
allKnowledge: 'All Knowledge',
|
||||
allKnowledgeDescription: 'Select to display all knowledge in this workspace. Only the Workspace Owner can manage all knowledge.',
|
||||
metadata: {
|
||||
metadata: 'Metadata',
|
||||
addMetadata: 'Add Metadata',
|
||||
createMetadata: {
|
||||
title: 'New Metadata',
|
||||
back: 'Back',
|
||||
type: 'Type',
|
||||
name: 'Name',
|
||||
namePlaceholder: 'Add metadata name',
|
||||
},
|
||||
batchEditMetadata: {
|
||||
editMetadata: 'Edit Metadata',
|
||||
editDocumentsNum: 'Editing {{num}} documents',
|
||||
applyToAllSelectDocument: 'Apply to all selected documents',
|
||||
applyToAllSelectDocumentTip: 'Automatically create all the above edited and new metadata for all selected documents, otherwise editing metadata will only apply to documents with it.',
|
||||
multipleValue: 'Multiple Value',
|
||||
},
|
||||
selectMetadata: {
|
||||
search: 'Search metadata',
|
||||
newAction: 'New Metadata',
|
||||
manageAction: 'Manage',
|
||||
},
|
||||
datasetMetadata: {
|
||||
description: 'You can manage all metadata in this knowledge here. Modifications will be synchronized to every document.',
|
||||
addMetaData: 'Add Metadata',
|
||||
values: '{{num}} Values',
|
||||
disabled: 'Disabled',
|
||||
rename: 'Rename',
|
||||
name: 'Name',
|
||||
namePlaceholder: 'Metadata name',
|
||||
builtIn: 'Built-in',
|
||||
builtInDescription: 'Built-in metadata is automatically extracted and generated. It must be enabled before use and cannot be edited.',
|
||||
},
|
||||
documentMetadata: {
|
||||
metadataToolTip: 'Metadata serves as a critical filter that enhances the accuracy and relevance of information retrieval. You can modify and add metadata for this document here.',
|
||||
startLabeling: 'Start Labeling',
|
||||
documentInformation: 'Document Information',
|
||||
technicalParameters: 'Technical Parameters',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
||||
@ -2,6 +2,9 @@ const translation = {
|
||||
common: {
|
||||
welcome: 'आपका स्वागत है',
|
||||
appUnavailable: 'ऐप उपलब्ध नहीं है',
|
||||
appUnknownError: 'अज्ञात त्रुटि, कृपया पुनः प्रयास करें',
|
||||
// @ts-expect-error TODO: fix this
|
||||
// eslint-disable-next-line no-dupe-keys
|
||||
appUnknownError: 'ऐप अनुपलब्ध है',
|
||||
},
|
||||
chat: {
|
||||
|
||||
@ -168,6 +168,46 @@ const translation = {
|
||||
preprocessDocument: '{{num}} 个预处理文档',
|
||||
allKnowledge: '所有知识库',
|
||||
allKnowledgeDescription: '选择以显示该工作区内所有知识库。只有工作区所有者才能管理所有知识库。',
|
||||
metadata: {
|
||||
metadata: '元数据',
|
||||
addMetadata: '添加元数据',
|
||||
createMetadata: {
|
||||
title: '新建元数据',
|
||||
back: '返回',
|
||||
type: '类型',
|
||||
name: '名称',
|
||||
namePlaceholder: '添加元数据名称',
|
||||
},
|
||||
batchEditMetadata: {
|
||||
editMetadata: '编辑元数据',
|
||||
editDocumentsNum: '编辑 {{num}} 个文档',
|
||||
applyToAllSelectDocument: '应用于所有选定文档',
|
||||
applyToAllSelectDocumentTip: '自动为所有选定文档创建上述编辑和新元数据,否则仅对具有元数据的文档应用编辑。',
|
||||
multipleValue: '多个值',
|
||||
},
|
||||
selectMetadata: {
|
||||
search: '搜索元数据',
|
||||
newAction: '新建元数据',
|
||||
manageAction: '管理',
|
||||
},
|
||||
datasetMetadata: {
|
||||
description: '元数据是关于文档的数据,用于描述文档的属性。元数据可以帮助您更好地组织和管理文档。',
|
||||
addMetaData: '添加元数据',
|
||||
values: '{{num}} 个值',
|
||||
disabled: '已禁用',
|
||||
rename: '重命名',
|
||||
name: '名称',
|
||||
namePlaceholder: '元数据名称',
|
||||
builtIn: '内置',
|
||||
builtInDescription: '内置元数据是系统预定义的元数据,您可以在此处查看和管理内置元数据。',
|
||||
},
|
||||
documentMetadata: {
|
||||
metadataToolTip: '元数据是关于文档的数据,用于描述文档的属性。元数据可以帮助您更好地组织和管理文档。',
|
||||
startLabeling: '开始标注',
|
||||
documentInformation: '文档信息',
|
||||
technicalParameters: '技术参数',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import type { I18nText } from '@/i18n/language'
|
||||
|
||||
export type CommonResponse = {
|
||||
export interface CommonResponse {
|
||||
result: 'success' | 'fail'
|
||||
}
|
||||
|
||||
export type OauthResponse = {
|
||||
export interface OauthResponse {
|
||||
redirect_url: string
|
||||
}
|
||||
|
||||
export type SetupStatusResponse = {
|
||||
export interface SetupStatusResponse {
|
||||
step: 'finished' | 'not_started'
|
||||
setup_at?: Date
|
||||
}
|
||||
|
||||
export type InitValidateStatusResponse = {
|
||||
export interface InitValidateStatusResponse {
|
||||
status: 'finished' | 'not_started'
|
||||
}
|
||||
|
||||
export type UserProfileResponse = {
|
||||
export interface UserProfileResponse {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
@ -33,13 +33,13 @@ export type UserProfileResponse = {
|
||||
created_at?: string
|
||||
}
|
||||
|
||||
export type UserProfileOriginResponse = {
|
||||
export interface UserProfileOriginResponse {
|
||||
json: () => Promise<UserProfileResponse>
|
||||
bodyUsed: boolean
|
||||
headers: any
|
||||
}
|
||||
|
||||
export type LangGeniusVersionResponse = {
|
||||
export interface LangGeniusVersionResponse {
|
||||
current_version: string
|
||||
latest_version: string
|
||||
version: string
|
||||
@ -49,7 +49,7 @@ export type LangGeniusVersionResponse = {
|
||||
current_env: string
|
||||
}
|
||||
|
||||
export type TenantInfoResponse = {
|
||||
export interface TenantInfoResponse {
|
||||
name: string
|
||||
created_at: string
|
||||
providers: Array<{
|
||||
@ -80,14 +80,14 @@ export enum ProviderName {
|
||||
Tongyi = 'tongyi',
|
||||
ChatGLM = 'chatglm',
|
||||
}
|
||||
export type ProviderAzureToken = {
|
||||
export interface ProviderAzureToken {
|
||||
openai_api_base?: string
|
||||
openai_api_key?: string
|
||||
}
|
||||
export type ProviderAnthropicToken = {
|
||||
export interface ProviderAnthropicToken {
|
||||
anthropic_api_key?: string
|
||||
}
|
||||
export type ProviderTokenType = {
|
||||
export interface ProviderTokenType {
|
||||
[ProviderName.OPENAI]: string
|
||||
[ProviderName.AZURE_OPENAI]: ProviderAzureToken
|
||||
[ProviderName.ANTHROPIC]: ProviderAnthropicToken
|
||||
@ -110,14 +110,14 @@ export type ProviderHosted = Provider & {
|
||||
quota_used: number
|
||||
}
|
||||
|
||||
export type AccountIntegrate = {
|
||||
export interface AccountIntegrate {
|
||||
provider: 'google' | 'github'
|
||||
created_at: number
|
||||
is_bound: boolean
|
||||
link: string
|
||||
}
|
||||
|
||||
export type IWorkspace = {
|
||||
export interface IWorkspace {
|
||||
id: string
|
||||
name: string
|
||||
plan: string
|
||||
@ -137,7 +137,7 @@ export type ICurrentWorkspace = Omit<IWorkspace, 'current'> & {
|
||||
}
|
||||
}
|
||||
|
||||
export type DataSourceNotionPage = {
|
||||
export interface DataSourceNotionPage {
|
||||
page_icon: null | {
|
||||
type: string | null
|
||||
url: string | null
|
||||
@ -156,7 +156,7 @@ export type NotionPage = DataSourceNotionPage & {
|
||||
|
||||
export type DataSourceNotionPageMap = Record<string, DataSourceNotionPage & { workspace_id: string }>
|
||||
|
||||
export type DataSourceNotionWorkspace = {
|
||||
export interface DataSourceNotionWorkspace {
|
||||
workspace_name: string
|
||||
workspace_id: string
|
||||
workspace_icon: string | null
|
||||
@ -166,7 +166,7 @@ export type DataSourceNotionWorkspace = {
|
||||
|
||||
export type DataSourceNotionWorkspaceMap = Record<string, DataSourceNotionWorkspace>
|
||||
|
||||
export type DataSourceNotion = {
|
||||
export interface DataSourceNotion {
|
||||
id: string
|
||||
provider: string
|
||||
is_bound: boolean
|
||||
@ -181,12 +181,12 @@ export enum DataSourceProvider {
|
||||
jinaReader = 'jinareader',
|
||||
}
|
||||
|
||||
export type FirecrawlConfig = {
|
||||
export interface FirecrawlConfig {
|
||||
api_key: string
|
||||
base_url: string
|
||||
}
|
||||
|
||||
export type DataSourceItem = {
|
||||
export interface DataSourceItem {
|
||||
id: string
|
||||
category: DataSourceCategory
|
||||
provider: DataSourceProvider
|
||||
@ -195,15 +195,15 @@ export type DataSourceItem = {
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
export type DataSources = {
|
||||
export interface DataSources {
|
||||
sources: DataSourceItem[]
|
||||
}
|
||||
|
||||
export type GithubRepo = {
|
||||
export interface GithubRepo {
|
||||
stargazers_count: number
|
||||
}
|
||||
|
||||
export type PluginProvider = {
|
||||
export interface PluginProvider {
|
||||
tool_name: string
|
||||
is_enabled: boolean
|
||||
credentials: {
|
||||
@ -211,7 +211,7 @@ export type PluginProvider = {
|
||||
} | null
|
||||
}
|
||||
|
||||
export type FileUploadConfigResponse = {
|
||||
export interface FileUploadConfigResponse {
|
||||
batch_count_limit: number
|
||||
image_file_size_limit?: number | string // default is 10MB
|
||||
file_size_limit: number // default is 15MB
|
||||
@ -234,14 +234,14 @@ export type InvitationResponse = CommonResponse & {
|
||||
invitation_results: InvitationResult[]
|
||||
}
|
||||
|
||||
export type ApiBasedExtension = {
|
||||
export interface ApiBasedExtension {
|
||||
id?: string
|
||||
name?: string
|
||||
api_endpoint?: string
|
||||
api_key?: string
|
||||
}
|
||||
|
||||
export type CodeBasedExtensionForm = {
|
||||
export interface CodeBasedExtensionForm {
|
||||
type: string
|
||||
label: I18nText
|
||||
variable: string
|
||||
@ -252,17 +252,17 @@ export type CodeBasedExtensionForm = {
|
||||
max_length?: number
|
||||
}
|
||||
|
||||
export type CodeBasedExtensionItem = {
|
||||
export interface CodeBasedExtensionItem {
|
||||
name: string
|
||||
label: any
|
||||
form_schema: CodeBasedExtensionForm[]
|
||||
}
|
||||
export type CodeBasedExtension = {
|
||||
export interface CodeBasedExtension {
|
||||
module: string
|
||||
data: CodeBasedExtensionItem[]
|
||||
}
|
||||
|
||||
export type ExternalDataTool = {
|
||||
export interface ExternalDataTool {
|
||||
type?: string
|
||||
label?: string
|
||||
icon?: string
|
||||
@ -274,7 +274,7 @@ export type ExternalDataTool = {
|
||||
} & Partial<Record<string, any>>
|
||||
}
|
||||
|
||||
export type ModerateResponse = {
|
||||
export interface ModerateResponse {
|
||||
flagged: boolean
|
||||
text: string
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ function waitUntilTokenRefreshed() {
|
||||
const isRefreshingSignAvailable = function (delta: number) {
|
||||
const nowTime = new Date().getTime()
|
||||
const lastTime = globalThis.localStorage.getItem('last_refresh_time') || '0'
|
||||
return nowTime - Number.parseInt(lastTime) <= delta
|
||||
return nowTime - parseInt(lastTime) <= delta
|
||||
}
|
||||
|
||||
// only one request can send
|
||||
|
||||
@ -11,6 +11,7 @@ import type {
|
||||
PluginDetail,
|
||||
PluginInfoFromMarketPlace,
|
||||
PluginTask,
|
||||
PluginType,
|
||||
PluginsFromMarketplaceByInfoResponse,
|
||||
PluginsFromMarketplaceResponse,
|
||||
VersionInfo,
|
||||
@ -31,6 +32,7 @@ import {
|
||||
import { useInvalidateAllBuiltInTools } from './use-tools'
|
||||
import usePermission from '@/app/components/plugins/plugin-page/use-permission'
|
||||
import { uninstallPlugin } from '@/service/plugins'
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
|
||||
const NAME_SPACE = 'plugins'
|
||||
|
||||
@ -367,10 +369,11 @@ export const useFetchPluginsInMarketPlaceByInfo = (infos: Record<string, any>[])
|
||||
}
|
||||
|
||||
const usePluginTaskListKey = [NAME_SPACE, 'pluginTaskList']
|
||||
export const usePluginTaskList = () => {
|
||||
export const usePluginTaskList = (category?: PluginType) => {
|
||||
const {
|
||||
canManagement,
|
||||
} = usePermission()
|
||||
const { refreshPluginList } = useRefreshPluginList()
|
||||
const {
|
||||
data,
|
||||
isFetched,
|
||||
@ -383,8 +386,12 @@ export const usePluginTaskList = () => {
|
||||
refetchInterval: (lastQuery) => {
|
||||
const lastData = lastQuery.state.data
|
||||
const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
|
||||
if (taskDone)
|
||||
const taskAllFailed = lastData?.tasks.every(task => task.status === TaskStatus.failed)
|
||||
if (taskDone) {
|
||||
if (lastData?.tasks.length && !taskAllFailed)
|
||||
refreshPluginList(category ? { category } as any : undefined, !category)
|
||||
return false
|
||||
}
|
||||
|
||||
return 5000
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user