mirror of
https://github.com/langgenius/dify.git
synced 2026-01-08 07:14:14 +00:00
Compare commits
33 Commits
feat/14009
...
fix/tool-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4154fb7d9 | ||
|
|
b0a804e22f | ||
|
|
ce545274a6 | ||
|
|
aa6c951e8c | ||
|
|
c4f4dfc3fb | ||
|
|
548f6ef2b6 | ||
|
|
b15ff4eb8c | ||
|
|
7790214620 | ||
|
|
3942e45cab | ||
|
|
2ace9ae4e4 | ||
|
|
5ac0ef6253 | ||
|
|
f552667312 | ||
|
|
5669a18bd8 | ||
|
|
a97d73ab05 | ||
|
|
252d2c425b | ||
|
|
09fc4bba61 | ||
|
|
79d4db8541 | ||
|
|
9c42626772 | ||
|
|
bbfe83c86b | ||
|
|
55aa4e424a | ||
|
|
8015f5c0c5 | ||
|
|
f3fe14863d | ||
|
|
d96c368660 | ||
|
|
3f34b8b0d1 | ||
|
|
6a58ea9e56 | ||
|
|
23888398d1 | ||
|
|
bfbc5eb91e | ||
|
|
98b0d4169e | ||
|
|
356cd271b2 | ||
|
|
baf7561cf8 | ||
|
|
b09f22961c | ||
|
|
f3ad3a5dfd | ||
|
|
ee49d321c5 |
16
README_FR.md
16
README_FR.md
@@ -55,7 +55,7 @@
|
||||
Dify est une plateforme de développement d'applications LLM open source. Son interface intuitive combine un flux de travail d'IA, un pipeline RAG, des capacités d'agent, une gestion de modèles, des fonctionnalités d'observabilité, et plus encore, vous permettant de passer rapidement du prototype à la production. Voici une liste des fonctionnalités principales:
|
||||
</br> </br>
|
||||
|
||||
**1. Flux de travail**:
|
||||
**1. Flux de travail** :
|
||||
Construisez et testez des flux de travail d'IA puissants sur un canevas visuel, en utilisant toutes les fonctionnalités suivantes et plus encore.
|
||||
|
||||
|
||||
@@ -63,27 +63,25 @@ Dify est une plateforme de développement d'applications LLM open source. Son in
|
||||
|
||||
|
||||
|
||||
**2. Prise en charge complète des modèles**:
|
||||
**2. Prise en charge complète des modèles** :
|
||||
Intégration transparente avec des centaines de LLM propriétaires / open source provenant de dizaines de fournisseurs d'inférence et de solutions auto-hébergées, couvrant GPT, Mistral, Llama3, et tous les modèles compatibles avec l'API OpenAI. Une liste complète des fournisseurs de modèles pris en charge se trouve [ici](https://docs.dify.ai/getting-started/readme/model-providers).
|
||||
|
||||

|
||||
|
||||
|
||||
**3. IDE de prompt**:
|
||||
**3. IDE de prompt** :
|
||||
Interface intuitive pour créer des prompts, comparer les performances des modèles et ajouter des fonctionnalités supplémentaires telles que la synthèse vocale à une application basée sur des chats.
|
||||
|
||||
**4. Pipeline RAG**:
|
||||
**4. Pipeline RAG** :
|
||||
Des capacités RAG étendues qui couvrent tout, de l'ingestion de documents à la récupération, avec un support prêt à l'emploi pour l'extraction de texte à partir de PDF, PPT et autres formats de document courants.
|
||||
|
||||
**5. Capac
|
||||
|
||||
ités d'agent**:
|
||||
**5. Capacités d'agent** :
|
||||
Vous pouvez définir des agents basés sur l'appel de fonction LLM ou ReAct, et ajouter des outils pré-construits ou personnalisés pour l'agent. Dify fournit plus de 50 outils intégrés pour les agents d'IA, tels que la recherche Google, DALL·E, Stable Diffusion et WolframAlpha.
|
||||
|
||||
**6. LLMOps**:
|
||||
**6. LLMOps** :
|
||||
Surveillez et analysez les journaux d'application et les performances au fil du temps. Vous pouvez continuellement améliorer les prompts, les ensembles de données et les modèles en fonction des données de production et des annotations.
|
||||
|
||||
**7. Backend-as-a-Service**:
|
||||
**7. Backend-as-a-Service** :
|
||||
Toutes les offres de Dify sont accompagnées d'API correspondantes, vous permettant d'intégrer facilement Dify dans votre propre logique métier.
|
||||
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ DifyはオープンソースのLLMアプリケーション開発プラットフ
|
||||
|
||||
- **企業/組織向けのDify</br>**
|
||||
企業中心の機能を提供しています。[メールを送信](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry)して企業のニーズについて相談してください。 </br>
|
||||
> AWSを使用しているスタートアップ企業や中小企業の場合は、[AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6)のDify Premiumをチェックして、ワンクリックで自分のAWS VPCにデプロイできます。さらに、手頃な価格のAMIオファリングどして、ロゴやブランディングをカスタマイズしてアプリケーションを作成するオプションがあります。
|
||||
> AWSを使用しているスタートアップ企業や中小企業の場合は、[AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-t23mebxzwjhu6)のDify Premiumをチェックして、ワンクリックで自分のAWS VPCにデプロイできます。さらに、手頃な価格のAMIオファリングとして、ロゴやブランディングをカスタマイズしてアプリケーションを作成するオプションがあります。
|
||||
|
||||
|
||||
## 最新の情報を入手
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from urllib.parse import quote
|
||||
|
||||
from flask import Response, request
|
||||
from flask_restful import Resource, reqparse # type: ignore
|
||||
from werkzeug.exceptions import NotFound
|
||||
@@ -71,7 +73,8 @@ class FilePreviewApi(Resource):
|
||||
if upload_file.size > 0:
|
||||
response.headers["Content-Length"] = str(upload_file.size)
|
||||
if args["as_attachment"]:
|
||||
response.headers["Content-Disposition"] = f"attachment; filename={upload_file.name}"
|
||||
encoded_filename = quote(upload_file.name)
|
||||
response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@@ -336,6 +336,10 @@ class DocumentUpdateByFileApi(DatasetApiResource):
|
||||
|
||||
if not dataset:
|
||||
raise ValueError("Dataset is not exist.")
|
||||
|
||||
# indexing_technique is already set in dataset since this is an update
|
||||
args["indexing_technique"] = dataset.indexing_technique
|
||||
|
||||
if "file" in request.files:
|
||||
# save file info
|
||||
file = request.files["file"]
|
||||
|
||||
@@ -42,7 +42,6 @@ class MessageBasedAppGenerator(BaseAppGenerator):
|
||||
ChatAppGenerateEntity,
|
||||
CompletionAppGenerateEntity,
|
||||
AgentChatAppGenerateEntity,
|
||||
AgentChatAppGenerateEntity,
|
||||
],
|
||||
queue_manager: AppQueueManager,
|
||||
conversation: Conversation,
|
||||
|
||||
@@ -101,11 +101,13 @@ class ProviderManager:
|
||||
)
|
||||
|
||||
# append providers with langgenius/openai/openai
|
||||
for provider_name in list(provider_name_to_provider_records_dict.keys()):
|
||||
provider_name_list = list(provider_name_to_provider_records_dict.keys())
|
||||
for provider_name in provider_name_list:
|
||||
provider_id = ModelProviderID(provider_name)
|
||||
provider_name_to_provider_records_dict[str(provider_id)] = provider_name_to_provider_records_dict[
|
||||
provider_name
|
||||
]
|
||||
if str(provider_id) not in provider_name_list:
|
||||
provider_name_to_provider_records_dict[str(provider_id)] = provider_name_to_provider_records_dict[
|
||||
provider_name
|
||||
]
|
||||
|
||||
# Get all provider model records of the workspace
|
||||
provider_name_to_provider_model_records_dict = self._get_all_provider_models(tenant_id)
|
||||
|
||||
@@ -111,8 +111,9 @@ class ChromaVector(BaseVector):
|
||||
for index in range(len(ids)):
|
||||
distance = distances[index]
|
||||
metadata = dict(metadatas[index])
|
||||
if distance >= score_threshold:
|
||||
metadata["score"] = distance
|
||||
score = 1 - distance
|
||||
if score > score_threshold:
|
||||
metadata["score"] = score
|
||||
doc = Document(
|
||||
page_content=documents[index],
|
||||
metadata=metadata,
|
||||
|
||||
@@ -105,10 +105,10 @@ class ApiTool(Tool):
|
||||
needed_parameters = [parameter for parameter in (self.api_bundle.parameters or []) if parameter.required]
|
||||
for parameter in needed_parameters:
|
||||
if parameter.required and parameter.name not in parameters:
|
||||
raise ToolParameterValidationError(f"Missing required parameter {parameter.name}")
|
||||
|
||||
if parameter.default is not None and parameter.name not in parameters:
|
||||
parameters[parameter.name] = parameter.default
|
||||
if parameter.default is not None:
|
||||
parameters[parameter.name] = parameter.default
|
||||
else:
|
||||
raise ToolParameterValidationError(f"Missing required parameter {parameter.name}")
|
||||
|
||||
return headers
|
||||
|
||||
|
||||
@@ -246,10 +246,11 @@ class ToolEngine:
|
||||
+ "you do not need to create it, just tell the user to check it now."
|
||||
)
|
||||
elif response.type == ToolInvokeMessage.MessageType.JSON:
|
||||
text = json.dumps(cast(ToolInvokeMessage.JsonMessage, response.message).json_object, ensure_ascii=False)
|
||||
result += f"tool response: {text}."
|
||||
result = json.dumps(
|
||||
cast(ToolInvokeMessage.JsonMessage, response.message).json_object, ensure_ascii=False
|
||||
)
|
||||
else:
|
||||
result += f"tool response: {response.message!r}."
|
||||
result += str(response.message)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -590,6 +590,7 @@ class IterationNode(BaseNode[IterationNodeData]):
|
||||
with flask_app.app_context():
|
||||
parallel_mode_run_id = uuid.uuid4().hex
|
||||
graph_engine_copy = graph_engine.create_copy()
|
||||
graph_engine_copy.graph_runtime_state.total_tokens = 0
|
||||
variable_pool_copy = graph_engine_copy.graph_runtime_state.variable_pool
|
||||
variable_pool_copy.add([self.node_id, "index"], index)
|
||||
variable_pool_copy.add([self.node_id, "item"], item)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any
|
||||
|
||||
from core.workflow.entities.node_entities import NodeRunResult
|
||||
from core.workflow.nodes.base import BaseNode
|
||||
from core.workflow.nodes.enums import NodeType
|
||||
@@ -36,16 +33,3 @@ class VariableAggregatorNode(BaseNode[VariableAssignerNodeData]):
|
||||
break
|
||||
|
||||
return NodeRunResult(status=WorkflowNodeExecutionStatus.SUCCEEDED, outputs=outputs, inputs=inputs)
|
||||
|
||||
@classmethod
|
||||
def _extract_variable_selector_to_variable_mapping(
|
||||
cls, *, graph_config: Mapping[str, Any], node_id: str, node_data: VariableAssignerNodeData
|
||||
) -> Mapping[str, Sequence[str]]:
|
||||
"""
|
||||
Extract variable selector to variable mapping
|
||||
:param graph_config: graph config
|
||||
:param node_id: node id
|
||||
:param node_data: node data
|
||||
:return:
|
||||
"""
|
||||
return {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from core.helper import marketplace
|
||||
from core.plugin.entities.plugin import GenericProviderID, PluginDependency, PluginInstallationSource
|
||||
from core.plugin.entities.plugin import ModelProviderID, PluginDependency, PluginInstallationSource, ToolProviderID
|
||||
from core.plugin.manager.plugin import PluginInstallationManager
|
||||
|
||||
|
||||
@@ -12,10 +12,7 @@ class DependenciesAnalysisService:
|
||||
Convert the tool id to the plugin_id
|
||||
"""
|
||||
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
|
||||
return ToolProviderID(tool_id).plugin_id
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
@@ -27,11 +24,7 @@ class DependenciesAnalysisService:
|
||||
Convert the model provider id to the plugin_id
|
||||
"""
|
||||
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
|
||||
return ModelProviderID(model_provider_id).plugin_id
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@ from flask import Flask, current_app
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.agent.entities import AgentToolEntity
|
||||
from core.entities import DEFAULT_PLUGIN_ID
|
||||
from core.helper import marketplace
|
||||
from core.plugin.entities.plugin import PluginInstallationSource
|
||||
from core.plugin.entities.plugin import ModelProviderID, PluginInstallationSource, ToolProviderID
|
||||
from core.plugin.entities.plugin_daemon import PluginInstallTaskStatus
|
||||
from core.plugin.manager.plugin import PluginInstallationManager
|
||||
from core.tools.entities.tool_entities import ToolProviderType
|
||||
@@ -203,13 +202,7 @@ class PluginMigration:
|
||||
result = []
|
||||
for row in rs:
|
||||
provider_name = str(row[0])
|
||||
if provider_name and "/" not in provider_name:
|
||||
if provider_name == "google":
|
||||
provider_name = "gemini"
|
||||
|
||||
result.append(DEFAULT_PLUGIN_ID + "/" + provider_name)
|
||||
elif provider_name:
|
||||
result.append(provider_name)
|
||||
result.append(ModelProviderID(provider_name).plugin_id)
|
||||
|
||||
return result
|
||||
|
||||
@@ -222,30 +215,10 @@ class PluginMigration:
|
||||
rs = session.query(BuiltinToolProvider).filter(BuiltinToolProvider.tenant_id == tenant_id).all()
|
||||
result = []
|
||||
for row in rs:
|
||||
if "/" not in row.provider:
|
||||
result.append(DEFAULT_PLUGIN_ID + "/" + row.provider)
|
||||
else:
|
||||
result.append(row.provider)
|
||||
result.append(ToolProviderID(row.provider).plugin_id)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _handle_builtin_tool_provider(cls, provider_name: str) -> str:
|
||||
"""
|
||||
Handle builtin tool provider.
|
||||
"""
|
||||
if provider_name == "jina":
|
||||
provider_name = "jina_tool"
|
||||
elif provider_name == "siliconflow":
|
||||
provider_name = "siliconflow_tool"
|
||||
elif provider_name == "stepfun":
|
||||
provider_name = "stepfun_tool"
|
||||
|
||||
if "/" not in provider_name:
|
||||
return DEFAULT_PLUGIN_ID + "/" + provider_name
|
||||
else:
|
||||
return provider_name
|
||||
|
||||
@classmethod
|
||||
def extract_workflow_tables(cls, tenant_id: str) -> Sequence[str]:
|
||||
"""
|
||||
@@ -266,8 +239,7 @@ class PluginMigration:
|
||||
provider_name = data.get("provider_name")
|
||||
provider_type = data.get("provider_type")
|
||||
if provider_name not in excluded_providers and provider_type == ToolProviderType.BUILT_IN.value:
|
||||
provider_name = cls._handle_builtin_tool_provider(provider_name)
|
||||
result.append(provider_name)
|
||||
result.append(ToolProviderID(provider_name).plugin_id)
|
||||
|
||||
return result
|
||||
|
||||
@@ -298,7 +270,7 @@ class PluginMigration:
|
||||
tool_entity.provider_type == ToolProviderType.BUILT_IN.value
|
||||
and tool_entity.provider_id not in excluded_providers
|
||||
):
|
||||
result.append(cls._handle_builtin_tool_provider(tool_entity.provider_id))
|
||||
result.append(ToolProviderID(tool_entity.provider_id).plugin_id)
|
||||
|
||||
except Exception:
|
||||
logger.exception(f"Failed to process tool {tool}")
|
||||
@@ -386,7 +358,7 @@ class PluginMigration:
|
||||
batch_plugin_identifiers = [
|
||||
plugins["plugins"][plugin_id]
|
||||
for plugin_id in batch_plugin_ids
|
||||
if plugin_id not in installed_plugins_ids
|
||||
if plugin_id not in installed_plugins_ids and plugin_id in plugins["plugins"]
|
||||
]
|
||||
manager.install_from_identifiers(
|
||||
tenant_id,
|
||||
|
||||
@@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.0.0
|
||||
image: langgenius/dify-api:0.15.3
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -27,7 +27,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:1.0.0
|
||||
image: langgenius/dify-api:0.15.3
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -51,7 +51,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.0.0
|
||||
image: langgenius/dify-web:0.15.3
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
@@ -64,6 +64,7 @@ services:
|
||||
MARKETPLACE_URL: ${MARKETPLACE_URL:-https://marketplace.dify.ai}
|
||||
TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-}
|
||||
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-}
|
||||
PM2_INSTANCES: ${PM2_INSTANCES:-2}
|
||||
|
||||
# The postgres database.
|
||||
db:
|
||||
@@ -128,7 +129,7 @@ services:
|
||||
|
||||
# plugin daemon
|
||||
plugin_daemon:
|
||||
image: langgenius/dify-plugin-daemon:0.0.1-local
|
||||
image: langgenius/dify-plugin-daemon:0.0.2-local
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
|
||||
# plugin daemon
|
||||
plugin_daemon:
|
||||
image: langgenius/dify-plugin-daemon:0.0.1-local
|
||||
image: langgenius/dify-plugin-daemon:0.0.2-local
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
||||
@@ -414,7 +414,7 @@ x-shared-env: &shared-api-worker-env
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.0.0
|
||||
image: langgenius/dify-api:0.15.3
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -439,7 +439,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:1.0.0
|
||||
image: langgenius/dify-api:0.15.3
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -463,7 +463,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.0.0
|
||||
image: langgenius/dify-web:0.15.3
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
@@ -476,6 +476,7 @@ services:
|
||||
MARKETPLACE_URL: ${MARKETPLACE_URL:-https://marketplace.dify.ai}
|
||||
TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-}
|
||||
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-}
|
||||
PM2_INSTANCES: ${PM2_INSTANCES:-2}
|
||||
|
||||
# The postgres database.
|
||||
db:
|
||||
@@ -540,7 +541,7 @@ services:
|
||||
|
||||
# plugin daemon
|
||||
plugin_daemon:
|
||||
image: langgenius/dify-plugin-daemon:0.0.1-local
|
||||
image: langgenius/dify-plugin-daemon:0.0.2-local
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
||||
@@ -46,6 +46,7 @@ ENV MARKETPLACE_API_URL=http://127.0.0.1:5001
|
||||
ENV MARKETPLACE_URL=http://127.0.0.1:5001
|
||||
ENV PORT=3000
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV PM2_INSTANCES=2
|
||||
|
||||
# set timezone
|
||||
ENV TZ=UTC
|
||||
@@ -58,7 +59,6 @@ COPY --from=builder /app/web/public ./public
|
||||
COPY --from=builder /app/web/.next/standalone ./
|
||||
COPY --from=builder /app/web/.next/static ./.next/static
|
||||
|
||||
COPY docker/pm2.json ./pm2.json
|
||||
COPY docker/entrypoint.sh ./entrypoint.sh
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@ If you want to customize the host and port:
|
||||
pnpm run start --port=3001 --host=0.0.0.0
|
||||
```
|
||||
|
||||
If you want to customize the number of instances launched by PM2, you can configure `PM2_INSTANCES` in `docker-compose.yaml` or `Dockerfile`.
|
||||
|
||||
## Storybook
|
||||
|
||||
This project uses [Storybook](https://storybook.js.org/) for UI component development.
|
||||
|
||||
@@ -53,7 +53,7 @@ export default function ChartView({ appId }: IChartViewProps) {
|
||||
className='mt-0 !w-40'
|
||||
onSelect={(item) => {
|
||||
const id = item.value
|
||||
const value = TIME_PERIOD_MAPPING[id]?.value || '-1'
|
||||
const value = TIME_PERIOD_MAPPING[id]?.value ?? '-1'
|
||||
const name = item.name || t('appLog.filter.period.allTime')
|
||||
onSelect({ value, name })
|
||||
}}
|
||||
|
||||
@@ -59,8 +59,8 @@ const Apps = () => {
|
||||
const [activeTab, setActiveTab] = useTabSearchParams({
|
||||
defaultTab: 'all',
|
||||
})
|
||||
const { query: { tagIDs = [], keywords = '' }, setQuery } = useAppsQueryState()
|
||||
const [isCreatedByMe, setIsCreatedByMe] = useState(false)
|
||||
const { query: { tagIDs = [], keywords = '', isCreatedByMe: queryIsCreatedByMe = false }, setQuery } = useAppsQueryState()
|
||||
const [isCreatedByMe, setIsCreatedByMe] = useState(queryIsCreatedByMe)
|
||||
const [tagFilterValue, setTagFilterValue] = useState<string[]>(tagIDs)
|
||||
const [searchKeywords, setSearchKeywords] = useState(keywords)
|
||||
const setKeywords = useCallback((keywords: string) => {
|
||||
@@ -126,6 +126,12 @@ const Apps = () => {
|
||||
handleTagsUpdate()
|
||||
}
|
||||
|
||||
const handleCreatedByMeChange = useCallback(() => {
|
||||
const newValue = !isCreatedByMe
|
||||
setIsCreatedByMe(newValue)
|
||||
setQuery(prev => ({ ...prev, isCreatedByMe: newValue }))
|
||||
}, [isCreatedByMe, setQuery])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'>
|
||||
@@ -139,7 +145,7 @@ const Apps = () => {
|
||||
className='mr-2'
|
||||
label={t('app.showMyCreatedAppsOnly')}
|
||||
isChecked={isCreatedByMe}
|
||||
onChange={() => setIsCreatedByMe(!isCreatedByMe)}
|
||||
onChange={handleCreatedByMeChange}
|
||||
/>
|
||||
<TagFilter type='app' value={tagFilterValue} onChange={handleTagsChange} />
|
||||
<Input
|
||||
|
||||
@@ -4,18 +4,20 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
type AppsQuery = {
|
||||
tagIDs?: string[]
|
||||
keywords?: string
|
||||
isCreatedByMe?: boolean
|
||||
}
|
||||
|
||||
// Parse the query parameters from the URL search string.
|
||||
function parseParams(params: ReadonlyURLSearchParams): AppsQuery {
|
||||
const tagIDs = params.get('tagIDs')?.split(';')
|
||||
const keywords = params.get('keywords') || undefined
|
||||
return { tagIDs, keywords }
|
||||
const isCreatedByMe = params.get('isCreatedByMe') === 'true'
|
||||
return { tagIDs, keywords, isCreatedByMe }
|
||||
}
|
||||
|
||||
// Update the URL search string with the given query parameters.
|
||||
function updateSearchParams(query: AppsQuery, current: URLSearchParams) {
|
||||
const { tagIDs, keywords } = query || {}
|
||||
const { tagIDs, keywords, isCreatedByMe } = query || {}
|
||||
|
||||
if (tagIDs && tagIDs.length > 0)
|
||||
current.set('tagIDs', tagIDs.join(';'))
|
||||
@@ -26,6 +28,11 @@ function updateSearchParams(query: AppsQuery, current: URLSearchParams) {
|
||||
current.set('keywords', keywords)
|
||||
else
|
||||
current.delete('keywords')
|
||||
|
||||
if (isCreatedByMe)
|
||||
current.set('isCreatedByMe', 'true')
|
||||
else
|
||||
current.delete('isCreatedByMe')
|
||||
}
|
||||
|
||||
function useAppsQueryState() {
|
||||
|
||||
@@ -26,12 +26,12 @@ import { MAX_TOOLS_NUM } from '@/config'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other'
|
||||
// import AddToolModal from '@/app/components/tools/add-tool-modal'
|
||||
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
||||
import { updateBuiltInToolCredential } from '@/service/tools'
|
||||
import cn from '@/utils/classnames'
|
||||
import ToolPicker from '@/app/components/workflow/block-selector/tool-picker'
|
||||
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
|
||||
import { canFindTool } from '@/utils'
|
||||
|
||||
type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null
|
||||
const AgentTools: FC = () => {
|
||||
@@ -43,7 +43,7 @@ const AgentTools: FC = () => {
|
||||
const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null)
|
||||
const currentCollection = useMemo(() => {
|
||||
if (!currentTool) return null
|
||||
const collection = collectionList.find(collection => collection.id.split('/').pop() === currentTool?.provider_id.split('/').pop() && collection.type === currentTool?.provider_type)
|
||||
const collection = collectionList.find(collection => canFindTool(collection.id, currentTool?.provider_id) && collection.type === currentTool?.provider_type)
|
||||
return collection
|
||||
}, [currentTool, collectionList])
|
||||
const [isShowSettingTool, setIsShowSettingTool] = useState(false)
|
||||
@@ -51,7 +51,7 @@ const AgentTools: FC = () => {
|
||||
const tools = (modelConfig?.agentConfig?.tools as AgentTool[] || []).map((item) => {
|
||||
const collection = collectionList.find(
|
||||
collection =>
|
||||
collection.id.split('/').pop() === item.provider_id.split('/').pop()
|
||||
canFindTool(collection.id, item.provider_id)
|
||||
&& collection.type === item.provider_type,
|
||||
)
|
||||
const icon = collection?.icon
|
||||
|
||||
@@ -97,7 +97,7 @@ const Uploader: FC<Props> = ({
|
||||
style={{ display: 'none' }}
|
||||
type="file"
|
||||
id="fileUploader"
|
||||
accept='.yml'
|
||||
accept='.yaml,.yml'
|
||||
onChange={fileChangeHandle}
|
||||
/>
|
||||
<div ref={dropRef}>
|
||||
|
||||
@@ -635,9 +635,10 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
|
||||
const [currentConversation, setCurrentConversation] = useState<ChatConversationGeneralDetail | CompletionConversationGeneralDetail | undefined>() // Currently selected conversation
|
||||
const isChatMode = appDetail.mode !== 'completion' // Whether the app is a chat app
|
||||
const isChatflow = appDetail.mode === 'advanced-chat' // Whether the app is a chatflow app
|
||||
const { setShowPromptLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({
|
||||
const { setShowPromptLogModal, setShowAgentLogModal, setShowMessageLogModal } = useAppStore(useShallow(state => ({
|
||||
setShowPromptLogModal: state.setShowPromptLogModal,
|
||||
setShowAgentLogModal: state.setShowAgentLogModal,
|
||||
setShowMessageLogModal: state.setShowMessageLogModal,
|
||||
})))
|
||||
|
||||
// Annotated data needs to be highlighted
|
||||
@@ -664,6 +665,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
|
||||
setCurrentConversation(undefined)
|
||||
setShowPromptLogModal(false)
|
||||
setShowAgentLogModal(false)
|
||||
setShowMessageLogModal(false)
|
||||
}
|
||||
|
||||
if (!logs)
|
||||
|
||||
@@ -100,7 +100,7 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch
|
||||
let targetNode: ChatItemInTree | undefined
|
||||
|
||||
// find path to the target message
|
||||
const stack = tree.toReversed().map(rootNode => ({
|
||||
const stack = tree.slice().reverse().map(rootNode => ({
|
||||
node: rootNode,
|
||||
path: [rootNode],
|
||||
}))
|
||||
|
||||
@@ -68,6 +68,9 @@ export const ThinkBlock = ({ children, ...props }: any) => {
|
||||
const displayContent = removeEndThink(children)
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!(props['data-think'] ?? false))
|
||||
return (<details {...props}>{children}</details>)
|
||||
|
||||
return (
|
||||
<details {...(!isComplete && { open: true })} className="group">
|
||||
<summary className="text-gray-500 font-bold list-none pl-2 flex items-center cursor-pointer select-none whitespace-nowrap">
|
||||
|
||||
@@ -68,13 +68,9 @@ const preprocessLaTeX = (content: string) => {
|
||||
}
|
||||
|
||||
const preprocessThinkTag = (content: string) => {
|
||||
if (!(content.trim().startsWith('<think>\n') || content.trim().startsWith('<details style=')))
|
||||
return content
|
||||
|
||||
return flow([
|
||||
(str: string) => str.replaceAll('<think>\n', '<details>\n'),
|
||||
(str: string) => str.replaceAll('\n</think>', '\n[ENDTHINKFLAG]</details>'),
|
||||
(str: string) => str.replaceAll('\n</details>', '\n[ENDTHINKFLAG]</details>'),
|
||||
(str: string) => str.replace('<think>\n', '<details data-think=true>\n'),
|
||||
(str: string) => str.replace('\n</think>', '\n[ENDTHINKFLAG]</details>'),
|
||||
])(content)
|
||||
}
|
||||
|
||||
@@ -269,7 +265,7 @@ export function Markdown(props: { content: string; className?: string }) {
|
||||
}
|
||||
},
|
||||
]}
|
||||
disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body']}
|
||||
disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', 'input']}
|
||||
components={{
|
||||
code: CodeBlock,
|
||||
img: Img,
|
||||
|
||||
@@ -243,7 +243,7 @@ const FileUploader = ({
|
||||
}, [handleDrop])
|
||||
|
||||
return (
|
||||
<div className="mb-5 w-[640px]">
|
||||
<div className="mb-5 max-w-[640px]">
|
||||
{!hideUpload && (
|
||||
<input
|
||||
ref={fileUploader}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
.dataSourceItem {
|
||||
@apply box-border relative grow shrink-0 flex items-center p-3 h-14 bg-white rounded-xl cursor-pointer;
|
||||
@apply w-full box-border relative flex items-center p-3 h-14 bg-white rounded-xl cursor-pointer;
|
||||
border: 0.5px solid #EAECF0;
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
font-weight: 500;
|
||||
@@ -64,7 +64,7 @@
|
||||
}
|
||||
|
||||
.datasetIcon {
|
||||
@apply flex mr-2 w-8 h-8 rounded-lg bg-center bg-no-repeat;
|
||||
@apply flex shrink-0 mr-2 w-8 h-8 rounded-lg bg-center bg-no-repeat;
|
||||
background-color: #F5FAFF;
|
||||
background-image: url(../assets/file.svg);
|
||||
background-size: 16px;
|
||||
|
||||
@@ -137,7 +137,7 @@ const StepOne = ({
|
||||
}
|
||||
{
|
||||
shouldShowDataSourceTypeList && (
|
||||
<div className='flex items-center mb-8 flex-wrap gap-4'>
|
||||
<div className='grid grid-cols-3 mb-8 gap-4'>
|
||||
<div
|
||||
className={cn(
|
||||
s.dataSourceItem,
|
||||
@@ -153,7 +153,12 @@ const StepOne = ({
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.datasetIcon)} />
|
||||
{t('datasetCreation.stepOne.dataSourceType.file')}
|
||||
<span
|
||||
title={t('datasetCreation.stepOne.dataSourceType.file')}
|
||||
className='truncate'
|
||||
>
|
||||
{t('datasetCreation.stepOne.dataSourceType.file')}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
@@ -170,7 +175,12 @@ const StepOne = ({
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.datasetIcon, s.notion)} />
|
||||
{t('datasetCreation.stepOne.dataSourceType.notion')}
|
||||
<span
|
||||
title={t('datasetCreation.stepOne.dataSourceType.notion')}
|
||||
className='truncate'
|
||||
>
|
||||
{t('datasetCreation.stepOne.dataSourceType.notion')}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
@@ -181,7 +191,12 @@ const StepOne = ({
|
||||
onClick={() => changeType(DataSourceType.WEB)}
|
||||
>
|
||||
<span className={cn(s.datasetIcon, s.web)} />
|
||||
{t('datasetCreation.stepOne.dataSourceType.web')}
|
||||
<span
|
||||
title={t('datasetCreation.stepOne.dataSourceType.web')}
|
||||
className='truncate'
|
||||
>
|
||||
{t('datasetCreation.stepOne.dataSourceType.web')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -50,7 +50,6 @@ const ChildSegmentDetail: FC<IChildSegmentDetailProps> = ({
|
||||
|
||||
const handleCancel = () => {
|
||||
onCancel()
|
||||
setContent(childChunkInfo?.content || '')
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
|
||||
@@ -80,7 +80,7 @@ export const useSegmentListContext = (selector: (value: SegmentListContextValue)
|
||||
return useContextSelector(SegmentListContext, selector)
|
||||
}
|
||||
|
||||
interface ICompletedProps {
|
||||
type ICompletedProps = {
|
||||
embeddingAvailable: boolean
|
||||
showNewSegmentModal: boolean
|
||||
onNewSegmentModalChange: (state: boolean) => void
|
||||
@@ -357,6 +357,7 @@ const Completed: FC<ICompletedProps> = ({
|
||||
if (seg.id === segmentId) {
|
||||
seg.answer = res.data.answer
|
||||
seg.content = res.data.content
|
||||
seg.sign_content = res.data.sign_content
|
||||
seg.keywords = res.data.keywords
|
||||
seg.word_count = res.data.word_count
|
||||
seg.hit_count = res.data.hit_count
|
||||
|
||||
@@ -68,7 +68,6 @@ const NewChildSegmentModal: FC<NewChildSegmentModalProps> = ({
|
||||
const handleCancel = (actionType: 'esc' | 'add' = 'esc') => {
|
||||
if (actionType === 'esc' || !addAnother)
|
||||
onCancel()
|
||||
setContent('')
|
||||
}
|
||||
|
||||
const { mutateAsync: addChildSegment } = useAddChildSegment()
|
||||
|
||||
@@ -49,7 +49,7 @@ const ChunkContent: FC<ChunkContentProps> = ({
|
||||
isFullDocMode ? 'line-clamp-3' : isCollapsed ? 'line-clamp-2' : 'line-clamp-20',
|
||||
className,
|
||||
)}
|
||||
content={sign_content || content}
|
||||
content={sign_content || content || ''}
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
@@ -57,9 +57,6 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
|
||||
|
||||
const handleCancel = () => {
|
||||
onCancel()
|
||||
setQuestion(segInfo?.content || '')
|
||||
setAnswer(segInfo?.answer || '')
|
||||
setKeywords(segInfo?.keywords || [])
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
|
||||
@@ -70,9 +70,6 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
|
||||
const handleCancel = (actionType: 'esc' | 'add' = 'esc') => {
|
||||
if (actionType === 'esc' || !addAnother)
|
||||
onCancel()
|
||||
setQuestion('')
|
||||
setAnswer('')
|
||||
setKeywords([])
|
||||
}
|
||||
|
||||
const { mutateAsync: addSegment } = useAddSegment()
|
||||
|
||||
@@ -8,7 +8,6 @@ import { switchWorkspace } from '@/service/common'
|
||||
import { useWorkspacesContext } from '@/context/workspace-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
|
||||
const WorkplaceSelector = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -72,15 +71,6 @@ const WorkplaceSelector = () => {
|
||||
<div className='flex py-1 pl-3 pr-2 items-center gap-2 self-stretch hover:bg-state-base-hover rounded-lg' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
|
||||
<div className='flex items-center justify-center w-6 h-6 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600'>{workspace.name[0].toLocaleUpperCase()}</div>
|
||||
<div className='line-clamp-1 grow overflow-hidden text-text-secondary text-ellipsis system-md-regular cursor-pointer'>{workspace.name}</div>
|
||||
{
|
||||
<PremiumBadge size='s' color='gray' allowHover={false}>
|
||||
<div className='system-2xs-medium'>
|
||||
<span className='p-[2px]'>
|
||||
{plan.type === 'professional' ? 'PRO' : plan.type.toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
|
||||
{
|
||||
currentQuota && (
|
||||
<div className='flex items-center h-4 text-xs text-text-tertiary'>
|
||||
<span className='mr-0.5 system-md-semibold-uppercase text-text-secondary'>{formatNumber((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0))}</span>
|
||||
<span className='mr-0.5 system-md-semibold-uppercase text-text-secondary'>{formatNumber(Math.max((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0), 0))}</span>
|
||||
{
|
||||
currentQuota?.quota_unit === QuotaUnitEnum.tokens && 'Tokens'
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import Link from 'next/link'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
import { Bars3Icon } from '@heroicons/react/20/solid'
|
||||
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
|
||||
import PremiumBadge from '../base/premium-badge'
|
||||
import AccountDropdown from './account-dropdown'
|
||||
import AppNav from './app-nav'
|
||||
import DatasetNav from './dataset-nav'
|
||||
@@ -13,7 +11,6 @@ import EnvNav from './env-nav'
|
||||
import PluginsNav from './plugins-nav'
|
||||
import ExploreNav from './explore-nav'
|
||||
import ToolsNav from './tools-nav'
|
||||
import LicenseNav from './license-env'
|
||||
import { WorkspaceProvider } from '@/context/workspace-context'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import LogoSite from '@/app/components/base/logo/logo-site'
|
||||
@@ -21,7 +18,9 @@ import WorkplaceSelector from '@/app/components/header/account-dropdown/workplac
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import LicenseNav from './license-env'
|
||||
import PlanBadge from './plan-badge'
|
||||
import { Plan } from '../billing/type'
|
||||
|
||||
const navClassName = `
|
||||
flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl
|
||||
@@ -31,15 +30,13 @@ const navClassName = `
|
||||
|
||||
const Header = () => {
|
||||
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const selectedSegment = useSelectedLayoutSegment()
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false)
|
||||
const { enableBilling, plan } = useProviderContext()
|
||||
const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
|
||||
const isFreePlan = plan.type === 'sandbox'
|
||||
const isFreePlan = plan.type === Plan.sandbox
|
||||
const handlePlanClick = useCallback(() => {
|
||||
if (isFreePlan)
|
||||
setShowPricingModal()
|
||||
@@ -71,18 +68,7 @@ const Header = () => {
|
||||
<WorkspaceProvider>
|
||||
<WorkplaceSelector />
|
||||
</WorkspaceProvider>
|
||||
{enableBilling && (
|
||||
<div className='select-none'>
|
||||
<PremiumBadge color='blue' allowHover={true} onClick={handlePlanClick}>
|
||||
<SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' />
|
||||
<div className='system-xs-medium'>
|
||||
<span className='p-1'>
|
||||
{t('billing.upgradeBtn.encourageShort')}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
</div>
|
||||
)}
|
||||
{enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -93,20 +79,7 @@ const Header = () => {
|
||||
<LogoSite />
|
||||
</Link>
|
||||
<div className='font-light text-divider-deep'>/</div>
|
||||
{
|
||||
enableBilling && (
|
||||
<div className='select-none'>
|
||||
<PremiumBadge color='blue' allowHover={true} onClick={handlePlanClick}>
|
||||
<SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' />
|
||||
<div className='system-xs-medium'>
|
||||
<span className='p-1'>
|
||||
{t('billing.upgradeBtn.encourageShort')}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />}
|
||||
</div >
|
||||
)}
|
||||
{
|
||||
@@ -120,7 +93,6 @@ const Header = () => {
|
||||
)
|
||||
}
|
||||
<div className='flex items-center shrink-0'>
|
||||
<LicenseNav />
|
||||
<EnvNav />
|
||||
<div className='mr-3'>
|
||||
<PluginsNav />
|
||||
|
||||
@@ -5,6 +5,8 @@ import { LicenseStatus } from '@/types/feature'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContextSelector } from 'use-context-selector'
|
||||
import dayjs from 'dayjs'
|
||||
import PremiumBadge from '../../base/premium-badge'
|
||||
import { RiHourglass2Fill } from '@remixicon/react'
|
||||
|
||||
const LicenseNav = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -13,15 +15,16 @@ const LicenseNav = () => {
|
||||
if (systemFeatures.license?.status === LicenseStatus.EXPIRING) {
|
||||
const expiredAt = systemFeatures.license?.expired_at
|
||||
const count = dayjs(expiredAt).diff(dayjs(), 'days')
|
||||
return <div className='px-2 py-1 mr-4 rounded-full bg-util-colors-orange-orange-50 border-util-colors-orange-orange-100 system-xs-medium text-util-colors-orange-orange-600'>
|
||||
{count <= 1 && <span>{t('common.license.expiring', { count })}</span>}
|
||||
{count > 1 && <span>{t('common.license.expiring_plural', { count })}</span>}
|
||||
</div>
|
||||
return <PremiumBadge color='orange' className='select-none'>
|
||||
<RiHourglass2Fill className='flex items-center pl-0.5 size-3 text-components-premium-badge-indigo-text-stop-0' />
|
||||
{count <= 1 && <span className='system-xs-medium px-0.5'>{t('common.license.expiring', { count })}</span>}
|
||||
{count > 1 && <span className='system-xs-medium px-0.5'>{t('common.license.expiring_plural', { count })}</span>}
|
||||
</PremiumBadge>
|
||||
}
|
||||
if (systemFeatures.license.status === LicenseStatus.ACTIVE) {
|
||||
return <div className='px-2 py-1 mr-4 rounded-md bg-util-colors-indigo-indigo-50 border-util-colors-indigo-indigo-100 system-xs-medium text-util-colors-indigo-indigo-600'>
|
||||
Enterprise
|
||||
</div>
|
||||
return <PremiumBadge color="indigo" className='select-none'>
|
||||
<span className='system-xs-medium px-1'>Enterprise</span>
|
||||
</PremiumBadge>
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
import { usePathname, useSearchParams, useSelectedLayoutSegment } from 'next/navigation'
|
||||
import type { INavSelectorProps } from './nav-selector'
|
||||
import NavSelector from './nav-selector'
|
||||
import classNames from '@/utils/classnames'
|
||||
@@ -35,6 +35,14 @@ const Nav = ({
|
||||
const [hovered, setHovered] = useState(false)
|
||||
const segment = useSelectedLayoutSegment()
|
||||
const isActivated = Array.isArray(activeSegment) ? activeSegment.includes(segment!) : segment === activeSegment
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
const [linkLastSearchParams, setLinkLastSearchParams] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname === link)
|
||||
setLinkLastSearchParams(searchParams.toString())
|
||||
}, [pathname, searchParams])
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
@@ -42,7 +50,7 @@ const Nav = ({
|
||||
${isActivated && 'bg-components-main-nav-nav-button-bg-active shadow-md font-semibold'}
|
||||
${!curNav && !isActivated && 'hover:bg-components-main-nav-nav-button-bg-hover'}
|
||||
`}>
|
||||
<Link href={link}>
|
||||
<Link href={link + (linkLastSearchParams && `?${linkLastSearchParams}`)}>
|
||||
<div
|
||||
onClick={() => setAppDetail()}
|
||||
className={classNames(`
|
||||
|
||||
70
web/app/components/header/plan-badge/index.tsx
Normal file
70
web/app/components/header/plan-badge/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import classNames from '@/utils/classnames'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SparklesSoft } from '../../base/icons/src/public/common'
|
||||
import PremiumBadge from '../../base/premium-badge'
|
||||
import { Plan } from '../../billing/type'
|
||||
|
||||
type PlanBadgeProps = {
|
||||
plan: Plan
|
||||
size?: 's' | 'm'
|
||||
allowHover?: boolean
|
||||
sandboxAsUpgrade?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const PlanBadge: FC<PlanBadgeProps> = ({ plan, allowHover, size = 'm', sandboxAsUpgrade = false, onClick }) => {
|
||||
const { isFetchedPlan } = useProviderContext()
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!isFetchedPlan) return null
|
||||
if (plan === Plan.sandbox && sandboxAsUpgrade) {
|
||||
return <div className='select-none'>
|
||||
<PremiumBadge color='blue' allowHover={allowHover} onClick={onClick}>
|
||||
<SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' />
|
||||
<div className='system-xs-medium'>
|
||||
<span className='p-1'>
|
||||
{t('billing.upgradeBtn.encourageShort')}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
</div>
|
||||
}
|
||||
if (plan === Plan.sandbox) {
|
||||
return <div className='select-none'>
|
||||
<PremiumBadge size={size} color='gray' allowHover={allowHover} onClick={onClick}>
|
||||
<div className={classNames(size === 's' ? 'system-2xs-medium-uppercase' : 'system-xs-medium-uppercase')}>
|
||||
<span className='p-1'>
|
||||
{plan}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
</div>
|
||||
}
|
||||
if (plan === Plan.professional) {
|
||||
return <div className='select-none'>
|
||||
<PremiumBadge size={size} color='blue' allowHover={allowHover} onClick={onClick}>
|
||||
<div className={classNames(size === 's' ? 'system-2xs-medium-uppercase' : 'system-xs-medium-uppercase')}>
|
||||
<span className='p-1'>
|
||||
pro
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
</div>
|
||||
}
|
||||
if (plan === Plan.team) {
|
||||
return <div className='select-none'>
|
||||
<PremiumBadge size={size} color='indigo' allowHover={allowHover} onClick={onClick}>
|
||||
<div className={classNames(size === 's' ? 'system-2xs-medium-uppercase' : 'system-xs-medium-uppercase')}>
|
||||
<span className='p-1'>
|
||||
{plan}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
</div>
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export default PlanBadge
|
||||
@@ -23,17 +23,15 @@ const useRefreshPluginList = () => {
|
||||
// installed list
|
||||
invalidateInstalledPluginList()
|
||||
|
||||
if (!manifest) return
|
||||
|
||||
// tool page, tool select
|
||||
if (PluginType.tool.includes(manifest.category) || refreshAllType) {
|
||||
if ((manifest && PluginType.tool.includes(manifest.category)) || refreshAllType) {
|
||||
invalidateAllToolProviders()
|
||||
invalidateAllBuiltInTools()
|
||||
// TODO: update suggested tools. It's a function in hook useMarketplacePlugins,handleUpdatePlugins
|
||||
}
|
||||
|
||||
// model select
|
||||
if (PluginType.model.includes(manifest.category) || refreshAllType) {
|
||||
if ((manifest && PluginType.model.includes(manifest.category)) || refreshAllType) {
|
||||
refreshModelProviders()
|
||||
refetchLLMModelList()
|
||||
refetchEmbeddingModelList()
|
||||
@@ -41,7 +39,7 @@ const useRefreshPluginList = () => {
|
||||
}
|
||||
|
||||
// agent select
|
||||
if (PluginType.agent.includes(manifest.category) || refreshAllType)
|
||||
if ((manifest && PluginType.agent.includes(manifest.category)) || refreshAllType)
|
||||
invalidateStrategyProviders()
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const ModelList = ({
|
||||
detail,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: res } = useModelProviderModelList(`${detail.plugin_id}/${detail.name}`)
|
||||
const { data: res } = useModelProviderModelList(`${detail.plugin_id}/${detail.declaration.model.provider}`)
|
||||
|
||||
if (!res)
|
||||
return null
|
||||
|
||||
@@ -82,6 +82,7 @@ import {
|
||||
initialNodes,
|
||||
} from './utils'
|
||||
import {
|
||||
CUSTOM_EDGE,
|
||||
CUSTOM_NODE,
|
||||
DSL_EXPORT_CHECK,
|
||||
ITERATION_CHILDREN_Z_INDEX,
|
||||
@@ -103,7 +104,7 @@ const nodeTypes = {
|
||||
[CUSTOM_ITERATION_START_NODE]: CustomIterationStartNode,
|
||||
}
|
||||
const edgeTypes = {
|
||||
[CUSTOM_NODE]: CustomEdge,
|
||||
[CUSTOM_EDGE]: CustomEdge,
|
||||
}
|
||||
|
||||
type WorkflowProps = {
|
||||
|
||||
@@ -28,4 +28,4 @@ export NEXT_PUBLIC_CSP_WHITELIST=${CSP_WHITELIST}
|
||||
export NEXT_PUBLIC_TOP_K_MAX_VALUE=${TOP_K_MAX_VALUE}
|
||||
export NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH}
|
||||
|
||||
pm2 start ./pm2.json --no-daemon
|
||||
pm2 start /app/web/server.js --name dify-web --cwd /app/web -i ${PM2_INSTANCES} --no-daemon
|
||||
|
||||
@@ -552,16 +552,16 @@ const translation = {
|
||||
'variable': '변수',
|
||||
'operations': {
|
||||
'*=': '*=',
|
||||
'overwrite': '덮어쓸',
|
||||
'overwrite': '덮어쓰기',
|
||||
'-=': '-=',
|
||||
'append': '덧붙이다',
|
||||
'over-write': '덮어쓸',
|
||||
'append': '추가',
|
||||
'over-write': '덮어쓰기',
|
||||
'+=': '+=',
|
||||
'title': '수술',
|
||||
'extend': '뻗치다',
|
||||
'clear': '맑다',
|
||||
'title': '제목',
|
||||
'extend': '연장',
|
||||
'clear': '초기화',
|
||||
'/=': '/=',
|
||||
'set': '집합',
|
||||
'set': '설정',
|
||||
},
|
||||
'variables': '변수',
|
||||
'noAssignedVars': '사용 가능한 할당된 변수가 없습니다.',
|
||||
|
||||
@@ -148,15 +148,15 @@ const translation = {
|
||||
},
|
||||
avgSessionInteractions: {
|
||||
title: '平均会话互动数',
|
||||
explanation: '反应每个会话用户的持续沟通次数,如果用户与 AI 问答了 10 轮,即为 10。该指标反映了用户粘性。仅在对话型应用提供。',
|
||||
explanation: '反映每个会话用户的持续沟通次数,如果用户与 AI 问答了 10 轮,即为 10。该指标反映了用户粘性。仅在对话型应用提供。',
|
||||
},
|
||||
avgUserInteractions: {
|
||||
title: '平均用户调用次数',
|
||||
explanation: '反应每天用户的使用次数。该指标反映了用户粘性。',
|
||||
explanation: '反映每天用户的使用次数。该指标反映了用户粘性。',
|
||||
},
|
||||
userSatisfactionRate: {
|
||||
title: '用户满意度',
|
||||
explanation: '每 1000 条消息的点赞数。反应了用户对回答十分满意的比例。',
|
||||
explanation: '每 1000 条消息的点赞数。反映了用户对回答十分满意的比例。',
|
||||
},
|
||||
avgResponseTime: {
|
||||
title: '平均响应时间',
|
||||
|
||||
@@ -52,7 +52,7 @@ const translation = {
|
||||
communityForums: '社区论坛',
|
||||
emailSupport: '电子邮件支持',
|
||||
priorityEmail: '优先电子邮件和聊天支持',
|
||||
logoChange: 'Logo更改',
|
||||
logoChange: 'Logo 更改',
|
||||
SSOAuthentication: 'SSO 认证',
|
||||
personalizedSupport: '个性化支持',
|
||||
dedicatedAPISupport: '专用 API 支持',
|
||||
|
||||
@@ -19,7 +19,7 @@ const translation = {
|
||||
steps: '运行步数',
|
||||
},
|
||||
resultEmpty: {
|
||||
title: '本次运行仅输出JSON格式,',
|
||||
title: '本次运行仅输出 JSON 格式,',
|
||||
tipLeft: '请转到',
|
||||
link: '详细信息面板',
|
||||
tipRight: '查看它。',
|
||||
|
||||
@@ -148,15 +148,15 @@ const translation = {
|
||||
},
|
||||
avgSessionInteractions: {
|
||||
title: '平均會話互動數',
|
||||
explanation: '反應每個會話使用者的持續溝通次數,如果使用者與 AI 問答了 10 輪,即為 10。該指標反映了使用者粘性。僅在對話型應用提供。',
|
||||
explanation: '反映每個會話使用者的持續溝通次數,如果使用者與 AI 問答了 10 輪,即為 10。該指標反映了使用者粘性。僅在對話型應用提供。',
|
||||
},
|
||||
avgUserInteractions: {
|
||||
title: '平均使用者呼叫次數',
|
||||
explanation: '反應每天使用者的使用次數。該指標反映了使用者粘性。',
|
||||
explanation: '反映每天使用者的使用次數。該指標反映了使用者粘性。',
|
||||
},
|
||||
userSatisfactionRate: {
|
||||
title: '使用者滿意度',
|
||||
explanation: '每 1000 條訊息的點贊數。反應了使用者對回答十分滿意的比例。',
|
||||
explanation: '每 1000 條訊息的點贊數。反映了使用者對回答十分滿意的比例。',
|
||||
},
|
||||
avgResponseTime: {
|
||||
title: '平均響應時間',
|
||||
|
||||
@@ -51,7 +51,7 @@ const translation = {
|
||||
communityForums: '社群論壇',
|
||||
emailSupport: '電子郵件支援',
|
||||
priorityEmail: '優先電子郵件和聊天支援',
|
||||
logoChange: 'Logo更改',
|
||||
logoChange: 'Logo 更改',
|
||||
SSOAuthentication: 'SSO 認證',
|
||||
personalizedSupport: '個性化支援',
|
||||
dedicatedAPISupport: '專用 API 支援',
|
||||
|
||||
@@ -19,7 +19,7 @@ const translation = {
|
||||
steps: '執行步數',
|
||||
},
|
||||
resultEmpty: {
|
||||
title: '本運行僅輸出JSON格式,',
|
||||
title: '本運行僅輸出 JSON 格式,',
|
||||
tipLeft: '請到',
|
||||
link: '詳細資訊面板',
|
||||
tipRight: '查看它。',
|
||||
|
||||
2
web/pnpm-lock.yaml
generated
2
web/pnpm-lock.yaml
generated
@@ -18309,4 +18309,4 @@ snapshots:
|
||||
immer: 9.0.21
|
||||
react: 18.2.0
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
zwitch@2.0.4: {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import type {
|
||||
ModelProvider,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
@@ -39,6 +39,7 @@ 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'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
const NAME_SPACE = 'plugins'
|
||||
|
||||
@@ -326,7 +327,7 @@ export const useMutationPluginsFromMarketplace = () => {
|
||||
pageSize = 40,
|
||||
} = pluginsSearchParams
|
||||
const pluginOrBundle = type === 'bundle' ? 'bundles' : 'plugins'
|
||||
return postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/basic`, {
|
||||
return postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/advanced`, {
|
||||
body: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
@@ -383,6 +384,7 @@ export const usePluginTaskList = (category?: PluginType) => {
|
||||
const {
|
||||
data,
|
||||
isFetched,
|
||||
isRefetching,
|
||||
refetch,
|
||||
...rest
|
||||
} = useQuery({
|
||||
@@ -392,16 +394,24 @@ export const usePluginTaskList = (category?: PluginType) => {
|
||||
refetchInterval: (lastQuery) => {
|
||||
const lastData = lastQuery.state.data
|
||||
const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
|
||||
return taskDone ? false : 5000
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
// After first fetch, refresh plugin list each time all tasks are done
|
||||
if (!isRefetching) {
|
||||
const lastData = cloneDeep(data)
|
||||
const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
|
||||
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
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isRefetching])
|
||||
|
||||
return 5000
|
||||
},
|
||||
})
|
||||
const handleRefetch = useCallback(() => {
|
||||
refetch()
|
||||
}, [refetch])
|
||||
|
||||
Reference in New Issue
Block a user