Compare commits

..

11 Commits

71 changed files with 203 additions and 535 deletions

View File

@ -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}"

View File

@ -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"

View File

@ -14,6 +14,7 @@ from controllers.console.wraps import account_initialization_required, enterpris
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
from core.indexing_runner import IndexingRunner
from core.model_runtime.entities.model_entities import ModelType
from core.plugin.entities.plugin import ModelProviderID
from core.provider_manager import ProviderManager
from core.rag.datasource.vdb.vector_type import VectorType
from core.rag.extractor.entity.extract_setting import ExtractSetting
@ -72,7 +73,9 @@ class DatasetListApi(Resource):
data = marshal(datasets, dataset_detail_fields)
for item in data:
# convert embedding_model_provider to plugin standard format
if item["indexing_technique"] == "high_quality":
item["embedding_model_provider"] = str(ModelProviderID(item["embedding_model_provider"]))
item_model = f"{item['embedding_model']}:{item['embedding_model_provider']}"
if item_model in model_names:
item["embedding_available"] = True

View File

@ -329,6 +329,7 @@ class BaseAgentRunner(AppRunner):
)
if not updated_agent_thought:
raise ValueError("agent thought not found")
agent_thought = updated_agent_thought
if thought:
agent_thought.thought = thought

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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

View File

@ -864,6 +864,5 @@ class WorkflowCycleManage:
status=event.status,
data=event.data,
metadata=event.metadata,
node_id=event.node_id,
),
)

View File

@ -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):

View File

@ -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

View File

@ -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.plugin_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.plugin_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

View File

@ -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"},
)

View File

@ -30,6 +30,7 @@ from core.model_runtime.entities.provider_entities import (
ProviderEntity,
)
from core.model_runtime.model_providers.model_provider_factory import ModelProviderFactory
from core.plugin.entities.plugin import ModelProviderID
from extensions import ext_hosting_provider
from extensions.ext_database import db
from extensions.ext_redis import redis_client
@ -191,7 +192,7 @@ class ProviderManager:
model_settings=model_settings,
)
provider_configurations[provider_name] = provider_configuration
provider_configurations[str(ModelProviderID(provider_name))] = provider_configuration
# Return the encapsulated object
return provider_configurations

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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.")

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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)")
)

View File

@ -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

View File

@ -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(

View File

@ -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
]

View File

@ -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)

View File

@ -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

View File

@ -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
"""

View File

@ -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(

View File

@ -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(

View File

@ -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}

View File

@ -31,6 +31,7 @@ server {
location /e {
proxy_pass http://plugin_daemon:5002;
proxy_set_header Dify-Hook-Url $scheme://$host$request_uri;
include proxy.conf;
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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'>

View File

@ -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

View File

@ -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>
)

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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...',
},
}

View File

@ -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

View File

@ -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}`)}
/>
)

View File

@ -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])

View File

@ -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') {

View File

@ -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])

View File

@ -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') {

View File

@ -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])

View File

@ -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') {

View File

@ -63,7 +63,7 @@ const Description = async ({
</>
)
}
</h2 >
</h2>
</>
)
}

View File

@ -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,

View File

@ -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'>

View File

@ -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>

View File

@ -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>

View File

@ -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
}

View File

@ -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 ({

View File

@ -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({

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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()

View File

@ -112,10 +112,13 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
if (userProfileResponse && !userProfileResponse.bodyUsed) {
const result = await userProfileResponse.json()
setUserProfile(result)
const current_version = userProfileResponse.headers.get('x-version')
const current_env = process.env.NODE_ENV === 'development' ? 'DEVELOPMENT' : userProfileResponse.headers.get('x-env')
const versionData = await fetchLanggeniusVersion({ url: '/version', params: { current_version } })
setLangeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env })
if (process.env.NEXT_PUBLIC_SITE_ABOUT !== 'hide' && document?.body?.getAttribute('data-public-site-about') !== 'hide') {
const current_version = userProfileResponse.headers.get('x-version')
const current_env = process.env.NODE_ENV === 'development' ? 'DEVELOPMENT' : userProfileResponse.headers.get('x-env')
const versionData = await fetchLanggeniusVersion({ url: '/version', params: { current_version } })
setLangeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env })
}
}
}, [userProfileResponse])

View File

@ -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: {

View File

@ -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
}

View File

@ -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

View File

@ -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
},