Commit 3ea06d28 authored by Joel's avatar Joel

merge main

parents 8d6984e2 f25cec26
......@@ -41,6 +41,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup NodeJS
uses: actions/setup-node@v4
......@@ -60,11 +62,10 @@ jobs:
yarn run lint
- name: Super-linter
uses: super-linter/super-linter/slim@v5
uses: super-linter/super-linter/slim@v6
env:
BASH_SEVERITY: warning
DEFAULT_BRANCH: main
ERROR_ON_MISSING_EXEC_BIT: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IGNORE_GENERATED_FILES: true
IGNORE_GITIGNORED_FILES: true
......
......@@ -82,7 +82,7 @@ UPLOAD_IMAGE_FILE_SIZE_LIMIT=10
MULTIMODAL_SEND_IMAGE_FORMAT=base64
# Mail configuration, support: resend, smtp
MAIL_TYPE=resend
MAIL_TYPE=
MAIL_DEFAULT_SEND_FROM=no-reply <no-reply@dify.ai>
RESEND_API_KEY=
RESEND_API_URL=https://api.resend.com
......@@ -131,4 +131,4 @@ UNSTRUCTURED_API_URL=
SSRF_PROXY_HTTP_URL=
SSRF_PROXY_HTTPS_URL=
BATCH_UPLOAD_LIMIT=10
\ No newline at end of file
BATCH_UPLOAD_LIMIT=10
......@@ -131,6 +131,8 @@ def vdb_migrate():
"""
click.echo(click.style('Start migrate vector db.', fg='green'))
create_count = 0
skipped_count = 0
total_count = 0
config = current_app.config
vector_type = config.get('VECTOR_STORE')
page = 1
......@@ -143,14 +145,19 @@ def vdb_migrate():
page += 1
for dataset in datasets:
total_count = total_count + 1
click.echo(f'Processing the {total_count} dataset {dataset.id}. '
+ f'{create_count} created, ${skipped_count} skipped.')
try:
click.echo('Create dataset vdb index: {}'.format(dataset.id))
if dataset.index_struct_dict:
if dataset.index_struct_dict['type'] == vector_type:
skipped_count = skipped_count + 1
continue
collection_name = ''
if vector_type == "weaviate":
dataset_id = dataset.id
collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node'
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
index_struct_dict = {
"type": 'weaviate',
"vector_store": {"class_prefix": collection_name}
......@@ -167,7 +174,7 @@ def vdb_migrate():
raise ValueError('Dataset Collection Bindings is not exist!')
else:
dataset_id = dataset.id
collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node'
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
index_struct_dict = {
"type": 'qdrant',
"vector_store": {"class_prefix": collection_name}
......@@ -176,7 +183,7 @@ def vdb_migrate():
elif vector_type == "milvus":
dataset_id = dataset.id
collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node'
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
index_struct_dict = {
"type": 'milvus',
"vector_store": {"class_prefix": collection_name}
......@@ -186,11 +193,17 @@ def vdb_migrate():
raise ValueError(f"Vector store {config.get('VECTOR_STORE')} is not supported.")
vector = Vector(dataset)
click.echo(f"vdb_migrate {dataset.id}")
click.echo(f"Start to migrate dataset {dataset.id}.")
try:
vector.delete()
click.echo(
click.style(f'Successfully delete vector index {collection_name} for dataset {dataset.id}.',
fg='green'))
except Exception as e:
click.echo(
click.style(f'Failed to delete vector index {collection_name} for dataset {dataset.id}.',
fg='red'))
raise e
dataset_documents = db.session.query(DatasetDocument).filter(
......@@ -201,6 +214,7 @@ def vdb_migrate():
).all()
documents = []
segments_count = 0
for dataset_document in dataset_documents:
segments = db.session.query(DocumentSegment).filter(
DocumentSegment.document_id == dataset_document.id,
......@@ -220,15 +234,22 @@ def vdb_migrate():
)
documents.append(document)
segments_count = segments_count + 1
if documents:
try:
click.echo(click.style(
f'Start to created vector index with {len(documents)} documents of {segments_count} segments for dataset {dataset.id}.',
fg='green'))
vector.create(documents)
click.echo(
click.style(f'Successfully created vector index for dataset {dataset.id}.', fg='green'))
except Exception as e:
click.echo(click.style(f'Failed to created vector index for dataset {dataset.id}.', fg='red'))
raise e
click.echo(f"Dataset {dataset.id} create successfully.")
db.session.add(dataset)
db.session.commit()
click.echo(f'Successfully migrated dataset {dataset.id}.')
create_count += 1
except Exception as e:
db.session.rollback()
......@@ -237,7 +258,9 @@ def vdb_migrate():
fg='red'))
continue
click.echo(click.style('Congratulations! Create {} dataset indexes.'.format(create_count), fg='green'))
click.echo(
click.style(f'Congratulations! Create {create_count} dataset indexes, and skipped {skipped_count} datasets.',
fg='green'))
def register_commands(app):
......
......@@ -13,30 +13,14 @@ model_templates = {
'status': 'normal'
},
'model_config': {
'provider': 'openai',
'model_id': 'gpt-3.5-turbo-instruct',
'configs': {
'prompt_template': '',
'prompt_variables': [],
'completion_params': {
'max_token': 512,
'temperature': 1,
'top_p': 1,
'presence_penalty': 0,
'frequency_penalty': 0,
}
},
'provider': '',
'model_id': '',
'configs': {},
'model': json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo-instruct",
"mode": "completion",
"completion_params": {
"max_tokens": 512,
"temperature": 1,
"top_p": 1,
"presence_penalty": 0,
"frequency_penalty": 0
}
"completion_params": {}
}),
'user_input_form': json.dumps([
{
......@@ -64,30 +48,14 @@ model_templates = {
'status': 'normal'
},
'model_config': {
'provider': 'openai',
'model_id': 'gpt-3.5-turbo',
'configs': {
'prompt_template': '',
'prompt_variables': [],
'completion_params': {
'max_token': 512,
'temperature': 1,
'top_p': 1,
'presence_penalty': 0,
'frequency_penalty': 0,
}
},
'provider': '',
'model_id': '',
'configs': {},
'model': json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo",
"mode": "chat",
"completion_params": {
"max_tokens": 512,
"temperature": 1,
"top_p": 1,
"presence_penalty": 0,
"frequency_penalty": 0
}
"completion_params": {}
})
}
},
......
......@@ -11,7 +11,7 @@ from controllers.console.datasets.error import (
UnsupportedFileTypeError,
)
from controllers.console.setup import setup_required
from controllers.console.wraps import account_initialization_required
from controllers.console.wraps import account_initialization_required, cloud_edition_billing_resource_check
from fields.file_fields import file_fields, upload_config_fields
from libs.login import login_required
from services.file_service import ALLOWED_EXTENSIONS, UNSTRUSTURED_ALLOWED_EXTENSIONS, FileService
......@@ -39,6 +39,7 @@ class FileApi(Resource):
@login_required
@account_initialization_required
@marshal_with(file_fields)
@cloud_edition_billing_resource_check(resource='documents')
def post(self):
# get file from request
......
......@@ -56,6 +56,7 @@ def cloud_edition_billing_resource_check(resource: str,
members = features.members
apps = features.apps
vector_space = features.vector_space
documents_upload_quota = features.documents_upload_quota
annotation_quota_limit = features.annotation_quota_limit
if resource == 'members' and 0 < members.limit <= members.size:
......@@ -64,6 +65,13 @@ def cloud_edition_billing_resource_check(resource: str,
abort(403, error_msg)
elif resource == 'vector_space' and 0 < vector_space.limit <= vector_space.size:
abort(403, error_msg)
elif resource == 'documents' and 0 < documents_upload_quota.limit <= documents_upload_quota.size:
# The api of file upload is used in the multiple places, so we need to check the source of the request from datasets
source = request.args.get('source')
if source == 'datasets':
abort(403, error_msg)
else:
return view(*args, **kwargs)
elif resource == 'workspace_custom' and not features.can_replace_logo:
abort(403, error_msg)
elif resource == 'annotation' and 0 < annotation_quota_limit.limit < annotation_quota_limit.size:
......
......@@ -28,6 +28,7 @@ class DocumentAddByTextApi(DatasetApiResource):
"""Resource for documents."""
@cloud_edition_billing_resource_check('vector_space', 'dataset')
@cloud_edition_billing_resource_check('documents', 'dataset')
def post(self, tenant_id, dataset_id):
"""Create document by text."""
parser = reqparse.RequestParser()
......@@ -153,6 +154,7 @@ class DocumentUpdateByTextApi(DatasetApiResource):
class DocumentAddByFileApi(DatasetApiResource):
"""Resource for documents."""
@cloud_edition_billing_resource_check('vector_space', 'dataset')
@cloud_edition_billing_resource_check('documents', 'dataset')
def post(self, tenant_id, dataset_id):
"""Create document by upload file."""
args = {}
......
......@@ -89,6 +89,7 @@ def cloud_edition_billing_resource_check(resource: str,
members = features.members
apps = features.apps
vector_space = features.vector_space
documents_upload_quota = features.documents_upload_quota
if resource == 'members' and 0 < members.limit <= members.size:
raise Unauthorized(error_msg)
......@@ -96,6 +97,8 @@ def cloud_edition_billing_resource_check(resource: str,
raise Unauthorized(error_msg)
elif resource == 'vector_space' and 0 < vector_space.limit <= vector_space.size:
raise Unauthorized(error_msg)
elif resource == 'documents' and 0 < documents_upload_quota.limit <= documents_upload_quota.size:
raise Unauthorized(error_msg)
else:
return view(*args, **kwargs)
......
from collections.abc import Generator
from os.path import join
from typing import cast
from urllib.parse import urljoin
from httpx import Timeout
from openai import (
......@@ -313,10 +313,13 @@ class LocalAILarguageModel(LargeLanguageModel):
:param credentials: credentials dict
:return: client kwargs
"""
if not credentials['server_url'].endswith('/'):
credentials['server_url'] += '/'
client_kwargs = {
"timeout": Timeout(315.0, read=300.0, write=10.0, connect=5.0),
"api_key": "1",
"base_url": join(credentials['server_url'], 'v1'),
"base_url": urljoin(credentials['server_url'], 'v1'),
}
return client_kwargs
......
......@@ -39,7 +39,7 @@ class Vector:
collection_name = class_prefix
else:
dataset_id = self._dataset.id
collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node'
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
index_struct_dict = {
"type": 'weaviate',
"vector_store": {"class_prefix": collection_name}
......@@ -70,7 +70,7 @@ class Vector:
collection_name = class_prefix
else:
dataset_id = self._dataset.id
collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node'
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
if not self._dataset.index_struct_dict:
index_struct_dict = {
......@@ -96,7 +96,7 @@ class Vector:
collection_name = class_prefix
else:
dataset_id = self._dataset.id
collection_name = "Vector_index_" + dataset_id.replace("-", "_") + '_Node'
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
index_struct_dict = {
"type": 'milvus',
"vector_store": {"class_prefix": collection_name}
......
......@@ -70,7 +70,7 @@ class WeaviateVector(BaseVector):
return class_prefix
dataset_id = dataset.id
return "Vector_index_" + dataset_id.replace("-", "_") + '_Node'
return Dataset.gen_collection_name_by_id(dataset_id)
def to_index_struct(self) -> dict:
return {
......
- google
- bing
- duckduckgo
- yahoo
- wikipedia
- arxiv
- pubmed
- dalle
- azuredalle
- stablediffusion
- webscraper
- youtube
- wolframalpha
- maths
- github
- chart
- time
- yahoo
- stablediffusion
- vectorizer
- youtube
- gaode
- maths
- wecom
<svg id="logomark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.732 24.269"><g id="tiny"><path d="M573.549,280.916l2.266,2.738,6.674-7.84c.353-.47.52-.717.353-1.117a1.218,1.218,0,0,0-1.061-.748h0a.953.953,0,0,0-.712.262Z" transform="translate(-566.984 -271.548)" fill="#bdb9b4"/><path d="M579.525,282.225l-10.606-10.174a1.413,1.413,0,0,0-.834-.5,1.09,1.09,0,0,0-1.027.66c-.167.4-.047.681.319,1.206l8.44,10.242h0l-6.282,7.716a1.336,1.336,0,0,0-.323,1.3,1.114,1.114,0,0,0,1.04.69A.992.992,0,0,0,571,293l8.519-7.92A1.924,1.924,0,0,0,579.525,282.225Z" transform="translate(-566.984 -271.548)" fill="#b31b1b"/><path d="M584.32,293.912l-8.525-10.275,0,0L573.53,280.9l-1.389,1.254a2.063,2.063,0,0,0,0,2.965l10.812,10.419a.925.925,0,0,0,.742.282,1.039,1.039,0,0,0,.953-.667A1.261,1.261,0,0,0,584.32,293.912Z" transform="translate(-566.984 -271.548)" fill="#bdb9b4"/></g></svg>
\ No newline at end of file
from core.tools.errors import ToolProviderCredentialValidationError
from core.tools.provider.builtin.arxiv.tools.arxiv_search import ArxivSearchTool
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
class ArxivProvider(BuiltinToolProviderController):
def _validate_credentials(self, credentials: dict) -> None:
try:
ArxivSearchTool().fork_tool_runtime(
meta={
"credentials": credentials,
}
).invoke(
user_id='',
tool_parameters={
"query": "John Doe",
},
)
except Exception as e:
raise ToolProviderCredentialValidationError(str(e))
\ No newline at end of file
identity:
author: Yash Parmar
name: arxiv
label:
en_US: ArXiv
zh_Hans: ArXiv
description:
en_US: Access to a vast repository of scientific papers and articles in various fields of research.
zh_Hans: 访问各个研究领域大量科学论文和文章的存储库。
icon: icon.svg
from typing import Any
from langchain.utilities import ArxivAPIWrapper
from pydantic import BaseModel, Field
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class ArxivSearchInput(BaseModel):
query: str = Field(..., description="Search query.")
class ArxivSearchTool(BuiltinTool):
"""
A tool for searching articles on Arxiv.
"""
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]:
"""
Invokes the Arxiv search tool with the given user ID and tool parameters.
Args:
user_id (str): The ID of the user invoking the tool.
tool_parameters (dict[str, Any]): The parameters for the tool, including the 'query' parameter.
Returns:
ToolInvokeMessage | list[ToolInvokeMessage]: The result of the tool invocation, which can be a single message or a list of messages.
"""
query = tool_parameters.get('query', '')
if not query:
return self.create_text_message('Please input query')
arxiv = ArxivAPIWrapper()
response = arxiv.run(query)
return self.create_text_message(self.summary(user_id=user_id, content=response))
identity:
name: arxiv_search
author: Yash Parmar
label:
en_US: Arxiv Search
zh_Hans: Arxiv 搜索
description:
human:
en_US: A tool for searching scientific papers and articles from the Arxiv repository. Input can be an Arxiv ID or an author's name.
zh_Hans: 一个用于从Arxiv存储库搜索科学论文和文章的工具。 输入可以是Arxiv ID或作者姓名。
llm: A tool for searching scientific papers and articles from the Arxiv repository. Input can be an Arxiv ID or an author's name.
parameters:
- name: query
type: string
required: true
label:
en_US: Query string
zh_Hans: 查询字符串
human_description:
en_US: The Arxiv ID or author's name used for searching.
zh_Hans: 用于搜索的Arxiv ID或作者姓名。
llm_description: The Arxiv ID or author's name used for searching.
form: llm
......@@ -54,4 +54,4 @@ class GaodeRepositoriesTool(BuiltinTool):
s.close()
return self.create_text_message(f'No weather information for {city} was found.')
except Exception as e:
return self.create_text_message("Github API Key and Api Version is invalid. {}".format(e))
return self.create_text_message("Gaode API Key and Api Version is invalid. {}".format(e))
<svg height="512" viewBox="0 0 448 512" width="448" xmlns="http://www.w3.org/2000/svg"><path d="m48 32c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-352c0-26.5-21.5-48-48-48zm69.56445 64s49.09165 11.12539 46.59571 94.78125c0 0 41.47034-117.171493 204.5664 1.64844 0 42.78788-.31445 172.24246-.31445 223.57031-176.89733-149.87989-207.38477-22.06836-207.38477-22.06836 0-79.8558-81.753902-70.33984-81.753902-70.33984v-212.65039s18.755175 1.4021 38.291012 11.11132zm86.14649 98.2832-24.00196 141.34961h36.5625l11.81446-81.3789h.37304l32.44727 81.3789h14.63281l33.93946-81.3789h.37304l10.31446 81.3789h36.7832l-21.40234-141.34961h-36.5625l-30.38868 75.54102-28.69531-75.54102z"/></svg>
\ No newline at end of file
from core.tools.errors import ToolProviderCredentialValidationError
from core.tools.provider.builtin.pubmed.tools.pubmed_search import PubMedSearchTool
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
class PubMedProvider(BuiltinToolProviderController):
def _validate_credentials(self, credentials: dict) -> None:
try:
PubMedSearchTool().fork_tool_runtime(
meta={
"credentials": credentials,
}
).invoke(
user_id='',
tool_parameters={
"query": "John Doe",
},
)
except Exception as e:
raise ToolProviderCredentialValidationError(str(e))
\ No newline at end of file
identity:
author: Pink Banana
name: pubmed
label:
en_US: PubMed
zh_Hans: PubMed
description:
en_US: A search engine for biomedical literature.
zh_Hans: 一款生物医学文献搜索引擎。
icon: icon.svg
from typing import Any
from langchain.tools import PubmedQueryRun
from pydantic import BaseModel, Field
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class PubMedInput(BaseModel):
query: str = Field(..., description="Search query.")
class PubMedSearchTool(BuiltinTool):
"""
Tool for performing a search using PubMed search engine.
"""
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]:
"""
Invoke the PubMed search tool.
Args:
user_id (str): The ID of the user invoking the tool.
tool_parameters (dict[str, Any]): The parameters for the tool invocation.
Returns:
ToolInvokeMessage | list[ToolInvokeMessage]: The result of the tool invocation.
"""
query = tool_parameters.get('query', '')
if not query:
return self.create_text_message('Please input query')
tool = PubmedQueryRun(args_schema=PubMedInput)
result = tool.run(query)
return self.create_text_message(self.summary(user_id=user_id, content=result))
\ No newline at end of file
identity:
name: pubmed_search
author: Pink Banana
label:
en_US: PubMed Search
zh_Hans: PubMed 搜索
description:
human:
en_US: PubMed® comprises more than 35 million citations for biomedical literature from MEDLINE, life science journals, and online books. Citations may include links to full text content from PubMed Central and publisher web sites.
zh_Hans: PubMed® 包含来自 MEDLINE、生命科学期刊和在线书籍的超过 3500 万篇生物医学文献引用。引用可能包括来自 PubMed Central 和出版商网站的全文内容链接。
llm: Perform searches on PubMed and get results.
parameters:
- name: query
type: string
required: true
label:
en_US: Query string
zh_Hans: 查询语句
human_description:
en_US: The search query.
zh_Hans: 搜索查询语句。
llm_description: Key words for searching
form: llm
......@@ -70,7 +70,7 @@ class StableDiffusionTool(BuiltinTool):
if not base_url:
return self.create_text_message('Please input base_url')
if 'model' in tool_parameters:
if 'model' in tool_parameters and tool_parameters['model']:
self.runtime.credentials['model'] = tool_parameters['model']
model = self.runtime.credentials.get('model', None)
......
from typing import Any, Union
import httpx
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class WecomRepositoriesTool(BuiltinTool):
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
"""
invoke tools
"""
content = tool_parameters.get('content', '')
if not content:
return self.create_text_message('Invalid parameter content')
hook_key = tool_parameters.get('hook_key', '')
if not hook_key:
return self.create_text_message('Invalid parameter hook_key')
msgtype = 'text'
api_url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send'
headers = {
'Content-Type': 'application/json',
}
params = {
'key': hook_key,
}
payload = {
"msgtype": msgtype,
"text": {
"content": content,
}
}
try:
res = httpx.post(api_url, headers=headers, params=params, json=payload)
if res.is_success:
return self.create_text_message("Text message sent successfully")
else:
return self.create_text_message(
f"Failed to send the text message, status code: {res.status_code}, response: {res.text}")
except Exception as e:
return self.create_text_message("Failed to send message to group chat bot. {}".format(e))
identity:
name: wecom_group_bot
author: Bowen Liang
label:
en_US: Send Group Message
zh_Hans: 发送群消息
pt_BR: Send Group Message
icon: icon.svg
description:
human:
en_US: Sending a group message on Wecom via the webhook of group bot
zh_Hans: 通过企业微信的群机器人webhook发送群消息
pt_BR: Sending a group message on Wecom via the webhook of group bot
llm: A tool for sending messages to a chat group on Wecom(企业微信) .
parameters:
- name: hook_key
type: string
required: true
label:
en_US: Wecom Group bot webhook key
zh_Hans: 群机器人webhook的key
pt_BR: Wecom Group bot webhook key
human_description:
en_US: Wecom Group bot webhook key
zh_Hans: 群机器人webhook的key
pt_BR: Wecom Group bot webhook key
form: form
- name: content
type: string
required: true
label:
en_US: content
zh_Hans: 消息内容
pt_BR: content
human_description:
en_US: Content to sent to the group.
zh_Hans: 群消息文本
pt_BR: Content to sent to the group.
llm_description: Content of the message
form: llm
from core.tools.provider.builtin.wecom.tools.wecom_group_bot import WecomRepositoriesTool
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
class GaodeProvider(BuiltinToolProviderController):
def _validate_credentials(self, credentials: dict) -> None:
WecomRepositoriesTool()
pass
identity:
author: Bowen Liang
name: wecom
label:
en_US: Wecom
zh_Hans: 企业微信
pt_BR: Wecom
description:
en_US: Wecom group bot
zh_Hans: 企业微信群机器人
pt_BR: Wecom group bot
icon: icon.png
credentials_for_provider:
......@@ -67,9 +67,9 @@ class ApiTool(Tool):
if 'api_key_header_prefix' in credentials:
api_key_header_prefix = credentials['api_key_header_prefix']
if api_key_header_prefix == 'basic':
if api_key_header_prefix == 'basic' and credentials['api_key_value']:
credentials['api_key_value'] = f'Basic {credentials["api_key_value"]}'
elif api_key_header_prefix == 'bearer':
elif api_key_header_prefix == 'bearer' and credentials['api_key_value']:
credentials['api_key_value'] = f'Bearer {credentials["api_key_value"]}'
elif api_key_header_prefix == 'custom':
pass
......@@ -184,21 +184,7 @@ class ApiTool(Tool):
for name, property in properties.items():
if name in parameters:
# convert type
try:
value = parameters[name]
if property['type'] == 'integer':
value = int(value)
elif property['type'] == 'number':
# check if it is a float
if '.' in value:
value = float(value)
else:
value = int(value)
elif property['type'] == 'boolean':
value = bool(value)
body[name] = value
except ValueError as e:
body[name] = parameters[name]
body[name] = self._convert_body_property_type(property, parameters[name])
elif name in required:
raise ToolProviderCredentialValidationError(
f"Missing required parameter {name} in operation {self.api_bundle.operation_id}"
......@@ -228,10 +214,6 @@ class ApiTool(Tool):
elif method == 'put':
response = ssrf_proxy.put(url, params=params, headers=headers, cookies=cookies, data=body, timeout=10, follow_redirects=True)
elif method == 'delete':
"""
request body data is unsupported for DELETE method in standard http protocol
however, OpenAPI 3.0 supports request body data for DELETE method, so we support it here by using requests
"""
response = ssrf_proxy.delete(url, params=params, headers=headers, cookies=cookies, data=body, timeout=10, allow_redirects=True)
elif method == 'patch':
response = ssrf_proxy.patch(url, params=params, headers=headers, cookies=cookies, data=body, timeout=10, follow_redirects=True)
......@@ -243,6 +225,66 @@ class ApiTool(Tool):
raise ValueError(f'Invalid http method {method}')
return response
def _convert_body_property_any_of(self, property: dict[str, Any], value: Any, any_of: list[dict[str, Any]], max_recursive=10) -> Any:
if max_recursive <= 0:
raise Exception("Max recursion depth reached")
for option in any_of or []:
try:
if 'type' in option:
# Attempt to convert the value based on the type.
if option['type'] == 'integer' or option['type'] == 'int':
return int(value)
elif option['type'] == 'number':
if '.' in str(value):
return float(value)
else:
return int(value)
elif option['type'] == 'string':
return str(value)
elif option['type'] == 'boolean':
if str(value).lower() in ['true', '1']:
return True
elif str(value).lower() in ['false', '0']:
return False
else:
continue # Not a boolean, try next option
elif option['type'] == 'null' and not value:
return None
else:
continue # Unsupported type, try next option
elif 'anyOf' in option and isinstance(option['anyOf'], list):
# Recursive call to handle nested anyOf
return self._convert_body_property_any_of(property, value, option['anyOf'], max_recursive - 1)
except ValueError:
continue # Conversion failed, try next option
# If no option succeeded, you might want to return the value as is or raise an error
return value # or raise ValueError(f"Cannot convert value '{value}' to any specified type in anyOf")
def _convert_body_property_type(self, property: dict[str, Any], value: Any) -> Any:
try:
if 'type' in property:
if property['type'] == 'integer' or property['type'] == 'int':
return int(value)
elif property['type'] == 'number':
# check if it is a float
if '.' in value:
return float(value)
else:
return int(value)
elif property['type'] == 'string':
return str(value)
elif property['type'] == 'boolean':
return bool(value)
elif property['type'] == 'null':
if value is None:
return None
else:
raise ValueError(f"Invalid type {property['type']} for property {property}")
elif 'anyOf' in property and isinstance(property['anyOf'], list):
return self._convert_body_property_any_of(property, value, property['anyOf'])
except ValueError as e:
return value
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]:
"""
......
......@@ -146,7 +146,8 @@ class ApiBasedToolSchemaParser:
bundles.append(ApiBasedToolBundle(
server_url=server_url + interface['path'],
method=interface['method'],
summary=interface['operation']['summary'] if 'summary' in interface['operation'] else None,
summary=interface['operation']['description'] if 'description' in interface['operation'] else
interface['operation']['summary'] if 'summary' in interface['operation'] else None,
operation_id=interface['operation']['operationId'],
parameters=parameters,
author='',
......@@ -249,12 +250,10 @@ class ApiBasedToolSchemaParser:
if 'operationId' not in operation:
raise ToolApiSchemaError(f'No operationId found in operation {method} {path}.')
if 'summary' not in operation or len(operation['summary']) == 0:
warning['missing_summary'] = f'No summary found in operation {method} {path}.'
if ('summary' not in operation or len(operation['summary']) == 0) and \
('description' not in operation or len(operation['description']) == 0):
warning['missing_summary'] = f'No summary or description found in operation {method} {path}.'
if 'description' not in operation or len(operation['description']) == 0:
warning['missing_description'] = f'No description found in operation {method} {path}.'
openapi['paths'][path][method] = {
'operationId': operation['operationId'],
'summary': operation.get('summary', ''),
......
......@@ -116,6 +116,10 @@ class Dataset(db.Model):
}
return self.retrieval_model if self.retrieval_model else default_retrieval_model
@staticmethod
def gen_collection_name_by_id(dataset_id: str) -> str:
normalized_dataset_id = dataset_id.replace("-", "_")
return f'Vector_index_{normalized_dataset_id}_Node'
class DatasetProcessRule(db.Model):
__tablename__ = 'dataset_process_rules'
......
......@@ -66,4 +66,5 @@ yfinance~=0.2.35
pydub~=0.25.1
gmpy2~=2.1.5
numexpr~=2.9.0
duckduckgo-search==4.4.3
\ No newline at end of file
duckduckgo-search==4.4.3
arxiv==2.1.0
\ No newline at end of file
......@@ -37,7 +37,7 @@ from services.errors.account import NoPermissionError
from services.errors.dataset import DatasetNameDuplicateError
from services.errors.document import DocumentIndexingError
from services.errors.file import FileNotExistsError
from services.feature_service import FeatureService
from services.feature_service import FeatureModel, FeatureService
from services.vector_service import VectorService
from tasks.clean_notion_document_task import clean_notion_document_task
from tasks.deal_dataset_vector_index_task import deal_dataset_vector_index_task
......@@ -469,6 +469,9 @@ class DocumentService:
batch_upload_limit = int(current_app.config['BATCH_UPLOAD_LIMIT'])
if count > batch_upload_limit:
raise ValueError(f"You have reached the batch upload limit of {batch_upload_limit}.")
DocumentService.check_documents_upload_quota(count, features)
# if dataset is empty, update dataset data_source_type
if not dataset.data_source_type:
dataset.data_source_type = document_data["data_source"]["type"]
......@@ -619,6 +622,12 @@ class DocumentService:
return documents, batch
@staticmethod
def check_documents_upload_quota(count: int, features: FeatureModel):
can_upload_size = features.documents_upload_quota.limit - features.documents_upload_quota.size
if count > can_upload_size:
raise ValueError(f'You have reached the limit of your subscription. Only {can_upload_size} documents can be uploaded.')
@staticmethod
def build_document(dataset: Dataset, process_rule_id: str, data_source_type: str, document_form: str,
document_language: str, data_source_info: dict, created_from: str, position: int,
......@@ -763,6 +772,8 @@ class DocumentService:
if count > batch_upload_limit:
raise ValueError(f"You have reached the batch upload limit of {batch_upload_limit}.")
DocumentService.check_documents_upload_quota(count, features)
embedding_model = None
dataset_collection_binding_id = None
retrieval_model = None
......@@ -1244,7 +1255,7 @@ class DatasetCollectionBindingService:
dataset_collection_binding = DatasetCollectionBinding(
provider_name=provider_name,
model_name=model_name,
collection_name="Vector_index_" + str(uuid.uuid4()).replace("-", "_") + '_Node',
collection_name=Dataset.gen_collection_name_by_id(str(uuid.uuid4())),
type=collection_type
)
db.session.add(dataset_collection_binding)
......
......@@ -25,6 +25,7 @@ class FeatureModel(BaseModel):
apps: LimitationModel = LimitationModel(size=0, limit=10)
vector_space: LimitationModel = LimitationModel(size=0, limit=5)
annotation_quota_limit: LimitationModel = LimitationModel(size=0, limit=10)
documents_upload_quota: LimitationModel = LimitationModel(size=0, limit=50)
docs_processing: str = 'standard'
can_replace_logo: bool = False
......@@ -63,6 +64,9 @@ class FeatureService:
features.vector_space.size = billing_info['vector_space']['size']
features.vector_space.limit = billing_info['vector_space']['limit']
features.documents_upload_quota.size = billing_info['documents_upload_quota']['size']
features.documents_upload_quota.limit = billing_info['documents_upload_quota']['limit']
features.annotation_quota_limit.size = billing_info['annotation_quota_limit']['size']
features.annotation_quota_limit.limit = billing_info['annotation_quota_limit']['limit']
......
......@@ -209,8 +209,8 @@ class ToolManageService:
# extra info like description will be set here
tool_bundles, schema_type = ToolManageService.convert_schema_to_tool_bundles(schema, extra_info)
if len(tool_bundles) > 10:
raise ValueError('the number of apis should be less than 10')
if len(tool_bundles) > 100:
raise ValueError('the number of apis should be less than 100')
# create db provider
db_provider = ApiToolProvider(
......
......@@ -153,18 +153,18 @@ const AgentTools: FC = () => {
)
: (
<div className='hidden group-hover:flex items-center'>
{item.provider_type === CollectionType.builtIn && (
<TooltipPlus
popupContent={t('tools.setBuiltInTools.infoAndSetting')}
>
<div className='mr-1 p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
setCurrentTool(item)
setIsShowSettingTool(true)
}}>
<InfoCircle className='w-4 h-4 text-gray-500' />
</div>
</TooltipPlus>
)}
{/* {item.provider_type === CollectionType.builtIn && ( */}
<TooltipPlus
popupContent={t('tools.setBuiltInTools.infoAndSetting')}
>
<div className='mr-1 p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
setCurrentTool(item)
setIsShowSettingTool(true)
}}>
<InfoCircle className='w-4 h-4 text-gray-500' />
</div>
</TooltipPlus>
{/* )} */}
<div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
const newModelConfig = produce(modelConfig, (draft) => {
......@@ -209,6 +209,7 @@ const AgentTools: FC = () => {
toolName={currentTool?.tool_name as string}
setting={currentTool?.tool_parameters as any}
collection={currentTool?.collection as Collection}
isBuiltIn={currentTool?.collection?.type === CollectionType.builtIn}
onSave={handleToolSettingChange}
onHide={() => setIsShowSettingTool(false)}
/>)
......
......@@ -8,14 +8,17 @@ import Drawer from '@/app/components/base/drawer-plus'
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
import type { Collection, Tool } from '@/app/components/tools/types'
import { fetchBuiltInToolList } from '@/service/tools'
import { fetchBuiltInToolList, fetchCustomToolList } from '@/service/tools'
import I18n from '@/context/i18n'
import Button from '@/app/components/base/button'
import Loading from '@/app/components/base/loading'
import { DiagonalDividingLine } from '@/app/components/base/icons/src/public/common'
import { getLanguage } from '@/i18n/language'
import AppIcon from '@/app/components/base/app-icon'
type Props = {
collection: Collection
isBuiltIn?: boolean
toolName: string
setting?: Record<string, any>
readonly?: boolean
......@@ -25,6 +28,7 @@ type Props = {
const SettingBuiltInTool: FC<Props> = ({
collection,
isBuiltIn = true,
toolName,
setting = {},
readonly,
......@@ -52,7 +56,7 @@ const SettingBuiltInTool: FC<Props> = ({
(async () => {
setIsLoading(true)
try {
const list = await fetchBuiltInToolList(collection.name)
const list = isBuiltIn ? await fetchBuiltInToolList(collection.name) : await fetchCustomToolList(collection.name)
setTools(list)
const currTool = list.find(tool => tool.name === toolName)
if (currTool) {
......@@ -135,12 +139,24 @@ const SettingBuiltInTool: FC<Props> = ({
onHide={onHide}
title={(
<div className='flex'>
<div
className='w-6 h-6 bg-cover bg-center rounded-md'
style={{
backgroundImage: `url(${collection.icon})`,
}}
></div>
{collection.icon === 'string'
? (
<div
className='w-6 h-6 bg-cover bg-center rounded-md'
style={{
backgroundImage: `url(${collection.icon})`,
}}
></div>
)
: (
<AppIcon
className='rounded-md'
size='tiny'
icon={(collection.icon as any)?.content}
background={(collection.icon as any)?.background}
/>
)}
<div className='ml-2 leading-6 text-base font-semibold text-gray-900'>{currTool?.label[language]}</div>
{(hasSetting && !readonly) && (<>
<DiagonalDividingLine className='mx-4' />
......
......@@ -16,6 +16,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = {
teamMembers: 1,
buildApps: 10,
vectorSpace: 5,
documentsUploadQuota: 50,
documentProcessingPriority: Priority.standard,
logHistory: 30,
customTools: unAvailable,
......@@ -32,6 +33,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = {
teamMembers: 3,
buildApps: 50,
vectorSpace: 200,
documentsUploadQuota: 500,
documentProcessingPriority: Priority.priority,
logHistory: NUM_INFINITE,
customTools: 10,
......@@ -48,6 +50,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = {
teamMembers: NUM_INFINITE,
buildApps: NUM_INFINITE,
vectorSpace: 1000,
documentsUploadQuota: 1000,
documentProcessingPriority: Priority.topPriority,
logHistory: NUM_INFINITE,
customTools: NUM_INFINITE,
......@@ -64,6 +67,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = {
teamMembers: NUM_INFINITE,
buildApps: NUM_INFINITE,
vectorSpace: NUM_INFINITE,
documentsUploadQuota: NUM_INFINITE,
documentProcessingPriority: Priority.topPriority,
logHistory: NUM_INFINITE,
customTools: NUM_INFINITE,
......
......@@ -129,6 +129,9 @@ const PlanItem: FC<Props> = ({
<div className='mt-3.5 flex items-center space-x-1'>
<div>+ {t('billing.plansCommon.supportItems.logoChange')}</div>
</div>
<div className='mt-3.5 flex items-center space-x-1'>
<div>+ {t('billing.plansCommon.supportItems.bulkUpload')}</div>
</div>
<div className='mt-3.5 flex items-center space-x-1'>
<div className='flex items-center'>
+
......@@ -264,6 +267,10 @@ const PlanItem: FC<Props> = ({
value={planInfo.vectorSpace === NUM_INFINITE ? t('billing.plansCommon.unlimited') as string : (planInfo.vectorSpace >= 1000 ? `${planInfo.vectorSpace / 1000}G` : `${planInfo.vectorSpace}MB`)}
tooltip={t('billing.plansCommon.vectorSpaceBillingTooltip') as string}
/>
<KeyValue
label={t('billing.plansCommon.documentsUploadQuota')}
value={planInfo.vectorSpace === NUM_INFINITE ? t('billing.plansCommon.unlimited') as string : planInfo.documentsUploadQuota}
/>
<KeyValue
label={t('billing.plansCommon.documentProcessingPriority')}
value={t(`billing.plansCommon.priority.${planInfo.documentProcessingPriority}`) as string}
......
......@@ -17,6 +17,7 @@ export type PlanInfo = {
teamMembers: number
buildApps: number
vectorSpace: number
documentsUploadQuota: number
documentProcessingPriority: Priority
logHistory: number
customTools: string | number
......
......@@ -23,6 +23,7 @@ type IFileUploaderProps = {
onFileUpdate: (fileItem: FileItem, progress: number, list: FileItem[]) => void
onFileListUpdate?: (files: FileItem[]) => void
onPreview: (file: File) => void
notSupportBatchUpload?: boolean
}
const FileUploader = ({
......@@ -32,6 +33,7 @@ const FileUploader = ({
onFileUpdate,
onFileListUpdate,
onPreview,
notSupportBatchUpload,
}: IFileUploaderProps) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
......@@ -40,6 +42,7 @@ const FileUploader = ({
const dropRef = useRef<HTMLDivElement>(null)
const dragRef = useRef<HTMLDivElement>(null)
const fileUploader = useRef<HTMLInputElement>(null)
const hideUpload = notSupportBatchUpload && fileList.length > 0
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
const { data: supportFileTypesResponse } = useSWR({ url: '/files/support-type' }, fetchSupportFileTypes)
......@@ -131,7 +134,7 @@ const FileUploader = ({
xhr: new XMLHttpRequest(),
data: formData,
onprogress: onProgress,
})
}, false, undefined, '?source=datasets')
.then((res: File) => {
const completeFile = {
fileID: fileItem.fileID,
......@@ -143,8 +146,8 @@ const FileUploader = ({
onFileUpdate(completeFile, 100, fileListCopy)
return Promise.resolve({ ...completeFile })
})
.catch(() => {
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.failed') })
.catch((e) => {
notify({ type: 'error', message: e?.response?.code === 'forbidden' ? e?.response?.message : t('datasetCreation.stepOne.uploader.failed') })
onFileUpdate(fileItem, -2, fileListCopy)
return Promise.resolve({ ...fileItem })
})
......@@ -252,30 +255,36 @@ const FileUploader = ({
return (
<div className={s.fileUploader}>
<input
ref={fileUploader}
id="fileUploader"
style={{ display: 'none' }}
type="file"
multiple
accept={ACCEPTS.join(',')}
onChange={fileChangeHandle}
/>
{!hideUpload && (
<input
ref={fileUploader}
id="fileUploader"
style={{ display: 'none' }}
type="file"
multiple={!notSupportBatchUpload}
accept={ACCEPTS.join(',')}
onChange={fileChangeHandle}
/>
)}
<div className={cn(s.title, titleClassName)}>{t('datasetCreation.stepOne.uploader.title')}</div>
<div ref={dropRef} className={cn(s.uploader, dragging && s.dragging)}>
<div className='flex justify-center items-center min-h-6 mb-2'>
<span className={s.uploadIcon} />
<span>
{t('datasetCreation.stepOne.uploader.button')}
<label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
</span>
{!hideUpload && (
<div ref={dropRef} className={cn(s.uploader, dragging && s.dragging)}>
<div className='flex justify-center items-center min-h-6 mb-2'>
<span className={s.uploadIcon} />
<span>
{t('datasetCreation.stepOne.uploader.button')}
<label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
</span>
</div>
<div className={s.tip}>{t('datasetCreation.stepOne.uploader.tip', {
size: fileUploadConfig.file_size_limit,
supportTypes: supportTypesShowNames,
})}</div>
{dragging && <div ref={dragRef} className={s.draggingCover} />}
</div>
<div className={s.tip}>{t('datasetCreation.stepOne.uploader.tip', {
size: fileUploadConfig.file_size_limit,
supportTypes: supportTypesShowNames,
})}</div>
{dragging && <div ref={dragRef} className={s.draggingCover} />}
</div>
)}
<div className={s.fileList}>
{fileList.map((fileItem, index) => (
<div
......
......@@ -39,7 +39,7 @@ export const NotionConnector = ({ onSetting }: NotionConnectorProps) => {
return (
<div className={s.notionConnectionTip}>
<span className={s.notionIcon}/>
<span className={s.notionIcon} />
<div className={s.title}>{t('datasetCreation.stepOne.notionSyncTitle')}</div>
<div className={s.tip}>{t('datasetCreation.stepOne.notionSyncTip')}</div>
<Button className='h-8' type='primary' onClick={onSetting}>{t('datasetCreation.stepOne.connect')}</Button>
......@@ -92,7 +92,7 @@ const StepOne = ({
const hasNotin = notionPages.length > 0
const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace
const isShowVectorSpaceFull = (allFileLoaded || hasNotin) && isVectorSpaceFull && enableBilling
const notSupportBatchUpload = enableBilling && plan.type === 'sandbox'
const nextDisabled = useMemo(() => {
if (!files.length)
return true
......@@ -169,6 +169,7 @@ const StepOne = ({
onFileListUpdate={updateFileList}
onFileUpdate={updateFile}
onPreview={updateCurrentFile}
notSupportBatchUpload={notSupportBatchUpload}
/>
{isShowVectorSpaceFull && (
<div className='max-w-[640px] mb-4'>
......
......@@ -37,7 +37,8 @@ const Apps = ({
const { isCurrentWorkspaceManager } = useAppContext()
const router = useRouter()
const { hasEditPermission } = useContext(ExploreContext)
const allCategoriesEn = t('explore.apps.allCategories')
const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' })
const [currCategory, setCurrCategory] = useTabSearchParams({
defaultTab: allCategoriesEn,
})
......@@ -58,11 +59,10 @@ const Apps = ({
},
)
const currList = (() => {
if (currCategory === allCategoriesEn)
return allList
return allList.filter(item => item.category === currCategory)
})()
const currList
= currCategory === allCategoriesEn
? allList
: allList.filter(item => item.category === currCategory)
const [currApp, setCurrApp] = React.useState<App | null>(null)
const [isShowCreateModal, setIsShowCreateModal] = React.useState(false)
......@@ -127,6 +127,7 @@ const Apps = ({
list={categories}
value={currCategory}
onChange={setCurrCategory}
allCategoriesEn={allCategoriesEn}
/>
<div className={cn(
'relative flex flex-1 pb-6 flex-col overflow-auto bg-gray-100 shrink-0 grow',
......
......@@ -12,7 +12,11 @@ export type ICategoryProps = {
className?: string
list: AppCategory[]
value: string
onChange: (value: AppCategory | '') => void
onChange: (value: AppCategory | string) => void
/**
* default value for searchparam 'category' in en
*/
allCategoriesEn: string
}
const Category: FC<ICategoryProps> = ({
......@@ -20,8 +24,10 @@ const Category: FC<ICategoryProps> = ({
list,
value,
onChange,
allCategoriesEn,
}) => {
const { t } = useTranslation()
const isAllCategories = !list.includes(value)
const itemClassName = (isSelected: boolean) => cn(
'px-3 py-[5px] h-[28px] rounded-lg border-[0.5px] border-transparent text-gray-700 font-medium leading-[18px] cursor-pointer hover:bg-gray-200',
......@@ -31,8 +37,8 @@ const Category: FC<ICategoryProps> = ({
return (
<div className={cn(className, 'flex space-x-1 text-[13px] flex-wrap')}>
<div
className={itemClassName(value === '')}
onClick={() => onChange('')}
className={itemClassName(isAllCategories)}
onClick={() => onChange(allCategoriesEn)}
>
{t('explore.apps.allCategories')}
</div>
......@@ -48,4 +54,5 @@ const Category: FC<ICategoryProps> = ({
</div>
)
}
export default React.memo(Category)
......@@ -2,14 +2,11 @@
import { useTranslation } from 'react-i18next'
import classNames from 'classnames'
import Link from 'next/link'
import { useContext } from 'use-context-selector'
import s from './index.module.css'
import Modal from '@/app/components/base/modal'
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
import type { LangGeniusVersionResponse } from '@/models/common'
import { IS_CE_EDITION } from '@/config'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
import LogoSite from '@/app/components/base/logo/logo-site'
type IAccountSettingProps = {
......@@ -25,7 +22,6 @@ export default function AccountAbout({
onCancel,
}: IAccountSettingProps) {
const { t } = useTranslation()
const { locale } = useContext(I18n)
const isLatest = langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version
return (
......@@ -48,8 +44,8 @@ export default function AccountAbout({
IS_CE_EDITION
? <Link href={'https://github.com/langgenius/dify/blob/main/LICENSE'} target='_blank' rel='noopener noreferrer'>Open Source License</Link>
: <>
<Link href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/user-agreement/privacy-policy' : 'https://docs.dify.ai/v/zh-hans/user-agreement/privacy-policy'} target='_blank' rel='noopener noreferrer'>Privacy Policy</Link>,
<Link href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/user-agreement/terms-of-service' : 'https://docs.dify.ai/v/zh-hans/user-agreement/terms-of-service'} target='_blank' rel='noopener noreferrer'>Terms of Service</Link>
<Link href='https://dify.ai/privacy' target='_blank' rel='noopener noreferrer'>Privacy Policy</Link>,
<Link href='https://dify.ai/terms' target='_blank' rel='noopener noreferrer'>Terms of Service</Link>
</>
}
</div>
......
......@@ -26,7 +26,7 @@ const ModelIcon: FC<ModelIconProps> = ({
return (
<img
alt='model-icon'
src={`${provider.icon_small[language]}?_token=${localStorage.getItem('console_token')}`}
src={`${provider.icon_small[language] || provider.icon_small.en_US}?_token=${localStorage.getItem('console_token')}`}
className={`w-4 h-4 ${className}`}
/>
)
......
......@@ -69,7 +69,7 @@ const Form: FC<FormProps> = ({
<Tooltip popupContent={
// w-[100px] caused problem
<div className=''>
{tooltip[language]}
{tooltip[language] || tooltip.en_US}
</div>
} >
<HelpCircle className='w-3 h-3 text-gray-500' />
......@@ -91,7 +91,7 @@ const Form: FC<FormProps> = ({
return (
<div key={variable} className='py-3'>
<div className='py-2 text-sm text-gray-900'>
{label[language]}
{label[language] || label.en_US}
{
required && (
<span className='ml-1 text-red-500'>*</span>
......@@ -104,7 +104,7 @@ const Form: FC<FormProps> = ({
value={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]}
onChange={val => handleFormChange(variable, val)}
validated={validatedSuccess}
placeholder={placeholder?.[language]}
placeholder={placeholder?.[language] || placeholder?.en_US}
disabled={disabed}
type={formSchema.type === FormTypeEnum.textNumber ? 'number' : 'text'}
{...(formSchema.type === FormTypeEnum.textNumber ? { min: (formSchema as CredentialFormSchemaNumberInput).min, max: (formSchema as CredentialFormSchemaNumberInput).max } : {})}
......@@ -132,7 +132,7 @@ const Form: FC<FormProps> = ({
return (
<div key={variable} className='py-3'>
<div className='py-2 text-sm text-gray-900'>
{label[language]}
{label[language] || label.en_US}
{
required && (
<span className='ml-1 text-red-500'>*</span>
......@@ -188,7 +188,7 @@ const Form: FC<FormProps> = ({
return (
<div key={variable} className='py-3'>
<div className='py-2 text-sm text-gray-900'>
{label[language]}
{label[language] || label.en_US}
{
required && (
......@@ -230,7 +230,7 @@ const Form: FC<FormProps> = ({
<div key={variable} className='py-3'>
<div className='flex items-center justify-between py-2 text-sm text-gray-900'>
<div className='flex items-center space-x-2'>
<span>{label[language]}</span>
<span>{label[language] || label.en_US}</span>
{tooltipContent}
</div>
<Radio.Group
......
......@@ -246,12 +246,12 @@ const ModelModal: FC<ModelModalProps> = ({
(provider.help && (provider.help.title || provider.help.url))
? (
<a
href={provider.help?.url[language]}
href={provider.help?.url[language] || provider.help?.url.en_US}
target='_blank' rel='noopener noreferrer'
className='inline-flex items-center text-xs text-primary-600'
onClick={e => !provider.help.url && e.preventDefault()}
>
{provider.help.title?.[language] || provider.help.url[language]}
{provider.help.title?.[language] || provider.help.url[language] || provider.help.title?.en_US || provider.help.url.en_US}
<LinkExternal02 className='ml-1 w-3 h-3' />
</a>
)
......
......@@ -34,7 +34,6 @@ const ModelName: FC<ModelNameProps> = ({
if (!modelItem)
return null
return (
<div
className={`
......@@ -44,9 +43,9 @@ const ModelName: FC<ModelNameProps> = ({
>
<div
className='mr-1 truncate'
title={modelItem.label[language]}
title={modelItem.label[language] || modelItem.label.en_US}
>
{modelItem.label[language]}
{modelItem.label[language] || modelItem.label.en_US}
</div>
{
showModelType && modelItem.model_type && (
......
......@@ -115,6 +115,11 @@ const ParameterItem: FC<ParameterItemProps> = ({
}
}
useEffect(() => {
if ((parameterRule.type === 'int' || parameterRule.type === 'float') && numberInputRef.current)
numberInputRef.current.value = `${renderValue}`
}, [value])
const renderInput = () => {
const numberInputWithSlide = (parameterRule.type === 'int' || parameterRule.type === 'float')
&& !isNullOrUndefined(parameterRule.min)
......@@ -207,27 +212,22 @@ const ParameterItem: FC<ParameterItemProps> = ({
return null
}
useEffect(() => {
if (numberInputRef.current)
numberInputRef.current.value = `${renderValue}`
}, [])
return (
<div className={`flex items-center justify-between ${className}`}>
<div>
<div className='shrink-0 flex items-center w-[200px]'>
<div
className='mr-0.5 text-[13px] font-medium text-gray-700 truncate'
title={parameterRule.label[language]}
title={parameterRule.label[language] || parameterRule.label.en_US}
>
{parameterRule.label[language]}
{parameterRule.label[language] || parameterRule.label.en_US}
</div>
{
parameterRule.help && (
<Tooltip
selector={`model-parameter-rule-${parameterRule.name}`}
htmlContent={(
<div className='w-[200px] whitespace-pre-wrap'>{parameterRule.help[language]}</div>
<div className='w-[200px] whitespace-pre-wrap'>{parameterRule.help[language] || parameterRule.help.en_US}</div>
)}
>
<HelpCircle className='mr-1.5 w-3.5 h-3.5 text-gray-400' />
......
......@@ -65,7 +65,7 @@ const PopupItem: FC<PopupItemProps> = ({
return (
<div className='mb-1'>
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>
{model.label[language]}
{model.label[language] || model.label.en_US}
</div>
{
model.models.map(modelItem => (
......
......@@ -23,7 +23,22 @@ const Popup: FC<PopupProps> = ({
const language = useLanguage()
const [searchText, setSearchText] = useState('')
const filteredModelList = modelList.filter(model => model.models.filter(modelItem => modelItem.label[language].includes(searchText)).length)
const filteredModelList = modelList.filter(
model => model.models.filter(
(modelItem) => {
if (modelItem.label[language] !== undefined)
return modelItem.label[language].includes(searchText)
let found = false
Object.keys(modelItem.label).forEach((key) => {
if (modelItem.label[key].includes(searchText))
found = true
})
return found
},
).length,
)
return (
<div className='w-[320px] max-h-[480px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg overflow-y-auto'>
......
......@@ -69,7 +69,7 @@ const ProviderCard: FC<ProviderCardProps> = ({
</div>
{
provider.description && (
<div className='mt-1 leading-4 text-xs text-black/[48]'>{provider.description[language]}</div>
<div className='mt-1 leading-4 text-xs text-black/[48]'>{provider.description[language] || provider.description.en_US}</div>
)
}
</div>
......
......@@ -16,7 +16,7 @@ const ProviderIcon: FC<ProviderIconProps> = ({
return (
<img
alt='provider-icon'
src={`${provider.icon_large[language]}?_token=${localStorage.getItem('console_token')}`}
src={`${provider.icon_large[language] || provider.icon_large.en_US}?_token=${localStorage.getItem('console_token')}`}
className={`w-auto h-6 ${className}`}
/>
)
......
......@@ -23,9 +23,9 @@ export const AppInfo: FC<{ siteInfo: SiteInfo }> = ({ siteInfo }) => {
export const PromptTemplate: FC<{ html: string }> = ({ html }) => {
return (
<div
className={' box-border text-sm text-gray-700'}
className={'box-border text-sm text-gray-700'}
dangerouslySetInnerHTML={{ __html: html }}
></div>
/>
)
}
......
......@@ -118,6 +118,13 @@ const EditCustomCollectionModal: FC<Props> = ({
const handleSave = () => {
const postData = clone(customCollection)
delete postData.tools
if (postData.credentials.auth_type === AuthType.none) {
delete postData.credentials.api_key_header
delete postData.credentials.api_key_header_prefix
delete postData.credentials.api_key_value
}
if (isAdd) {
onAdd?.(postData)
return
......
......@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { Settings01 } from '../../base/icons/src/vender/line/general'
import ConfigCredentials from './config-credentials'
import type { Credential, CustomCollectionBackend, CustomParamSchema } from '@/app/components/tools/types'
import { AuthType, type Credential, type CustomCollectionBackend, type CustomParamSchema } from '@/app/components/tools/types'
import Button from '@/app/components/base/button'
import Drawer from '@/app/components/base/drawer-plus'
import I18n from '@/context/i18n'
......@@ -34,9 +34,16 @@ const TestApi: FC<Props> = ({
const { operation_id: toolName, parameters } = tool
const [parametersValue, setParametersValue] = useState<Record<string, string>>({})
const handleTest = async () => {
// clone test schema
const credentials = JSON.parse(JSON.stringify(tempCredential)) as Credential
if (credentials.auth_type === AuthType.none) {
delete credentials.api_key_header_prefix
delete credentials.api_key_header
delete credentials.api_key_value
}
const data = {
tool_name: toolName,
credentials: tempCredential,
credentials,
schema_type: customCollection.schema_type,
schema: customCollection.schema,
parameters: parametersValue,
......
......@@ -69,9 +69,22 @@ const Tools: FC<Props> = ({
})()
const [query, setQuery] = useState('')
const [collectionType, setCollectionType] = useTabSearchParams({
const [toolPageCollectionType, setToolPageCollectionType] = useTabSearchParams({
defaultTab: collectionTypeOptions[0].value,
})
const [appPageCollectionType, setAppPageCollectionType] = useState(collectionTypeOptions[0].value)
const { collectionType, setCollectionType } = (() => {
if (isInToolsPage) {
return {
collectionType: toolPageCollectionType,
setCollectionType: setToolPageCollectionType,
}
}
return {
collectionType: appPageCollectionType,
setCollectionType: setAppPageCollectionType,
}
})()
const showCollectionList = (() => {
let typeFilteredList: Collection[] = []
......
......@@ -35,9 +35,10 @@ const Item: FC<Props> = ({
const language = getLanguage(locale)
const isBuiltIn = collection.type === CollectionType.builtIn
const canShowDetail = !isBuiltIn || (isBuiltIn && isInToolsPage)
const canShowDetail = isInToolsPage
const [showDetail, setShowDetail] = useState(false)
const addBtn = <Button className='shrink-0 flex items-center h-7 !px-3 !text-xs !font-medium !text-gray-700' disabled={added || !collection.is_team_authorization} onClick={() => onAdd?.(payload)}>{t(`common.operation.${added ? 'added' : 'add'}`)}</Button>
return (
<>
<div
......@@ -63,7 +64,7 @@ const Item: FC<Props> = ({
)}
</div>
</div>
{showDetail && isBuiltIn && (
{showDetail && (
<SettingBuiltInTool
collection={collection}
toolName={payload.name}
......@@ -71,6 +72,7 @@ const Item: FC<Props> = ({
onHide={() => {
setShowDetail(false)
}}
isBuiltIn={isBuiltIn}
/>
)}
</>
......
......@@ -12,7 +12,6 @@ import { IS_CE_EDITION, apiPrefix } from '@/config'
import Button from '@/app/components/base/button'
import { login, oauth } from '@/service/common'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
import { getPurifyHref } from '@/utils'
const validEmailReg = /^[\w\.-]+@([\w-]+\.)+[\w-]{2,}$/
......@@ -282,13 +281,13 @@ const NormalForm = () => {
<Link
className='text-primary-600'
target='_blank' rel='noopener noreferrer'
href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/user-agreement/terms-of-service' : 'https://docs.dify.ai/v/zh-hans/user-agreement/terms-of-service'}
href='https://dify.ai/terms'
>{t('login.tos')}</Link>
&nbsp;&&nbsp;
<Link
className='text-primary-600'
target='_blank' rel='noopener noreferrer'
href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/user-agreement/privacy-policy' : 'https://docs.dify.ai/v/zh-hans/user-agreement/privacy-policy'}
href='https://dify.ai/privacy'
>{t('login.pp')}</Link>
</div>
......
......@@ -32,6 +32,7 @@ const translation = {
vectorSpace: 'Vector Space',
vectorSpaceBillingTooltip: 'Each 1MB can store about 1.2million characters of vectorized data(estimated using OpenAI Embeddings, varies across models).',
vectorSpaceTooltip: 'Vector Space is the long-term memory system required for LLMs to comprehend your data.',
documentsUploadQuota: 'Documents Upload Quota',
documentProcessingPriority: 'Document Processing Priority',
documentProcessingPriorityTip: 'For higher document processing priority, please upgrade your plan.',
documentProcessingPriorityUpgrade: 'Process more data with higher accuracy at faster speeds.',
......@@ -56,6 +57,7 @@ const translation = {
dedicatedAPISupport: 'Dedicated API support',
customIntegration: 'Custom integration and support',
ragAPIRequest: 'RAG API Requests',
bulkUpload: 'Bulk upload documents',
agentMode: 'Agent Mode',
workflow: 'Workflow',
},
......
const translation = {
title: '注釈',
name: '注釈の返信',
editBy: '{{author}} によって編集された回答',
noData: {
title: '注釈がありません',
description: 'ここではアプリのデバッグ中に注釈を編集したり、一括で注釈をインポートしたりして高品質の応答を行うことができます。',
},
table: {
header: {
question: '質問',
answer: '回答',
createdAt: '作成日時',
hits: 'ヒット数',
actions: 'アクション',
addAnnotation: '注釈を追加',
bulkImport: '一括インポート',
bulkExport: '一括エクスポート',
clearAll: 'すべての注釈をクリア',
},
},
editModal: {
title: '注釈の返信を編集',
queryName: 'ユーザーのクエリ',
answerName: 'ストーリーテラーボット',
yourAnswer: 'あなたの回答',
answerPlaceholder: 'ここに回答を入力してください',
yourQuery: 'あなたのクエリ',
queryPlaceholder: 'ここにクエリを入力してください',
removeThisCache: 'この注釈を削除',
createdAt: '作成日時',
},
addModal: {
title: '注釈の返信を追加',
queryName: '質問',
answerName: '回答',
answerPlaceholder: 'ここに回答を入力してください',
queryPlaceholder: 'ここに質問を入力してください',
createNext: '別の注釈付きの応答を追加',
},
batchModal: {
title: '一括インポート',
csvUploadTitle: 'CSVファイルをここにドラッグ&ドロップするか、',
browse: '参照',
tip: 'CSVファイルは以下の構造に準拠する必要があります:',
question: '質問',
answer: '回答',
contentTitle: 'チャンクの内容',
content: '内容',
template: 'テンプレートをここからダウンロード',
cancel: 'キャンセル',
run: '一括実行',
runError: '一括実行に失敗しました',
processing: '一括処理中',
completed: 'インポートが完了しました',
error: 'インポートエラー',
ok: 'OK',
},
errorMessage: {
answerRequired: '回答は必須です',
queryRequired: '質問は必須です',
},
viewModal: {
annotatedResponse: '注釈の返信',
hitHistory: 'ヒット履歴',
hit: 'ヒット',
hits: 'ヒット数',
noHitHistory: 'ヒット履歴はありません',
},
hitHistoryTable: {
query: 'クエリ',
match: '一致',
response: '応答',
source: 'ソース',
score: 'スコア',
time: '時間',
},
initSetup: {
title: '注釈の返信の初期設定',
configTitle: '注釈の返信の設定',
confirmBtn: '保存して有効にする',
configConfirmBtn: '保存',
},
embeddingModelSwitchTip: '注釈テキストのベクトル化モデルです。モデルを切り替えると再埋め込みが行われ、追加のコストが発生します。',
}
export default translation
const translation = {
apiServer: 'APIサーバー',
apiKey: 'APIキー',
status: 'ステータス',
disabled: '無効',
ok: '稼働中',
copy: 'コピー',
copied: 'コピー済み',
play: '再生',
pause: '一時停止',
playing: '再生中',
merMaind: {
rerender: '再レンダリング',
},
never: 'なし',
apiKeyModal: {
apiSecretKey: 'APIシークレットキー',
apiSecretKeyTips: 'APIの悪用を防ぐために、APIキーを保護してください。フロントエンドのコードで平文として使用しないでください。:)',
createNewSecretKey: '新しいシークレットキーを作成',
secretKey: 'シークレットキー',
created: '作成日時',
lastUsed: '最終使用日時',
generateTips: 'このキーを安全でアクセス可能な場所に保管してください。',
},
actionMsg: {
deleteConfirmTitle: 'このシークレットキーを削除しますか?',
deleteConfirmTips: 'この操作は元に戻すことはできません。',
ok: 'OK',
},
completionMode: {
title: '補完アプリAPI',
info: '記事、要約、翻訳などの高品質なテキスト生成には、ユーザーの入力を使用した補完メッセージAPIを使用します。テキスト生成は、Dify Prompt Engineeringで設定されたモデルパラメータとプロンプトテンプレートに依存しています。',
createCompletionApi: '補完メッセージの作成',
createCompletionApiTip: '質疑応答モードをサポートするために、補完メッセージを作成します。',
inputsTips: '(オプション)Prompt Engの変数に対応するキーと値のペアとしてユーザー入力フィールドを提供します。キーは変数名で、値はパラメータの値です。フィールドのタイプがSelectの場合、送信される値は事前に設定された選択肢のいずれかである必要があります。',
queryTips: 'ユーザーの入力テキスト内容。',
blocking: 'ブロッキングタイプで、実行が完了して結果が返されるまで待機します。(処理が長い場合、リクエストは中断される場合があります)',
streaming: 'ストリーミングの返却。SSE(Server-Sent Events)に基づいたストリーミングの返却の実装。',
messageFeedbackApi: 'メッセージフィードバック(いいね)',
messageFeedbackApiTip: 'エンドユーザーの代わりに受信したメッセージを「いいね」または「いいね」で評価します。このデータはログ&注釈ページで表示され、将来のモデルの微調整に使用されます。',
messageIDTip: 'メッセージID',
ratingTip: 'いいねまたはいいね、nullは元に戻す',
parametersApi: 'アプリケーションパラメータ情報の取得',
parametersApiTip: '変数名、フィールド名、タイプ、デフォルト値を含む設定済みの入力パラメータを取得します。通常、これらのフィールドをフォームに表示したり、クライアントの読み込み後にデフォルト値を入力したりするために使用されます。',
},
chatMode: {
title: 'チャットアプリAPI',
info: '質疑応答形式を使用した多目的の対話型アプリケーションには、チャットメッセージAPIを呼び出して対話を開始します。返されたconversation_idを渡すことで、継続的な会話を維持します。応答パラメータとテンプレートは、Dify Prompt Engの設定に依存します。',
createChatApi: 'チャットメッセージの作成',
createChatApiTip: '新しい会話メッセージを作成するか、既存の対話を継続します。',
inputsTips: '(オプション)Prompt Engの変数に対応するキーと値のペアとしてユーザー入力フィールドを提供します。キーは変数名で、値はパラメータの値です。フィールドのタイプがSelectの場合、送信される値は事前に設定された選択肢のいずれかである必要があります。',
queryTips: 'ユーザーの入力/質問内容',
blocking: 'ブロッキングタイプで、実行が完了して結果が返されるまで待機します。(処理が長い場合、リクエストは中断される場合があります)',
streaming: 'ストリーミングの返却。SSE(Server-Sent Events)に基づいたストリーミングの返却の実装。',
conversationIdTip: '(オプション)会話ID:初回の会話の場合は空白のままにしておき、継続する場合はコンテキストからconversation_idを渡します。',
messageFeedbackApi: 'メッセージ端末ユーザーフィードバック、いいね',
messageFeedbackApiTip: 'エンドユーザーの代わりに受信したメッセージを「いいね」または「いいね」で評価します。このデータはログ&注釈ページで表示され、将来のモデルの微調整に使用されます。',
messageIDTip: 'メッセージID',
ratingTip: 'いいねまたはいいね、nullは元に戻す',
chatMsgHistoryApi: 'チャット履歴メッセージの取得',
chatMsgHistoryApiTip: '最初のページは最新の「limit」バーを返します。逆順です。',
chatMsgHistoryConversationIdTip: '会話ID',
chatMsgHistoryFirstId: '現在のページの最初のチャットレコードのID。デフォルトはなし。',
chatMsgHistoryLimit: '1回のリクエストで返されるチャットの数',
conversationsListApi: '会話リストの取得',
conversationsListApiTip: '現在のユーザーのセッションリストを取得します。デフォルトでは、最後の20のセッションが返されます。',
conversationsListFirstIdTip: '現在のページの最後のレコードのID、デフォルトはなし。',
conversationsListLimitTip: '1回のリクエストで返されるチャットの数',
conversationRenamingApi: '会話の名前変更',
conversationRenamingApiTip: '会話の名前を変更します。名前はマルチセッションクライアントインターフェースに表示されます。',
conversationRenamingNameTip: '新しい名前',
parametersApi: 'アプリケーションパラメータ情報の取得',
parametersApiTip: '変数名、フィールド名、タイプ、デフォルト値を含む設定済みの入力パラメータを取得します。通常、これらのフィールドをフォームに表示したり、クライアントの読み込み後にデフォルト値を入力したりするために使用されます。',
},
develop: {
requestBody: 'リクエストボディ',
pathParams: 'パスパラメータ',
query: 'クエリ',
},
}
export default translation
This diff is collapsed.
const translation = {
title: 'ログ',
description: 'ログは、アプリケーションの実行状態を記録します。ユーザーの入力やAIの応答などが含まれます。',
dateTimeFormat: 'MM/DD/YYYY hh:mm A',
table: {
header: {
time: '時間',
endUser: 'エンドユーザー',
input: '入力',
output: '出力',
summary: 'タイトル',
messageCount: 'メッセージ数',
userRate: 'ユーザー評価',
adminRate: 'オペレータ評価',
},
pagination: {
previous: '前へ',
next: '次へ',
},
empty: {
noChat: 'まだ会話がありません',
noOutput: '出力なし',
element: {
title: '誰かいますか?',
content: 'ここではエンドユーザーとAIアプリケーションの相互作用を観察し、注釈を付けることでAIの精度を継続的に向上させることができます。自分自身でWebアプリを<shareLink>共有</shareLink>したり<testLink>テスト</testLink>したりして、このページに戻ってください。',
},
},
},
detail: {
time: '時間',
conversationId: '会話ID',
promptTemplate: 'プロンプトテンプレート',
promptTemplateBeforeChat: 'チャット前のプロンプトテンプレート · システムメッセージとして',
annotationTip: '{{user}}による改善',
timeConsuming: '',
second: '秒',
tokenCost: 'トークン消費',
loading: '読み込み中',
operation: {
like: 'いいね',
dislike: 'いまいち',
addAnnotation: '改善を追加',
editAnnotation: '改善を編集',
annotationPlaceholder: 'AIが返答することを期待する回答を入力してください。これはモデルの微調整やテキスト生成品質の継続的な改善に使用されます。',
},
variables: '変数',
uploadImages: 'アップロードされた画像',
},
filter: {
period: {
today: '今日',
last7days: '過去7日間',
last4weeks: '過去4週間',
last3months: '過去3ヶ月',
last12months: '過去12ヶ月',
monthToDate: '今月まで',
quarterToDate: '四半期まで',
yearToDate: '今年まで',
allTime: 'すべての期間',
},
annotation: {
all: 'すべて',
annotated: '注釈付きの改善({{count}}件)',
not_annotated: '注釈なし',
},
},
}
export default translation
const translation = {
welcome: {
firstStepTip: '始めるには、',
enterKeyTip: '以下にOpenAI APIキーを入力してください',
getKeyTip: 'OpenAIダッシュボードからAPIキーを取得してください',
placeholder: 'OpenAI APIキー(例:sk-xxxx)',
},
apiKeyInfo: {
cloud: {
trial: {
title: '{{providerName}}のトライアルクオータを使用しています。',
description: 'トライアルクオータはテスト用に提供されています。トライアルクオータの使用回数が尽きる前に、独自のモデルプロバイダを設定するか、追加のクオータを購入してください。',
},
exhausted: {
title: 'トライアルクオータが使い果たされました。APIキーを設定してください。',
description: 'トライアルクオータが使い果たされました。独自のモデルプロバイダを設定するか、追加のクオータを購入してください。',
},
},
selfHost: {
title: {
row1: '始めるには、',
row2: 'まずモデルプロバイダを設定してください。',
},
},
callTimes: '呼び出し回数',
usedToken: '使用済みトークン',
setAPIBtn: 'モデルプロバイダの設定へ',
tryCloud: 'または無料クオートでDifyのクラウドバージョンを試してみてください',
},
overview: {
title: '概要',
appInfo: {
explanation: '使いやすいAI Webアプリ',
accessibleAddress: '公開URL',
preview: 'プレビュー',
regenerate: '再生成',
preUseReminder: '続行する前にWebアプリを有効にしてください。',
settings: {
entry: '設定',
title: 'Webアプリの設定',
webName: 'Webアプリ名',
webDesc: 'Webアプリの説明',
webDescTip: 'このテキストはクライアント側に表示され、アプリの使用方法に関する基本的なガイダンスを提供します。',
webDescPlaceholder: 'Webアプリの説明を入力してください',
language: '言語',
more: {
entry: '詳細設定を表示',
copyright: '著作権',
copyRightPlaceholder: '著作者または組織の名前を入力してください',
privacyPolicy: 'プライバシーポリシー',
privacyPolicyPlaceholder: 'プライバシーポリシーリンクを入力してください',
privacyPolicyTip: '訪問者がアプリが収集するデータを理解するのに役立ちます。Difyの<privacyPolicyLink>プライバシーポリシー</privacyPolicyLink>を参照してください。',
},
},
embedded: {
entry: '埋め込み',
title: 'ウェブサイトに埋め込む',
explanation: 'チャットアプリをウェブサイトに埋め込む方法を選択してください。',
iframe: 'ウェブサイトの任意の場所にチャットアプリを追加するには、このiframeをHTMLコードに追加してください。',
scripts: 'ウェブサイトの右下にチャットアプリを追加するには、このコードをHTMLに追加してください。',
chromePlugin: 'Dify Chatbot Chrome拡張機能をインストール',
copied: 'コピー済み',
copy: 'コピー',
},
qrcode: {
title: '共有用QRコード',
scan: 'アプリを共有するためにスキャン',
download: 'QRコードをダウンロード',
},
customize: {
way: '方法',
entry: 'カスタマイズ',
title: 'AI Webアプリのカスタマイズ',
explanation: 'シナリオとスタイルのニーズに合わせてWebアプリのフロントエンドをカスタマイズできます。',
way1: {
name: 'クライアントコードをフォークして変更し、Vercelにデプロイする(推奨)',
step1: 'クライアントコードをフォークして変更する',
step1Tip: 'ここをクリックしてソースコードをGitHubアカウントにフォークし、コードを変更してください',
step1Operation: 'Dify-WebClient',
step2: 'Vercelにデプロイする',
step2Tip: 'ここをクリックしてリポジトリをVercelにインポートし、デプロイしてください',
step2Operation: 'リポジトリのインポート',
step3: '環境変数を設定する',
step3Tip: 'Vercelに以下の環境変数を追加してください',
},
way2: {
name: 'APIを呼び出すためのクライアントサイドコードを記述し、サーバーにデプロイする',
operation: 'ドキュメント',
},
},
},
apiInfo: {
title: 'バックエンドサービスAPI',
explanation: 'アプリケーションに簡単に統合できます',
accessibleAddress: 'サービスAPIエンドポイント',
doc: 'APIリファレンス',
},
status: {
running: 'サービス中',
disable: '無効化',
},
},
analysis: {
title: '分析',
ms: 'ミリ秒',
tokenPS: 'トークン/秒',
totalMessages: {
title: '総メッセージ数',
explanation: 'AIとのやり取りのうち、プロンプトのエンジニアリングやデバッグを除いた日次の相互作用数です。',
},
activeUsers: {
title: 'アクティブユーザー',
explanation: 'AIとのQ&Aに参加しているユニークなユーザー数です。プロンプトのエンジニアリングやデバッグを除きます。',
},
tokenUsage: {
title: 'トークン使用量',
explanation: 'アプリケーションの言語モデルの日次トークン使用量を反映し、コスト管理の目的に役立ちます。',
consumed: '使用済み',
},
avgSessionInteractions: {
title: '平均セッション相互作用数',
explanation: '会話ベースのアプリケーションの連続したユーザーとAIのコミュニケーション数です。',
},
userSatisfactionRate: {
title: 'ユーザー満足率',
explanation: '1,000メッセージあたりの「いいね」の数です。これは、ユーザーが非常に満足している回答の割合を示します。',
},
avgResponseTime: {
title: '平均応答時間',
explanation: 'AIの処理/応答にかかる時間(ミリ秒)です。テキストベースのアプリケーションに適しています。',
},
tps: {
title: 'トークン出力速度',
explanation: 'LLMのパフォーマンスを測定します。リクエストの開始から出力の完了までのLLMのトークン出力速度をカウントします。',
},
},
}
export default translation
const translation = {
createApp: '新しいアプリを作成する',
types: {
all: 'すべて',
assistant: 'アシスタント',
completion: '補完',
},
modes: {
completion: 'テキスト生成',
chat: '基本アシスタント',
},
createFromConfigFile: '設定ファイルからアプリを作成する',
deleteAppConfirmTitle: 'このアプリを削除しますか?',
deleteAppConfirmContent:
'アプリの削除は元に戻せません。ユーザーはアプリにアクセスできなくなり、プロンプトの設定とログは永久に削除されます。',
appDeleted: 'アプリが削除されました',
appDeleteFailed: 'アプリの削除に失敗しました',
join: 'コミュニティに参加する',
communityIntro:
'チームメンバーや貢献者、開発者とさまざまなチャンネルでディスカッションを行います。',
roadmap: 'ロードマップを見る',
appNamePlaceholder: 'アプリの名前を入力してください',
newApp: {
startToCreate: '新しいアプリを作成しましょう',
captionName: 'アプリアイコンと名前',
captionAppType: 'どのタイプのアプリを作成しますか?',
previewDemo: 'デモをプレビューする',
chatApp: 'アシスタント',
chatAppIntro:
'チャットベースのアプリケーションを構築したいです。このアプリは質問と回答の形式を使用し、複数のラウンドの連続した会話が可能です。',
agentAssistant: '新しいエージェントアシスタント',
completeApp: 'テキスト生成',
completeAppIntro:
'プロンプトに基づいて高品質のテキストを生成するアプリケーションを作成したいです。記事、要約、翻訳などの生成が可能です。',
showTemplates: 'テンプレートから選択したいです',
hideTemplates: 'モード選択に戻る',
Create: '作成',
Cancel: 'キャンセル',
nameNotEmpty: '名前は空にできません',
appTemplateNotSelected: 'テンプレートを選択してください',
appTypeRequired: 'アプリのタイプを選択してください',
appCreated: 'アプリが作成されました',
appCreateFailed: 'アプリの作成に失敗しました',
},
editApp: {
startToEdit: 'アプリを編集する',
},
emoji: {
ok: 'OK',
cancel: 'キャンセル',
},
}
export default translation
const translation = {
currentPlan: '現在のプラン',
upgradeBtn: {
plain: 'プランをアップグレード',
encourage: '今すぐアップグレード',
encourageShort: 'アップグレード',
},
viewBilling: '請求とサブスクリプションの管理',
buyPermissionDeniedTip: 'サブスクリプションするには、エンタープライズ管理者に連絡してください',
plansCommon: {
title: 'あなたに合ったプランを選択してください',
yearlyTip: '年間購読で2か月無料!',
mostPopular: '最も人気のある',
planRange: {
monthly: '月額',
yearly: '年額',
},
month: '月',
year: '年',
save: '節約 ',
free: '無料',
currentPlan: '現在のプラン',
contractSales: '営業に連絡する',
contractOwner: 'チームマネージャーに連絡する',
startForFree: '無料で始める',
getStartedWith: '始める ',
contactSales: '営業に連絡する',
talkToSales: '営業と話す',
modelProviders: 'モデルプロバイダー',
teamMembers: 'チームメンバー',
buildApps: 'アプリを作成する',
vectorSpace: 'ベクトルスペース',
vectorSpaceBillingTooltip: '1MBあたり約120万文字のベクトル化データを保存できます(OpenAI Embeddingsを使用して推定され、モデルによって異なります)。',
vectorSpaceTooltip: 'ベクトルスペースは、LLMがデータを理解するために必要な長期記憶システムです。',
documentProcessingPriority: 'ドキュメント処理の優先度',
documentProcessingPriorityTip: 'より高いドキュメント処理の優先度をご希望の場合は、プランをアップグレードしてください。',
documentProcessingPriorityUpgrade: 'より高い精度と高速な速度でデータを処理します。',
priority: {
'standard': '標準',
'priority': '優先',
'top-priority': '最優先',
},
logsHistory: 'ログ履歴',
customTools: 'カスタムツール',
unavailable: '利用不可',
days: '日',
unlimited: '無制限',
support: 'サポート',
supportItems: {
communityForums: 'コミュニティフォーラム',
emailSupport: 'メールサポート',
priorityEmail: '優先メール&チャットサポート',
logoChange: 'ロゴ変更',
SSOAuthentication: 'SSO認証',
personalizedSupport: '個別サポート',
dedicatedAPISupport: '専用APIサポート',
customIntegration: 'カスタム統合とサポート',
ragAPIRequest: 'RAG APIリクエスト',
agentMode: 'エージェントモード',
workflow: 'ワークフロー',
},
comingSoon: '近日公開',
member: 'メンバー',
memberAfter: 'メンバー',
messageRequest: {
title: 'メッセージクレジット',
tooltip: 'OpenAIモデルを使用したさまざまなプランのメッセージ呼び出しクォータ(gpt4を除く)。制限を超えるメッセージはOpenAI APIキーを使用します。',
},
annotatedResponse: {
title: '注釈クォータ制限',
tooltip: '手動での編集と応答の注釈付けにより、アプリのカスタマイズ可能な高品質な質問応答機能が提供されます(チャットアプリのみ適用)。',
},
ragAPIRequestTooltip: 'Difyのナレッジベース処理機能のみを呼び出すAPI呼び出しの数を指します。',
receiptInfo: 'チームオーナーとチーム管理者のみが購読および請求情報を表示できます',
},
plans: {
sandbox: {
name: 'サンドボックス',
description: 'GPTの無料トライアル200回',
includesTitle: '含まれるもの:',
},
professional: {
name: 'プロフェッショナル',
description: '個人や小規模チーム向けにより多くのパワーを手頃な価格で提供します。',
includesTitle: '無料プランに加えて、次も含まれます:',
},
team: {
name: 'チーム',
description: '制限なく協力し、最高のパフォーマンスを楽しむ。',
includesTitle: 'プロフェッショナルプランに加えて、次も含まれます:',
},
enterprise: {
name: 'エンタープライズ',
description: '大規模なミッションクリティカルシステムのためのフル機能とサポートを提供します。',
includesTitle: 'チームプランに加えて、次も含まれます:',
},
},
vectorSpace: {
fullTip: 'ベクトルスペースがいっぱいです。',
fullSolution: 'より多くのスペースを得るためにプランをアップグレードしてください。',
},
apps: {
fullTipLine1: 'より多くのアプリを作成するには、',
fullTipLine2: 'プランをアップグレードしてください。',
},
annotatedResponse: {
fullTipLine1: 'より多くの会話を注釈するには、',
fullTipLine2: 'プランをアップグレードしてください。',
quotaTitle: '注釈返信クォータ',
},
}
export default translation
This diff is collapsed.
const translation = {
custom: 'カスタマイズ',
upgradeTip: {
prefix: 'プランをアップグレードして',
suffix: 'ブランドをカスタマイズしましょう。',
},
webapp: {
title: 'WebAppブランドのカスタマイズ',
removeBrand: 'Powered by Difyを削除',
changeLogo: 'Powered byブランド画像を変更',
changeLogoTip: '最小サイズ40x40pxのSVGまたはPNG形式',
},
app: {
title: 'アプリヘッダーブランドのカスタマイズ',
changeLogoTip: '最小サイズ80x80pxのSVGまたはPNG形式',
},
upload: 'アップロード',
uploading: 'アップロード中',
uploadedFail: '画像のアップロードに失敗しました。再度アップロードしてください。',
change: '変更',
apply: '適用',
restore: 'デフォルトに戻す',
customize: {
contactUs: 'お問い合わせ',
prefix: 'アプリ内のブランドロゴをカスタマイズするには、',
suffix: 'エンタープライズ版にアップグレードしてください。',
},
}
export default translation
const translation = {
steps: {
header: {
creation: 'ナレッジの作成',
update: 'データの追加',
},
one: 'データソースの選択',
two: 'テキストの前処理とクリーニング',
three: '実行して完了',
},
error: {
unavailable: 'このナレッジは利用できません',
},
stepOne: {
filePreview: 'ファイルプレビュー',
pagePreview: 'ページプレビュー',
dataSourceType: {
file: 'テキストファイルからインポート',
notion: 'Notionから同期',
web: 'ウェブサイトから同期',
},
uploader: {
title: 'テキストファイルをアップロード',
button: 'ファイルをドラッグ&ドロップするか',
browse: '参照',
tip: '{{supportTypes}}をサポートしています。1つあたりの最大サイズは{{size}}MBです。',
validation: {
typeError: 'サポートされていないファイルタイプです',
size: 'ファイルサイズが大きすぎます。最大サイズは{{size}}MBです',
count: '複数のファイルはサポートされていません',
filesNumber: 'バッチアップロードの制限({{filesNumber}}個)に達しました。',
},
cancel: 'キャンセル',
change: '変更',
failed: 'アップロードに失敗しました',
},
notionSyncTitle: 'Notionが接続されていません',
notionSyncTip: 'Notionと同期するには、まずNotionへの接続が必要です。',
connect: '接続する',
button: '次へ',
emptyDatasetCreation: '空のナレッジを作成します',
modal: {
title: '空のナレッジを作成',
tip: '空のナレッジにはドキュメントが含まれず、いつでもドキュメントをアップロードできます。',
input: 'ナレッジ名',
placeholder: '入力してください',
nameNotEmpty: '名前は空にできません',
nameLengthInvaild: '名前は1〜40文字である必要があります',
cancelButton: 'キャンセル',
confirmButton: '作成',
failed: '作成に失敗しました',
},
},
stepTwo: {
segmentation: 'チャンク設定',
auto: '自動',
autoDescription: 'チャンクと前処理ルールを自動的に設定します。初めてのユーザーはこれを選択することをおすすめします。',
custom: 'カスタム',
customDescription: 'チャンクのルール、チャンクの長さ、前処理ルールなどをカスタマイズします。',
separator: 'セグメント識別子',
separatorPlaceholder: '例えば改行(\\\\n)や特殊なセパレータ(例:「***」)',
maxLength: '最大チャンク長',
overlap: 'チャンクのオーバーラップ',
overlapTip: 'チャンクのオーバーラップを設定することで、それらの間の意味的な関連性を維持し、検索効果を向上させることができます。最大チャンクサイズの10%〜25%を設定することをおすすめします。',
overlapCheck: 'チャンクのオーバーラップは最大チャンク長を超えてはいけません',
rules: 'テキストの前処理ルール',
removeExtraSpaces: '連続するスペース、改行、タブを置換する',
removeUrlEmails: 'すべてのURLとメールアドレスを削除する',
removeStopwords: '「a」「an」「the」などのストップワードを削除する',
preview: '確認&プレビュー',
reset: 'リセット',
indexMode: 'インデックスモード',
qualified: '高品質',
recommend: 'おすすめ',
qualifiedTip: 'ユーザーのクエリに対してより高い精度を提供するために、デフォルトのシステム埋め込みインターフェースを呼び出して処理します。',
warning: 'モデルプロバイダのAPIキーを設定してください。',
click: '設定に移動',
economical: '経済的',
economicalTip: 'オフラインのベクトルエンジン、キーワードインデックスなどを使用して、トークンを消費せずに精度を低下させます。',
QATitle: '質問と回答形式でセグメント化',
QATip: 'このオプションを有効にすると、追加のトークンが消費されます',
QALanguage: '使用言語',
emstimateCost: '見積もり',
emstimateSegment: '推定チャンク数',
segmentCount: 'チャンク',
calculating: '計算中...',
fileSource: 'ドキュメントの前処理',
notionSource: 'ページの前処理',
other: 'その他',
fileUnit: 'ファイル',
notionUnit: 'ページ',
lastStep: '最後のステップ',
nextStep: '保存して処理',
save: '保存して処理',
cancel: 'キャンセル',
sideTipTitle: 'なぜチャンクと前処理が必要なのか',
sideTipP1: 'テキストデータを処理する際、チャンクとクリーニングは2つの重要な前処理ステップです。',
sideTipP2: 'セグメンテーションは長いテキストを段落に分割し、モデルがより理解しやすくします。これにより、モデルの結果の品質と関連性が向上します。',
sideTipP3: 'クリーニングは不要な文字や書式を削除し、ナレッジをよりクリーンで解析しやすいものにします。',
sideTipP4: '適切なチャンクとクリーニングはモデルのパフォーマンスを向上させ、より正確で価値のある結果を提供します。',
previewTitle: 'プレビュー',
previewTitleButton: 'プレビュー',
previewButton: 'Q&A形式に切り替える',
previewSwitchTipStart: '現在のチャンクプレビューはテキスト形式です。質問と回答形式のプレビューに切り替えると、',
previewSwitchTipEnd: ' 追加のトークンが消費されます',
characters: '文字',
indexSettedTip: 'インデックス方法を変更するには、',
retrivalSettedTip: 'インデックス方法を変更するには、',
datasetSettingLink: 'ナレッジ設定',
},
stepThree: {
creationTitle: '🎉 ナレッジが作成されました',
creationContent: 'ナレッジの名前は自動的に設定されましたが、いつでも変更できます',
label: 'ナレッジ名',
additionTitle: '🎉 ドキュメントがアップロードされました',
additionP1: 'ドキュメントはナレッジにアップロードされました',
additionP2: '、ナレッジのドキュメントリストで見つけることができます。',
stop: '処理を停止',
resume: '処理を再開',
navTo: 'ドキュメントに移動',
sideTipTitle: '次は何ですか',
sideTipContent: 'ドキュメントのインデックスが完了したら、ナレッジをアプリケーションのコンテキストとして統合することができます。プロンプトオーケストレーションページでコンテキスト設定を見つけることができます。また、独立したChatGPTインデックスプラグインとしてリリースすることもできます。',
modelTitle: '埋め込みを停止してもよろしいですか?',
modelContent: '後で処理を再開する必要がある場合は、中断した場所から続行します。',
modelButtonConfirm: '確認',
modelButtonCancel: 'キャンセル',
},
}
export default translation
This diff is collapsed.
const translation = {
title: '検索テスト',
desc: '与えられたクエリテキストに基づいたナレッジのヒット効果をテストします。',
dateTimeFormat: 'MM/DD/YYYY hh:mm A',
recents: '最近の結果',
table: {
header: {
source: 'ソース',
text: 'テキスト',
time: '時間',
},
},
input: {
title: 'ソーステキスト',
placeholder: 'テキストを入力してください。短い記述文がおすすめです。',
countWarning: '最大200文字まで入力できます。',
indexWarning: '高品質のナレッジのみ。',
testing: 'テスト中',
},
hit: {
title: '検索結果パラグラフ',
emptyTip: '検索テストの結果がここに表示されます。',
},
noRecentTip: '最近のクエリ結果はありません。',
viewChart: 'ベクトルチャートを表示',
}
export default translation
const translation = {
title: 'ナレッジの設定',
desc: 'ここではナレッジのプロパティと動作方法を変更できます。',
form: {
name: 'ナレッジ名',
namePlaceholder: 'ナレッジ名を入力してください',
nameError: '名前は空にできません',
desc: 'ナレッジの説明',
descInfo: 'ナレッジの内容を概説するための明確なテキストの説明を書いてください。この説明は、複数のナレッジから推論を選択する際の基準として使用されます。',
descPlaceholder: 'このナレッジに含まれる内容を説明してください。詳細な説明は、AIがナレッジの内容にタイムリーにアクセスできるようにします。空の場合、Difyはデフォルトのヒット戦略を使用します。',
descWrite: '良いナレッジの説明の書き方を学ぶ。',
permissions: '権限',
permissionsOnlyMe: '自分のみ',
permissionsAllMember: 'すべてのチームメンバー',
indexMethod: 'インデックス方法',
indexMethodHighQuality: '高品質',
indexMethodHighQualityTip: 'ユーザーのクエリ時により高い精度を提供するために、OpenAIの埋め込みインターフェースを呼び出して処理します。',
indexMethodEconomy: '経済的',
indexMethodEconomyTip: 'オフラインのベクトルエンジン、キーワードインデックスなどを使用して精度を低下させることなく、トークンを消費せずに処理します。',
embeddingModel: '埋め込みモデル',
embeddingModelTip: '埋め込みモデルを変更するには、',
embeddingModelTipLink: '設定',
retrievalSetting: {
title: '検索設定',
learnMore: '詳細を学ぶ',
description: ' 検索方法についての詳細',
longDescription: ' 検索方法についての詳細については、いつでもナレッジの設定で変更できます。',
},
save: '保存',
},
}
export default translation
const translation = {
knowledge: '知識',
documentCount: ' ドキュメント',
wordCount: 'k 単語',
appCount: ' リンクされたアプリ',
createDataset: '知識を作成',
createDatasetIntro: '独自のテキストデータをインポートするか、LLMコンテキストの強化のためにWebhookを介してリアルタイムでデータを書き込むことができます。',
deleteDatasetConfirmTitle: 'この知識を削除しますか?',
deleteDatasetConfirmContent:
'知識を削除すると元に戻すことはできません。ユーザーはもはやあなたの知識にアクセスできず、すべてのプロンプトの設定とログが永久に削除されます。',
datasetDeleted: '知識が削除されました',
datasetDeleteFailed: '知識の削除に失敗しました',
didYouKnow: 'ご存知ですか?',
intro1: '知識はDifyアプリケーションに統合することができます',
intro2: 'コンテキストとして',
intro3: '、',
intro4: 'または',
intro5: '作成することができます',
intro6: '単体のChatGPTインデックスプラグインとして公開するために',
unavailable: '利用不可',
unavailableTip: '埋め込みモデルが利用できません。デフォルトの埋め込みモデルを設定する必要があります',
datasets: '知識',
datasetsApi: 'API',
retrieval: {
semantic_search: {
title: 'ベクトル検索',
description: 'クエリの埋め込みを生成し、そのベクトル表現に最も類似したテキストチャンクを検索します。',
},
full_text_search: {
title: '全文検索',
description: 'ドキュメント内のすべての用語をインデックス化し、ユーザーが任意の用語を検索してそれに関連するテキストチャンクを取得できるようにします。',
},
hybrid_search: {
title: 'ハイブリッド検索',
description: '全文検索とベクトル検索を同時に実行し、ユーザーのクエリに最適なマッチを選択するために再ランク付けを行います。再ランクモデルAPIの設定が必要です。',
recommend: 'おすすめ',
},
invertedIndex: {
title: '逆インデックス',
description: '効率的な検索に使用される構造です。各用語が含まれるドキュメントまたはWebページを指すように、用語ごとに整理されています。',
},
change: '変更',
changeRetrievalMethod: '検索方法の変更',
},
}
export default translation
const translation = {
title: '探索する',
sidebar: {
discovery: '探索',
chat: 'チャット',
workspace: 'ワークスペース',
action: {
pin: 'ピン留め',
unpin: 'ピン留め解除',
rename: '名前変更',
delete: '削除',
},
delete: {
title: 'アプリを削除',
content: 'このアプリを削除してもよろしいですか?',
},
},
apps: {
title: 'Difyによるアプリの探索',
description: 'これらのテンプレートアプリを即座に使用するか、テンプレートに基づいて独自のアプリをカスタマイズしてください。',
allCategories: 'すべてのカテゴリ',
},
appCard: {
addToWorkspace: 'ワークスペースに追加',
customize: 'カスタマイズ',
},
appCustomize: {
title: '{{name}}からアプリを作成',
subTitle: 'アプリアイコンと名前',
nameRequired: 'アプリ名は必須です',
},
category: {
Assistant: 'アシスタント',
Writing: '執筆',
Translate: '翻訳',
Programming: 'プログラミング',
HR: '人事',
},
}
export default translation
const translation = {
}
export default translation
const translation = {
pageTitle: 'はじめましょう!👋',
welcome: 'Difyへようこそ。続行するにはログインしてください。',
email: 'メールアドレス',
emailPlaceholder: 'メールアドレスを入力してください',
password: 'パスワード',
passwordPlaceholder: 'パスワードを入力してください',
name: 'ユーザー名',
namePlaceholder: 'ユーザー名を入力してください',
forget: 'パスワードをお忘れですか?',
signBtn: 'サインイン',
installBtn: 'セットアップ',
setAdminAccount: '管理者アカウントの設定',
setAdminAccountDesc: 'アプリケーションの作成やLLMプロバイダの管理など、管理者アカウントの最大権限を設定します。',
createAndSignIn: '作成してサインイン',
oneMoreStep: 'あと一歩',
createSample: 'この情報を基に、サンプルアプリケーションを作成します',
invitationCode: '招待コード',
invitationCodePlaceholder: '招待コードを入力してください',
interfaceLanguage: 'インターフェース言語',
timezone: 'タイムゾーン',
go: 'Difyへ移動',
sendUsMail: '自己紹介をメールで送信し、招待リクエストを処理します。',
acceptPP: 'プライバシーポリシーを読み、同意します',
reset: 'パスワードをリセットするには、次のコマンドを実行してください',
withGitHub: 'GitHubで続行',
withGoogle: 'Googleで続行',
rightTitle: 'LLMのフルポテンシャルを解き放つ',
rightDesc: '魅力的で操作可能で改善可能なAIアプリケーションを簡単に構築します。',
tos: '利用規約',
pp: 'プライバシーポリシー',
tosDesc: 'サインアップすることで、以下に同意するものとします',
donthave: 'お持ちでない場合',
invalidInvitationCode: '無効な招待コード',
accountAlreadyInited: 'アカウントは既に初期化されています',
error: {
emailEmpty: 'メールアドレスは必須です',
emailInValid: '有効なメールアドレスを入力してください',
nameEmpty: '名前は必須です',
passwordEmpty: 'パスワードは必須です',
passwordInvalid: 'パスワードは文字と数字を含み、長さは8以上である必要があります',
},
license: {
tip: 'Dify Community Editionを開始する前に、GitHubの',
link: 'オープンソースライセンス',
},
join: '参加する',
joinTipStart: 'あなたを招待します',
joinTipEnd: 'チームに参加する',
invalid: 'リンクの有効期限が切れています',
explore: 'Difyを探索する',
activatedTipStart: 'あなたは',
activatedTipEnd: 'チームに参加しました',
activated: '今すぐサインイン',
adminInitPassword: '管理者初期化パスワード',
validate: '検証',
}
export default translation
const translation = {
}
export default translation
const translation = {
common: {
welcome: '利用していただきありがとうございます',
appUnavailable: 'アプリが利用できません',
appUnkonwError: 'アプリが利用できません',
},
chat: {
newChat: '新しいチャット',
pinnedTitle: 'ピン留めされた',
unpinnedTitle: 'チャット',
newChatDefaultName: '新しい会話',
resetChat: '会話をリセット',
powerBy: 'Powered by',
prompt: 'プロンプト',
privatePromptConfigTitle: '会話の設定',
publicPromptConfigTitle: '初期プロンプト',
configStatusDes: '開始前に、会話の設定を変更できます',
configDisabled:
'前回のセッションの設定がこのセッションで使用されました。',
startChat: 'チャットを開始',
privacyPolicyLeft:
'アプリ開発者が提供する',
privacyPolicyMiddle:
'プライバシーポリシー',
privacyPolicyRight:
'をお読みください。',
deleteConversation: {
title: '会話を削除する',
content: 'この会話を削除してもよろしいですか?',
},
tryToSolve: '解決しようとしています',
temporarySystemIssue: '申し訳ありません、一時的なシステムの問題が発生しました。',
},
generation: {
tabs: {
create: '一度だけ実行',
batch: '一括実行',
saved: '保存済み',
},
savedNoData: {
title: 'まだ結果が保存されていません!',
description: 'コンテンツの生成を開始し、保存された結果をこちらで見つけてください。',
startCreateContent: 'コンテンツの作成を開始',
},
title: 'AI Completion',
queryTitle: 'コンテンツのクエリ',
completionResult: 'Completion 結果',
queryPlaceholder: 'クエリコンテンツを書いてください...',
run: '実行',
copy: 'コピー',
resultTitle: 'AI Completion',
noData: 'AIはここで必要なものを提供します。',
csvUploadTitle: 'CSVファイルをここにドラッグアンドドロップするか、',
browse: '参照',
csvStructureTitle: 'CSVファイルは以下の構造に準拠する必要があります:',
downloadTemplate: 'こちらからテンプレートをダウンロード',
field: 'フィールド',
batchFailed: {
info: '{{num}} 回の実行が失敗しました',
retry: '再試行',
outputPlaceholder: '出力コンテンツなし',
},
errorMsg: {
empty: 'アップロードされたファイルにコンテンツを入力してください。',
fileStructNotMatch: 'アップロードされたCSVファイルが構造と一致しません。',
emptyLine: '行 {{rowIndex}} が空です',
invalidLine: '行 {{rowIndex}}: {{varName}} の値は空にできません',
moreThanMaxLengthLine: '行 {{rowIndex}}: {{varName}} の値は {{maxLength}} 文字を超えることはできません',
atLeastOne: 'アップロードされたファイルには少なくとも1行の入力が必要です。',
},
},
}
export default translation
const translation = {
title: 'ツール',
createCustomTool: 'カスタムツールを作成する',
type: {
all: 'すべて',
builtIn: '組み込み',
custom: 'カスタム',
},
contribute: {
line1: '私は',
line2: 'Difyへのツールの貢献に興味があります。',
viewGuide: 'ガイドを見る',
},
author: '著者',
auth: {
unauthorized: '認証する',
authorized: '認証済み',
setup: '使用するための認証を設定する',
setupModalTitle: '認証の設定',
setupModalTitleDescription: '資格情報を構成した後、ワークスペース内のすべてのメンバーがアプリケーションのオーケストレーション時にこのツールを使用できます。',
},
includeToolNum: '{{num}}個のツールが含まれています',
addTool: 'ツールを追加する',
createTool: {
title: 'カスタムツールを作成する',
editAction: '設定',
editTitle: 'カスタムツールを編集する',
name: '名前',
toolNamePlaceHolder: 'ツール名を入力してください',
schema: 'スキーマ',
schemaPlaceHolder: 'ここにOpenAPIスキーマを入力してください',
viewSchemaSpec: 'OpenAPI-Swagger仕様を表示する',
importFromUrl: 'URLからインポートする',
importFromUrlPlaceHolder: 'https://...',
urlError: '有効なURLを入力してください',
examples: '例',
exampleOptions: {
json: '天気(JSON)',
yaml: 'ペットストア(YAML)',
blankTemplate: '空白テンプレート',
},
availableTools: {
title: '利用可能なツール',
name: '名前',
description: '説明',
method: 'メソッド',
path: 'パス',
action: 'アクション',
test: 'テスト',
},
authMethod: {
title: '認証方法',
type: '認証タイプ',
keyTooltip: 'HTTPヘッダーキー。アイデアがない場合は "Authorization" として残しておいてもかまいません。またはカスタム値に設定できます。',
types: {
none: 'なし',
api_key: 'APIキー',
apiKeyPlaceholder: 'APIキーのHTTPヘッダー名',
apiValuePlaceholder: 'APIキーを入力してください',
},
key: 'キー',
value: '値',
},
authHeaderPrefix: {
title: '認証タイプ',
types: {
basic: 'ベーシック',
bearer: 'ベアラー',
custom: 'カスタム',
},
},
privacyPolicy: 'プライバシーポリシー',
privacyPolicyPlaceholder: 'プライバシーポリシーを入力してください',
},
test: {
title: 'テスト',
parametersValue: 'パラメーター&値',
parameters: 'パラメーター',
value: '値',
testResult: 'テスト結果',
testResultPlaceholder: 'ここにテスト結果が表示されます',
},
thought: {
using: '使用中',
used: '使用済み',
requestTitle: 'リクエスト先',
responseTitle: 'レスポンス先',
},
setBuiltInTools: {
info: '情報',
setting: '設定',
toolDescription: 'ツールの説明',
parameters: 'パラメーター',
string: '文字列',
number: '数',
required: '必須',
infoAndSetting: '情報と設定',
},
noCustomTool: {
title: 'カスタムツールがありません!',
content: 'AIアプリを構築するためのカスタムツールをここで追加および管理します。',
createTool: 'ツールを作成する',
},
noSearchRes: {
title: '申し訳ありません、結果がありません!',
content: '検索に一致するツールが見つかりませんでした。',
reset: '検索をリセット',
},
builtInPromptTitle: 'プロンプト',
toolRemoved: 'ツールが削除されました',
notAuthorized: 'ツールが認可されていません',
howToGet: '取得方法',
}
export default translation
......@@ -59,7 +59,7 @@ export const languages = [
value: 'ja-JP',
name: '日本語(日本)',
example: 'こんにちは、Dify!',
supported: false,
supported: true,
},
{
value: 'ko-KR',
......
......@@ -32,6 +32,7 @@ const translation = {
vectorSpace: '向量空间',
vectorSpaceTooltip: '向量空间是 LLMs 理解您的数据所需的长期记忆系统。',
vectorSpaceBillingTooltip: '向量存储是将知识库向量化处理后为让 LLMs 理解数据而使用的长期记忆存储,1MB 大约能满足1.2 million character 的向量化后数据存储(以 OpenAI Embedding 模型估算,不同模型计算方式有差异)。在向量化过程中,实际的压缩或尺寸减小取决于内容的复杂性和冗余性。',
documentsUploadQuota: '文档上传配额',
documentProcessingPriority: '文档处理优先级',
documentProcessingPriorityTip: '如需更高的文档处理优先级,请升级您的套餐',
documentProcessingPriorityUpgrade: '以更快的速度、更高的精度处理更多的数据。',
......@@ -56,6 +57,7 @@ const translation = {
dedicatedAPISupport: '专用 API 支持',
customIntegration: '自定义集成和支持',
ragAPIRequest: 'RAG API 请求',
bulkUpload: '批量上传文档',
agentMode: '代理模式',
workflow: '工作流',
},
......
......@@ -308,7 +308,7 @@ const baseFetch = <T>(
]) as Promise<T>
}
export const upload = (options: any, isPublicAPI?: boolean, url?: string): Promise<any> => {
export const upload = (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise<any> => {
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
let token = ''
if (isPublicAPI) {
......@@ -329,7 +329,7 @@ export const upload = (options: any, isPublicAPI?: boolean, url?: string): Promi
}
const defaultOptions = {
method: 'POST',
url: url ? `${urlPrefix}${url}` : `${urlPrefix}/files/upload`,
url: (url ? `${urlPrefix}${url}` : `${urlPrefix}/files/upload`) + (searchParams || ''),
headers: {
Authorization: `Bearer ${token}`,
},
......
// TODO: maybe use faker.js to randomize the data
export const mockApps = {
recommended_apps: [
{
app: {
id: 'b82da4c0-2887-48cc-a7d6-7edc0bdd6002',
name: 'AI 前端面试官',
mode: 'chat',
icon: '🤖',
icon_background: null,
},
app_id: 'b82da4c0-2887-48cc-a7d6-7edc0bdd6002',
description:
'一个模拟的前端面试官,通过提问的方式对前端开发的技能水平进行检验。',
copyright: null,
privacy_policy: null,
category: 'HR',
position: 20,
is_listed: true,
install_count: 0,
installed: false,
editable: true,
is_agent: false,
},
{
app: {
id: '1fa25f89-2883-41ac-877e-c372274020a4',
name: '扁平风插画生成',
mode: 'chat',
icon: '🖼️',
icon_background: '#D5F5F6',
},
app_id: '1fa25f89-2883-41ac-877e-c372274020a4',
description: '输入相关元素,为你生成扁平插画风格的封面图片',
copyright: null,
privacy_policy: null,
category: '绘画',
position: 10,
is_listed: true,
install_count: 0,
installed: false,
editable: true,
is_agent: true,
},
{
app: {
id: '94b509ad-4225-4924-8b50-5c25c2bd7e3c',
name: '文章翻译助理 ',
mode: 'completion',
icon: '🤖',
icon_background: null,
},
app_id: '94b509ad-4225-4924-8b50-5c25c2bd7e3c',
description:
'一个多语言翻译器,提供多种语言翻译能力,输入你需要翻译的文本,选择目标语言即可。提示词来自宝玉。',
copyright: null,
privacy_policy: null,
category: 'Assistant',
position: 10,
is_listed: true,
install_count: 0,
installed: false,
editable: true,
is_agent: false,
},
{
app: {
id: 'c8003ab3-9bb7-4693-9249-e603d48e58a6',
name: 'SQL 生成器',
mode: 'completion',
icon: '🤖',
icon_background: null,
},
app_id: 'c8003ab3-9bb7-4693-9249-e603d48e58a6',
description:
'我将帮助你把自然语言转化成指定的数据库查询 SQL 语句,请在下方输入你需要查询的条件,并选择目标数据库类型。',
copyright: null,
privacy_policy: null,
category: 'Programming',
position: 12,
is_listed: true,
install_count: 3142,
installed: false,
editable: true,
is_agent: false,
},
{
app: {
id: 'dad6a1e0-0fe9-47e1-91a9-e16de48f1276',
name: '代码解释器',
mode: 'chat',
icon: 'eye-in-speech-bubble',
icon_background: '#FFEAD5',
},
app_id: 'dad6a1e0-0fe9-47e1-91a9-e16de48f1276',
description: '阐明代码的语法和语义。',
copyright: 'Copyright 2023 Dify',
privacy_policy: 'https://dify.ai',
category: 'Programming',
position: 2,
is_listed: true,
install_count: 2344,
installed: false,
editable: true,
is_agent: false,
},
{
app: {
id: 'fae3e7ac-8ccc-4d43-8986-7c61d2bdde4f',
name: '赛博朋克插画生成',
mode: 'chat',
icon: '🖼️',
icon_background: '#FFEAD5',
},
app_id: 'fae3e7ac-8ccc-4d43-8986-7c61d2bdde4f',
description: '输入相关元素,为你生成赛博朋克风格的插画',
copyright: null,
privacy_policy: null,
category: '绘画',
position: 10,
is_listed: true,
install_count: 0,
installed: false,
editable: true,
is_agent: true,
},
{
app: {
id: '4e57bc83-ab95-4f8a-a955-70796b4804a0',
name: 'SEO 文章生成专家',
mode: 'completion',
icon: '🤖',
icon_background: '#FFEAD5',
},
app_id: '4e57bc83-ab95-4f8a-a955-70796b4804a0',
description:
'我是一名SEO专家,可以根据您提供的标题、关键词、相关信息来批量生成SEO文章。',
copyright: null,
privacy_policy: null,
category: 'Assistant',
position: 10,
is_listed: true,
install_count: 0,
installed: false,
editable: true,
is_agent: false,
},
{
app: {
id: '6786ce62-fa85-4ea7-a4d1-5dbe3e3ff59f',
name: '会议纪要',
mode: 'chat',
icon: 'clipboard',
icon_background: '#D1E0FF',
},
app_id: '6786ce62-fa85-4ea7-a4d1-5dbe3e3ff59f',
description: '帮你重新组织和输出混乱复杂的会议纪要。',
copyright: 'Copyright 2023 Dify',
privacy_policy: 'https://dify.ai',
category: 'Writing',
position: 6,
is_listed: true,
install_count: 1542,
installed: false,
editable: true,
is_agent: false,
},
{
app: {
id: '73dd96bb-49b7-4791-acbd-9ef2ef506900',
name: '美股投资分析助手',
mode: 'chat',
icon: '🤑',
icon_background: '#E4FBCC',
},
app_id: '73dd96bb-49b7-4791-acbd-9ef2ef506900',
description:
'欢迎使用您的个性化美股投资分析助手,在这里我们深入的进行股票分析,为您提供全面的洞察。',
copyright: 'Dify.AI',
privacy_policy: null,
category: '智能助理',
position: 0,
is_listed: true,
install_count: 2,
installed: false,
editable: true,
is_agent: true,
},
{
app: {
id: '93ca3c2c-3a47-4658-b230-d5a6cc61ff01',
name: 'SVG Logo 设计',
mode: 'chat',
icon: '🎨',
icon_background: '#E4FBCC',
},
app_id: '93ca3c2c-3a47-4658-b230-d5a6cc61ff01',
description:
'您好,我是您的创意伙伴,将帮助您将想法生动地实现!我可以协助您利用DALL·E 3的能力创造出令人惊叹的设计。',
copyright: 'Dify.AI',
privacy_policy: null,
category: '智能助理',
position: 4,
is_listed: true,
install_count: 6,
installed: false,
editable: true,
is_agent: true,
},
{
app: {
id: '59924f26-963f-4b4b-90cf-978bbfcddc49',
name: '中英文互译',
mode: 'chat',
icon: 'speaking_head_in_silhouette',
icon_background: '#FBE8FF',
},
app_id: '59924f26-963f-4b4b-90cf-978bbfcddc49',
description: '翻译专家:提供中英文互译',
copyright: 'Copyright 2023 Dify',
privacy_policy: 'https://dify.ai',
category: 'Translate',
position: 4,
is_listed: true,
install_count: 1662,
installed: false,
editable: true,
is_agent: false,
},
{
app: {
id: '89ad1e65-6711-4c80-b469-a71a434e2dbd',
name: '个人学习导师',
mode: 'chat',
icon: '🤖',
icon_background: '#FFEAD5',
},
app_id: '89ad1e65-6711-4c80-b469-a71a434e2dbd',
description: '您的私人学习导师,帮您制定学习计划并辅导',
copyright: 'Copyright 2023 Dify',
privacy_policy: 'https://dify.ai',
category: 'Assistant',
position: 26,
is_listed: true,
install_count: 1441,
installed: true,
editable: true,
is_agent: false,
},
{
app: {
id: 'ff551444-a3ff-4fd8-b297-f38581c98b4a',
name: '文献综述写作',
mode: 'completion',
icon: 'female-student',
icon_background: '#FBE8FF',
},
app_id: 'ff551444-a3ff-4fd8-b297-f38581c98b4a',
description: '帮你撰写论文文献综述',
copyright: 'Copyright 2023 Dify',
privacy_policy: 'https://dify.ai',
category: 'Writing',
position: 7,
is_listed: true,
install_count: 1651,
installed: false,
editable: true,
is_agent: false,
},
{
app: {
id: '79227a52-11f1-4cf9-8c49-0bd86f9be813',
name: 'Youtube 频道数据分析',
mode: 'chat',
icon: '🔢',
icon_background: '#E4FBCC',
},
app_id: '79227a52-11f1-4cf9-8c49-0bd86f9be813',
description:
'你好,告诉我您想分析的 YouTube 频道,我将为您整理一份完整的数据分析报告。',
copyright: null,
privacy_policy: null,
category: '智能助理',
position: 0,
is_listed: true,
install_count: 2,
installed: false,
editable: true,
is_agent: true,
},
{
app: {
id: '609f4a7f-36f7-4791-96a7-4ccbe6f8dfbb',
name: '旅行规划助手',
mode: 'chat',
icon: '✈️',
icon_background: '#E4FBCC',
},
app_id: '609f4a7f-36f7-4791-96a7-4ccbe6f8dfbb',
description:
'欢迎使用您的个性化旅行服务顾问!🌍✈️ 准备好踏上一段充满冒险与放松的旅程了吗?让我们一起深入打造您难忘的旅行体验吧。',
copyright: null,
privacy_policy: null,
category: '智能助理',
position: 0,
is_listed: true,
install_count: 3,
installed: false,
editable: true,
is_agent: true,
},
],
categories: [
'绘画',
'HR',
'Programming',
'Translate',
'Assistant',
'Writing',
'智能助理',
],
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment