Compare commits

...

41 Commits
0.6.0 ... 0.6.1

Author SHA1 Message Date
takatost
74de7cf33c version to 0.6.1 (#3253) 2024-04-09 21:21:09 +08:00
crazywoola
f5e65b98a9 feat: remove unregistered-llm-in-debug (#3251) 2024-04-09 20:49:52 +08:00
Chenhe Gu
eb76d7a226 make sure validation flow works for all model providers in bedrock (#3250) 2024-04-09 20:42:18 +08:00
Yeuoly
e635f3dc1d chore: remove langchain in tools (#3247) 2024-04-09 19:28:22 +08:00
takatost
2a6b7d57cb fix: token is not logging of question classifier node (#3249) 2024-04-09 19:25:08 +08:00
Joel
8bb225bec6 fix: number type in app would render as select type in webapp (#3244) 2024-04-09 18:33:47 +08:00
zxhlyh
39d3fc4742 feat: prompt-editor support undo (#3242) 2024-04-09 18:18:53 +08:00
KVOJJJin
5c98260cec Fix: picture of workflow (#3241) 2024-04-09 17:49:45 +08:00
takatost
f599f41336 fix: empty conversation list of explore chatbot (#3235) 2024-04-09 17:04:48 +08:00
zxhlyh
1384a6d0fd fix: workflow run edge status (#3236) 2024-04-09 16:53:34 +08:00
Bowen Liang
28089c98c1 fix: skip Celery warning by setting broker_connection_retry_on_startup config (#3188) 2024-04-09 16:14:43 +08:00
minakokojima
10d6d50b6c update link (#3226) 2024-04-09 16:09:46 +08:00
Joel
752f6fb15a fix: file not uploaded caused api error (#3228) 2024-04-09 15:54:36 +08:00
Jyong
8fcf459285 fix milvus database name parameter missed (#3229) 2024-04-09 15:54:13 +08:00
Leo Q
9c01bcb3e5 feat: support setting database used in Milvus (#3003) 2024-04-09 15:39:36 +08:00
Yeuoly
a2c068d949 feat: moonshot function call (#3227) 2024-04-09 15:30:09 +08:00
takatost
4ad3f2cdc2 fix: image text when retrieve chat histories (#3220) 2024-04-09 15:20:45 +08:00
legao
29918c498c fixed the issue of missing cleanup function in the AudioBtn component (#3133) 2024-04-09 15:10:58 +08:00
Joel
269432a5e6 fix: vision config doesn't enabled in llm (#3225) 2024-04-09 15:07:43 +08:00
takatost
a33b774314 fix: latest image tag not push in GitHub action (#3213) 2024-04-09 14:35:39 +08:00
Yeuoly
cc5ccaaca1 fix: incomplete response (#3215) 2024-04-09 14:35:25 +08:00
Jyong
33ea689861 fix detached instance error in keyword index create thread and fix question classifier node out of index error (#3219) 2024-04-09 14:34:51 +08:00
Chenhe Gu
581836b716 Update README.md (#3212) 2024-04-09 14:34:42 +08:00
Bowen Liang
0516b78d6f fix: index number in api/README (#3214) 2024-04-09 13:59:26 +08:00
Jyong
84d7cbf916 fix economy index search in workflow (#3205) 2024-04-09 13:20:51 +08:00
Chenhe Gu
f514fd2182 Update README.md (#3206) 2024-04-09 12:30:44 +08:00
zxhlyh
86707928d4 fix: node connect self (#3194) 2024-04-09 12:24:41 +08:00
Eric Wang
3c3fb3cd3f fix(code_executor): surrogates not allowed error in jinja2 template (#3191) 2024-04-09 12:21:03 +08:00
Yeuoly
337899a03d Fix/code transform result (#3203) 2024-04-09 12:20:34 +08:00
Jat
bae0c071cd Fix: remove unavailable return_preamble parameter in cohere (#3201)
Signed-off-by: Jat <jat@sinosky.org>
2024-04-09 12:11:53 +08:00
Joel
19cb3c7871 fix: sometimes chosed old selected knowledge may overwirte the new knowledge (#3199) 2024-04-09 11:46:59 +08:00
Jyong
2e4dec365d Compatible with unique index conflicts (#3183) 2024-04-09 02:16:19 +08:00
Chenhe Gu
ca3e2e6cc0 Update README.md to include workflows (#3180) 2024-04-09 01:49:19 +08:00
Jyong
283979fc46 fix keyword index error when storage source is S3 (#3182) 2024-04-09 01:42:58 +08:00
takatost
a81c1ab6ae version to 0.6.0-fix1 (#3179) 2024-04-09 00:10:20 +08:00
KVOJJJin
48d4d55ecc Fix: features of agent-chat (#3178) 2024-04-08 23:53:59 +08:00
zxhlyh
b7691f5658 fix: prompt editor variable picker (#3177) 2024-04-08 23:53:09 +08:00
crazywoola
1382f10433 feat: translations (#3176) 2024-04-08 23:17:16 +08:00
KVOJJJin
d8db728c33 Fix: prompt of expert mode (#3168) 2024-04-08 21:36:27 +08:00
takatost
d2259f20cb fix: app export dsl not include desc (#3167) 2024-04-08 21:30:18 +08:00
takatost
9720d6b7a5 fix: metadata in generate npe issue (#3166) 2024-04-08 21:30:03 +08:00
116 changed files with 4326 additions and 1040 deletions

View File

@@ -46,7 +46,7 @@ jobs:
with:
images: ${{ env[matrix.image_name_env] }}
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' && startsWith(github.ref, 'refs/tags/') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
type=ref,event=branch
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
type=raw,value=${{ github.ref_name }},enable=${{ startsWith(github.ref, 'refs/tags/') }}

View File

@@ -36,7 +36,7 @@ In terms of licensing, please take a minute to read our short [License and Contr
| Feature Type | Priority |
| ------------------------------------------------------------ | --------------- |
| High-Priority Features as being labeled by a team member | High Priority |
| Popular feature requests from our [community feedback board](https://feedback.dify.ai/) | Medium Priority |
| Popular feature requests from our [community feedback board](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Medium Priority |
| Non-core features and minor enhancements | Low Priority |
| Valuable but not immediate | Future-Feature |

View File

@@ -34,7 +34,7 @@
| Feature Type | Priority |
| ------------------------------------------------------------ | --------------- |
| High-Priority Features as being labeled by a team member | High Priority |
| Popular feature requests from our [community feedback board](https://feedback.dify.ai/) | Medium Priority |
| Popular feature requests from our [community feedback board](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Medium Priority |
| Non-core features and minor enhancements | Low Priority |
| Valuable but not immediate | Future-Feature |

View File

@@ -29,16 +29,35 @@
**Dify** is an open-source LLM app development platform. Dify's intuitive interface combines a RAG pipeline, AI workflow orchestration, agent capabilities, model management, observability features and more, letting you quickly go from prototype to production.
![](./images/demo.png)
https://github.com/langgenius/dify/assets/13230914/979e7a68-f067-4bbc-b38e-2deb2cc2bbb5
## Using Dify Cloud
## Using our Cloud Services
You can try out [Dify Cloud](https://dify.ai) now. It provides all the capabilities of the self-deployed version, and includes 200 free GPT-4 calls.
You can try out [Dify.AI Cloud](https://dify.ai) now. It provides all the capabilities of the self-deployed version, and includes 200 free requests to OpenAI GPT-3.5.
## Dify for Enterprise / Organizations
[Schedule a meeting with us](#Direct-Meetings) or [send us an email](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry) to discuss enterprise needs.
For startups and small businesses using AWS, check out [Dify Premium on AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6) and deploy it to your own AWS VPC with one-click. It's an affordable AMI offering with the option to create apps with custom logo and branding.
## Features
![](./images/models.png)
**1. Workflow**: Create and test complex AI workflows on a visual canvas, with pre-built nodes taking advantage of the power of all the following features and beyond.
**2. Extensive LLM support**: Seamless integration with hundreds of proprietary / open-source LLMs and dozens of inference providers, including GPT, Mistral, Llama2, and OpenAI API-compatible models. A full list of supported model providers is kept [here](https://docs.dify.ai/getting-started/readme/model-providers).
**3. Prompt IDE**: Visual orchestration of applications and services based on any LLMs. Easily share with your team.
**4. RAG Engine**: Includes various RAG capabilities based on full-text indexing or vector database embeddings, allowing direct upload of PDFs, TXTs, and other text formats.
**5. AI Agent**: Based on Function Calling and ReAct, the Agent inference framework allows users to customize tools, what you see is what you get. Dify provides more than a dozen built-in tools for AI agents, such as Google Search, DELL·E, Stable Diffusion, WolframAlpha, etc.
**6. LLMOps**: Monitor and analyze application logs and performance, continuously improving Prompts, datasets, or models based on production data.
### Looking to purchase via AWS?
Check out [Dify Premium on AWS](https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6) and deploy it to your own AWS VPC with one-click.
## Dify vs. LangChain vs. Assistants API
@@ -52,28 +71,10 @@ Check out [Dify Premium on AWS](https://aws.amazon.com/marketplace/pp/prodview-t
| **Local Deployment** | Supported | Not Supported | Not Applicable |
## Features
![](./images/models.png)
**1. LLM Support**: Integration with OpenAI's GPT family of models, or the open-source Llama2 family models. In fact, Dify supports mainstream commercial models and open-source models (locally deployed or based on MaaS).
**2. Prompt IDE**: Visual orchestration of applications and services based on LLMs with your team.
**3. RAG Engine**: Includes various RAG capabilities based on full-text indexing or vector database embeddings, allowing direct upload of PDFs, TXTs, and other text formats.
**4. AI Agent**: Based on Function Calling and ReAct, the Agent inference framework allows users to customize tools, what you see is what you get. Dify provides more than a dozen built-in tool calling capabilities, such as Google Search, DELL·E, Stable Diffusion, WolframAlpha, etc.
**5. Continuous Operations**: Monitor and analyze application logs and performance, continuously improving Prompts, datasets, or models using production data.
## Before You Start
**Star us on GitHub, and be instantly notified for new releases!**
![star-us](https://github.com/langgenius/dify/assets/100913391/95f37259-7370-4456-a9f0-0bc01ef8642f)
- [Website](https://dify.ai)
- [Docs](https://docs.dify.ai)
- [Deployment Docs](https://docs.dify.ai/getting-started/install-self-hosted)
@@ -138,21 +139,18 @@ We are looking for contributors to help with translating Dify to languages other
## Community & Support
* [Github Discussion](https://github.com/langgenius/dify/discussions). Best for: sharing feedback and checking out our feature roadmap.
* [Github Discussion](https://github.com/langgenius/dify/discussions). Best for: sharing feedback and asking questions.
* [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
* [Email Support](mailto:hello@dify.ai?subject=[GitHub]Questions%20About%20Dify). Best for: questions you have about using Dify.AI.
* [Discord](https://discord.gg/FngNHpbcY7). Best for: sharing your applications and hanging out with the community.
* [Twitter](https://twitter.com/dify_ai). Best for: sharing your applications and hanging out with the community.
* [Business Contact](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry). Best for: business inquiries of licensing Dify.AI for commercial use.
### Direct Meetings
**Help us make Dify better. Reach out directly to us**.
| Point of Contact | Purpose |
| :----------------------------------------------------------: | :----------------------------------------------------------: |
| <a href='https://cal.com/guchenhe/15min' target='_blank'><img src='https://i.postimg.cc/fWBqSmjP/Git-Hub-README-Button-3x.png' border='0' alt='Git-Hub-README-Button-3x' height="60" width="214"/></a> | Product design feedback, user experience discussions, feature planning and roadmaps. |
| <a href='https://cal.com/pinkbanana' target='_blank'><img src='https://i.postimg.cc/LsRTh87D/Git-Hub-README-Button-2x.png' border='0' alt='Git-Hub-README-Button-2x' height="60" width="225"/></a> | Technical support, issues, or feature requests |
| <a href='https://cal.com/guchenhe/15min' target='_blank'><img src='https://i.postimg.cc/fWBqSmjP/Git-Hub-README-Button-3x.png' border='0' alt='Git-Hub-README-Button-3x' height="60" width="214"/></a> | Business enquiries & product feedback. |
| <a href='https://cal.com/pinkbanana' target='_blank'><img src='https://i.postimg.cc/LsRTh87D/Git-Hub-README-Button-2x.png' border='0' alt='Git-Hub-README-Button-2x' height="60" width="225"/></a> | Contributions, issues & feature requests |
## Security Disclosure

View File

@@ -115,6 +115,7 @@ docker compose up -d
Difyに貢献していただき、コードの提出、問題の報告、新しいアイデアの提供、またはDifyを基に作成した興味深く有用なAIアプリケーションの共有により、Difyをより良いものにするお手伝いを歓迎します。同時に、さまざまなイベント、会議、ソーシャルメディアでDifyを共有することも歓迎します。
- [Github Discussion](https://github.com/langgenius/dify/discussions). 👉:アプリを共有し、コミュニティとコミュニケーション。
- [GitHub Issues](https://github.com/langgenius/dify/issues)。最適な使用法Dify.AIの使用中に遭遇するバグやエラー、[貢献ガイド](CONTRIBUTING.md)を参照。
- [Email サポート](mailto:hello@dify.ai?subject=[GitHub]Questions%20About%20Dify)。最適な使用法Dify.AIの使用に関する質問。
- [Discord](https://discord.gg/FngNHpbcY7)。最適な使用法:アプリケーションの共有とコミュニティとの交流。

View File

@@ -17,16 +17,16 @@
```bash
sed -i "/^SECRET_KEY=/c\SECRET_KEY=$(openssl rand -base64 42)" .env
```
3.5 If you use Anaconda, create a new environment and activate it
4. If you use Anaconda, create a new environment and activate it
```bash
conda create --name dify python=3.10
conda activate dify
```
4. Install dependencies
5. Install dependencies
```bash
pip install -r requirements.txt
```
5. Run migrate
6. Run migrate
Before the first launch, migrate the database to the latest version.
@@ -47,9 +47,11 @@
pip install -r requirements.txt --upgrade --force-reinstall
```
6. Start backend:
7. Start backend:
```bash
flask run --host 0.0.0.0 --port=5001 --debug
```
7. Setup your application by visiting http://localhost:5001/console/api/setup or other apis...
8. If you need to debug local async processing, you can run `celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail`, celery can do dataset importing and other async tasks.
8. Setup your application by visiting http://localhost:5001/console/api/setup or other apis...
9. If you need to debug local async processing, please start the worker service by running
`celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail`.
The started celery app handles the async tasks, e.g. dataset importing and documents indexing.

View File

@@ -67,6 +67,7 @@ DEFAULTS = {
'CODE_EXECUTION_ENDPOINT': '',
'CODE_EXECUTION_API_KEY': '',
'TOOL_ICON_CACHE_MAX_AGE': 3600,
'MILVUS_DATABASE': 'default',
'KEYWORD_DATA_SOURCE_TYPE': 'database',
}
@@ -98,7 +99,7 @@ class Config:
# ------------------------
# General Configurations.
# ------------------------
self.CURRENT_VERSION = "0.6.0"
self.CURRENT_VERSION = "0.6.1"
self.COMMIT_SHA = get_env('COMMIT_SHA')
self.EDITION = "SELF_HOSTED"
self.DEPLOY_ENV = get_env('DEPLOY_ENV')
@@ -212,6 +213,7 @@ class Config:
self.MILVUS_USER = get_env('MILVUS_USER')
self.MILVUS_PASSWORD = get_env('MILVUS_PASSWORD')
self.MILVUS_SECURE = get_env('MILVUS_SECURE')
self.MILVUS_DATABASE = get_env('MILVUS_DATABASE')
# weaviate settings
self.WEAVIATE_ENDPOINT = get_env('WEAVIATE_ENDPOINT')

View File

@@ -6,6 +6,7 @@ from werkzeug.exceptions import NotFound
from controllers.console import api
from controllers.console.explore.error import NotChatAppError
from controllers.console.explore.wraps import InstalledAppResource
from core.app.entities.app_invoke_entities import InvokeFrom
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
from libs.helper import uuid_value
from models.model import AppMode
@@ -39,8 +40,8 @@ class ConversationListApi(InstalledAppResource):
user=current_user,
last_id=args['last_id'],
limit=args['limit'],
invoke_from=InvokeFrom.EXPLORE,
pinned=pinned,
exclude_debug_conversation=True
)
except LastConversationNotExistsError:
raise NotFound("Last Conversation Not Exists.")

View File

@@ -6,6 +6,7 @@ import services
from controllers.service_api import api
from controllers.service_api.app.error import NotChatAppError
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from core.app.entities.app_invoke_entities import InvokeFrom
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
from libs.helper import uuid_value
from models.model import App, AppMode, EndUser
@@ -27,7 +28,13 @@ class ConversationApi(Resource):
args = parser.parse_args()
try:
return ConversationService.pagination_by_last_id(app_model, end_user, args['last_id'], args['limit'])
return ConversationService.pagination_by_last_id(
app_model=app_model,
user=end_user,
last_id=args['last_id'],
limit=args['limit'],
invoke_from=InvokeFrom.SERVICE_API
)
except services.errors.conversation.LastConversationNotExistsError:
raise NotFound("Last Conversation Not Exists.")

View File

@@ -5,6 +5,7 @@ from werkzeug.exceptions import NotFound
from controllers.web import api
from controllers.web.error import NotChatAppError
from controllers.web.wraps import WebApiResource
from core.app.entities.app_invoke_entities import InvokeFrom
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
from libs.helper import uuid_value
from models.model import AppMode
@@ -37,7 +38,8 @@ class ConversationListApi(WebApiResource):
user=end_user,
last_id=args['last_id'],
limit=args['limit'],
pinned=pinned
invoke_from=InvokeFrom.WEB_APP,
pinned=pinned,
)
except LastConversationNotExistsError:
raise NotFound("Last Conversation Not Exists.")

View File

@@ -687,4 +687,4 @@ class CotAgentRunner(BaseAgentRunner):
try:
return json.dumps(tools, ensure_ascii=False)
except json.JSONDecodeError:
return json.dumps(tools)
return json.dumps(tools)

View File

@@ -207,19 +207,25 @@ class FunctionCallAgentRunner(BaseAgentRunner):
)
)
assistant_message = AssistantPromptMessage(
content='',
tool_calls=[]
)
if tool_calls:
prompt_messages.append(AssistantPromptMessage(
content='',
name='',
tool_calls=[AssistantPromptMessage.ToolCall(
assistant_message.tool_calls=[
AssistantPromptMessage.ToolCall(
id=tool_call[0],
type='function',
function=AssistantPromptMessage.ToolCall.ToolCallFunction(
name=tool_call[1],
arguments=json.dumps(tool_call[2], ensure_ascii=False)
)
) for tool_call in tool_calls]
))
) for tool_call in tool_calls
]
else:
assistant_message.content = response
prompt_messages.append(assistant_message)
# save thought
self.save_agent_thought(
@@ -239,12 +245,6 @@ class FunctionCallAgentRunner(BaseAgentRunner):
final_answer += response + '\n'
# update prompt messages
if response.strip():
prompt_messages.append(AssistantPromptMessage(
content=response,
))
# call tools
tool_responses = []
for tool_call_id, tool_call_name, tool_call_args in tool_calls:

View File

@@ -122,7 +122,7 @@ class MessageEndStreamResponse(StreamResponse):
"""
event: StreamEvent = StreamEvent.MESSAGE_END
id: str
metadata: Optional[dict] = None
metadata: dict = {}
class MessageFileStreamResponse(StreamResponse):

View File

@@ -1,12 +1,32 @@
import os
from typing import Any, Optional, Union
from typing import Any, Optional, TextIO, Union
from langchain.callbacks.base import BaseCallbackHandler
from langchain.input import print_text
from pydantic import BaseModel
_TEXT_COLOR_MAPPING = {
"blue": "36;1",
"yellow": "33;1",
"pink": "38;5;200",
"green": "32;1",
"red": "31;1",
}
class DifyAgentCallbackHandler(BaseCallbackHandler, BaseModel):
def get_colored_text(text: str, color: str) -> str:
"""Get colored text."""
color_str = _TEXT_COLOR_MAPPING[color]
return f"\u001b[{color_str}m\033[1;3m{text}\u001b[0m"
def print_text(
text: str, color: Optional[str] = None, end: str = "", file: Optional[TextIO] = None
) -> None:
"""Print text with highlighting and no end characters."""
text_to_print = get_colored_text(text, color) if color else text
print(text_to_print, end=end, file=file)
if file:
file.flush() # ensure all printed content are written to file
class DifyAgentCallbackHandler(BaseModel):
"""Callback Handler that prints to std out."""
color: Optional[str] = ''
current_loop = 1

View File

@@ -41,7 +41,8 @@ class CacheEmbedding(Embeddings):
embedding_queue_embeddings = []
try:
model_type_instance = cast(TextEmbeddingModel, self._model_instance.model_type_instance)
model_schema = model_type_instance.get_model_schema(self._model_instance.model, self._model_instance.credentials)
model_schema = model_type_instance.get_model_schema(self._model_instance.model,
self._model_instance.credentials)
max_chunks = model_schema.model_properties[ModelPropertyKey.MAX_CHUNKS] \
if model_schema and ModelPropertyKey.MAX_CHUNKS in model_schema.model_properties else 1
for i in range(0, len(embedding_queue_texts), max_chunks):
@@ -61,17 +62,20 @@ class CacheEmbedding(Embeddings):
except Exception as e:
logging.exception('Failed transform embedding: ', e)
cache_embeddings = []
for i, embedding in zip(embedding_queue_indices, embedding_queue_embeddings):
text_embeddings[i] = embedding
hash = helper.generate_text_hash(texts[i])
if hash not in cache_embeddings:
embedding_cache = Embedding(model_name=self._model_instance.model,
hash=hash,
provider_name=self._model_instance.provider)
embedding_cache.set_embedding(embedding)
db.session.add(embedding_cache)
cache_embeddings.append(hash)
db.session.commit()
try:
for i, embedding in zip(embedding_queue_indices, embedding_queue_embeddings):
text_embeddings[i] = embedding
hash = helper.generate_text_hash(texts[i])
if hash not in cache_embeddings:
embedding_cache = Embedding(model_name=self._model_instance.model,
hash=hash,
provider_name=self._model_instance.provider)
embedding_cache.set_embedding(embedding)
db.session.add(embedding_cache)
cache_embeddings.append(hash)
db.session.commit()
except IntegrityError:
db.session.rollback()
except Exception as ex:
db.session.rollback()
logger.error('Failed to embed documents: ', ex)

View File

@@ -29,16 +29,16 @@ class NodeJsTemplateTransformer(TemplateTransformer):
:param inputs: inputs
:return:
"""
# transform inputs to json string
inputs_str = json.dumps(inputs, indent=4)
inputs_str = json.dumps(inputs, indent=4, ensure_ascii=False)
# replace code and inputs
runner = NODEJS_RUNNER.replace('{{code}}', code)
runner = runner.replace('{{inputs}}', inputs_str)
return runner, NODEJS_PRELOAD
@classmethod
def transform_response(cls, response: str) -> dict:
"""

View File

@@ -62,10 +62,10 @@ class Jinja2TemplateTransformer(TemplateTransformer):
# transform jinja2 template to python code
runner = PYTHON_RUNNER.replace('{{code}}', code)
runner = runner.replace('{{inputs}}', json.dumps(inputs, indent=4))
runner = runner.replace('{{inputs}}', json.dumps(inputs, indent=4, ensure_ascii=False))
return runner, JINJA2_PRELOAD
@classmethod
def transform_response(cls, response: str) -> dict:
"""
@@ -81,4 +81,4 @@ class Jinja2TemplateTransformer(TemplateTransformer):
return {
'result': result
}
}

View File

@@ -34,7 +34,7 @@ class PythonTemplateTransformer(TemplateTransformer):
"""
# transform inputs to json string
inputs_str = json.dumps(inputs, indent=4)
inputs_str = json.dumps(inputs, indent=4, ensure_ascii=False)
# replace code and inputs
runner = PYTHON_RUNNER.replace('{{code}}', code)

View File

@@ -19,6 +19,7 @@ from core.model_manager import ModelInstance, ModelManager
from core.model_runtime.entities.model_entities import ModelType, PriceType
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
from core.model_runtime.model_providers.__base.text_embedding_model import TextEmbeddingModel
from core.rag.datasource.keyword.keyword_factory import Keyword
from core.rag.extractor.entity.extract_setting import ExtractSetting
from core.rag.index_processor.index_processor_base import BaseIndexProcessor
from core.rag.index_processor.index_processor_factory import IndexProcessorFactory
@@ -657,18 +658,25 @@ class IndexingRunner:
if embedding_model_instance:
embedding_model_type_instance = embedding_model_instance.model_type_instance
embedding_model_type_instance = cast(TextEmbeddingModel, embedding_model_type_instance)
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
futures = []
for i in range(0, len(documents), chunk_size):
chunk_documents = documents[i:i + chunk_size]
futures.append(executor.submit(self._process_chunk, current_app._get_current_object(), index_processor,
chunk_documents, dataset,
dataset_document, embedding_model_instance,
embedding_model_type_instance))
# create keyword index
create_keyword_thread = threading.Thread(target=self._process_keyword_index,
args=(current_app._get_current_object(),
dataset.id, dataset_document.id, documents))
create_keyword_thread.start()
if dataset.indexing_technique == 'high_quality':
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
futures = []
for i in range(0, len(documents), chunk_size):
chunk_documents = documents[i:i + chunk_size]
futures.append(executor.submit(self._process_chunk, current_app._get_current_object(), index_processor,
chunk_documents, dataset,
dataset_document, embedding_model_instance,
embedding_model_type_instance))
for future in futures:
tokens += future.result()
for future in futures:
tokens += future.result()
create_keyword_thread.join()
indexing_end_at = time.perf_counter()
# update document status to completed
@@ -682,6 +690,27 @@ class IndexingRunner:
}
)
def _process_keyword_index(self, flask_app, dataset_id, document_id, documents):
with flask_app.app_context():
dataset = Dataset.query.filter_by(id=dataset_id).first()
if not dataset:
raise ValueError("no dataset found")
keyword = Keyword(dataset)
keyword.create(documents)
if dataset.indexing_technique != 'high_quality':
document_ids = [document.metadata['doc_id'] for document in documents]
db.session.query(DocumentSegment).filter(
DocumentSegment.document_id == document_id,
DocumentSegment.index_node_id.in_(document_ids),
DocumentSegment.status == "indexing"
).update({
DocumentSegment.status: "completed",
DocumentSegment.enabled: True,
DocumentSegment.completed_at: datetime.datetime.utcnow()
})
db.session.commit()
def _process_chunk(self, flask_app, index_processor, chunk_documents, dataset, dataset_document,
embedding_model_instance, embedding_model_type_instance):
with flask_app.app_context():
@@ -700,7 +729,7 @@ class IndexingRunner:
)
# load index
index_processor.load(dataset, chunk_documents)
index_processor.load(dataset, chunk_documents, with_keywords=False)
document_ids = [document.metadata['doc_id'] for document in chunk_documents]
db.session.query(DocumentSegment).filter(

View File

@@ -3,6 +3,7 @@ from core.file.message_file_parser import MessageFileParser
from core.model_manager import ModelInstance
from core.model_runtime.entities.message_entities import (
AssistantPromptMessage,
ImagePromptMessageContent,
PromptMessage,
PromptMessageRole,
TextPromptMessageContent,
@@ -124,7 +125,17 @@ class TokenBufferMemory:
else:
continue
message = f"{role}: {m.content}"
string_messages.append(message)
if isinstance(m.content, list):
inner_msg = ""
for content in m.content:
if isinstance(content, TextPromptMessageContent):
inner_msg += f"{content.data}\n"
elif isinstance(content, ImagePromptMessageContent):
inner_msg += "[image]\n"
string_messages.append(f"{role}: {inner_msg.strip()}")
else:
message = f"{role}: {m.content}"
string_messages.append(message)
return "\n".join(string_messages)

View File

@@ -74,7 +74,7 @@ provider_credential_schema:
label:
en_US: Available Model Name
zh_Hans: 可用模型名称
type: secret-input
type: text-input
placeholder:
en_US: A model you have access to (e.g. amazon.titan-text-lite-v1) for validation.
zh_Hans: 为了进行验证,请输入一个您可用的模型名称 (例如amazon.titan-text-lite-v1)

View File

@@ -402,25 +402,25 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
:param credentials: model credentials
:return:
"""
if "anthropic.claude-3" in model:
try:
self._invoke_claude(model=model,
credentials=credentials,
prompt_messages=[{"role": "user", "content": "ping"}],
model_parameters={},
stop=None,
stream=False)
except Exception as ex:
raise CredentialsValidateFailedError(str(ex))
required_params = {}
if "anthropic" in model:
required_params = {
"max_tokens": 32,
}
elif "ai21" in model:
# ValidationException: Malformed input request: #/temperature: expected type: Number, found: Null#/maxTokens: expected type: Integer, found: Null#/topP: expected type: Number, found: Null, please reformat your input and try again.
required_params = {
"temperature": 0.7,
"topP": 0.9,
"maxTokens": 32,
}
try:
ping_message = UserPromptMessage(content="ping")
self._generate(model=model,
self._invoke(model=model,
credentials=credentials,
prompt_messages=[ping_message],
model_parameters={},
model_parameters=required_params,
stream=False)
except ClientError as ex:

View File

@@ -297,7 +297,6 @@ class CohereLargeLanguageModel(LargeLanguageModel):
chat_history=chat_histories,
model=real_model,
stream=stream,
return_preamble=True,
**model_parameters,
)

View File

@@ -1,8 +1,31 @@
import json
from collections.abc import Generator
from typing import Optional, Union
from typing import Optional, Union, cast
from core.model_runtime.entities.llm_entities import LLMResult
from core.model_runtime.entities.message_entities import PromptMessage, PromptMessageTool
import requests
from core.model_runtime.entities.common_entities import I18nObject
from core.model_runtime.entities.llm_entities import LLMMode, LLMResult, LLMResultChunk, LLMResultChunkDelta
from core.model_runtime.entities.message_entities import (
AssistantPromptMessage,
ImagePromptMessageContent,
PromptMessage,
PromptMessageContent,
PromptMessageContentType,
PromptMessageTool,
SystemPromptMessage,
ToolPromptMessage,
UserPromptMessage,
)
from core.model_runtime.entities.model_entities import (
AIModelEntity,
FetchFrom,
ModelFeature,
ModelPropertyKey,
ModelType,
ParameterRule,
ParameterType,
)
from core.model_runtime.model_providers.openai_api_compatible.llm.llm import OAIAPICompatLargeLanguageModel
@@ -13,6 +36,7 @@ class MoonshotLargeLanguageModel(OAIAPICompatLargeLanguageModel):
stream: bool = True, user: Optional[str] = None) \
-> Union[LLMResult, Generator]:
self._add_custom_parameters(credentials)
self._add_function_call(model, credentials)
user = user[:32] if user else None
return super()._invoke(model, credentials, prompt_messages, model_parameters, tools, stop, stream, user)
@@ -20,7 +44,293 @@ class MoonshotLargeLanguageModel(OAIAPICompatLargeLanguageModel):
self._add_custom_parameters(credentials)
super().validate_credentials(model, credentials)
@staticmethod
def _add_custom_parameters(credentials: dict) -> None:
def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity | None:
return AIModelEntity(
model=model,
label=I18nObject(en_US=model, zh_Hans=model),
model_type=ModelType.LLM,
features=[ModelFeature.TOOL_CALL, ModelFeature.MULTI_TOOL_CALL, ModelFeature.STREAM_TOOL_CALL]
if credentials.get('function_calling_type') == 'tool_call'
else [],
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
model_properties={
ModelPropertyKey.CONTEXT_SIZE: int(credentials.get('context_size', 4096)),
ModelPropertyKey.MODE: LLMMode.CHAT.value,
},
parameter_rules=[
ParameterRule(
name='temperature',
use_template='temperature',
label=I18nObject(en_US='Temperature', zh_Hans='温度'),
type=ParameterType.FLOAT,
),
ParameterRule(
name='max_tokens',
use_template='max_tokens',
default=512,
min=1,
max=int(credentials.get('max_tokens', 4096)),
label=I18nObject(en_US='Max Tokens', zh_Hans='最大标记'),
type=ParameterType.INT,
),
ParameterRule(
name='top_p',
use_template='top_p',
label=I18nObject(en_US='Top P', zh_Hans='Top P'),
type=ParameterType.FLOAT,
),
]
)
def _add_custom_parameters(self, credentials: dict) -> None:
credentials['mode'] = 'chat'
credentials['endpoint_url'] = 'https://api.moonshot.cn/v1'
def _add_function_call(self, model: str, credentials: dict) -> None:
model_schema = self.get_model_schema(model, credentials)
if model_schema and set([
ModelFeature.TOOL_CALL, ModelFeature.MULTI_TOOL_CALL
]).intersection(model_schema.features or []):
credentials['function_calling_type'] = 'tool_call'
def _convert_prompt_message_to_dict(self, message: PromptMessage) -> dict:
"""
Convert PromptMessage to dict for OpenAI API format
"""
if isinstance(message, UserPromptMessage):
message = cast(UserPromptMessage, message)
if isinstance(message.content, str):
message_dict = {"role": "user", "content": message.content}
else:
sub_messages = []
for message_content in message.content:
if message_content.type == PromptMessageContentType.TEXT:
message_content = cast(PromptMessageContent, message_content)
sub_message_dict = {
"type": "text",
"text": message_content.data
}
sub_messages.append(sub_message_dict)
elif message_content.type == PromptMessageContentType.IMAGE:
message_content = cast(ImagePromptMessageContent, message_content)
sub_message_dict = {
"type": "image_url",
"image_url": {
"url": message_content.data,
"detail": message_content.detail.value
}
}
sub_messages.append(sub_message_dict)
message_dict = {"role": "user", "content": sub_messages}
elif isinstance(message, AssistantPromptMessage):
message = cast(AssistantPromptMessage, message)
message_dict = {"role": "assistant", "content": message.content}
if message.tool_calls:
message_dict["tool_calls"] = []
for function_call in message.tool_calls:
message_dict["tool_calls"].append({
"id": function_call.id,
"type": function_call.type,
"function": {
"name": f"functions.{function_call.function.name}",
"arguments": function_call.function.arguments
}
})
elif isinstance(message, ToolPromptMessage):
message = cast(ToolPromptMessage, message)
message_dict = {"role": "tool", "content": message.content, "tool_call_id": message.tool_call_id}
if not message.name.startswith("functions."):
message.name = f"functions.{message.name}"
elif isinstance(message, SystemPromptMessage):
message = cast(SystemPromptMessage, message)
message_dict = {"role": "system", "content": message.content}
else:
raise ValueError(f"Got unknown type {message}")
if message.name:
message_dict["name"] = message.name
return message_dict
def _extract_response_tool_calls(self, response_tool_calls: list[dict]) -> list[AssistantPromptMessage.ToolCall]:
"""
Extract tool calls from response
:param response_tool_calls: response tool calls
:return: list of tool calls
"""
tool_calls = []
if response_tool_calls:
for response_tool_call in response_tool_calls:
function = AssistantPromptMessage.ToolCall.ToolCallFunction(
name=response_tool_call["function"]["name"] if response_tool_call.get("function", {}).get("name") else "",
arguments=response_tool_call["function"]["arguments"] if response_tool_call.get("function", {}).get("arguments") else ""
)
tool_call = AssistantPromptMessage.ToolCall(
id=response_tool_call["id"] if response_tool_call.get("id") else "",
type=response_tool_call["type"] if response_tool_call.get("type") else "",
function=function
)
tool_calls.append(tool_call)
return tool_calls
def _handle_generate_stream_response(self, model: str, credentials: dict, response: requests.Response,
prompt_messages: list[PromptMessage]) -> Generator:
"""
Handle llm stream response
:param model: model name
:param credentials: model credentials
:param response: streamed response
:param prompt_messages: prompt messages
:return: llm response chunk generator
"""
full_assistant_content = ''
chunk_index = 0
def create_final_llm_result_chunk(index: int, message: AssistantPromptMessage, finish_reason: str) \
-> LLMResultChunk:
# calculate num tokens
prompt_tokens = self._num_tokens_from_string(model, prompt_messages[0].content)
completion_tokens = self._num_tokens_from_string(model, full_assistant_content)
# transform usage
usage = self._calc_response_usage(model, credentials, prompt_tokens, completion_tokens)
return LLMResultChunk(
model=model,
prompt_messages=prompt_messages,
delta=LLMResultChunkDelta(
index=index,
message=message,
finish_reason=finish_reason,
usage=usage
)
)
tools_calls: list[AssistantPromptMessage.ToolCall] = []
finish_reason = "Unknown"
def increase_tool_call(new_tool_calls: list[AssistantPromptMessage.ToolCall]):
def get_tool_call(tool_name: str):
if not tool_name:
return tools_calls[-1]
tool_call = next((tool_call for tool_call in tools_calls if tool_call.function.name == tool_name), None)
if tool_call is None:
tool_call = AssistantPromptMessage.ToolCall(
id='',
type='',
function=AssistantPromptMessage.ToolCall.ToolCallFunction(name=tool_name, arguments="")
)
tools_calls.append(tool_call)
return tool_call
for new_tool_call in new_tool_calls:
# get tool call
tool_call = get_tool_call(new_tool_call.function.name)
# update tool call
if new_tool_call.id:
tool_call.id = new_tool_call.id
if new_tool_call.type:
tool_call.type = new_tool_call.type
if new_tool_call.function.name:
# remove the functions. prefix
if new_tool_call.function.name.startswith('functions.'):
parts = new_tool_call.function.name.split('functions.')
if len(parts) > 1:
new_tool_call.function.name = parts[1]
tool_call.function.name = new_tool_call.function.name
if new_tool_call.function.arguments:
tool_call.function.arguments += new_tool_call.function.arguments
for chunk in response.iter_lines(decode_unicode=True, delimiter="\n\n"):
if chunk:
# ignore sse comments
if chunk.startswith(':'):
continue
decoded_chunk = chunk.strip().lstrip('data: ').lstrip()
chunk_json = None
try:
chunk_json = json.loads(decoded_chunk)
# stream ended
except json.JSONDecodeError as e:
yield create_final_llm_result_chunk(
index=chunk_index + 1,
message=AssistantPromptMessage(content=""),
finish_reason="Non-JSON encountered."
)
break
if not chunk_json or len(chunk_json['choices']) == 0:
continue
choice = chunk_json['choices'][0]
finish_reason = chunk_json['choices'][0].get('finish_reason')
chunk_index += 1
if 'delta' in choice:
delta = choice['delta']
delta_content = delta.get('content')
assistant_message_tool_calls = delta.get('tool_calls', None)
# assistant_message_function_call = delta.delta.function_call
# extract tool calls from response
if assistant_message_tool_calls:
tool_calls = self._extract_response_tool_calls(assistant_message_tool_calls)
increase_tool_call(tool_calls)
if delta_content is None or delta_content == '':
continue
# transform assistant message to prompt message
assistant_prompt_message = AssistantPromptMessage(
content=delta_content,
tool_calls=tool_calls if assistant_message_tool_calls else []
)
full_assistant_content += delta_content
elif 'text' in choice:
choice_text = choice.get('text', '')
if choice_text == '':
continue
# transform assistant message to prompt message
assistant_prompt_message = AssistantPromptMessage(content=choice_text)
full_assistant_content += choice_text
else:
continue
# check payload indicator for completion
yield LLMResultChunk(
model=model,
prompt_messages=prompt_messages,
delta=LLMResultChunkDelta(
index=chunk_index,
message=assistant_prompt_message,
)
)
chunk_index += 1
if tools_calls:
yield LLMResultChunk(
model=model,
prompt_messages=prompt_messages,
delta=LLMResultChunkDelta(
index=chunk_index,
message=AssistantPromptMessage(
tool_calls=tools_calls,
content=""
),
)
)
yield create_final_llm_result_chunk(
index=chunk_index,
message=AssistantPromptMessage(content=""),
finish_reason=finish_reason
)

View File

@@ -20,6 +20,7 @@ supported_model_types:
- llm
configurate_methods:
- predefined-model
- customizable-model
provider_credential_schema:
credential_form_schemas:
- variable: api_key
@@ -30,3 +31,51 @@ provider_credential_schema:
placeholder:
zh_Hans: 在此输入您的 API Key
en_US: Enter your API Key
model_credential_schema:
model:
label:
en_US: Model Name
zh_Hans: 模型名称
placeholder:
en_US: Enter your model name
zh_Hans: 输入模型名称
credential_form_schemas:
- variable: api_key
label:
en_US: API Key
type: secret-input
required: true
placeholder:
zh_Hans: 在此输入您的 API Key
en_US: Enter your API Key
- variable: context_size
label:
zh_Hans: 模型上下文长度
en_US: Model context size
required: true
type: text-input
default: '4096'
placeholder:
zh_Hans: 在此输入您的模型上下文长度
en_US: Enter your Model context size
- variable: max_tokens
label:
zh_Hans: 最大 token 上限
en_US: Upper bound for max tokens
default: '4096'
type: text-input
- variable: function_calling_type
label:
en_US: Function calling
type: select
required: false
default: no_call
options:
- value: no_call
label:
en_US: Not supported
zh_Hans: 不支持
- value: tool_call
label:
en_US: Tool Call
zh_Hans: Tool Call

View File

@@ -378,6 +378,34 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
delimiter = credentials.get("stream_mode_delimiter", "\n\n")
delimiter = codecs.decode(delimiter, "unicode_escape")
tools_calls: list[AssistantPromptMessage.ToolCall] = []
def increase_tool_call(new_tool_calls: list[AssistantPromptMessage.ToolCall]):
def get_tool_call(tool_call_id: str):
tool_call = next(
(tool_call for tool_call in tools_calls if tool_call.id == tool_call_id), None
)
if tool_call is None:
tool_call = AssistantPromptMessage.ToolCall(
id='',
type='function',
function=AssistantPromptMessage.ToolCall.ToolCallFunction(
name='',
arguments=''
)
)
tools_calls.append(tool_call)
return tool_call
for new_tool_call in new_tool_calls:
# get tool call
tool_call = get_tool_call(new_tool_call.id)
# update tool call
tool_call.id = new_tool_call.id
tool_call.type = new_tool_call.type
tool_call.function.name = new_tool_call.function.name
tool_call.function.arguments += new_tool_call.function.arguments
for chunk in response.iter_lines(decode_unicode=True, delimiter=delimiter):
if chunk:
# ignore sse comments
@@ -405,8 +433,6 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
if 'delta' in choice:
delta = choice['delta']
delta_content = delta.get('content')
if delta_content is None or delta_content == '':
continue
assistant_message_tool_calls = delta.get('tool_calls', None)
# assistant_message_function_call = delta.delta.function_call
@@ -414,6 +440,11 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
# extract tool calls from response
if assistant_message_tool_calls:
tool_calls = self._extract_response_tool_calls(assistant_message_tool_calls)
increase_tool_call(tool_calls)
if delta_content is None or delta_content == '':
continue
# function_call = self._extract_response_function_call(assistant_message_function_call)
# tool_calls = [function_call] if function_call else []
@@ -437,6 +468,18 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
# check payload indicator for completion
if finish_reason is not None:
yield LLMResultChunk(
model=model,
prompt_messages=prompt_messages,
delta=LLMResultChunkDelta(
index=chunk_index,
message=AssistantPromptMessage(
tool_calls=tools_calls,
),
finish_reason=finish_reason
)
)
yield create_final_llm_result_chunk(
index=chunk_index,
message=assistant_prompt_message,
@@ -735,4 +778,4 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel):
function=function
)
return tool_call
return tool_call

View File

@@ -1,6 +1,6 @@
model: ernie-3.5-8k
model: ernie-3.5-4k-0205
label:
en_US: Ernie-3.5-8K
en_US: Ernie-3.5-4k-0205
model_type: llm
features:
- agent-thought

View File

@@ -24,56 +24,64 @@ class Jieba(BaseKeyword):
self._config = KeywordTableConfig()
def create(self, texts: list[Document], **kwargs) -> BaseKeyword:
keyword_table_handler = JiebaKeywordTableHandler()
keyword_table = self._get_dataset_keyword_table()
for text in texts:
keywords = keyword_table_handler.extract_keywords(text.page_content, self._config.max_keywords_per_chunk)
self._update_segment_keywords(self.dataset.id, text.metadata['doc_id'], list(keywords))
keyword_table = self._add_text_to_keyword_table(keyword_table, text.metadata['doc_id'], list(keywords))
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
with redis_client.lock(lock_name, timeout=600):
keyword_table_handler = JiebaKeywordTableHandler()
keyword_table = self._get_dataset_keyword_table()
for text in texts:
keywords = keyword_table_handler.extract_keywords(text.page_content, self._config.max_keywords_per_chunk)
self._update_segment_keywords(self.dataset.id, text.metadata['doc_id'], list(keywords))
keyword_table = self._add_text_to_keyword_table(keyword_table, text.metadata['doc_id'], list(keywords))
self._save_dataset_keyword_table(keyword_table)
self._save_dataset_keyword_table(keyword_table)
return self
return self
def add_texts(self, texts: list[Document], **kwargs):
keyword_table_handler = JiebaKeywordTableHandler()
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
with redis_client.lock(lock_name, timeout=600):
keyword_table_handler = JiebaKeywordTableHandler()
keyword_table = self._get_dataset_keyword_table()
keywords_list = kwargs.get('keywords_list', None)
for i in range(len(texts)):
text = texts[i]
if keywords_list:
keywords = keywords_list[i]
else:
keywords = keyword_table_handler.extract_keywords(text.page_content, self._config.max_keywords_per_chunk)
self._update_segment_keywords(self.dataset.id, text.metadata['doc_id'], list(keywords))
keyword_table = self._add_text_to_keyword_table(keyword_table, text.metadata['doc_id'], list(keywords))
keyword_table = self._get_dataset_keyword_table()
keywords_list = kwargs.get('keywords_list', None)
for i in range(len(texts)):
text = texts[i]
if keywords_list:
keywords = keywords_list[i]
else:
keywords = keyword_table_handler.extract_keywords(text.page_content, self._config.max_keywords_per_chunk)
self._update_segment_keywords(self.dataset.id, text.metadata['doc_id'], list(keywords))
keyword_table = self._add_text_to_keyword_table(keyword_table, text.metadata['doc_id'], list(keywords))
self._save_dataset_keyword_table(keyword_table)
self._save_dataset_keyword_table(keyword_table)
def text_exists(self, id: str) -> bool:
keyword_table = self._get_dataset_keyword_table()
return id in set.union(*keyword_table.values())
def delete_by_ids(self, ids: list[str]) -> None:
keyword_table = self._get_dataset_keyword_table()
keyword_table = self._delete_ids_from_keyword_table(keyword_table, ids)
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
with redis_client.lock(lock_name, timeout=600):
keyword_table = self._get_dataset_keyword_table()
keyword_table = self._delete_ids_from_keyword_table(keyword_table, ids)
self._save_dataset_keyword_table(keyword_table)
self._save_dataset_keyword_table(keyword_table)
def delete_by_document_id(self, document_id: str):
# get segment ids by document_id
segments = db.session.query(DocumentSegment).filter(
DocumentSegment.dataset_id == self.dataset.id,
DocumentSegment.document_id == document_id
).all()
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
with redis_client.lock(lock_name, timeout=600):
# get segment ids by document_id
segments = db.session.query(DocumentSegment).filter(
DocumentSegment.dataset_id == self.dataset.id,
DocumentSegment.document_id == document_id
).all()
ids = [segment.index_node_id for segment in segments]
ids = [segment.index_node_id for segment in segments]
keyword_table = self._get_dataset_keyword_table()
keyword_table = self._delete_ids_from_keyword_table(keyword_table, ids)
keyword_table = self._get_dataset_keyword_table()
keyword_table = self._delete_ids_from_keyword_table(keyword_table, ids)
self._save_dataset_keyword_table(keyword_table)
self._save_dataset_keyword_table(keyword_table)
def search(
self, query: str,
@@ -106,13 +114,15 @@ class Jieba(BaseKeyword):
return documents
def delete(self) -> None:
dataset_keyword_table = self.dataset.dataset_keyword_table
if dataset_keyword_table:
db.session.delete(dataset_keyword_table)
db.session.commit()
if dataset_keyword_table.data_source_type != 'database':
file_key = 'keyword_files/' + self.dataset.tenant_id + '/' + self.dataset.id + '.txt'
storage.delete(file_key)
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
with redis_client.lock(lock_name, timeout=600):
dataset_keyword_table = self.dataset.dataset_keyword_table
if dataset_keyword_table:
db.session.delete(dataset_keyword_table)
db.session.commit()
if dataset_keyword_table.data_source_type != 'database':
file_key = 'keyword_files/' + self.dataset.tenant_id + '/' + self.dataset.id + '.txt'
storage.delete(file_key)
def _save_dataset_keyword_table(self, keyword_table):
keyword_table_dict = {
@@ -135,33 +145,31 @@ class Jieba(BaseKeyword):
storage.save(file_key, json.dumps(keyword_table_dict, cls=SetEncoder).encode('utf-8'))
def _get_dataset_keyword_table(self) -> Optional[dict]:
lock_name = 'keyword_indexing_lock_{}'.format(self.dataset.id)
with redis_client.lock(lock_name, timeout=20):
dataset_keyword_table = self.dataset.dataset_keyword_table
if dataset_keyword_table:
keyword_table_dict = dataset_keyword_table.keyword_table_dict
if keyword_table_dict:
return keyword_table_dict['__data__']['table']
else:
keyword_data_source_type = current_app.config['KEYWORD_DATA_SOURCE_TYPE']
dataset_keyword_table = DatasetKeywordTable(
dataset_id=self.dataset.id,
keyword_table='',
data_source_type=keyword_data_source_type,
)
if keyword_data_source_type == 'database':
dataset_keyword_table.keyword_table = json.dumps({
'__type__': 'keyword_table',
'__data__': {
"index_id": self.dataset.id,
"summary": None,
"table": {}
}
}, cls=SetEncoder)
db.session.add(dataset_keyword_table)
db.session.commit()
dataset_keyword_table = self.dataset.dataset_keyword_table
if dataset_keyword_table:
keyword_table_dict = dataset_keyword_table.keyword_table_dict
if keyword_table_dict:
return keyword_table_dict['__data__']['table']
else:
keyword_data_source_type = current_app.config['KEYWORD_DATA_SOURCE_TYPE']
dataset_keyword_table = DatasetKeywordTable(
dataset_id=self.dataset.id,
keyword_table='',
data_source_type=keyword_data_source_type,
)
if keyword_data_source_type == 'database':
dataset_keyword_table.keyword_table = json.dumps({
'__type__': 'keyword_table',
'__data__': {
"index_id": self.dataset.id,
"summary": None,
"table": {}
}
}, cls=SetEncoder)
db.session.add(dataset_keyword_table)
db.session.commit()
return {}
return {}
def _add_text_to_keyword_table(self, keyword_table: dict, id: str, keywords: list[str]) -> dict:
for keyword in keywords:

View File

@@ -20,16 +20,17 @@ class MilvusConfig(BaseModel):
password: str
secure: bool = False
batch_size: int = 100
database: str = "default"
@root_validator()
def validate_config(cls, values: dict) -> dict:
if not values['host']:
if not values.get('host'):
raise ValueError("config MILVUS_HOST is required")
if not values['port']:
if not values.get('port'):
raise ValueError("config MILVUS_PORT is required")
if not values['user']:
if not values.get('user'):
raise ValueError("config MILVUS_USER is required")
if not values['password']:
if not values.get('password'):
raise ValueError("config MILVUS_PASSWORD is required")
return values
@@ -39,7 +40,8 @@ class MilvusConfig(BaseModel):
'port': self.port,
'user': self.user,
'password': self.password,
'secure': self.secure
'secure': self.secure,
'db_name': self.database,
}
@@ -128,7 +130,8 @@ class MilvusVector(BaseVector):
uri = "https://" + str(self._client_config.host) + ":" + str(self._client_config.port)
else:
uri = "http://" + str(self._client_config.host) + ":" + str(self._client_config.port)
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password)
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password,
db_name=self._client_config.database)
from pymilvus import utility
if utility.has_collection(self._collection_name, using=alias):
@@ -140,7 +143,8 @@ class MilvusVector(BaseVector):
uri = "https://" + str(self._client_config.host) + ":" + str(self._client_config.port)
else:
uri = "http://" + str(self._client_config.host) + ":" + str(self._client_config.port)
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password)
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password,
db_name=self._client_config.database)
from pymilvus import utility
if not utility.has_collection(self._collection_name, using=alias):
@@ -192,7 +196,7 @@ class MilvusVector(BaseVector):
else:
uri = "http://" + str(self._client_config.host) + ":" + str(self._client_config.port)
connections.connect(alias=alias, uri=uri, user=self._client_config.user,
password=self._client_config.password)
password=self._client_config.password, db_name=self._client_config.database)
if not utility.has_collection(self._collection_name, using=alias):
from pymilvus import CollectionSchema, DataType, FieldSchema
from pymilvus.orm.types import infer_dtype_bydata

View File

@@ -110,6 +110,7 @@ class Vector:
user=config.get('MILVUS_USER'),
password=config.get('MILVUS_PASSWORD'),
secure=config.get('MILVUS_SECURE'),
database=config.get('MILVUS_DATABASE'),
)
)
else:

View File

@@ -1,11 +1,92 @@
from typing import Any
import logging
from typing import Any, Optional
from langchain.utilities import ArxivAPIWrapper
import arxiv
from pydantic import BaseModel, Field
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
logger = logging.getLogger(__name__)
class ArxivAPIWrapper(BaseModel):
"""Wrapper around ArxivAPI.
To use, you should have the ``arxiv`` python package installed.
https://lukasschwab.me/arxiv.py/index.html
This wrapper will use the Arxiv API to conduct searches and
fetch document summaries. By default, it will return the document summaries
of the top-k results.
It limits the Document content by doc_content_chars_max.
Set doc_content_chars_max=None if you don't want to limit the content size.
Args:
top_k_results: number of the top-scored document used for the arxiv tool
ARXIV_MAX_QUERY_LENGTH: the cut limit on the query used for the arxiv tool.
load_max_docs: a limit to the number of loaded documents
load_all_available_meta:
if True: the `metadata` of the loaded Documents contains all available
meta info (see https://lukasschwab.me/arxiv.py/index.html#Result),
if False: the `metadata` contains only the published date, title,
authors and summary.
doc_content_chars_max: an optional cut limit for the length of a document's
content
Example:
.. code-block:: python
arxiv = ArxivAPIWrapper(
top_k_results = 3,
ARXIV_MAX_QUERY_LENGTH = 300,
load_max_docs = 3,
load_all_available_meta = False,
doc_content_chars_max = 40000
)
arxiv.run("tree of thought llm)
"""
arxiv_search = arxiv.Search #: :meta private:
arxiv_exceptions = (
arxiv.ArxivError,
arxiv.UnexpectedEmptyPageError,
arxiv.HTTPError,
) # :meta private:
top_k_results: int = 3
ARXIV_MAX_QUERY_LENGTH = 300
load_max_docs: int = 100
load_all_available_meta: bool = False
doc_content_chars_max: Optional[int] = 4000
def run(self, query: str) -> str:
"""
Performs an arxiv search and A single string
with the publish date, title, authors, and summary
for each article separated by two newlines.
If an error occurs or no documents found, error text
is returned instead. Wrapper for
https://lukasschwab.me/arxiv.py/index.html#Search
Args:
query: a plaintext search query
""" # noqa: E501
try:
results = self.arxiv_search( # type: ignore
query[: self.ARXIV_MAX_QUERY_LENGTH], max_results=self.top_k_results
).results()
except self.arxiv_exceptions as ex:
return f"Arxiv exception: {ex}"
docs = [
f"Published: {result.updated.date()}\n"
f"Title: {result.title}\n"
f"Authors: {', '.join(a.name for a in result.authors)}\n"
f"Summary: {result.summary}"
for result in results
]
if docs:
return "\n\n".join(docs)[: self.doc_content_chars_max]
else:
return "No good Arxiv Result was found"
class ArxivSearchInput(BaseModel):
query: str = Field(..., description="Search query.")

View File

@@ -1,11 +1,95 @@
from typing import Any
import json
from typing import Any, Optional
from langchain.tools import BraveSearch
import requests
from pydantic import BaseModel, Field
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class BraveSearchWrapper(BaseModel):
"""Wrapper around the Brave search engine."""
api_key: str
"""The API key to use for the Brave search engine."""
search_kwargs: dict = Field(default_factory=dict)
"""Additional keyword arguments to pass to the search request."""
base_url = "https://api.search.brave.com/res/v1/web/search"
"""The base URL for the Brave search engine."""
def run(self, query: str) -> str:
"""Query the Brave search engine and return the results as a JSON string.
Args:
query: The query to search for.
Returns: The results as a JSON string.
"""
web_search_results = self._search_request(query=query)
final_results = [
{
"title": item.get("title"),
"link": item.get("url"),
"snippet": item.get("description"),
}
for item in web_search_results
]
return json.dumps(final_results)
def _search_request(self, query: str) -> list[dict]:
headers = {
"X-Subscription-Token": self.api_key,
"Accept": "application/json",
}
req = requests.PreparedRequest()
params = {**self.search_kwargs, **{"q": query}}
req.prepare_url(self.base_url, params)
if req.url is None:
raise ValueError("prepared url is None, this should not happen")
response = requests.get(req.url, headers=headers)
if not response.ok:
raise Exception(f"HTTP error {response.status_code}")
return response.json().get("web", {}).get("results", [])
class BraveSearch(BaseModel):
"""Tool that queries the BraveSearch."""
name = "brave_search"
description = (
"a search engine. "
"useful for when you need to answer questions about current events."
" input should be a search query."
)
search_wrapper: BraveSearchWrapper
@classmethod
def from_api_key(
cls, api_key: str, search_kwargs: Optional[dict] = None, **kwargs: Any
) -> "BraveSearch":
"""Create a tool from an api key.
Args:
api_key: The api key to use.
search_kwargs: Any additional kwargs to pass to the search wrapper.
**kwargs: Any additional kwargs to pass to the tool.
Returns:
A tool.
"""
wrapper = BraveSearchWrapper(api_key=api_key, search_kwargs=search_kwargs or {})
return cls(search_wrapper=wrapper, **kwargs)
def _run(
self,
query: str,
) -> str:
"""Use the tool."""
return self.search_wrapper.run(query)
class BraveSearchTool(BuiltinTool):
"""
Tool for performing a search using Brave search engine.
@@ -31,7 +115,7 @@ class BraveSearchTool(BuiltinTool):
tool = BraveSearch.from_api_key(api_key=api_key, search_kwargs={"count": count})
results = tool.run(query)
results = tool._run(query)
if not results:
return self.create_text_message(f"No results found for '{query}' in Tavily")

View File

@@ -1,16 +1,147 @@
from typing import Any
from typing import Any, Optional
from langchain.tools import DuckDuckGoSearchRun
from pydantic import BaseModel, Field
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class DuckDuckGoSearchAPIWrapper(BaseModel):
"""Wrapper for DuckDuckGo Search API.
Free and does not require any setup.
"""
region: Optional[str] = "wt-wt"
safesearch: str = "moderate"
time: Optional[str] = "y"
max_results: int = 5
def get_snippets(self, query: str) -> list[str]:
"""Run query through DuckDuckGo and return concatenated results."""
from duckduckgo_search import DDGS
with DDGS() as ddgs:
results = ddgs.text(
query,
region=self.region,
safesearch=self.safesearch,
timelimit=self.time,
)
if results is None:
return ["No good DuckDuckGo Search Result was found"]
snippets = []
for i, res in enumerate(results, 1):
if res is not None:
snippets.append(res["body"])
if len(snippets) == self.max_results:
break
return snippets
def run(self, query: str) -> str:
snippets = self.get_snippets(query)
return " ".join(snippets)
def results(
self, query: str, num_results: int, backend: str = "api"
) -> list[dict[str, str]]:
"""Run query through DuckDuckGo and return metadata.
Args:
query: The query to search for.
num_results: The number of results to return.
Returns:
A list of dictionaries with the following keys:
snippet - The description of the result.
title - The title of the result.
link - The link to the result.
"""
from duckduckgo_search import DDGS
with DDGS() as ddgs:
results = ddgs.text(
query,
region=self.region,
safesearch=self.safesearch,
timelimit=self.time,
backend=backend,
)
if results is None:
return [{"Result": "No good DuckDuckGo Search Result was found"}]
def to_metadata(result: dict) -> dict[str, str]:
if backend == "news":
return {
"date": result["date"],
"title": result["title"],
"snippet": result["body"],
"source": result["source"],
"link": result["url"],
}
return {
"snippet": result["body"],
"title": result["title"],
"link": result["href"],
}
formatted_results = []
for i, res in enumerate(results, 1):
if res is not None:
formatted_results.append(to_metadata(res))
if len(formatted_results) == num_results:
break
return formatted_results
class DuckDuckGoSearchRun(BaseModel):
"""Tool that queries the DuckDuckGo search API."""
name = "duckduckgo_search"
description = (
"A wrapper around DuckDuckGo Search. "
"Useful for when you need to answer questions about current events. "
"Input should be a search query."
)
api_wrapper: DuckDuckGoSearchAPIWrapper = Field(
default_factory=DuckDuckGoSearchAPIWrapper
)
def _run(
self,
query: str,
) -> str:
"""Use the tool."""
return self.api_wrapper.run(query)
class DuckDuckGoSearchResults(BaseModel):
"""Tool that queries the DuckDuckGo search API and gets back json."""
name = "DuckDuckGo Results JSON"
description = (
"A wrapper around Duck Duck Go Search. "
"Useful for when you need to answer questions about current events. "
"Input should be a search query. Output is a JSON array of the query results"
)
num_results: int = 4
api_wrapper: DuckDuckGoSearchAPIWrapper = Field(
default_factory=DuckDuckGoSearchAPIWrapper
)
backend: str = "api"
def _run(
self,
query: str,
) -> str:
"""Use the tool."""
res = self.api_wrapper.results(query, self.num_results, backend=self.backend)
res_strs = [", ".join([f"{k}: {v}" for k, v in d.items()]) for d in res]
return ", ".join([f"[{rs}]" for rs in res_strs])
class DuckDuckGoInput(BaseModel):
query: str = Field(..., description="Search query.")
class DuckDuckGoSearchTool(BuiltinTool):
"""
Tool for performing a search using DuckDuckGo search engine.
@@ -34,7 +165,7 @@ class DuckDuckGoSearchTool(BuiltinTool):
tool = DuckDuckGoSearchRun(args_schema=DuckDuckGoInput)
result = tool.run(query)
result = tool._run(query)
return self.create_text_message(self.summary(user_id=user_id, content=result))

View File

@@ -70,43 +70,44 @@ class SerpAPI:
raise ValueError(f"Got error from SerpAPI: {res['error']}")
if typ == "text":
toret = ""
if "answer_box" in res.keys() and type(res["answer_box"]) == list:
res["answer_box"] = res["answer_box"][0]
res["answer_box"] = res["answer_box"][0] + "\n"
if "answer_box" in res.keys() and "answer" in res["answer_box"].keys():
toret = res["answer_box"]["answer"]
elif "answer_box" in res.keys() and "snippet" in res["answer_box"].keys():
toret = res["answer_box"]["snippet"]
elif (
toret += res["answer_box"]["answer"] + "\n"
if "answer_box" in res.keys() and "snippet" in res["answer_box"].keys():
toret += res["answer_box"]["snippet"] + "\n"
if (
"answer_box" in res.keys()
and "snippet_highlighted_words" in res["answer_box"].keys()
):
toret = res["answer_box"]["snippet_highlighted_words"][0]
elif (
for item in res["answer_box"]["snippet_highlighted_words"]:
toret += item + "\n"
if (
"sports_results" in res.keys()
and "game_spotlight" in res["sports_results"].keys()
):
toret = res["sports_results"]["game_spotlight"]
elif (
toret += res["sports_results"]["game_spotlight"] + "\n"
if (
"shopping_results" in res.keys()
and "title" in res["shopping_results"][0].keys()
):
toret = res["shopping_results"][:3]
elif (
toret += res["shopping_results"][:3] + "\n"
if (
"knowledge_graph" in res.keys()
and "description" in res["knowledge_graph"].keys()
):
toret = res["knowledge_graph"]["description"]
elif "snippet" in res["organic_results"][0].keys():
toret = res["organic_results"][0]["snippet"]
elif "link" in res["organic_results"][0].keys():
toret = res["organic_results"][0]["link"]
elif (
toret = res["knowledge_graph"]["description"] + "\n"
if "snippet" in res["organic_results"][0].keys():
for item in res["organic_results"]:
toret += "content: " + item["snippet"] + "\n" + "link: " + item["link"] + "\n"
if (
"images_results" in res.keys()
and "thumbnail" in res["images_results"][0].keys()
):
thumbnails = [item["thumbnail"] for item in res["images_results"][:10]]
toret = thumbnails
else:
if toret == "":
toret = "No good search result found"
elif typ == "link":
if "knowledge_graph" in res.keys() and "title" in res["knowledge_graph"].keys() \

View File

@@ -1,16 +1,187 @@
import json
import time
import urllib.error
import urllib.parse
import urllib.request
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 PubMedAPIWrapper(BaseModel):
"""
Wrapper around PubMed API.
This wrapper will use the PubMed API to conduct searches and fetch
document summaries. By default, it will return the document summaries
of the top-k results of an input search.
Parameters:
top_k_results: number of the top-scored document used for the PubMed tool
load_max_docs: a limit to the number of loaded documents
load_all_available_meta:
if True: the `metadata` of the loaded Documents gets all available meta info
(see https://www.ncbi.nlm.nih.gov/books/NBK25499/#chapter4.ESearch)
if False: the `metadata` gets only the most informative fields.
"""
base_url_esearch = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?"
base_url_efetch = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?"
max_retry = 5
sleep_time = 0.2
# Default values for the parameters
top_k_results: int = 3
load_max_docs: int = 25
ARXIV_MAX_QUERY_LENGTH = 300
doc_content_chars_max: int = 2000
load_all_available_meta: bool = False
email: str = "your_email@example.com"
def run(self, query: str) -> str:
"""
Run PubMed search and get the article meta information.
See https://www.ncbi.nlm.nih.gov/books/NBK25499/#chapter4.ESearch
It uses only the most informative fields of article meta information.
"""
try:
# Retrieve the top-k results for the query
docs = [
f"Published: {result['pub_date']}\nTitle: {result['title']}\n"
f"Summary: {result['summary']}"
for result in self.load(query[: self.ARXIV_MAX_QUERY_LENGTH])
]
# Join the results and limit the character count
return (
"\n\n".join(docs)[:self.doc_content_chars_max]
if docs
else "No good PubMed Result was found"
)
except Exception as ex:
return f"PubMed exception: {ex}"
def load(self, query: str) -> list[dict]:
"""
Search PubMed for documents matching the query.
Return a list of dictionaries containing the document metadata.
"""
url = (
self.base_url_esearch
+ "db=pubmed&term="
+ str({urllib.parse.quote(query)})
+ f"&retmode=json&retmax={self.top_k_results}&usehistory=y"
)
result = urllib.request.urlopen(url)
text = result.read().decode("utf-8")
json_text = json.loads(text)
articles = []
webenv = json_text["esearchresult"]["webenv"]
for uid in json_text["esearchresult"]["idlist"]:
article = self.retrieve_article(uid, webenv)
articles.append(article)
# Convert the list of articles to a JSON string
return articles
def retrieve_article(self, uid: str, webenv: str) -> dict:
url = (
self.base_url_efetch
+ "db=pubmed&retmode=xml&id="
+ uid
+ "&webenv="
+ webenv
)
retry = 0
while True:
try:
result = urllib.request.urlopen(url)
break
except urllib.error.HTTPError as e:
if e.code == 429 and retry < self.max_retry:
# Too Many Requests error
# wait for an exponentially increasing amount of time
print(
f"Too Many Requests, "
f"waiting for {self.sleep_time:.2f} seconds..."
)
time.sleep(self.sleep_time)
self.sleep_time *= 2
retry += 1
else:
raise e
xml_text = result.read().decode("utf-8")
# Get title
title = ""
if "<ArticleTitle>" in xml_text and "</ArticleTitle>" in xml_text:
start_tag = "<ArticleTitle>"
end_tag = "</ArticleTitle>"
title = xml_text[
xml_text.index(start_tag) + len(start_tag) : xml_text.index(end_tag)
]
# Get abstract
abstract = ""
if "<AbstractText>" in xml_text and "</AbstractText>" in xml_text:
start_tag = "<AbstractText>"
end_tag = "</AbstractText>"
abstract = xml_text[
xml_text.index(start_tag) + len(start_tag) : xml_text.index(end_tag)
]
# Get publication date
pub_date = ""
if "<PubDate>" in xml_text and "</PubDate>" in xml_text:
start_tag = "<PubDate>"
end_tag = "</PubDate>"
pub_date = xml_text[
xml_text.index(start_tag) + len(start_tag) : xml_text.index(end_tag)
]
# Return article as dictionary
article = {
"uid": uid,
"title": title,
"summary": abstract,
"pub_date": pub_date,
}
return article
class PubmedQueryRun(BaseModel):
"""Tool that searches the PubMed API."""
name = "PubMed"
description = (
"A wrapper around PubMed.org "
"Useful for when you need to answer questions about Physics, Mathematics, "
"Computer Science, Quantitative Biology, Quantitative Finance, Statistics, "
"Electrical Engineering, and Economics "
"from scientific articles on PubMed.org. "
"Input should be a search query."
)
api_wrapper: PubMedAPIWrapper = Field(default_factory=PubMedAPIWrapper)
def _run(
self,
query: str,
) -> str:
"""Use the Arxiv tool."""
return self.api_wrapper.run(query)
class PubMedInput(BaseModel):
query: str = Field(..., description="Search query.")
class PubMedSearchTool(BuiltinTool):
"""
Tool for performing a search using PubMed search engine.
@@ -34,7 +205,7 @@ class PubMedSearchTool(BuiltinTool):
tool = PubmedQueryRun(args_schema=PubMedInput)
result = tool.run(query)
result = tool._run(query)
return self.create_text_message(self.summary(user_id=user_id, content=result))

View File

@@ -1,11 +1,81 @@
from typing import Any, Union
from typing import Any, Optional, Union
from langchain.utilities import TwilioAPIWrapper
from pydantic import BaseModel, validator
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class TwilioAPIWrapper(BaseModel):
"""Messaging Client using Twilio.
To use, you should have the ``twilio`` python package installed,
and the environment variables ``TWILIO_ACCOUNT_SID``, ``TWILIO_AUTH_TOKEN``, and
``TWILIO_FROM_NUMBER``, or pass `account_sid`, `auth_token`, and `from_number` as
named parameters to the constructor.
Example:
.. code-block:: python
from langchain.utilities.twilio import TwilioAPIWrapper
twilio = TwilioAPIWrapper(
account_sid="ACxxx",
auth_token="xxx",
from_number="+10123456789"
)
twilio.run('test', '+12484345508')
"""
client: Any #: :meta private:
account_sid: Optional[str] = None
"""Twilio account string identifier."""
auth_token: Optional[str] = None
"""Twilio auth token."""
from_number: Optional[str] = None
"""A Twilio phone number in [E.164](https://www.twilio.com/docs/glossary/what-e164)
format, an
[alphanumeric sender ID](https://www.twilio.com/docs/sms/send-messages#use-an-alphanumeric-sender-id),
or a [Channel Endpoint address](https://www.twilio.com/docs/sms/channels#channel-addresses)
that is enabled for the type of message you want to send. Phone numbers or
[short codes](https://www.twilio.com/docs/sms/api/short-code) purchased from
Twilio also work here. You cannot, for example, spoof messages from a private
cell phone number. If you are using `messaging_service_sid`, this parameter
must be empty.
""" # noqa: E501
@validator("client", pre=True, always=True)
def set_validator(cls, values: dict) -> dict:
"""Validate that api key and python package exists in environment."""
try:
from twilio.rest import Client
except ImportError:
raise ImportError(
"Could not import twilio python package. "
"Please install it with `pip install twilio`."
)
account_sid = values.get("account_sid")
auth_token = values.get("auth_token")
values["from_number"] = values.get("from_number")
values["client"] = Client(account_sid, auth_token)
return values
def run(self, body: str, to: str) -> str:
"""Run body through Twilio and respond with message sid.
Args:
body: The text of the message you want to send. Can be up to 1,600
characters in length.
to: The destination phone number in
[E.164](https://www.twilio.com/docs/glossary/what-e164) format for
SMS/MMS or
[Channel user address](https://www.twilio.com/docs/sms/channels#channel-addresses)
for other 3rd-party channels.
""" # noqa: E501
message = self.client.messages.create(to, from_=self.from_number, body=body)
return message.sid
class SendMessageTool(BuiltinTool):
"""
A tool for sending messages using Twilio API.

View File

@@ -1,16 +1,79 @@
from typing import Any, Union
from typing import Any, Optional, Union
from langchain import WikipediaAPIWrapper
from langchain.tools import WikipediaQueryRun
from pydantic import BaseModel, Field
import wikipedia
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
WIKIPEDIA_MAX_QUERY_LENGTH = 300
class WikipediaInput(BaseModel):
query: str = Field(..., description="search query.")
class WikipediaAPIWrapper:
"""Wrapper around WikipediaAPI.
To use, you should have the ``wikipedia`` python package installed.
This wrapper will use the Wikipedia API to conduct searches and
fetch page summaries. By default, it will return the page summaries
of the top-k results.
It limits the Document content by doc_content_chars_max.
"""
top_k_results: int = 3
lang: str = "en"
load_all_available_meta: bool = False
doc_content_chars_max: int = 4000
def __init__(self, doc_content_chars_max: int = 4000):
self.doc_content_chars_max = doc_content_chars_max
def run(self, query: str) -> str:
wikipedia.set_lang(self.lang)
wiki_client = wikipedia
"""Run Wikipedia search and get page summaries."""
page_titles = wiki_client.search(query[:WIKIPEDIA_MAX_QUERY_LENGTH])
summaries = []
for page_title in page_titles[: self.top_k_results]:
if wiki_page := self._fetch_page(page_title):
if summary := self._formatted_page_summary(page_title, wiki_page):
summaries.append(summary)
if not summaries:
return "No good Wikipedia Search Result was found"
return "\n\n".join(summaries)[: self.doc_content_chars_max]
@staticmethod
def _formatted_page_summary(page_title: str, wiki_page: Any) -> Optional[str]:
return f"Page: {page_title}\nSummary: {wiki_page.summary}"
def _fetch_page(self, page: str) -> Optional[str]:
try:
return wikipedia.page(title=page, auto_suggest=False)
except (
wikipedia.exceptions.PageError,
wikipedia.exceptions.DisambiguationError,
):
return None
class WikipediaQueryRun:
"""Tool that searches the Wikipedia API."""
name = "Wikipedia"
description = (
"A wrapper around Wikipedia. "
"Useful for when you need to answer general questions about "
"people, places, companies, facts, historical events, or other subjects. "
"Input should be a search query."
)
api_wrapper: WikipediaAPIWrapper
def __init__(self, api_wrapper: WikipediaAPIWrapper):
self.api_wrapper = api_wrapper
def _run(
self,
query: str,
) -> str:
"""Use the Wikipedia tool."""
return self.api_wrapper.run(query)
class WikiPediaSearchTool(BuiltinTool):
def _invoke(self,
user_id: str,
@@ -24,14 +87,10 @@ class WikiPediaSearchTool(BuiltinTool):
return self.create_text_message('Please input query')
tool = WikipediaQueryRun(
name="wikipedia",
api_wrapper=WikipediaAPIWrapper(doc_content_chars_max=4000),
args_schema=WikipediaInput
)
result = tool.run(tool_input={
'query': query
})
result = tool._run(query)
return self.create_text_message(self.summary(user_id=user_id,content=result))

View File

@@ -234,6 +234,9 @@ class CodeNode(BaseNode):
parameters_validated = {}
for output_name, output_config in output_schema.items():
dot = '.' if prefix else ''
if output_name not in result:
raise ValueError(f'Output {prefix}{dot}{output_name} is missing.')
if output_config.type == 'object':
# check if output is object
if not isinstance(result.get(output_name), dict):

View File

@@ -242,7 +242,10 @@ class KnowledgeRetrievalNode(BaseNode):
# get top k
top_k = retrieval_model_config['top_k']
# get retrieval method
retrival_method = retrieval_model_config['search_method']
if dataset.indexing_technique == "economy":
retrival_method = 'keyword_search'
else:
retrival_method = retrieval_model_config['search_method']
# get reranking model
reranking_model=retrieval_model_config['reranking_model'] \
if retrieval_model_config['reranking_enable'] else None

View File

@@ -13,7 +13,7 @@ from core.prompt.entities.advanced_prompt_entities import ChatModelMessage, Comp
from core.prompt.simple_prompt_transform import ModelMode
from core.prompt.utils.prompt_message_util import PromptMessageUtil
from core.workflow.entities.base_node_data_entities import BaseNodeData
from core.workflow.entities.node_entities import NodeRunResult, NodeType
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult, NodeType
from core.workflow.entities.variable_pool import VariablePool
from core.workflow.nodes.llm.llm_node import LLMNode
from core.workflow.nodes.question_classifier.entities import QuestionClassifierNodeData
@@ -65,7 +65,9 @@ class QuestionClassifierNode(LLMNode):
categories = [_class.name for _class in node_data.classes]
try:
result_text_json = json.loads(result_text.strip('```JSON\n'))
categories = result_text_json.get('categories', [])
categories_result = result_text_json.get('categories', [])
if categories_result:
categories = categories_result
except Exception:
logging.error(f"Failed to parse result text: {result_text}")
try:
@@ -89,14 +91,24 @@ class QuestionClassifierNode(LLMNode):
inputs=variables,
process_data=process_data,
outputs=outputs,
edge_source_handle=classes_map.get(categories[0], None)
edge_source_handle=classes_map.get(categories[0], None),
metadata={
NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens,
NodeRunMetadataKey.TOTAL_PRICE: usage.total_price,
NodeRunMetadataKey.CURRENCY: usage.currency
}
)
except ValueError as e:
return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED,
inputs=variables,
error=str(e)
error=str(e),
metadata={
NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens,
NodeRunMetadataKey.TOTAL_PRICE: usage.total_price,
NodeRunMetadataKey.CURRENCY: usage.currency
}
)
@classmethod

View File

@@ -1,9 +1,10 @@
import logging
import time
from typing import Optional
from typing import Optional, cast
from core.app.app_config.entities import FileExtraConfig
from core.app.apps.base_app_queue_manager import GenerateTaskStoppedException
from core.file.file_obj import FileVar
from core.file.file_obj import FileTransferMethod, FileType, FileVar
from core.workflow.callbacks.base_workflow_callback import BaseWorkflowCallback
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult, NodeType
from core.workflow.entities.variable_pool import VariablePool, VariableValue
@@ -16,6 +17,7 @@ from core.workflow.nodes.end.end_node import EndNode
from core.workflow.nodes.http_request.http_request_node import HttpRequestNode
from core.workflow.nodes.if_else.if_else_node import IfElseNode
from core.workflow.nodes.knowledge_retrieval.knowledge_retrieval_node import KnowledgeRetrievalNode
from core.workflow.nodes.llm.entities import LLMNodeData
from core.workflow.nodes.llm.llm_node import LLMNode
from core.workflow.nodes.question_classifier.question_classifier_node import QuestionClassifierNode
from core.workflow.nodes.start.start_node import StartNode
@@ -219,7 +221,8 @@ class WorkflowEngineManager:
raise ValueError('node id not found in workflow graph')
# Get node class
node_cls = node_classes.get(NodeType.value_of(node_config.get('data', {}).get('type')))
node_type = NodeType.value_of(node_config.get('data', {}).get('type'))
node_cls = node_classes.get(node_type)
# init workflow run state
node_instance = node_cls(
@@ -252,11 +255,40 @@ class WorkflowEngineManager:
variable_node_id = variable_selector[0]
variable_key_list = variable_selector[1:]
# get value
value = user_inputs.get(variable_key)
# temp fix for image type
if node_type == NodeType.LLM:
new_value = []
if isinstance(value, list):
node_data = node_instance.node_data
node_data = cast(LLMNodeData, node_data)
detail = node_data.vision.configs.detail if node_data.vision.configs else None
for item in value:
if isinstance(item, dict) and 'type' in item and item['type'] == 'image':
transfer_method = FileTransferMethod.value_of(item.get('transfer_method'))
file = FileVar(
tenant_id=workflow.tenant_id,
type=FileType.IMAGE,
transfer_method=transfer_method,
url=item.get('url') if transfer_method == FileTransferMethod.REMOTE_URL else None,
related_id=item.get(
'upload_file_id') if transfer_method == FileTransferMethod.LOCAL_FILE else None,
extra_config=FileExtraConfig(image_config={'detail': detail} if detail else None),
)
new_value.append(file)
if new_value:
value = new_value
# append variable and value to variable pool
variable_pool.append_variable(
node_id=variable_node_id,
variable_key_list=variable_key_list,
value=user_inputs.get(variable_key)
value=value
)
# run node
node_run_result = node_instance.run(

View File

@@ -28,6 +28,7 @@ def init_app(app: Flask) -> Celery:
celery_app.conf.update(
result_backend=app.config["CELERY_RESULT_BACKEND"],
broker_connection_retry_on_startup=True,
)
if app.config["BROKER_USE_SSL"]:

View File

@@ -815,7 +815,7 @@ class Message(db.Model):
@property
def workflow_run(self):
if self.workflow_run_id:
from api.models.workflow import WorkflowRun
from .workflow import WorkflowRun
return db.session.query(WorkflowRun).filter(WorkflowRun.id == self.workflow_run_id).first()
return None

View File

@@ -299,6 +299,10 @@ class WorkflowRun(db.Model):
Message.workflow_run_id == self.id
).first()
@property
def workflow(self):
return db.session.query(Workflow).filter(Workflow.id == self.workflow_id).first()
class WorkflowNodeExecutionTriggeredFrom(Enum):
"""

View File

@@ -221,7 +221,8 @@ class AppService:
"name": app.name,
"mode": app.mode,
"icon": app.icon,
"icon_background": app.icon_background
"icon_background": app.icon_background,
"description": app.description
}
}

View File

@@ -1,5 +1,8 @@
from typing import Optional, Union
from sqlalchemy import or_
from core.app.entities.app_invoke_entities import InvokeFrom
from core.llm_generator.llm_generator import LLMGenerator
from extensions.ext_database import db
from libs.infinite_scroll_pagination import InfiniteScrollPagination
@@ -13,8 +16,9 @@ class ConversationService:
@classmethod
def pagination_by_last_id(cls, app_model: App, user: Optional[Union[Account, EndUser]],
last_id: Optional[str], limit: int,
include_ids: Optional[list] = None, exclude_ids: Optional[list] = None,
exclude_debug_conversation: bool = False) -> InfiniteScrollPagination:
invoke_from: InvokeFrom,
include_ids: Optional[list] = None,
exclude_ids: Optional[list] = None) -> InfiniteScrollPagination:
if not user:
return InfiniteScrollPagination(data=[], limit=limit, has_more=False)
@@ -24,6 +28,7 @@ class ConversationService:
Conversation.from_source == ('api' if isinstance(user, EndUser) else 'console'),
Conversation.from_end_user_id == (user.id if isinstance(user, EndUser) else None),
Conversation.from_account_id == (user.id if isinstance(user, Account) else None),
or_(Conversation.invoke_from.is_(None), Conversation.invoke_from == invoke_from.value)
)
if include_ids is not None:
@@ -32,9 +37,6 @@ class ConversationService:
if exclude_ids is not None:
base_query = base_query.filter(~Conversation.id.in_(exclude_ids))
if exclude_debug_conversation:
base_query = base_query.filter(Conversation.override_model_configs == None)
if last_id:
last_conversation = base_query.filter(
Conversation.id == last_id,

View File

@@ -1,5 +1,6 @@
from typing import Optional, Union
from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db
from libs.infinite_scroll_pagination import InfiniteScrollPagination
from models.account import Account
@@ -11,8 +12,8 @@ from services.conversation_service import ConversationService
class WebConversationService:
@classmethod
def pagination_by_last_id(cls, app_model: App, user: Optional[Union[Account, EndUser]],
last_id: Optional[str], limit: int, pinned: Optional[bool] = None,
exclude_debug_conversation: bool = False) -> InfiniteScrollPagination:
last_id: Optional[str], limit: int, invoke_from: InvokeFrom,
pinned: Optional[bool] = None) -> InfiniteScrollPagination:
include_ids = None
exclude_ids = None
if pinned is not None:
@@ -32,9 +33,9 @@ class WebConversationService:
user=user,
last_id=last_id,
limit=limit,
invoke_from=invoke_from,
include_ids=include_ids,
exclude_ids=exclude_ids,
exclude_debug_conversation=exclude_debug_conversation
)
@classmethod

View File

@@ -0,0 +1,24 @@
import pytest
from pydantic.error_wrappers import ValidationError
from core.rag.datasource.vdb.milvus.milvus_vector import MilvusConfig
def test_default_value():
valid_config = {
'host': 'localhost',
'port': 19530,
'user': 'root',
'password': 'Milvus'
}
for key in valid_config:
config = valid_config.copy()
del config[key]
with pytest.raises(ValidationError) as e:
MilvusConfig(**config)
assert e.value.errors()[1]['msg'] == f'config MILVUS_{key.upper()} is required'
config = MilvusConfig(**valid_config)
assert config.secure is False
assert config.database == 'default'

View File

@@ -2,7 +2,7 @@ version: '3'
services:
# API service
api:
image: langgenius/dify-api:0.6.0
image: langgenius/dify-api:0.6.1
restart: always
environment:
# Startup mode, 'api' starts the API server.
@@ -150,7 +150,7 @@ services:
# worker service
# The Celery worker for processing the queue.
worker:
image: langgenius/dify-api:0.6.0
image: langgenius/dify-api:0.6.1
restart: always
environment:
# Startup mode, 'worker' starts the Celery worker for processing the queue.
@@ -232,7 +232,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:0.6.0
image: langgenius/dify-web:0.6.1
restart: always
environment:
EDITION: SELF_HOSTED

View File

@@ -333,7 +333,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
)}/>
<div className='px-4 pb-2'>
<div className='flex items-center gap-1 text-gray-700 text-md leading-6 font-semibold'>
{t('app.newApp.advanced')}
{showSwitchTip === 'chat' ? t('app.newApp.advanced') : t('app.types.workflow')}
<span className='px-1 rounded-[5px] bg-white border border-black/8 text-gray-500 text-[10px] leading-[18px] font-medium'>BETA</span>
</div>
<div className='text-orange-500 text-xs leading-[18px] font-medium'>{t('app.newApp.advancedFor').toLocaleUpperCase()}</div>

View File

@@ -20,7 +20,8 @@ import ConfigContext from '@/context/debug-configuration'
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
import ConfigVar from '@/app/components/app/configuration/config-var'
import { type CitationConfig, type ModelConfig, type ModerationConfig, type MoreLikeThisConfig, type PromptVariable, type SpeechToTextConfig, type SuggestedQuestionsAfterAnswerConfig, type TextToSpeechConfig } from '@/models/debug'
import { AppType, ModelModeType } from '@/types/app'
import type { AppType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import { useModalContext } from '@/context/modal-context'
import ConfigParamModal from '@/app/components/app/configuration/toolbox/annotation/config-param-modal'
import AnnotationFullModal from '@/app/components/billing/annotation-full/modal'
@@ -60,7 +61,7 @@ const Config: FC = () => {
moderationConfig,
setModerationConfig,
} = useContext(ConfigContext)
const isChatApp = mode === AppType.chat
const isChatApp = ['advanced-chat', 'agent-chat', 'chat'].includes(mode)
const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text)
const { data: text2speechDefaultModel } = useDefaultModel(ModelTypeEnum.tts)
const { setShowModerationSettingModal } = useModalContext()

View File

@@ -3,7 +3,7 @@ import { clone } from 'lodash-es'
import produce from 'immer'
import type { ChatPromptConfig, CompletionPromptConfig, ConversationHistoriesRole, PromptItem } from '@/models/debug'
import { PromptMode } from '@/models/debug'
import { AppType, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
import { PRE_PROMPT_PLACEHOLDER_TEXT, checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
import { fetchPromptTemplate } from '@/service/debug'
@@ -152,7 +152,7 @@ const useAdvancedPromptConfig = ({
else
draft.prompt.text = completionPromptConfig.prompt?.text.replace(PRE_PROMPT_PLACEHOLDER_TEXT, toReplacePrePrompt)
if (appMode === AppType.chat && completionPromptConfig.conversation_histories_role.assistant_prefix && completionPromptConfig.conversation_histories_role.user_prefix)
if (['advanced-chat', 'agent-chat', 'chat'].includes(appMode) && completionPromptConfig.conversation_histories_role.assistant_prefix && completionPromptConfig.conversation_histories_role.user_prefix)
draft.conversation_histories_role = completionPromptConfig.conversation_histories_role
})
setCompletionPromptConfig(newPromptConfig)

View File

@@ -388,7 +388,10 @@ const Configuration: FC = () => {
const promptMode = modelConfig.prompt_type === PromptMode.advanced ? PromptMode.advanced : PromptMode.simple
doSetPromptMode(promptMode)
if (promptMode === PromptMode.advanced) {
setChatPromptConfig(modelConfig.chat_prompt_config || clone(DEFAULT_CHAT_PROMPT_CONFIG) as any)
if (modelConfig.chat_prompt_config && modelConfig.chat_prompt_config.prompt.length > 0)
setChatPromptConfig(modelConfig.chat_prompt_config)
else
setChatPromptConfig(clone(DEFAULT_CHAT_PROMPT_CONFIG) as any)
setCompletionPromptConfig(modelConfig.completion_prompt_config || clone(DEFAULT_COMPLETION_PROMPT_CONFIG) as any)
setCanReturnToSimpleMode(false)
}

View File

@@ -243,7 +243,7 @@ const CreateAppModal = ({ show, onSuccess, onClose }: CreateAppDialogProps) => {
'hidden z-20 absolute right-[26px] top-[-158px] w-[376px] rounded-xl bg-white border-[0.5px] border-[rgba(0,0,0,0.05)] shadow-lg group-hover:block',
)}
>
<div className={cn('w-full h-[256px] bg-center bg-no-repeat bg-contain rounded-xl', s.basicPic)}/>
<div className={cn('w-full h-[256px] bg-center bg-no-repeat bg-contain rounded-xl', s.advancedPic)}/>
<div className='px-4 pb-2'>
<div className='flex items-center justify-between'>
<div className='flex items-center'>

View File

@@ -1,11 +1,12 @@
'use client'
import { useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { t } from 'i18next'
import { useParams, usePathname } from 'next/navigation'
import s from './style.module.css'
import Tooltip from '@/app/components/base/tooltip'
import { randomString } from '@/utils'
import { textToAudio } from '@/service/share'
import Loading from '@/app/components/base/loading'
type AudioBtnProps = {
value: string
@@ -14,6 +15,8 @@ type AudioBtnProps = {
isAudition?: boolean
}
type AudioState = 'initial' | 'loading' | 'playing' | 'paused' | 'ended'
const AudioBtn = ({
value,
voice,
@@ -21,9 +24,8 @@ const AudioBtn = ({
isAudition,
}: AudioBtnProps) => {
const audioRef = useRef<HTMLAudioElement | null>(null)
const [isPlaying, setIsPlaying] = useState(false)
const [isPause, setPause] = useState(false)
const [hasEnded, setHasEnded] = useState(false)
const [audioState, setAudioState] = useState<AudioState>('initial')
const selector = useRef(`play-tooltip-${randomString(4)}`)
const params = useParams()
const pathname = usePathname()
@@ -34,9 +36,11 @@ const AudioBtn = ({
return ''
}
const playAudio = async () => {
const loadAudio = async () => {
const formData = new FormData()
if (value !== '') {
setAudioState('loading')
formData.append('text', removeCodeBlocks(value))
formData.append('voice', removeCodeBlocks(voice))
@@ -59,67 +63,80 @@ const AudioBtn = ({
const blob_bytes = Buffer.from(audioResponse.data, 'latin1')
const blob = new Blob([blob_bytes], { type: 'audio/wav' })
const audioUrl = URL.createObjectURL(blob)
const audio = new Audio(audioUrl)
audioRef.current = audio
audio.play().then(() => {}).catch(() => {
setIsPlaying(false)
URL.revokeObjectURL(audioUrl)
})
audio.onended = () => {
setHasEnded(true)
setIsPlaying(false)
}
audioRef.current!.src = audioUrl
}
catch (error) {
setIsPlaying(false)
setAudioState('initial')
console.error('Error playing audio:', error)
}
}
}
const togglePlayPause = () => {
const handleToggle = () => {
if (audioState === 'initial')
loadAudio()
if (audioRef.current) {
if (isPlaying) {
if (!hasEnded) {
setPause(false)
audioRef.current.play()
}
if (!isPause) {
setPause(true)
audioRef.current.pause()
}
if (audioState === 'playing') {
audioRef.current.pause()
setAudioState('paused')
}
else if (!isPlaying) {
if (isPause) {
setPause(false)
audioRef.current.play()
}
else {
setHasEnded(false)
playAudio().then()
}
else if (audioState === 'paused' || audioState === 'ended') {
audioRef.current.play()
setAudioState('playing')
}
setIsPlaying(prevIsPlaying => !prevIsPlaying)
}
else {
setIsPlaying(true)
if (!isPlaying)
playAudio().then()
}
}
useEffect(() => {
const currentAudio = audioRef.current
const handleLoading = () => {
setAudioState('loading')
}
const handlePlay = () => {
currentAudio?.play()
setAudioState('playing')
}
const handleEnded = () => {
setAudioState('ended')
}
currentAudio?.addEventListener('progress', handleLoading)
currentAudio?.addEventListener('canplaythrough', handlePlay)
currentAudio?.addEventListener('ended', handleEnded)
return () => {
if (currentAudio) {
currentAudio.removeEventListener('progress', handleLoading)
currentAudio.removeEventListener('canplaythrough', handlePlay)
currentAudio.removeEventListener('ended', handleEnded)
URL.revokeObjectURL(currentAudio.src)
currentAudio.src = ''
}
}
}, [])
const tooltipContent = {
initial: t('appApi.play'),
ended: t('appApi.play'),
paused: t('appApi.pause'),
playing: t('appApi.playing'),
loading: t('appApi.loading'),
}[audioState]
return (
<div className={`${(isPlaying && !hasEnded) ? 'mr-1' : className}`}>
<div className={`${(audioState === 'loading' || audioState === 'playing') ? 'mr-1' : className}`}>
<Tooltip
selector={selector.current}
content={(!isPause ? ((isPlaying && !hasEnded) ? t('appApi.playing') : t('appApi.play')) : t('appApi.pause')) as string}
content={tooltipContent}
className='z-10'
>
<div
<button
disabled={audioState === 'loading'}
className={`box-border p-0.5 flex items-center justify-center cursor-pointer ${isAudition || '!p-0 rounded-md bg-white'}`}
onClick={togglePlayPause}>
<div className={`w-6 h-6 rounded-md ${!isAudition ? 'w-4 h-4 hover:bg-gray-50' : 'hover:bg-gray-50'} ${(isPlaying && !hasEnded) ? s.pauseIcon : s.playIcon}`}></div>
</div>
onClick={handleToggle}>
{audioState === 'loading' && <div className='w-6 h-6 rounded-md flex items-center justify-center p-2'><Loading /></div>}
{audioState !== 'loading' && <div className={`w-6 h-6 rounded-md ${!isAudition ? 'w-4 h-4 hover:bg-gray-50' : 'hover:bg-gray-50'} ${(audioState === 'playing') ? s.pauseIcon : s.playIcon}`}></div>}
</button>
</Tooltip>
<audio ref={audioRef} src='' className='hidden' />
</div>
)
}

View File

@@ -7,4 +7,4 @@
background-image: url(~@/app/components/develop/secret-key/assets/pause.svg);
background-position: center;
background-repeat: no-repeat;
}
}

View File

@@ -15,6 +15,7 @@ import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
// import TreeView from './plugins/tree-view'
import Placeholder from './plugins/placeholder'
import ComponentPickerBlock from './plugins/component-picker-block'
@@ -208,6 +209,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
<OnChangePlugin onChange={handleEditorChange} />
<OnBlurBlock onBlur={onBlur} onFocus={onFocus} />
<UpdateBlock instanceId={instanceId} />
<HistoryPlugin />
{/* <TreeView /> */}
</div>
</LexicalComposer>

View File

@@ -40,7 +40,7 @@ const OnBlurBlock: FC<OnBlurBlockProps> = ({
() => {
ref.current = setTimeout(() => {
editor.dispatchCommand(KEY_ESCAPE_COMMAND, new KeyboardEvent('keydown', { key: 'Escape' }))
}, 100)
}, 200)
if (onBlur)
onBlur()

View File

@@ -1,8 +1,8 @@
export type FormValue = Record<string, any>
export type TypeWithI18N<T = string> = {
'en-US': T
'zh-Hans': T
en_US: T
zh_Hans: T
[key: string]: T
}
@@ -67,16 +67,16 @@ export enum ModelStatusEnum {
export const MODEL_STATUS_TEXT: { [k: string]: TypeWithI18N } = {
'no-configure': {
'en-US': 'No Configure',
'zh-Hans': '未配置凭据',
en_US: 'No Configure',
zh_Hans: '未配置凭据',
},
'quota-exceeded': {
'en-US': 'Quota Exceeded',
'zh-Hans': '额度不足',
en_US: 'Quota Exceeded',
zh_Hans: '额度不足',
},
'no-permission': {
'en-US': 'No Permission',
'zh-Hans': '无使用权限',
en_US: 'No Permission',
zh_Hans: '无使用权限',
},
}

View File

@@ -11,11 +11,11 @@ import type {
DefaultModel,
DefaultModelResponse,
Model,
ModelTypeEnum,
} from './declarations'
import {
ConfigurateMethodEnum,
ModelStatusEnum,
} from './declarations'
import I18n from '@/context/i18n'
import {
@@ -132,6 +132,7 @@ export const useCurrentProviderAndModel = (modelList: Model[], defaultModel?: De
export const useTextGenerationCurrentProviderAndModelAndModelList = (defaultModel?: DefaultModel) => {
const { textGenerationModelList } = useProviderContext()
const activeTextGenerationModelList = textGenerationModelList.filter(model => model.status === ModelStatusEnum.active)
const {
currentProvider,
currentModel,
@@ -141,6 +142,7 @@ export const useTextGenerationCurrentProviderAndModelAndModelList = (defaultMode
currentProvider,
currentModel,
textGenerationModelList,
activeTextGenerationModelList,
}
}

View File

@@ -93,7 +93,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
const {
currentProvider,
currentModel,
textGenerationModelList,
activeTextGenerationModelList,
} = useTextGenerationCurrentProviderAndModelAndModelList(
{ provider, model: modelId },
)
@@ -114,7 +114,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
}
const handleChangeModel = ({ provider, model }: DefaultModel) => {
const targetProvider = textGenerationModelList.find(modelItem => modelItem.provider === provider)
const targetProvider = activeTextGenerationModelList.find(modelItem => modelItem.provider === provider)
const targetModelItem = targetProvider?.models.find(modelItem => modelItem.model === model)
setModel({
modelId: model,
@@ -223,7 +223,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
</div>
<ModelSelector
defaultModel={(provider || modelId) ? { provider, model: modelId } : undefined}
modelList={textGenerationModelList}
modelList={activeTextGenerationModelList}
onSelect={handleChangeModel}
/>
</div>

View File

@@ -301,6 +301,8 @@ export const useNodesInteractions = () => {
target,
targetHandle,
}) => {
if (source === target)
return
if (getNodesReadOnly())
return

View File

@@ -176,6 +176,8 @@ export const useWorkflowRun = () => {
const {
getNodes,
setNodes,
edges,
setEdges,
} = store.getState()
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
draft.task_id = task_id
@@ -192,6 +194,15 @@ export const useWorkflowRun = () => {
})
})
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
draft.forEach((edge) => {
edge.data = {
...edge.data,
_runned: false,
}
})
})
setEdges(newEdges)
if (onWorkflowStarted)
onWorkflowStarted(params)

View File

@@ -160,8 +160,10 @@ export const useWorkflow = () => {
if (incomers.length) {
incomers.forEach((node) => {
callback(node)
traverse(node, callback)
if (!list.find(n => node.id === n.id)) {
callback(node)
traverse(node, callback)
}
})
}
}
@@ -272,7 +274,10 @@ export const useWorkflow = () => {
}, [isVarUsedInNodes])
const isValidConnection = useCallback(({ source, target }: Connection) => {
const { getNodes } = store.getState()
const {
edges,
getNodes,
} = store.getState()
const nodes = getNodes()
const sourceNode: Node = nodes.find(node => node.id === source)!
const targetNode: Node = nodes.find(node => node.id === target)!
@@ -287,7 +292,21 @@ export const useWorkflow = () => {
return false
}
return true
const hasCycle = (node: Node, visited = new Set()) => {
if (visited.has(node.id))
return false
visited.add(node.id)
for (const outgoer of getOutgoers(node, nodes, edges)) {
if (outgoer.id === source)
return true
if (hasCycle(outgoer, visited))
return true
}
}
return !hasCycle(targetNode)
}, [store, nodesExtraData])
const formatTimeFromNow = useCallback((time: number) => {

View File

@@ -12,6 +12,7 @@ import Split from '@/app/components/workflow/nodes/_base/components/split'
import { InputVarType, NodeRunningStatus } from '@/app/components/workflow/types'
import ResultPanel from '@/app/components/workflow/run/result-panel'
import Toast from '@/app/components/base/toast'
import { TransferMethod } from '@/types/app'
const i18nPrefix = 'workflow.singleRun'
@@ -51,7 +52,18 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
const isFinished = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
const isRunning = runningStatus === NodeRunningStatus.Running
const isFileLoaded = (() => {
// system files
const filesForm = forms.find(item => !!item.values['#files#'])
if (!filesForm)
return true
const files = filesForm.values['#files#'] as any
if (files?.some((item: any) => item.transfer_method === TransferMethod.local_file && !item.upload_file_id))
return false
return true
})()
const handleRun = useCallback(() => {
let errMsg = ''
forms.forEach((form) => {
@@ -129,7 +141,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
<StopCircle className='w-4 h-4 text-gray-500' />
</div>
)}
<Button disabled={isRunning} type='primary' className='w-0 grow !h-8 flex items-center space-x-2 text-[13px]' onClick={handleRun}>
<Button disabled={!isFileLoaded || isRunning} type='primary' className='w-0 grow !h-8 flex items-center space-x-2 text-[13px]' onClick={handleRun}>
{isRunning && <Loading02 className='animate-spin w-4 h-4 text-white' />}
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
</Button>

View File

@@ -1,4 +1,4 @@
import { type FC, useEffect, useState } from 'react'
import { type FC, useEffect, useRef, useState } from 'react'
import React from 'react'
import type { KnowledgeRetrievalNodeType } from './types'
import { Folder } from '@/app/components/base/icons/src/vender/solid/files'
@@ -10,10 +10,17 @@ const Node: FC<NodeProps<KnowledgeRetrievalNodeType>> = ({
data,
}) => {
const [selectedDatasets, setSelectedDatasets] = useState<DataSet[]>([])
const updateTime = useRef(0)
useEffect(() => {
(async () => {
updateTime.current = updateTime.current + 1
const currUpdateTime = updateTime.current
if (data.dataset_ids?.length > 0) {
const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: data.dataset_ids } })
// avoid old data overwrite new data
if (currUpdateTime < updateTime.current)
return
setSelectedDatasets(dataSetsWithDetail)
}
else {
@@ -33,7 +40,7 @@ const Node: FC<NodeProps<KnowledgeRetrievalNodeType>> = ({
<div className='mr-1 shrink-0 p-1 bg-[#F5F8FF] rounded-md border-[0.5px] border-[#E0EAFF]'>
<Folder className='w-3 h-3 text-[#444CE7]' />
</div>
<div className='text-xs font-normal text-gray-700'>
<div className='grow w-0 text-xs font-normal text-gray-700 truncate'>
{name}
</div>
</div>

View File

@@ -23,7 +23,7 @@ const Item: FC<ItemProps> = ({ title, value, onSelect, isSelected }) => {
return (
<div
className={cn(isSelected ? 'bg-white border-[2px] border-primary-400 shadow-xs' : 'bg-gray-25 border border-gray-100', 'flex items-center h-8 px-3 rounded-xl text-[13px] font-normal text-gray-900 cursor-pointer')}
className={cn(isSelected ? 'bg-white border-[2px] border-primary-400 shadow-xs' : 'bg-gray-25 border border-gray-100', 'flex items-center h-8 px-3 rounded-lg text-[13px] font-normal text-gray-900 cursor-pointer')}
onClick={handleSelect}
>
{title}
@@ -43,7 +43,7 @@ const ResolutionPicker: FC<Props> = ({
const { t } = useTranslation()
return (
<div className='flex items-center'>
<div className='flex items-center justify-between'>
<div className='mr-2 text-xs font-medium text-gray-500 uppercase'>{t(`${i18nPrefix}.resolution.name`)}</div>
<div className='flex items-center space-x-1'>
<Item

View File

@@ -21,6 +21,7 @@ import ResultPanel from '@/app/components/workflow/run/result-panel'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
import Switch from '@/app/components/base/switch'
const i18nPrefix = 'workflow.nodes.llm'
const Panel: FC<NodePanelProps<LLMNodeType>> = ({
@@ -51,6 +52,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
filterVar,
handlePromptChange,
handleMemoryChange,
handleVisionResolutionEnabledChange,
handleVisionResolutionChange,
isShowSingleRun,
hideSingleRun,
@@ -240,12 +242,19 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
title={t(`${i18nPrefix}.vision`)}
tooltip={t('appDebug.vision.description')!}
operations={
<ResolutionPicker
value={inputs.vision.configs?.detail || Resolution.high}
onChange={handleVisionResolutionChange}
/>
<Switch size='md' defaultValue={inputs.vision.enabled} onChange={handleVisionResolutionEnabledChange} />
}
/>
>
{inputs.vision.enabled
? (
<ResolutionPicker
value={inputs.vision.configs?.detail || Resolution.high}
onChange={handleVisionResolutionChange}
/>
)
: null}
</Field>
</>
)}
</div>

View File

@@ -103,6 +103,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultConfig, isChatModel])
const [modelChanged, setModelChanged] = useState(false)
const {
currentProvider,
currentModel,
@@ -118,6 +119,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
appendDefaultPromptConfig(draft, defaultConfig, model.mode === 'chat')
})
setInputs(newInputs)
setModelChanged(true)
}, [setInputs, defaultConfig, appendDefaultPromptConfig])
useEffect(() => {
@@ -146,7 +148,35 @@ const useConfig = (id: string, payload: LLMNodeType) => {
},
)
const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision)
// change to vision model to set vision enabled, else disabled
useEffect(() => {
if (!modelChanged)
return
setModelChanged(false)
if (!isShowVisionConfig) {
const newInputs = produce(inputs, (draft) => {
draft.vision = {
enabled: false,
}
})
setInputs(newInputs)
return
}
if (!inputs.vision?.enabled) {
const newInputs = produce(inputs, (draft) => {
if (!draft.vision?.enabled) {
draft.vision = {
enabled: true,
configs: {
detail: Resolution.high,
},
}
}
})
setInputs(newInputs)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isShowVisionConfig, modelChanged])
// variables
const { handleVarListChange, handleAddVariable } = useVarList<LLMNodeType>({
inputs,
@@ -176,6 +206,28 @@ const useConfig = (id: string, payload: LLMNodeType) => {
setInputs(newInputs)
}, [inputs, setInputs])
const handleVisionResolutionEnabledChange = useCallback((enabled: boolean) => {
const newInputs = produce(inputs, (draft) => {
if (!draft.vision) {
draft.vision = {
enabled,
configs: {
detail: Resolution.high,
},
}
}
else {
draft.vision.enabled = enabled
if (!draft.vision.configs) {
draft.vision.configs = {
detail: Resolution.high,
}
}
}
})
setInputs(newInputs)
}, [inputs, setInputs])
const handleVisionResolutionChange = useCallback((newResolution: Resolution) => {
const newInputs = produce(inputs, (draft) => {
if (!draft.vision.configs) {
@@ -296,6 +348,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
filterVar,
handlePromptChange,
handleMemoryChange,
handleVisionResolutionEnabledChange,
handleVisionResolutionChange,
isShowSingleRun,
hideSingleRun,

View File

@@ -16,6 +16,7 @@ import {
} from '../store'
import { useWorkflowRun } from '../hooks'
import type { StartNodeType } from '../nodes/start/types'
import { TransferMethod } from '../../base/text-generation/types'
import Button from '@/app/components/base/button'
import { useFeatures } from '@/app/components/base/features/hooks'
@@ -75,6 +76,13 @@ const InputsPanel = ({ onRun }: Props) => {
handleRun({ inputs, files })
}
const canRun = (() => {
if (files?.some(item => (item.transfer_method as any) === TransferMethod.local_file && !item.upload_file_id))
return false
return true
})()
return (
<>
<div className='px-4 pb-2'>
@@ -97,7 +105,7 @@ const InputsPanel = ({ onRun }: Props) => {
<div className='flex items-center justify-between px-4 py-2'>
<Button
type='primary'
disabled={workflowRunningData?.result?.status === WorkflowRunningStatus.Running}
disabled={!canRun || workflowRunningData?.result?.status === WorkflowRunningStatus.Running}
className='py-0 w-full h-8 rounded-lg text-[13px] font-medium'
onClick={doRun}
>

View File

@@ -24,6 +24,60 @@ import type { ToolNodeType } from './nodes/tool/types'
import { CollectionType } from '@/app/components/tools/types'
import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
const WHITE = 'WHITE'
const GRAY = 'GRAY'
const BLACK = 'BLACK'
const isCyclicUtil = (nodeId: string, color: Record<string, string>, adjaList: Record<string, string[]>, stack: string[]) => {
color[nodeId] = GRAY
stack.push(nodeId)
for (let i = 0; i < adjaList[nodeId].length; ++i) {
const childId = adjaList[nodeId][i]
if (color[childId] === GRAY) {
stack.push(childId)
return true
}
if (color[childId] === WHITE && isCyclicUtil(childId, color, adjaList, stack))
return true
}
color[nodeId] = BLACK
if (stack.length > 0 && stack[stack.length - 1] === nodeId)
stack.pop()
return false
}
const getCycleEdges = (nodes: Node[], edges: Edge[]) => {
const adjaList: Record<string, string[]> = {}
const color: Record<string, string> = {}
const stack: string[] = []
for (const node of nodes) {
color[node.id] = WHITE
adjaList[node.id] = []
}
for (const edge of edges)
adjaList[edge.source].push(edge.target)
for (let i = 0; i < nodes.length; i++) {
if (color[nodes[i].id] === WHITE)
isCyclicUtil(nodes[i].id, color, adjaList, stack)
}
const cycleEdges = []
if (stack.length > 0) {
const cycleNodes = new Set(stack)
for (const edge of edges) {
if (cycleNodes.has(edge.source) && cycleNodes.has(edge.target))
cycleEdges.push(edge)
}
}
return cycleEdges
}
export const initialNodes = (nodes: Node[], edges: Edge[]) => {
const firstNode = nodes[0]
@@ -35,6 +89,7 @@ export const initialNodes = (nodes: Node[], edges: Edge[]) => {
}
})
}
return nodes.map((node) => {
node.type = 'custom'
@@ -75,7 +130,11 @@ export const initialEdges = (edges: Edge[], nodes: Node[]) => {
return acc
}, {} as Record<string, Node>)
return edges.map((edge) => {
const cycleEdges = getCycleEdges(nodes, edges)
return edges.filter((edge) => {
return !cycleEdges.find(cycEdge => cycEdge.source === edge.source && cycEdge.target === edge.target)
}).map((edge) => {
edge.type = 'custom'
if (!edge.sourceHandle)

View File

@@ -1,6 +1,7 @@
/* eslint-disable import/no-mutable-exports */
import { InputVarType } from '@/app/components/workflow/types'
import { AgentStrategy } from '@/types/app'
import { PromptRole } from '@/models/debug'
export let apiPrefix = ''
export let publicApiPrefix = ''
@@ -70,7 +71,12 @@ export const TONE_LIST = [
]
export const DEFAULT_CHAT_PROMPT_CONFIG = {
prompt: [],
prompt: [
{
role: PromptRole.system,
text: '',
},
],
}
export const DEFAULT_COMPLETION_PROMPT_CONFIG = {

View File

@@ -9,6 +9,7 @@ const translation = {
play: 'Play',
pause: 'Pause',
playing: 'Playing',
loading: 'Loading',
merMaind: {
rerender: 'Redo Rerender',
},

View File

@@ -266,18 +266,32 @@ const translation = {
queryNoBeEmpty: 'La requête doit être définie dans le prompt',
},
variableConig: {
modalTitle: 'Paramètres du champ',
description: 'Paramètre pour la variable {{varName}}',
fieldType: 'Type de champ',
string: 'Texte Court',
paragraph: 'Paragraphe',
select: 'Sélectionner',
notSet: 'Non défini, essayez de taper {{input}} dans l\'invite de préfixe',
stringTitle: 'Options de la boîte de texte du formulaire',
maxLength: 'Longueur maximale',
options: 'Options',
addOption: 'Ajouter une option',
apiBasedVar: 'Variable basée sur l\'API',
'addModalTitle': 'Add Input Field',
'editModalTitle': 'Edit Input Field',
'description': 'Setting for variable {{varName}}',
'fieldType': 'Field type',
'string': 'Short Text',
'text-input': 'Short Text',
'paragraph': 'Paragraph',
'select': 'Select',
'number': 'Number',
'notSet': 'Not set, try typing {{input}} in the prefix prompt',
'stringTitle': 'Form text box options',
'maxLength': 'Max length',
'options': 'Options',
'addOption': 'Add option',
'apiBasedVar': 'API-based Variable',
'varName': 'Variable Name',
'labelName': 'Label Name',
'inputPlaceholder': 'Please input',
'required': 'Required',
'errorMsg': {
varNameRequired: 'Variable name is required',
labelNameRequired: 'Label name is required',
varNameCanBeRepeat: 'Variable name can not be repeated',
atLeastOneOption: 'At least one option is required',
optionRepeat: 'Has repeat options',
},
},
vision: {
name: 'Vision',
@@ -348,6 +362,7 @@ const translation = {
result: 'Texte de sortie',
datasetConfig: {
settingTitle: 'Paramètres de récupération',
knowledgeTip: 'Cliquez sur le bouton “+” pour ajouter des connaissances',
retrieveOneWay: {
title: 'Récupération N-vers-1',
description: 'En fonction de l\'intention de l\'utilisateur et des descriptions de Connaissance, l\'Agent sélectionne de manière autonome la meilleure Connaissance pour interroger. Idéal pour les applications avec une Connaissance distincte et limitée.',

View File

@@ -1,17 +1,23 @@
const translation = {
title: 'Journaux',
description: 'Les journaux enregistrent l\'état de fonctionnement de l\'application, y compris les entrées de l\'utilisateur et les réponses de l\'IA.',
dateTimeFormat: 'JJ/MM/AAAA hh:mm A',
description: 'Les journaux enregistrent l\'état d\'exécution de l\'application, y compris les entrées utilisateur et les réponses de l\'IA.',
dateTimeFormat: 'MM/DD/YYYY hh:mm A',
table: {
header: {
time: 'Temps',
time: 'Heure',
endUser: 'Utilisateur final',
input: 'Entrée',
output: 'Sortie',
summary: 'Titre',
messageCount: 'Nombre de Messages',
userRate: 'Taux d\'utilisateur',
adminRate: 'Taux Op.',
messageCount: 'Nombre de messages',
userRate: 'Taux utilisateur',
adminRate: 'Taux op.',
startTime: 'HEURE DE DÉBUT',
status: 'STATUT',
runtime: 'TEMPS D\'EXÉCUTION',
tokens: 'JETONS',
user: 'UTILISATEUR FINAL',
version: 'VERSION',
},
pagination: {
previous: 'Précédent',
@@ -21,49 +27,57 @@ const translation = {
noChat: 'Aucune conversation pour le moment',
noOutput: 'Aucune sortie',
element: {
title: 'Quelqu\'un est là ?',
content: 'Observez et annotez les interactions entre les utilisateurs finaux et les applications IA ici pour améliorer continuellement la précision de l\'IA. Vous pouvez essayer de <shareLink>partager</shareLink> ou de <testLink>tester</testLink> l\'application Web',
title: 'Y a-t-il quelqu\'un ?',
content: 'Observez et annotez ici les interactions entre les utilisateurs finaux et les applications d\'IA pour améliorer en continu la précision de l\'IA. Vous pouvez essayer de <shareLink>partager</shareLink> ou de <testLink>tester</testLink> l\'application Web vous-même, puis revenir sur cette page.',
},
},
},
detail: {
time: 'Temps',
time: 'Heure',
conversationId: 'ID de conversation',
promptTemplate: 'Modèle de Prompt',
promptTemplateBeforeChat: 'Modèle de Prompt Avant le Chat · En Tant que Message Système',
annotationTip: 'Améliorations Marquées par {{user}}',
timeConsuming: 'Apologies, but you haven\'t provided any text to translate. Could you please provide the text so I can help you with the translation?',
second: '"s"',
promptTemplate: 'Modèle de consigne',
promptTemplateBeforeChat: 'Modèle de consigne avant la conversation · En tant que message système',
annotationTip: 'Améliorations marquées par {{user}}',
timeConsuming: '',
second: 's',
tokenCost: 'Jeton dépensé',
loading: 'chargement',
operation: {
like: 'comme',
dislike: 'déteste',
like: 'j\'aime',
dislike: 'je n\'aime pas',
addAnnotation: 'Ajouter une amélioration',
editAnnotation: 'Amélioration de l\'édition',
annotationPlaceholder: 'Entrez la réponse attendue que vous souhaitez que l\'IA donne, qui peut être utilisée pour l\'ajustement fin du modèle et l\'amélioration continue de la qualité de génération de texte à l\'avenir.',
editAnnotation: 'Modifier une amélioration',
annotationPlaceholder: 'Entrez la réponse attendue que vous souhaitez que l\'IA donne, cela peut être utilisé pour le réglage fin du modèle et l\'amélioration continue de la qualité de génération de texte à l\'avenir.',
},
variables: 'Variables',
uploadImages: 'Images Téléchargées',
uploadImages: 'Images téléchargées',
},
filter: {
period: {
today: 'Aujourd\'hui',
last7days: 'Les 7 Derniers Jours',
last4weeks: 'Les 4 dernières semaines',
last3months: 'Les 3 derniers mois',
last12months: 'Les 12 derniers mois',
last7days: '7 derniers jours',
last4weeks: '4 dernières semaines',
last3months: '3 derniers mois',
last12months: '12 derniers mois',
monthToDate: 'Mois à ce jour',
quarterToDate: 'Trimestre à ce jour',
yearToDate: 'Année à ce jour',
allTime: 'Tout le temps',
},
annotation: {
all: 'Tout',
all: 'Tous',
annotated: 'Améliorations annotées ({{count}} éléments)',
not_annotated: 'Non Annoté',
not_annotated: 'Non annoté',
},
},
workflowTitle: 'Journaux de workflow',
workflowSubtitle: 'Le journal enregistre l\'opération d\'Automate.',
runDetail: {
title: 'Journal de conversation',
workflowTitle: 'Détail du journal',
},
promptLog: 'Journal de consigne',
viewLog: 'Voir le journal',
}
export default translation

View File

@@ -1,18 +1,18 @@
const translation = {
welcome: {
firstStepTip: 'Pour commencer,',
enterKeyTip: 'entrez votre clé API OpenAI ci-dessous',
enterKeyTip: 'saisissez votre clé API OpenAI ci-dessous',
getKeyTip: 'Obtenez votre clé API depuis le tableau de bord OpenAI',
placeholder: 'Votre clé API OpenAI (par exemple, sk-xxxx)',
placeholder: 'Votre clé API OpenAI (ex. sk-xxxx)',
},
apiKeyInfo: {
cloud: {
trial: {
title: 'Vous utilisez le quota d\'essai de {{providerName}}.',
description: 'Le quota d\'essai est fourni pour votre utilisation de test. Avant que les appels de quota d\'essai ne soient épuisés, veuillez configurer votre propre fournisseur de modèle ou acheter un quota supplémentaire.',
description: 'Le quota d\'essai est fourni pour votre usage de test. Avant l\'épuisement des appels de quota d\'essai, veuillez configurer votre propre fournisseur de modèle ou acheter un quota supplémentaire.',
},
exhausted: {
title: 'Votre quota d\'essai a été utilisé, veuillez configurer votre APIKey.',
title: 'Votre quota d\'essai a été utilisé, veuillez configurer votre clé API.',
description: 'Votre quota d\'essai a été épuisé. Veuillez configurer votre propre fournisseur de modèle ou acheter un quota supplémentaire.',
},
},
@@ -22,78 +22,78 @@ const translation = {
row2: 'configurez d\'abord votre fournisseur de modèle.',
},
},
callTimes: 'Heures d\'appel',
usedToken: 'Token utilisé',
setAPIBtn: 'Allez configurer le fournisseur de modèle',
callTimes: 'Appels',
usedToken: 'Token utilisés',
setAPIBtn: 'Aller à la configuration du fournisseur de modèle',
tryCloud: 'Ou essayez la version cloud de Dify avec un devis gratuit',
},
overview: {
title: 'Aperçu',
appInfo: {
explanation: 'WebApp IA prête à l\'emploi',
explanation: 'WebApp AI prête à l\'emploi',
accessibleAddress: 'URL publique',
preview: 'Aperçu',
regenerate: 'Régénérer',
regenerate: 'Regénérer',
preUseReminder: 'Veuillez activer WebApp avant de continuer.',
settings: {
entry: 'Paramètres',
title: 'Paramètres de l\'application Web',
webName: 'Nom de l\'application Web',
webDesc: 'Description de l\'application web',
webDescTip: 'Ce texte sera affiché du côté du client, fournissant des indications de base sur comment utiliser l\'application',
webDescPlaceholder: 'Entrez la description de la WebApp',
webDesc: 'Description de l\'application Web',
webDescTip: 'Ce texte sera affiché côté client, fournissant des directives de base sur la façon d\'utiliser l\'application',
webDescPlaceholder: 'Entrez la description de l\'application Web',
language: 'Langue',
more: {
entry: 'Montrer plus de paramètres',
copyright: 'Droit d\'auteur',
entry: 'Afficher plus de paramètres',
copyright: 'Droits d\'auteur',
copyRightPlaceholder: 'Entrez le nom de l\'auteur ou de l\'organisation',
privacyPolicy: 'Politique de Confidentialité',
privacyPolicy: 'Politique de confidentialité',
privacyPolicyPlaceholder: 'Entrez le lien de la politique de confidentialité',
privacyPolicyTip: 'Aide les visiteurs à comprendre les données que l\'application collecte, voir la <privacyPolicyLink>Politique de Confidentialité</privacyPolicyLink> de Dify.',
privacyPolicyTip: 'Aide les visiteurs à comprendre les données collectées par l\'application, voir la <privacyPolicyLink>Politique de confidentialité</privacyPolicyLink> de Dify.',
},
},
embedded: {
entry: 'Intégré',
title: 'Intégrer sur le site web',
explanation: 'Choisissez la manière d\'intégrer l\'application de chat à votre site web',
iframe: 'Pour ajouter l\'application de chat n\'importe où sur votre site web, ajoutez cette iframe à votre code html.',
scripts: 'Pour ajouter une application de chat en bas à droite de votre site web, ajoutez ce code à votre html.',
chromePlugin: 'Installez l\'extension Chrome Dify Chatbot',
title: 'Intégrer sur un site Web',
explanation: 'Choisissez la manière d\'intégrer l\'application de chat à votre site Web',
iframe: 'Pour ajouter l\'application de chat n\'importe où sur votre site Web, ajoutez cette iframe à votre code HTML.',
scripts: 'Pour ajouter une application de chat en bas à droite de votre site Web, ajoutez ce code à votre HTML.',
chromePlugin: 'Installer l\'extension Chrome Dify Chatbot',
copied: 'Copié',
copy: 'Copier',
},
qrcode: {
title: 'QR code à partager',
scan: 'Application de Partage de Scan',
download: 'Télécharger le Code QR',
scan: 'Scanner et partager l\'application',
download: 'Télécharger le code QR',
},
customize: {
way: 'manière',
way: 'façon',
entry: 'Personnaliser',
title: 'Personnaliser l\'WebApp IA',
explanation: 'Vous pouvez personnaliser l\'interface utilisateur de l\'application Web pour répondre à vos besoins en termes de scénario et de style.',
title: 'Personnaliser l\'application Web AI',
explanation: 'Vous pouvez personnaliser l\'interface utilisateur de l\'application Web pour répondre à vos besoins de scénario et de style.',
way1: {
name: 'Faites une fourchette du code client, modifiez-le et déployez-le sur Vercel (recommandé)',
step1: 'Faites une fourchette du code client et modifiez-le',
step1Tip: 'Cliquez ici pour bifurquer le code source dans votre compte GitHub et modifier le code',
step1Operation: 'Dify-WebClient',
name: 'Faire une copie du code client, le modifier et le déployer sur Vercel (recommandé)',
step1: 'Faire une copie du code client et le modifier',
step1Tip: 'Cliquez ici pour faire une copie du code source dans votre compte GitHub et le modifier',
step1Operation: 'Client-Web-Dify',
step2: 'Déployer sur Vercel',
step2Tip: 'Cliquez ici pour importer le dépôt dans Vercel et déployer',
step2Tip: 'Cliquez ici pour importer le dépôt dans Vercel et le déployer',
step2Operation: 'Importer le dépôt',
step3: 'Configurer les variables d\'environnement',
step3Tip: 'Ajoutez les variables d\'environnement suivantes dans Vercel',
},
way2: {
name: 'Écrivez du code côté client pour appeler l\'API et déployez-le sur un serveur',
name: 'Écrire du code côté client pour appeler l\'API et le déployer sur un serveur',
operation: 'Documentation',
},
},
},
apiInfo: {
title: 'API du service Backend',
title: 'API de service Backend',
explanation: 'Facilement intégré dans votre application',
accessibleAddress: 'Point de terminaison du service API',
doc: 'Référence API',
doc: 'Référence de l\'API',
},
status: {
running: 'En service',
@@ -103,35 +103,39 @@ const translation = {
analysis: {
title: 'Analyse',
ms: 'ms',
tokenPS: 'Jeton/s',
tokenPS: 'Token/s',
totalMessages: {
title: 'Messages Totaux',
explanation: 'Nombre quotidien d\'interactions IA ; ingénierie/debuggage de prompt exclu.',
title: 'Total des messages',
explanation: 'Nombre d\'interactions quotidiennes avec l\'IA ; l\'ingénierie/le débogage des prompts sont exclus.',
},
activeUsers: {
title: 'Utilisateurs Actifs',
explanation: 'Utilisateurs uniques participant à des Q&A avec l\'IA ; l\'ingénierie/débogage de prompt exclu.',
title: 'Utilisateurs actifs',
explanation: 'Utilisateurs uniques engagés dans des Q&R avec l\'IA ; l\'ingénierie/le débogage des prompts sont exclus.',
},
tokenUsage: {
title: 'Utilisation de Token',
explanation: 'Reflet de l\'utilisation quotidienne des jetons du modèle de langage pour l\'application, utile à des fins de contrôle des coûts.',
title: 'Utilisation des tokens',
explanation: 'Reflet de l\'utilisation quotidienne des tokens du modèle de langue pour l\'application, utile pour le contrôle des coûts.',
consumed: 'Consommé',
},
avgSessionInteractions: {
title: 'Interactions Moyennes par Session',
explanation: 'Comptage continu de la communication utilisateur-IA ; pour les applications basées sur la conversation.',
title: 'Interactions moyennes par session',
explanation: 'Nombre de communications continu utilisateur-IA ; pour les applications basées sur la conversation.',
},
avgUserInteractions: {
title: 'Interactions moyennes par utilisateur',
explanation: 'Reflet de la fréquence d\'utilisation quotidienne des utilisateurs. Cette métrique reflète la fidélité des utilisateurs.',
},
userSatisfactionRate: {
title: 'Taux de Satisfaction de l\'Utilisateur',
explanation: 'Le nombre de "j\'aime" par 1 000 messages. Cela indique la proportion de réponses dont les utilisateurs sont très satisfaits.',
title: 'Taux de satisfaction des utilisateurs',
explanation: 'Le nombre de likes parmi 1 000 messages. Cela indique la proportion de réponses avec lesquelles les utilisateurs sont très satisfaits.',
},
avgResponseTime: {
title: 'Temps de réponse moyen',
explanation: 'Temps (ms) pour que l\'IA traite/réponde; pour les applications basées sur le texte.',
explanation: 'Temps (ms) pour l\'IA pour traiter/répondre ; pour les applications basées sur du texte.',
},
tps: {
title: 'Vitesse de Sortie des Tokens',
explanation: 'Mesurez la performance du LLM. Comptez la vitesse de sortie des Tokens du LLM depuis le début de la demande jusqu\'à l\'achèvement de la sortie.',
title: 'Vitesse de sortie de token',
explanation: 'Mesurer les performances du LLM. Compter la vitesse de sortie des tokens du LLM depuis le début de la requête jusqu\'à l\'achèvement de la sortie.',
},
},
}

View File

@@ -1,38 +1,59 @@
const translation = {
createApp: 'Créer une nouvelle application',
createApp: 'CRÉER UNE APPLICATION',
types: {
all: 'Tout',
assistant: 'Assistant',
completion: 'Complétion',
chatbot: 'Chatbot',
agent: 'Agent',
workflow: 'Flux de travail',
completion: 'Terminaison',
},
modes: {
completion: 'Générateur de Texte',
chat: 'Assistant de Base',
},
createFromConfigFile: 'Créer une application à partir du fichier de configuration',
duplicate: 'Dupliquer',
duplicateTitle: 'Dupliquer l\'application',
export: 'Exporter DSL',
exportFailed: 'Échec de l\'exportation du DSL.',
importDSL: 'Importer le fichier DSL',
createFromConfigFile: 'Créer à partir du fichier DSL',
deleteAppConfirmTitle: 'Supprimer cette application ?',
deleteAppConfirmContent:
'La suppression de l\'application est irréversible. Les utilisateurs ne pourront plus accéder à votre application, et toutes les configurations de prompt et les journaux seront définitivement supprimés.',
'La suppression de l\'application est irréversible. Les utilisateurs ne pourront plus accéder à votre application et toutes les configurations de prompt et les journaux seront définitivement supprimés.',
appDeleted: 'Application supprimée',
appDeleteFailed: 'Échec de la suppression de l\'application',
join: 'Rejoignez la communauté',
join: 'Rejoindre la communauté',
communityIntro:
'Discutez avec les membres de l\'équipe, les contributeurs et les développeurs sur différents canaux.',
roadmap: 'Voir notre feuille de route',
appNamePlaceholder: 'Veuillez entrer le nom de l\'application',
newApp: {
startToCreate: 'Commençons avec votre nouvelle application',
startFromBlank: 'Créer à partir de zéro',
startFromTemplate: 'Créer à partir d\'un modèle',
captionAppType: 'Quel type d\'application souhaitez-vous créer ?',
chatbotDescription: 'Construisez une application basée sur le chat. Cette application utilise un format question-réponse, permettant ainsi plusieurs tours de conversation continue.',
completionDescription: 'Construisez une application qui génère du texte de haute qualité en fonction des invites, telles que la génération d\'articles, de résumés, de traductions, et plus encore.',
completionWarning: 'Ce type d\'application ne sera plus pris en charge.',
agentDescription: 'Construisez un agent intelligent capable de choisir automatiquement les outils pour accomplir les tâches',
workflowDescription: 'Construisez une application qui génère du texte de haute qualité en fonction d\'un flux de travail avec un haut degré de personnalisation. Il convient aux utilisateurs expérimentés.',
workflowWarning: 'Actuellement en version bêta',
chatbotType: 'Méthode d\'orchestration du chatbot',
basic: 'Basique',
basicTip: 'Pour les débutants, peut passer à Chatflow plus tard',
basicFor: 'POUR LES DÉBUTANTS',
basicDescription: 'L\'orchestration de base permet d\'orchestrer une application Chatbot à l\'aide de paramètres simples, sans possibilité de modifier les invites intégrées. Il convient aux débutants.',
advanced: 'Chatflow',
advancedFor: 'Pour les utilisateurs avancés',
advancedDescription: 'L\'orchestration de flux de travail orchestre les Chatbots sous forme de workflows, offrant un haut degré de personnalisation, y compris la possibilité de modifier les invites intégrées. Il convient aux utilisateurs expérimentés.',
captionName: 'Icône et nom de l\'application',
captionAppType: 'Quel type d\'application voulez-vous créer ?',
appNamePlaceholder: 'Donnez un nom à votre application',
captionDescription: 'Description',
appDescriptionPlaceholder: 'Entrez la description de l\'application',
useTemplate: 'Utiliser ce modèle',
previewDemo: 'Aperçu de la démo',
chatApp: 'Assistant',
chatAppIntro:
'Je veux construire une application basée sur le chat. Cette application utilise un format de questions-réponses, permettant plusieurs tours de conversation continue.',
agentAssistant: 'Nouvel Assistant Agent',
completeApp: 'Générateur de Texte',
'Je veux construire une application basée sur le chat. Cette application utilise un format question-réponse, permettant plusieurs tours de conversation continue.',
agentAssistant: 'Nouvel assistant agent',
completeApp: 'Générateur de texte',
completeAppIntro:
'Je veux créer une application qui génère du texte de haute qualité basé sur des prompts, tels que la génération d\'articles, de résumés, de traductions, et plus encore.',
showTemplates: 'Je veux choisir à partir d\'un modèle',
'Je veux créer une application qui génère du texte de haute qualité en fonction des invites, telles que la génération d\'articles, de résumés, de traductions, et plus encore.',
showTemplates: 'Je veux choisir parmi un modèle',
hideTemplates: 'Revenir à la sélection de mode',
Create: 'Créer',
Cancel: 'Annuler',
@@ -42,13 +63,28 @@ const translation = {
appCreated: 'Application créée',
appCreateFailed: 'Échec de la création de l\'application',
},
editApp: {
startToEdit: 'Modifier l\'application',
},
editApp: 'Modifier les informations',
editAppTitle: 'Modifier les informations de l\'application',
editDone: 'Informations sur l\'application mises à jour',
editFailed: 'Échec de la mise à jour des informations de l\'application',
emoji: {
ok: 'D\'accord',
ok: 'OK',
cancel: 'Annuler',
},
switch: 'Passer à l\'orchestration de flux de travail',
switchTipStart: 'Une nouvelle copie de l\'application sera créée pour vous, et la nouvelle copie passera à l\'orchestration de flux de travail. La nouvelle copie ne permettra pas le ',
switchTip: 'retour',
switchTipEnd: ' à l\'orchestration de base.',
switchLabel: 'La copie de l\'application à créer',
removeOriginal: 'Supprimer l\'application d\'origine',
switchStart: 'Commencer la commutation',
typeSelector: {
all: 'Tous Types',
chatbot: 'Chatbot',
agent: 'Agent',
workflow: 'Flux de travail',
completion: 'Terminaison',
},
}
export default translation

View File

@@ -400,6 +400,7 @@ const translation = {
promptEng: 'Orchestrer',
apiAccess: 'Accès API',
logAndAnn: 'Journaux & Annonces.',
logs: 'Journaux',
},
environment: {
testing: 'TESTER',
@@ -477,6 +478,10 @@ const translation = {
title: 'Variables & Outils Externes',
desc: 'Insérer des Variables & Outils Externes',
},
outputToolDisabledItem: {
title: 'Variables',
desc: 'Insérer Variables',
},
modal: {
add: 'Nouvelle variable',
addTool: 'Nouvel outil',

View File

@@ -18,7 +18,7 @@ const translation = {
apps: {
title: 'Explorez les applications par Dify',
description: 'Utilisez ces applications modèles instantanément ou personnalisez vos propres applications basées sur les modèles.',
allCategories: 'Toutes les catégories',
allCategories: 'Recommandé',
},
appCard: {
addToWorkspace: 'Ajouter à l\'espace de travail',

View File

@@ -1,5 +1,23 @@
const translation = {
input: 'ENTRÉE',
result: 'RÉSULTAT',
detail: 'DÉTAIL',
tracing: 'TRACE',
resultPanel: {
status: 'STATUT',
time: 'TEMPS ÉCOULÉ',
tokens: 'TOTAL DES JETONS',
},
meta: {
title: 'MÉTADONNÉES',
status: 'Statut',
version: 'Version',
executor: 'Exécuteur',
startTime: 'Heure de début',
time: 'Temps écoulé',
tokens: 'Total des jetons',
steps: 'Étapes d\'exécution',
},
}
export default translation

View File

@@ -1,3 +1,333 @@
const translation = {}
const translation = {
common: {
editing: 'Édition',
autoSaved: 'Enregistré automatiquement',
unpublished: 'Non publié',
published: 'Publié',
publish: 'Publier',
update: 'Mettre à jour',
run: 'Exécuter',
running: 'En cours',
inRunMode: 'En mode exécution',
inPreview: 'En prévisualisation',
inPreviewMode: 'En mode prévisualisation',
preview: 'Aperçu',
viewRunHistory: 'Voir l\'historique d\'exécution',
runHistory: 'Historique d\'exécution',
goBackToEdit: 'Retourner à l\'éditeur',
conversationLog: 'Journal de conversation',
features: 'Fonctionnalités',
debugAndPreview: 'Déboguer et prévisualiser',
restart: 'Redémarrer',
currentDraft: 'Brouillon actuel',
currentDraftUnpublished: 'Brouillon actuel non publié',
latestPublished: 'Dernière publication',
publishedAt: 'Publié',
restore: 'Restaurer',
runApp: 'Exécuter l\'application',
batchRunApp: 'Exécuter l\'application en lot',
accessAPIReference: 'Accéder à la référence de l\'API',
embedIntoSite: 'Intégrer dans le site',
addTitle: 'Ajouter un titre...',
addDescription: 'Ajouter une description...',
noVar: 'Aucune variable',
searchVar: 'Rechercher une variable',
variableNamePlaceholder: 'Nom de la variable',
setVarValuePlaceholder: 'Définir la variable',
needConnecttip: 'Cette étape n\'est connectée à rien',
maxTreeDepth: 'Limite maximale de {{depth}} nœuds par branche',
needEndNode: 'Le bloc de fin doit être ajouté',
needAnswerNode: 'Le bloc de réponse doit être ajouté',
workflowProcess: 'Processus de workflow',
notRunning: 'Pas encore en cours d\'exécution',
previewPlaceholder: 'Saisissez du contenu dans la zone ci-dessous pour commencer le débogage du Chatbot',
effectVarConfirm: {
title: 'Supprimer la variable',
content: 'La variable est utilisée dans d\'autres nœuds. Voulez-vous toujours la supprimer ?',
},
insertVarTip: 'Appuyez sur la touche \'/\' pour insérer rapidement',
},
errorMsg: {
fieldRequired: '{{field}} est requis',
authRequired: 'L\'autorisation est requise',
invalidJson: '{{field}} est un JSON invalide',
fields: {
variable: 'Nom de la variable',
variableValue: 'Valeur de la variable',
code: 'Code',
model: 'Modèle',
rerankModel: 'Modèle de retrait',
},
invalidVariable: 'Variable invalide',
},
singleRun: {
testRun: 'Exécution de test ',
startRun: 'Démarrer l\'exécution',
running: 'En cours',
},
tabs: {
'searchBlock': 'Rechercher un bloc',
'blocks': 'Blocs',
'builtInTool': 'Outil intégré',
'customTool': 'Outil personnalisé',
'question-understand': 'Compréhension des questions',
'logic': 'Logique',
'transform': 'Transformer',
'utilities': 'Utilitaires',
'noResult': 'Aucune correspondance trouvée',
},
blocks: {
'start': 'Démarrer',
'end': 'Fin',
'answer': 'Réponse',
'llm': 'LLM',
'knowledge-retrieval': 'Récupération de connaissances',
'question-classifier': 'Classificateur de questions',
'if-else': 'SI/SINON',
'code': 'Code',
'template-transform': 'Modèle',
'http-request': 'Requête HTTP',
'variable-assigner': 'Assignateur de variables',
},
blocksAbout: {
'start': 'Définir les paramètres initiaux pour lancer un flux de travail',
'end': 'Définir la fin et le type de résultat d\'un flux de travail',
'answer': 'Définir le contenu de réponse d\'une conversation',
'llm': 'Appeler de grands modèles de langage pour répondre aux questions ou traiter le langage naturel',
'knowledge-retrieval': 'Vous permet de interroger le contenu textuel lié aux questions des utilisateurs à partir des connaissances',
'question-classifier': 'Définir les conditions de classification des questions des utilisateurs, LLM peut définir comment la conversation progresse en fonction de la description de la classification',
'if-else': 'Vous permet de diviser le flux de travail en deux branches en fonction de conditions SI/SINON',
'code': 'Exécuter un morceau de code Python ou NodeJS pour implémenter une logique personnalisée',
'template-transform': 'Convertir des données en chaîne à l\'aide de la syntaxe du modèle Jinja',
'http-request': 'Permet d\'envoyer des requêtes serveur via le protocole HTTP',
'variable-assigner': 'Attribuer des variables dans différentes branches à la même variable pour obtenir une configuration unifiée des post-nœuds',
},
operator: {
zoomIn: 'Zoomer',
zoomOut: 'Dézoomer',
zoomTo50: 'Zoom à 50%',
zoomTo100: 'Zoom à 100%',
zoomToFit: 'Ajuster à la fenêtre',
},
panel: {
userInputField: 'Champ de saisie utilisateur',
changeBlock: 'Changer de bloc',
helpLink: 'Lien d\'aide',
about: 'À propos',
createdBy: 'Créé par ',
nextStep: 'Étape suivante',
addNextStep: 'Ajouter le prochain bloc dans ce flux de travail',
selectNextStep: 'Sélectionner le bloc suivant',
runThisStep: 'Exécuter cette étape',
checklist: 'Liste de contrôle',
checklistTip: 'Assurez-vous que tous les problèmes sont résolus avant de publier',
checklistResolved: 'Tous les problèmes sont résolus',
organizeBlocks: 'Organiser les blocs',
change: 'Changer',
},
nodes: {
common: {
outputVars: 'Variables de sortie',
insertVarTip: 'Insérer une variable',
memory: {
memory: 'Mémoire',
memoryTip: 'Paramètres de mémoire de chat',
windowSize: 'Taille de la fenêtre',
conversationRoleName: 'Nom du rôle de conversation',
user: 'Préfixe utilisateur',
assistant: 'Préfixe assistant',
},
memories: {
title: 'Mémoires',
tip: 'Mémoire de chat',
builtIn: 'Intégré',
},
},
start: {
required: 'requis',
inputField: 'Champ d\'entrée',
builtInVar: 'Variables intégrées',
outputVars: {
query: 'Entrée utilisateur',
memories: {
des: 'Historique de conversation',
type: 'type de message',
content: 'contenu du message',
},
files: 'Liste de fichiers',
},
noVarTip: 'Définissez les entrées pouvant être utilisées dans le flux de travail',
},
end: {
outputs: 'Sorties',
output: {
type: 'type de sortie',
variable: 'variable de sortie',
},
type: {
'none': 'Aucun',
'plain-text': 'Texte brut',
'structured': 'Structuré',
},
},
answer: {
answer: 'Réponse',
outputVars: 'Variables de sortie',
},
llm: {
model: 'modèle',
variables: 'variables',
context: 'contexte',
contextTooltip: 'Vous pouvez importer des connaissances comme contexte',
notSetContextInPromptTip: 'Pour activer la fonction de contexte, veuillez remplir la variable de contexte dans PROMPT.',
prompt: 'invite',
roleDescription: {
system: 'Donnez des instructions générales pour la conversation',
user: 'Fournir des instructions, des requêtes ou toute entrée basée sur du texte au modèle',
assistant: 'Les réponses du modèle basées sur les messages de l\'utilisateur',
},
addMessage: 'Ajouter un message',
vision: 'vision',
files: 'Fichiers',
resolution: {
name: 'Résolution',
high: 'Élevée',
low: 'Faible',
},
outputVars: {
output: 'Générer du contenu',
usage: 'Informations sur l\'utilisation du modèle',
},
singleRun: {
variable: 'Variable',
},
},
knowledgeRetrieval: {
queryVariable: 'Variable de requête',
knowledge: 'Connaissances',
outputVars: {
output: 'Données segmentées de récupération',
content: 'Contenu segmenté',
title: 'Titre segmenté',
icon: 'Icône segmentée',
url: 'URL segmentée',
metadata: 'Autres métadonnées',
},
},
http: {
inputVars: 'Variables d\'entrée',
api: 'API',
apiPlaceholder: 'Saisissez l\'URL, tapez / pour insérer une variable',
notStartWithHttp: 'L\'API doit commencer par http:// ou https://',
key: 'Clé',
value: 'Valeur',
bulkEdit: 'Édition en masse',
keyValueEdit: 'Édition clé-valeur',
headers: 'En-têtes',
params: 'Paramètres',
body: 'Corps',
outputVars: {
body: 'Contenu de la réponse',
statusCode: 'Code d\'état de la réponse',
headers: 'Liste d\'en-têtes de réponse JSON',
files: 'Liste de fichiers',
},
authorization: {
'authorization': 'Autorisation',
'authorizationType': 'Type d\'autorisation',
'no-auth': 'Aucune',
'api-key': 'Clé API',
'auth-type': 'Type d\'authentification',
'basic': 'De base',
'bearer': 'Porteur',
'custom': 'Personnalisé',
'api-key-title': 'Clé API',
'header': 'En-tête',
},
insertVarPlaceholder: 'tapez \'/\' pour insérer une variable',
},
code: {
inputVars: 'Variables d\'entrée',
outputVars: 'Variables de sortie',
},
templateTransform: {
inputVars: 'Variables d\'entrée',
code: 'Code',
codeSupportTip: 'Ne prend en charge que Jinja2',
outputVars: {
output: 'Contenu transformé',
},
},
ifElse: {
if: 'Si',
else: 'Sinon',
elseDescription: 'Utilisé pour définir la logique qui doit être exécutée lorsque la condition SI n\'est pas remplie.',
and: 'et',
or: 'ou',
operator: 'Opérateur',
notSetVariable: 'Veuillez d\'abord définir la variable',
comparisonOperator: {
'contains': 'contient',
'not contains': 'ne contient pas',
'start with': 'commence par',
'end with': 'se termine par',
'is': 'est',
'is not': 'n\'est pas',
'empty': 'est vide',
'not empty': 'n\'est pas vide',
'null': 'est nul',
'not null': 'n\'est pas nul',
},
enterValue: 'Entrer une valeur',
addCondition: 'Ajouter une condition',
conditionNotSetup: 'Condition NON configurée',
},
variableAssigner: {
title: 'Attribuer des variables',
outputType: 'Type de sortie',
outputVarType: 'Type de variable de sortie',
varNotSet: 'Variable non définie',
noVarTip: 'Ajoutez les variables à attribuer',
type: {
string: 'Chaîne',
number: 'Nombre',
object: 'Objet',
array: 'Tableau',
},
outputVars: {
output: 'Valeur de la variable attribuée',
},
},
tool: {
toAuthorize: 'Pour autoriser',
inputVars: 'Variables d\'entrée',
outputVars: {
text: 'contenu généré par l\'outil',
files: {
title: 'fichiers générés par l\'outil',
type: 'Type de support. Actuellement, seul le support de l\'image est pris en charge',
transfer_method: 'Méthode de transfert. La valeur est remote_url ou local_file',
url: 'URL de l\'image',
upload_file_id: 'ID du fichier téléchargé',
},
},
},
questionClassifiers: {
model: 'modèle',
inputVars: 'Variables d\'entrée',
class: 'Classe',
classNamePlaceholder: 'Écrivez votre nom de classe',
advancedSetting: 'Paramètre avancé',
topicName: 'Nom du sujet',
topicPlaceholder: 'Écrivez votre nom de sujet',
addClass: 'Ajouter une classe',
instruction: 'Instruction',
instructionPlaceholder: 'Écrivez votre instruction',
},
},
tracing: {
stopBy: 'Arrêté par {{user}}',
},
}
export default translation

View File

@@ -263,20 +263,33 @@ const translation = {
queryNoBeEmpty: 'プロンプトにクエリを設定する必要があります',
},
variableConig: {
modalTitle: 'フィールド設定',
description: '変数 {{varName}} の設定',
fieldType: 'フィールドタイプ',
string: '短いテキスト',
paragraph: '段落',
select: '選択',
notSet: '未設定、接頭辞プロンプトに {{input}} を入力してみてください',
stringTitle: 'フォームテキストボックスのオプション',
maxLength: '最大長',
options: 'オプション',
addOption: 'オプションを追加',
apiBasedVar: 'APIベースの変数',
'addModalTitle': '入力フィールドを追加',
'editModalTitle': '入力フィールドを編集',
'description': '{{varName}} の変数設定',
'fieldType': 'フィールドタイプ',
'string': 'ショートテキスト',
'text-input': 'ショートテキスト',
'paragraph': '段落',
'select': '選択',
'number': '数値',
'notSet': '設定されていません。プレフィックスのプロンプトで {{input}} を入力してみてください。',
'stringTitle': 'フォームテキストボックスオプション',
'maxLength': '最大長',
'options': 'オプション',
'addOption': 'オプションを追加',
'apiBasedVar': 'APIベースの変数',
'varName': '変数名',
'labelName': 'ラベル名',
'inputPlaceholder': '入力してください',
'required': '必須',
'errorMsg': {
varNameRequired: '変数名は必須です',
labelNameRequired: 'ラベル名は必須です',
varNameCanBeRepeat: '変数名は繰り返すことができません',
atLeastOneOption: '少なくとも1つのオプションが必要です',
optionRepeat: '繰り返しオプションがあります',
},
},
vision: {
name: 'ビジョン',
description: 'ビジョンを有効にすると、モデルが画像を受け取り、それに関する質問に答えることができます。',
@@ -346,6 +359,7 @@ const translation = {
result: '出力テキスト',
datasetConfig: {
settingTitle: 'リトリーバル設定',
knowledgeTip: 'ナレッジを追加するには「+」ボタンをクリックしてください',
retrieveOneWay: {
title: 'N-to-1 リトリーバル',
description: 'ユーザーの意図とナレッジの説明に基づいて、エージェントが自律的に最適なナレッジを選択します。個々の、限られたナレッジを持つアプリケーションに最適です。',

View File

@@ -10,19 +10,25 @@ const translation = {
output: '出力',
summary: 'タイトル',
messageCount: 'メッセージ数',
userRate: 'ユーザー評価',
adminRate: 'オペレータ評価',
userRate: 'ユーザーレート',
adminRate: '操作レート',
startTime: '開始時間',
status: 'ステータス',
runtime: 'ランタイム',
tokens: 'トークン',
user: 'エンドユーザー',
version: 'バージョン',
},
pagination: {
previous: '前へ',
next: '次へ',
},
empty: {
noChat: 'まだ会話ありません',
noOutput: '出力なし',
noChat: 'まだ会話ありません',
noOutput: '出力がありません',
element: {
title: '誰かいますか?',
content: 'ここではエンドユーザーとAIアプリケーションの相互作用を観察し、注釈を付けることでAIの精度を継続的に向上させることができます。自分自身でWebアプリを<shareLink>共有</shareLink>したり<testLink>テスト</testLink>したりして、このページに戻ってください。',
content: 'ここではエンドユーザーとAIアプリケーションの相互作用を観察し、注釈を付けることでAIの精度を継続的に向上させます。Webアプリを<shareLink>共有</shareLink>または<testLink>テスト</testLink>してみて、このページに戻ってください。',
},
},
},
@@ -30,18 +36,18 @@ const translation = {
time: '時間',
conversationId: '会話ID',
promptTemplate: 'プロンプトテンプレート',
promptTemplateBeforeChat: 'チャット前のプロンプトテンプレート · システムメッセージとして',
annotationTip: '{{user}}によ改善',
promptTemplateBeforeChat: 'チャット前のプロンプトテンプレートシステムメッセージとして',
annotationTip: '{{user}} によってマークされた改善',
timeConsuming: '',
second: '秒',
tokenCost: 'トークン消費',
loading: '読み込み中',
operation: {
like: 'いいね',
dislike: 'いまいち',
dislike: 'いいね解除',
addAnnotation: '改善を追加',
editAnnotation: '改善を編集',
annotationPlaceholder: 'AIが返答することを期待する回答を入力してください。これはモデルの微調整やテキスト生成品質の継続的改善に使用されます。',
annotationPlaceholder: '将来のモデルの微調整やテキスト生成品質の継続的改善のためにAIが返信することを期待する答えを入力してください。',
},
variables: '変数',
uploadImages: 'アップロードされた画像',
@@ -53,17 +59,25 @@ const translation = {
last4weeks: '過去4週間',
last3months: '過去3ヶ月',
last12months: '過去12ヶ月',
monthToDate: '月まで',
quarterToDate: '四半期まで',
yearToDate: '年まで',
monthToDate: '月初から今日まで',
quarterToDate: '四半期初から今日まで',
yearToDate: '年初から今日まで',
allTime: 'すべての期間',
},
annotation: {
all: 'すべて',
annotated: '注釈付きの改善{{count}}件)',
annotated: '注釈付きの改善 ({{count}} アイテム)',
not_annotated: '注釈なし',
},
},
workflowTitle: 'ワークフローログ',
workflowSubtitle: 'このログは Automate の操作を記録しました。',
runDetail: {
title: '会話ログ',
workflowTitle: 'ログの詳細',
},
promptLog: 'プロンプトログ',
viewLog: 'ログを表示',
}
export default translation

View File

@@ -1,137 +1,141 @@
const translation = {
welcome: {
firstStepTip: 'めるには、',
firstStepTip: 'はじめるには、',
enterKeyTip: '以下にOpenAI APIキーを入力してください',
getKeyTip: 'OpenAIダッシュボードからAPIキーを取得してください',
placeholder: 'OpenAI APIキーsk-xxxx',
placeholder: 'あなたのOpenAI APIキーsk-xxxx',
},
apiKeyInfo: {
cloud: {
trial: {
title: '{{providerName}}トライアルクータを使用しています。',
description: 'トライアルクータはテスト用に提供されています。トライアルクータの使用回数が尽きる前に、独自のモデルプロバイダを設定するか、追加のクータを購入してください。',
title: '{{providerName}}トライアルクータを使用しています。',
description: 'トライアルクータはテスト用に提供されます。トライアルクータのコールが使い切られる前に、独自のモデルプロバイダを設定するか、追加のクータを購入してください。',
},
exhausted: {
title: 'トライアルクータが使い果たされました。APIキーを設定してください。',
description: 'トライアルクータが使い果たされました。独自のモデルプロバイダを設定するか、追加のクータを購入してください。',
title: 'トライアルクータが使いれました。APIキーを設定してください。',
description: 'トライアルクータが使いれました。独自のモデルプロバイダを設定するか、追加のクータを購入してください。',
},
},
selfHost: {
title: {
row1: 'めるには、',
row1: 'はじめるには、',
row2: 'まずモデルプロバイダを設定してください。',
},
},
callTimes: '呼び出し回数',
callTimes: 'コール回数',
usedToken: '使用済みトークン',
setAPIBtn: 'モデルプロバイダの設定へ',
tryCloud: 'または無料クオートでDifyのクラウドバージョンを試してみてください',
tryCloud: 'またはDifyのクラウドバージョンを無料見積もりでお試しください',
},
overview: {
title: '概要',
appInfo: {
explanation: '使いやすいAI Webアプリ',
explanation: '使いやすいAI WebApp',
accessibleAddress: '公開URL',
preview: 'プレビュー',
regenerate: '再生成',
preUseReminder: '続行する前にWebアプリを有効にしてください。',
preUseReminder: '続行する前にWebAppを有効にしてください。',
settings: {
entry: '設定',
title: 'Webアプリの設定',
webName: 'Webアプリ名',
webDesc: 'Webアプリの説明',
webDescTip: 'このテキストはクライアント側に表示され、アプリの使用方法に関する基本的なガイダンスを提供します。',
webDescPlaceholder: 'Webアプリの説明を入力してください',
title: 'WebApp設定',
webName: 'WebApp名',
webDesc: 'WebAppの説明',
webDescTip: 'このテキストはクライアント側に表示され、アプリケーションの使用方法基本的なガイダンスを提供します。',
webDescPlaceholder: 'WebAppの説明を入力してください',
language: '言語',
more: {
entry: '詳細設定を表示',
entry: 'その他の設定を表示',
copyright: '著作権',
copyRightPlaceholder: '著作者または組織の名前を入力してください',
copyRightPlaceholder: '著作者または組織を入力してください',
privacyPolicy: 'プライバシーポリシー',
privacyPolicyPlaceholder: 'プライバシーポリシーリンクを入力してください',
privacyPolicyTip: '訪問者がアプリが収集するデータを理解するのに役立ちます。Difyの<privacyPolicyLink>プライバシーポリシー</privacyPolicyLink>を参照してください。',
privacyPolicyTip: '訪問者がアプリケーションが収集するデータを理解し、Difyの<privacyPolicyLink>プライバシーポリシー</privacyPolicyLink>を参照できるようにします。',
},
},
embedded: {
entry: '埋め込み',
title: 'ウェブサイトに埋め込む',
explanation: 'チャットアプリをウェブサイトに埋め込む方法を選択してください。',
explanation: 'チャットアプリをウェブサイトに埋め込む方法を選択します。',
iframe: 'ウェブサイトの任意の場所にチャットアプリを追加するには、このiframeをHTMLコードに追加してください。',
scripts: 'ウェブサイトの右下にチャットアプリを追加するには、このコードをHTMLに追加してください。',
chromePlugin: 'Dify Chatbot Chrome拡張機能をインストール',
copied: 'コピー済み',
copied: 'コピーしました',
copy: 'コピー',
},
qrcode: {
title: '共有用QRコード',
scan: 'アプリを共有するためにスキャン',
scan: 'アプリケーションの共有をスキャン',
download: 'QRコードをダウンロード',
},
customize: {
way: '方法',
entry: 'カスタマイズ',
title: 'AI Webアプリのカスタマイズ',
explanation: 'シナリオとスタイルのニーズに合わせてWebアプリのフロントエンドをカスタマイズできます。',
title: 'AI WebAppのカスタマイズ',
explanation: 'シナリオとスタイルのニーズに合わせてWeb Appのフロントエンドをカスタマイズできます。',
way1: {
name: 'クライアントコードをフォークして変更し、Vercelにデプロイす(推奨)',
step1: 'クライアントコードをフォークして変更する',
step1Tip: 'ここをクリックしてソースコードをGitHubアカウントにフォークし、コードを変更してください',
name: 'クライアントコードをフォークして修正し、Vercelにデプロイします(推奨)',
step1: 'クライアントコードをフォークして修正します',
step1Tip: 'ここをクリックしてソースコードをGitHubアカウントにフォークし、コードを修正します',
step1Operation: 'Dify-WebClient',
step2: 'Vercelにデプロイす',
step2Tip: 'ここをクリックしてリポジトリをVercelにインポートし、デプロイしてください',
step2Operation: 'リポジトリインポート',
step3: '環境変数を設定す',
step3Tip: 'Vercelに以下の環境変数を追加してください',
step2: 'Vercelにデプロイします',
step2Tip: 'ここをクリックしてリポジトリをVercelにインポートし、デプロイします',
step2Operation: 'リポジトリインポート',
step3: '環境変数を設定します',
step3Tip: 'Vercelにの環境変数を追加します',
},
way2: {
name: 'APIを呼び出すためのクライアントサイドコードを記述し、サーバーにデプロイす',
name: 'クライアントサイドコードを記述してAPIを呼び出し、サーバーにデプロイします',
operation: 'ドキュメント',
},
},
},
apiInfo: {
title: 'バックエンドサービスAPI',
explanation: 'アプリケーションに簡単に統合できます',
explanation: 'あなたのアプリケーションに簡単に統合できます',
accessibleAddress: 'サービスAPIエンドポイント',
doc: 'APIリファレンス',
},
status: {
running: 'サービス中',
disable: '無効',
running: '稼働中',
disable: '無効',
},
},
analysis: {
title: '分析',
ms: 'ミリ秒',
ms: 'ms',
tokenPS: 'トークン/秒',
totalMessages: {
title: 'メッセージ数',
explanation: 'AIとのやり取りのうち、プロンプトのエンジニアリングやデバッグを除いた日次の相互作用数です。',
title: 'トータルメッセージ数',
explanation: '日次AIインタラクション数工学的/デバッグ目的のプロンプトは除外されます。',
},
activeUsers: {
title: 'アクティブユーザー',
explanation: 'AIとのQ&Aに参加しているユニークユーザー数です。プロンプトのエンジニアリングやデバッグを除きます。',
title: 'アクティブユーザー',
explanation: 'AIとのQ&Aに参加しているユニークユーザー数;工学的/デバッグ目的のプロンプトは除外されます。',
},
tokenUsage: {
title: 'トークン使用量',
explanation: 'アプリケーションの言語モデルの日次トークン使用量を反映し、コスト管理の目的に役立ちます。',
consumed: '使用済み',
explanation: 'アプリケーションの言語モデルの日次トークン使用量を反映し、コスト管理に役立ちます。',
consumed: '消費されたトークン',
},
avgSessionInteractions: {
title: '平均セッション相互作用数',
explanation: '会話ベースのアプリケーションの連続したユーザーとAIのコミュニケーション数です。',
title: '平均セッションインタラクション数',
explanation: 'ユーザーとAIの連続的なコミュニケーション数;対話型アプリケーション向け。',
},
avgUserInteractions: {
title: '平均ユーザーインタラクション数',
explanation: 'ユーザーの日次使用頻度を反映します。この指標はユーザーの定着度を反映しています。',
},
userSatisfactionRate: {
title: 'ユーザー満足率',
explanation: '1,000メッセージあたりの「いいね」の数です。これは、ユーザーが非常に満足している回答の割合を示します。',
title: 'ユーザー満足率',
explanation: '1,000件のメッセージあたりの「いいね」の数。これは、ユーザーが非常に満足している回答の割合を示します。',
},
avgResponseTime: {
title: '平均応答時間',
explanation: 'AI処理/応答にかかる時間(ミリ秒)です。テキストベースのアプリケーションに適しています。',
explanation: 'AI処理/応答る時間(ミリ秒)テキストベースのアプリケーション向け。',
},
tps: {
title: 'トークン出力速度',
explanation: 'LLMのパフォーマンスを測定します。リクエストの開始から出力の完了までのLLMのトークン出力速度をカウントします。',
explanation: 'LLMのパフォーマンスを測定します。リクエストの開始から出力の完了までのLLMのトークン出力速度を数えます。',
},
},
}

View File

@@ -1,54 +1,90 @@
const translation = {
createApp: '新しいアプリを作成する',
createApp: 'アプリを作成する',
types: {
all: 'すべて',
assistant: 'アシスタント',
completion: '補完',
all: 'て',
chatbot: 'チャットボット',
agent: 'エージェント',
workflow: 'ワークフロー',
completion: '完了',
},
modes: {
completion: 'テキスト生成',
chat: '基本アシスタント',
},
createFromConfigFile: '設定ファイルからアプリを作成する',
duplicate: '複製',
duplicateTitle: 'アプリを複製する',
export: 'DSL をエクスポート',
exportFailed: 'DSL のエクスポートに失敗しました。',
importDSL: 'DSL ファイルをインポート',
createFromConfigFile: 'DSL ファイルから作成する',
deleteAppConfirmTitle: 'このアプリを削除しますか?',
deleteAppConfirmContent:
'アプリ削除は元に戻せません。ユーザーはアプリにアクセスできなくなり、プロンプトの設定とログ永久に削除されます。',
'アプリ削除すると、元に戻すことはできません。ユーザーはもはやあなたのアプリにアクセスできず、すべてのプロンプトの設定とログ永久に削除されます。',
appDeleted: 'アプリが削除されました',
appDeleteFailed: 'アプリの削除に失敗しました',
join: 'コミュニティに参加する',
communityIntro:
'チームメンバーや貢献者、開発者とさまざまなチャンネルでディスカッションを行います。',
'さまざまなチャンネルでチームメンバーや貢献者、開発者と議論します。',
roadmap: 'ロードマップを見る',
appNamePlaceholder: 'アプリの名前を入力してください',
newApp: {
startToCreate: '新しいアプリを作成しましょう',
captionName: 'アプリアイコンと名前',
startFromBlank: 'から作成',
startFromTemplate: 'テンプレートから作成',
captionAppType: 'どのタイプのアプリを作成しますか?',
previewDemo: 'デモをプレビューする',
chatbotDescription: 'チャット形式のアプリケーションを構築します。このアプリは質問と回答の形式を使用し、複数のラウンドの継続的な会話を可能にします。',
completionDescription: 'プロンプトに基づいて高品質のテキストを生成するアプリケーションを構築します。記事、要約、翻訳などを生成します。',
completionWarning: 'この種類のアプリはもうサポートされなくなります。',
agentDescription: 'タスクを自動的に完了するためのツールを選択できるインテリジェント エージェントを構築します',
workflowDescription: '高度なカスタマイズが可能なワークフローに基づいて高品質のテキストを生成するアプリケーションを構築します。経験豊富なユーザー向けです。',
workflowWarning: '現在ベータ版です',
chatbotType: 'チャットボットのオーケストレーション方法',
basic: '基本',
basicTip: '初心者向け。後で Chatflow に切り替えることができます',
basicFor: '初心者向け',
basicDescription: '基本オーケストレートは、組み込みのプロンプトを変更する機能がなく、簡単な設定を使用してチャットボット アプリをオーケストレートします。初心者向けです。',
advanced: 'Chatflow',
advancedFor: '上級ユーザー向け',
advancedDescription: 'ワークフロー オーケストレートは、ワークフロー形式でチャットボットをオーケストレートし、組み込みのプロンプトを編集する機能を含む高度なカスタマイズを提供します。経験豊富なユーザー向けです。',
captionName: 'アプリのアイコンと名前',
appNamePlaceholder: 'アプリに名前を付ける',
captionDescription: '説明',
appDescriptionPlaceholder: 'アプリの説明を入力してください',
useTemplate: 'このテンプレートを使用する',
previewDemo: 'デモをプレビュー',
chatApp: 'アシスタント',
chatAppIntro:
'チャットベースのアプリケーションを構築したいです。このアプリは質問と回答の形式を使用し、複数のラウンドの連続した会話可能す。',
agentAssistant: '新しいエージェントアシスタント',
completeApp: 'テキスト生成',
'チャット形式のアプリケーションを構築したい。このアプリは質問と回答の形式を使用し、複数のラウンドの継続的な会話可能にします。',
agentAssistant: '新しいエージェント アシスタント',
completeApp: 'テキスト ジェネレーター',
completeAppIntro:
'プロンプトに基づいて高品質のテキストを生成するアプリケーションを作成したいです。記事、要約、翻訳など生成が可能です。',
showTemplates: 'テンプレートから選択したいです',
'プロンプトに基づいて高品質のテキストを生成するアプリケーションを作成したい。記事、要約、翻訳など生成します。',
showTemplates: 'テンプレートから選択したい',
hideTemplates: 'モード選択に戻る',
Create: '作成',
Create: '作成する',
Cancel: 'キャンセル',
nameNotEmpty: '名前は空にできません',
nameNotEmpty: '名前を入力してください',
appTemplateNotSelected: 'テンプレートを選択してください',
appTypeRequired: 'アプリのタイプを選択してください',
appTypeRequired: 'アプリの種類を選択してください',
appCreated: 'アプリが作成されました',
appCreateFailed: 'アプリの作成に失敗しました',
},
editApp: {
startToEdit: 'アプリを編集する',
},
editApp: '情報を編集する',
editAppTitle: 'アプリ情報を編集する',
editDone: 'アプリ情報が更新されました',
editFailed: 'アプリ情報の更新に失敗しました',
emoji: {
ok: 'OK',
cancel: 'キャンセル',
},
switch: 'ワークフロー オーケストレートに切り替える',
switchTipStart: '新しいアプリのコピーが作成され、新しいコピーがワークフロー オーケストレートに切り替わります。新しいコピーは ',
switchTip: '切り替えを許可しません',
switchTipEnd: ' 基本的なオーケストレートに戻ることはできません。',
switchLabel: '作成されるアプリのコピー',
removeOriginal: '元のアプリを削除する',
switchStart: '切り替えを開始する',
typeSelector: {
all: 'すべてのタイプ',
chatbot: 'チャットボット',
agent: 'エージェント',
workflow: 'ワークフロー',
completion: '完了',
},
}
export default translation

View File

@@ -351,6 +351,7 @@ const translation = {
promptEng: 'Orchestrate',
apiAccess: 'APIアクセス',
logAndAnn: 'ログ&アナウンス',
logs: 'ログ',
},
environment: {
testing: 'テスト',
@@ -428,6 +429,10 @@ const translation = {
title: '変数&外部ツール',
desc: '変数&外部ツールを挿入',
},
outputToolDisabledItem: {
title: '変数',
desc: '変数を挿入',
},
modal: {
add: '新しい変数',
addTool: '新しいツール',

View File

@@ -1,5 +1,5 @@
const translation = {
title: '探索する',
title: '探索',
sidebar: {
discovery: '探索',
chat: 'チャット',
@@ -18,7 +18,7 @@ const translation = {
apps: {
title: 'Difyによるアプリの探索',
description: 'これらのテンプレートアプリを即座に使用するか、テンプレートに基づいて独自のアプリをカスタマイズしてください。',
allCategories: 'すべてのカテゴリ',
allCategories: 'おすすめ',
},
appCard: {
addToWorkspace: 'ワークスペースに追加',

View File

@@ -1,5 +1,23 @@
const translation = {
input: '入力',
result: '結果',
detail: '詳細',
tracing: 'トレース',
resultPanel: {
status: 'ステータス',
time: '経過時間',
tokens: 'トークンの合計',
},
meta: {
title: 'メタデータ',
status: 'ステータス',
version: 'バージョン',
executor: '実行者',
startTime: '開始時間',
time: '経過時間',
tokens: 'トークンの合計',
steps: '実行ステップ',
},
}
export default translation

View File

@@ -1,5 +1,333 @@
const translation = {
common: {
editing: '編集中',
autoSaved: '自動保存済み',
unpublished: '未公開',
published: '公開済み',
publish: '公開する',
update: '更新',
run: '実行',
running: '実行中',
inRunMode: '実行モード中',
inPreview: 'プレビュー中',
inPreviewMode: 'プレビューモード中',
preview: 'プレビュー',
viewRunHistory: '実行履歴を表示',
runHistory: '実行履歴',
goBackToEdit: '編集に戻る',
conversationLog: '会話ログ',
features: '機能',
debugAndPreview: 'デバッグとプレビュー',
restart: '再起動',
currentDraft: '現在の下書き',
currentDraftUnpublished: '現在の下書き(未公開)',
latestPublished: '最新の公開済み',
publishedAt: '公開日時',
restore: '復元',
runApp: 'アプリを実行',
batchRunApp: 'バッチでアプリを実行',
accessAPIReference: 'APIリファレンスにアクセス',
embedIntoSite: 'サイトに埋め込む',
addTitle: 'タイトルを追加...',
addDescription: '説明を追加...',
noVar: '変数なし',
searchVar: '変数を検索',
variableNamePlaceholder: '変数名',
setVarValuePlaceholder: '変数を設定',
needConnecttip: 'このステップは何にも接続されていません',
maxTreeDepth: 'ブランチごとの最大制限は{{depth}}ノードです',
needEndNode: '終了ブロックを追加する必要があります',
needAnswerNode: '回答ブロックを追加する必要があります',
workflowProcess: 'ワークフロー処理',
notRunning: 'まだ実行されていません',
previewPlaceholder: 'チャットボットのデバッグを開始するには、以下のボックスにコンテンツを入力してください',
effectVarConfirm: {
title: '変数を削除',
content: '他のノードで変数が使用されています。それでも削除しますか?',
},
insertVarTip: 'クイック挿入のために\'/\'キーを押します',
},
errorMsg: {
fieldRequired: '{{field}}は必須です',
authRequired: '認証が必要です',
invalidJson: '{{field}}は無効です',
fields: {
variable: '変数名',
variableValue: '変数値',
code: 'コード',
model: 'モデル',
rerankModel: '再ランクモデル',
},
invalidVariable: '無効な変数',
},
singleRun: {
testRun: 'テスト実行',
startRun: '実行を開始',
running: '実行中',
},
tabs: {
'searchBlock': 'ブロックを検索',
'blocks': 'ブロック',
'builtInTool': '組み込みツール',
'customTool': 'カスタムツール',
'question-understand': '質問の理解',
'logic': 'ロジック',
'transform': '変換',
'utilities': 'ユーティリティ',
'noResult': '一致するものが見つかりませんでした',
},
blocks: {
'start': '開始',
'end': '終了',
'answer': '回答',
'llm': 'LLM',
'knowledge-retrieval': '知識取得',
'question-classifier': '質問分類器',
'if-else': 'IF/ELSE',
'code': 'コード',
'template-transform': 'テンプレート',
'http-request': 'HTTPリクエスト',
'variable-assigner': '変数割り当て',
},
blocksAbout: {
'start': 'ワークフローの開始に必要なパラメータを定義します',
'end': 'ワークフローの終了と結果のタイプを定義します',
'answer': 'チャット会話の応答内容を定義します',
'llm': '大規模言語モデルを呼び出して質問に回答したり、自然言語を処理したりします',
'knowledge-retrieval': 'ユーザーの質問に関連するテキストコンテンツを知識からクエリできるようにします',
'question-classifier': 'ユーザーの質問の分類条件を定義し、LLMは分類記述に基づいて会話がどのように進行するかを定義できます',
'if-else': 'IF/ELSE条件に基づいてワークフローを2つのブランチに分割できます',
'code': 'カスタムロジックを実装するためにPythonまたはNodeJSコードを実行します',
'template-transform': 'Jinjaテンプレート構文を使用してデータを文字列に変換します',
'http-request': 'HTTPプロトコル経由でサーバーリクエストを送信できます',
'variable-assigner': '異なるブランチで同じ変数に変数を割り当てて、後続のノードの一元化された構成を実現できます',
},
operator: {
zoomIn: '拡大',
zoomOut: '縮小',
zoomTo50: '50にズーム',
zoomTo100: '100にズーム',
zoomToFit: 'フィットにズーム',
},
panel: {
userInputField: 'ユーザー入力フィールド',
changeBlock: 'ブロックを変更',
helpLink: 'ヘルプリンク',
about: '情報',
createdBy: '作成者 ',
nextStep: '次のステップ',
addNextStep: 'このワークフローで次のブロックを追加',
selectNextStep: '次のブロックを選択',
runThisStep: 'このステップを実行',
checklist: 'チェックリスト',
checklistTip: '公開する前にすべての問題が解決されていることを確認してください',
checklistResolved: 'すべての問題が解決されました',
organizeBlocks: 'ブロックを整理',
change: '変更',
},
nodes: {
common: {
outputVars: '出力変数',
insertVarTip: '変数を挿入',
memory: {
memory: 'メモリ',
memoryTip: 'チャットメモリ設定',
windowSize: 'ウィンドウサイズ',
conversationRoleName: '会話ロール名',
user: 'ユーザー接頭辞',
assistant: 'アシスタント接頭辞',
},
memories: {
title: 'メモリ',
tip: 'チャットメモリ',
builtIn: '組み込み',
},
},
start: {
required: '必須',
inputField: '入力フィールド',
builtInVar: '組み込み変数',
outputVars: {
query: 'ユーザー入力',
memories: {
des: '会話履歴',
type: 'メッセージタイプ',
content: 'メッセージ内容',
},
files: 'ファイルリスト',
},
noVarTip: 'ワークフローで使用できる入力を設定します',
},
end: {
outputs: '出力',
output: {
type: '出力タイプ',
variable: '出力変数',
},
type: {
'none': 'なし',
'plain-text': 'プレーンテキスト',
'structured': '構造化',
},
},
answer: {
answer: '回答',
outputVars: '出力変数',
},
llm: {
model: 'モデル',
variables: '変数',
context: 'コンテキスト',
contextTooltip: 'コンテキストとして知識をインポートできます',
notSetContextInPromptTip: 'コンテキスト機能を有効にするには、PROMPTにコンテキスト変数を記入してください。',
prompt: 'プロンプト',
roleDescription: {
system: '会話の高レベルな命令を与えます',
user: 'モデルへの指示、クエリ、またはテキストベースの入力を提供します',
assistant: 'ユーザーメッセージに基づいてモデルの応答',
},
addMessage: 'メッセージを追加',
vision: 'ビジョン',
files: 'ファイル',
resolution: {
name: '解像度',
high: '高い',
low: '低い',
},
outputVars: {
output: 'コンテンツを生成',
usage: 'モデルの使用情報',
},
singleRun: {
variable: '変数',
},
},
knowledgeRetrieval: {
queryVariable: 'クエリ変数',
knowledge: '知識',
outputVars: {
output: '検索されたセグメント化されたデータ',
content: 'セグメント化されたコンテンツ',
title: 'セグメント化されたタイトル',
icon: 'セグメント化されたアイコン',
url: 'セグメント化されたURL',
metadata: 'その他のメタデータ',
},
},
http: {
inputVars: '入力変数',
api: 'API',
apiPlaceholder: 'URLを入力、「/」を入力して変数を挿入',
notStartWithHttp: 'APIはhttp://またはhttps://で始まる必要があります',
key: 'キー',
value: '値',
bulkEdit: '一括編集',
keyValueEdit: 'キー-値の編集',
headers: 'ヘッダー',
params: 'パラメータ',
body: 'ボディ',
outputVars: {
body: 'レスポンスコンテンツ',
statusCode: 'レスポンスステータスコード',
headers: 'レスポンスヘッダーリストJSON',
files: 'ファイルリスト',
},
authorization: {
'authorization': '認証',
'authorizationType': '認証タイプ',
'no-auth': 'なし',
'api-key': 'APIキー',
'auth-type': '認証タイプ',
'basic': '基本',
'bearer': 'Bearer',
'custom': 'カスタム',
'api-key-title': 'APIキー',
'header': 'ヘッダー',
},
insertVarPlaceholder: '変数を挿入するには\'/\'を入力してください',
},
code: {
inputVars: '入力変数',
outputVars: '出力変数',
},
templateTransform: {
inputVars: '入力変数',
code: 'コード',
codeSupportTip: 'Jinja2のみをサポートしています',
outputVars: {
output: '変換されたコンテンツ',
},
},
ifElse: {
if: 'もし',
else: 'それ以外',
elseDescription: 'IF条件が満たされない場合に実行するロジックを定義します。',
and: 'かつ',
or: 'または',
operator: '演算子',
notSetVariable: 'まず変数を設定してください',
comparisonOperator: {
'contains': '含む',
'not contains': '含まない',
'start with': 'で始まる',
'end with': 'で終わる',
'is': 'である',
'is not': 'でない',
'empty': '空',
'not empty': '空でない',
'null': 'null',
'not null': 'nullでない',
},
enterValue: '値を入力',
addCondition: '条件を追加',
conditionNotSetup: '条件が設定されていません',
},
variableAssigner: {
title: '変数を割り当てる',
outputType: '出力タイプ',
outputVarType: '出力変数のタイプ',
varNotSet: '変数が設定されていません',
noVarTip: '割り当てる変数を追加してください',
type: {
string: '文字列',
number: '数値',
object: 'オブジェクト',
array: '配列',
},
outputVars: {
output: '割り当てられた変数の値',
},
},
tool: {
toAuthorize: '承認するには',
inputVars: '入力変数',
outputVars: {
text: 'ツールが生成したコンテンツ',
files: {
title: 'ツールが生成したファイル',
type: 'サポートタイプ。現在は画像のみサポートされています',
transfer_method: '転送方法。値はremote_urlまたはlocal_fileです',
url: '画像URL',
upload_file_id: 'アップロードファイルID',
},
},
},
questionClassifiers: {
model: 'モデル',
inputVars: '入力変数',
class: 'クラス',
classNamePlaceholder: 'クラス名を入力してください',
advancedSetting: '高度な設定',
topicName: 'トピック名',
topicPlaceholder: 'トピック名を入力してください',
addClass: 'クラスを追加',
instruction: '指示',
instructionPlaceholder: '指示を入力してください',
},
},
tracing: {
stopBy: '{{user}}によって停止',
},
}
export default translation

View File

@@ -266,19 +266,32 @@ const translation = {
queryNoBeEmpty: 'A consulta deve ser definida na solicitação',
},
variableConig: {
'addModalTitle': 'Configurações do Campo',
'addModalTitle': 'Adicionar Campo de Entrada',
'editModalTitle': 'Editar Campo de Entrada',
'description': 'Configuração para a variável {{varName}}',
'fieldType': 'Tipo de Campo',
'string': 'Texto Curto',
'text-input': 'Texto Curto',
'paragraph': 'Parágrafo',
'select': 'Selecionar',
'notSet': 'Não definido, tente digitar {{input}} na solicitação',
'stringTitle': 'Opções da Caixa de Texto do Formulário',
'number': 'Número',
'notSet': 'Não definido, tente digitar {{input}} no prompt de prefixo',
'stringTitle': 'Opções da caixa de texto do formulário',
'maxLength': 'Comprimento Máximo',
'options': 'Opções',
'addOption': 'Adicionar opção',
'apiBasedVar': 'Variável Baseada em API',
'varName': 'Nome da Variável',
'labelName': 'Nome do Rótulo',
'inputPlaceholder': 'Por favor, insira',
'required': 'Obrigatório',
'errorMsg': {
varNameRequired: 'O nome da variável é obrigatório',
labelNameRequired: 'O nome do rótulo é obrigatório',
varNameCanBeRepeat: 'O nome da variável não pode ser repetido',
atLeastOneOption: 'Pelo menos uma opção é obrigatória',
optionRepeat: 'Tem opções repetidas',
},
},
vision: {
name: 'Visão',
@@ -349,6 +362,7 @@ const translation = {
result: 'Texto de Saída',
datasetConfig: {
settingTitle: 'Configurações de Recuperação',
knowledgeTip: 'Clique no botão “+” para adicionar conhecimento',
retrieveOneWay: {
title: 'Recuperação N-para-1',
description: 'Com base na intenção do usuário e nas descrições do Conhecimento, o Agente seleciona autonomamente o melhor Conhecimento para consulta. Melhor para aplicativos com Conhecimento distinto e limitado.',

View File

@@ -1,10 +1,10 @@
const translation = {
title: 'Registros',
description: 'Os registros registram o status de execução do aplicativo, incluindo as entradas do usuário e as respostas da IA.',
description: 'Os registros registram o status de execução do aplicativo, incluindo entradas do usuário e respostas do AI.',
dateTimeFormat: 'MM/DD/YYYY hh:mm A',
table: {
header: {
time: 'Tempo',
time: 'Hora',
endUser: 'Usuário Final',
input: 'Entrada',
output: 'Saída',
@@ -12,58 +12,72 @@ const translation = {
messageCount: 'Contagem de Mensagens',
userRate: 'Taxa de Usuário',
adminRate: 'Taxa de Op.',
startTime: 'HORA DE INÍCIO',
status: 'STATUS',
runtime: 'TEMPO DE EXECUÇÃO',
tokens: 'TOKENS',
user: 'USUÁRIO FINAL',
version: 'VERSÃO',
},
pagination: {
previous: 'Anterior',
next: 'Próximo',
},
empty: {
noChat: 'Nenhuma conversa ainda',
noOutput: 'Nenhuma saída',
noChat: 'Ainda não há conversas',
noOutput: 'Sem saída',
element: {
title: 'Tem alguém aí?',
content: 'Observe e anote as interações entre usuários finais e aplicativos de IA aqui para melhorar continuamente a precisão da IA. Você pode tentar <shareLink>compartilhar</shareLink> ou <testLink>testar</testLink> o aplicativo da Web você mesmo e depois voltar para esta página.',
content: 'Observe e anote as interações entre os usuários finais e os aplicativos de IA aqui para melhorar continuamente a precisão da IA. Você pode tentar <shareLink>compartilhar</shareLink> ou <testLink>testar</testLink> o aplicativo da Web você mesmo, e depois voltar para esta página.',
},
},
},
detail: {
time: 'Tempo',
time: 'Hora',
conversationId: 'ID da Conversa',
promptTemplate: 'Modelo de Prompt',
promptTemplateBeforeChat: 'Modelo de Prompt Antes da Conversa · Como Mensagem do Sistema',
promptTemplateBeforeChat: 'Modelo de Prompt Antes do Chat · Como Mensagem do Sistema',
annotationTip: 'Melhorias Marcadas por {{user}}',
timeConsuming: '',
second: 's',
tokenCost: 'Tokens gastos',
tokenCost: 'Token gasto',
loading: 'carregando',
operation: {
like: 'curtir',
dislike: 'não curtir',
addAnnotation: 'Adicionar Melhoria',
editAnnotation: 'Editar Melhoria',
annotationPlaceholder: 'Digite a resposta esperada que você deseja que a IA responda, que pode ser usada para ajustar o modelo e melhorar continuamente a qualidade da geração de texto no futuro.',
annotationPlaceholder: 'Digite a resposta esperada que você deseja que o AI responda, o que pode ser usado para ajustar o modelo e melhorar continuamente a qualidade da geração de texto no futuro.',
},
variables: 'Variáveis',
uploadImages: 'Imagens Enviadas',
uploadImages: 'Imagens Carregadas',
},
filter: {
period: {
today: 'Hoje',
last7days: 'Últimos 7 Dias',
last7days: 'Últimos 7 dias',
last4weeks: 'Últimas 4 semanas',
last3months: 'Últimos 3 meses',
last12months: 'Últimos 12 meses',
monthToDate: 'Mês até a data',
quarterToDate: 'Trimestre até a data',
yearToDate: 'Ano até a data',
monthToDate: 'Mês até hoje',
quarterToDate: 'Trimestre até hoje',
yearToDate: 'Ano até hoje',
allTime: 'Todo o tempo',
},
annotation: {
all: 'Todos',
all: 'Tudo',
annotated: 'Melhorias Anotadas ({{count}} itens)',
not_annotated: 'Não Anotadas',
not_annotated: 'Não Anotado',
},
},
workflowTitle: 'Registros de Fluxo de Trabalho',
workflowSubtitle: 'O registro registrou a operação do Automate.',
runDetail: {
title: 'Registro de Conversa',
workflowTitle: 'Detalhes do Registro',
},
promptLog: 'Registro de Prompt',
viewLog: 'Ver Registro',
}
export default translation

View File

@@ -1,47 +1,47 @@
const translation = {
welcome: {
firstStepTip: 'Para começar,',
enterKeyTip: 'insira sua chave de API do OpenAI abaixo',
getKeyTip: 'Obtenha sua chave de API no painel do OpenAI',
placeholder: 'Sua chave de API do OpenAI (por exemplo, sk-xxxx)',
enterKeyTip: 'insira sua chave de API OpenAI abaixo',
getKeyTip: 'Obtenha sua chave de API no painel da OpenAI',
placeholder: 'Sua chave de API OpenAI (ex. sk-xxxx)',
},
apiKeyInfo: {
cloud: {
trial: {
title: 'Você está usando a cota de teste do {{providerName}}.',
description: 'A cota de teste é fornecida para uso de teste. Antes que as chamadas da cota de teste se esgotem, configure seu próprio provedor de modelo ou compre uma cota adicional.',
title: 'Você está usando a cota de teste da {{providerName}}.',
description: 'A cota de teste é fornecida para seu uso de teste. Antes que as chamadas de cota de teste se esgotem, configure seu próprio provedor de modelo ou compre cota adicional.',
},
exhausted: {
title: 'Sua cota de teste foi esgotada, configure sua chave de API.',
description: 'Sua cota de teste foi esgotada. Configure seu próprio provedor de modelo ou compre uma cota adicional.',
title: 'Sua cota de teste foi usada, configure sua chave de API.',
description: 'Sua cota de teste foi esgotada. Configure seu próprio provedor de modelo ou compre cota adicional.',
},
},
selfHost: {
title: {
row1: 'Para começar,',
row2: 'configure seu próprio provedor de modelo primeiro.',
row2: 'configure primeiro seu provedor de modelo.',
},
},
callTimes: 'Número de chamadas',
usedToken: 'Tokens usados',
setAPIBtn: 'Ir para configurar provedor de modelo',
setAPIBtn: 'Ir para configurar o provedor de modelo',
tryCloud: 'Ou experimente a versão em nuvem do Dify com cota gratuita',
},
overview: {
title: 'Visão geral',
title: 'Visão Geral',
appInfo: {
explanation: 'Aplicativo Web de IA pronto para uso',
accessibleAddress: 'URL pública',
explanation: 'WebApp de IA Pronta para Uso',
accessibleAddress: 'URL Pública',
preview: 'Visualização',
regenerate: 'Regenerar',
preUseReminder: 'Ative o aplicativo da Web antes de continuar.',
preUseReminder: 'Por favor, ative o WebApp antes de continuar.',
settings: {
entry: 'Configurações',
title: 'Configurações do aplicativo da Web',
webName: 'Nome do aplicativo da Web',
webDesc: 'Descrição do aplicativo da Web',
title: 'Configurações do WebApp',
webName: 'Nome do WebApp',
webDesc: 'Descrição do WebApp',
webDescTip: 'Este texto será exibido no lado do cliente, fornecendo orientações básicas sobre como usar o aplicativo',
webDescPlaceholder: 'Insira a descrição do aplicativo da Web',
webDescPlaceholder: 'Insira a descrição do WebApp',
language: 'Idioma',
more: {
entry: 'Mostrar mais configurações',
@@ -49,55 +49,55 @@ const translation = {
copyRightPlaceholder: 'Insira o nome do autor ou organização',
privacyPolicy: 'Política de Privacidade',
privacyPolicyPlaceholder: 'Insira o link da política de privacidade',
privacyPolicyTip: 'Ajuda os visitantes a entender os dados que o aplicativo coleta, consulte a <privacyPolicyLink>Política de Privacidade</privacyPolicyLink> do Dify.',
privacyPolicyTip: 'Ajuda os visitantes a entender os dados coletados pelo aplicativo, consulte a <privacyPolicyLink>Política de Privacidade</privacyPolicyLink> do Dify.',
},
},
embedded: {
entry: 'Embutido',
title: 'Incorporar no site',
explanation: 'Escolha a maneira de incorporar o aplicativo de bate-papo ao seu site',
iframe: 'Para adicionar o aplicativo de bate-papo em qualquer lugar do seu site, adicione este iframe ao seu código HTML.',
scripts: 'Para adicionar um aplicativo de bate-papo na parte inferior direita do seu site, adicione este código ao seu HTML.',
chromePlugin: 'Instalar Extensão do Chatbot Dify para o Chrome',
explanation: 'Escolha a maneira de incorporar o aplicativo de chat ao seu site',
iframe: 'Para adicionar o aplicativo de chat em qualquer lugar do seu site, adicione este iframe ao seu código HTML.',
scripts: 'Para adicionar um aplicativo de chat no canto inferior direito do seu site, adicione este código ao seu HTML.',
chromePlugin: 'Instalar a Extensão do Chrome Dify Chatbot',
copied: 'Copiado',
copy: 'Copiar',
},
qrcode: {
title: 'Código QR para compartilhar',
scan: 'Digitalizar para compartilhar o aplicativo',
scan: 'Digitalizar e compartilhar o aplicativo',
download: 'Baixar código QR',
},
customize: {
way: 'maneira',
way: 'modo',
entry: 'Personalizar',
title: 'Personalizar aplicativo Web de IA',
explanation: 'Você pode personalizar a interface do usuário do aplicativo Web para se adequar ao seu cenário e necessidades de estilo.',
title: 'Personalizar WebApp de IA',
explanation: 'Você pode personalizar a interface do usuário do Web App para atender às suas necessidades de cenário e estilo.',
way1: {
name: 'Fork do código do cliente, modifique-o e implante no Vercel (recomendado)',
step1: 'Fork do código do cliente e modifique-o',
step1Tip: 'Clique aqui para fazer um fork do código-fonte em sua conta do GitHub e modificar o código',
step1Operation: 'Dify-WebClient',
name: 'Faça um fork do código do cliente, modifique-o e implante-o no Vercel (recomendado)',
step1: 'Faça um fork do código do cliente e modifique-o',
step1Tip: 'Clique aqui para fazer um fork do código-fonte na sua conta GitHub e modificar o código',
step1Operation: 'Cliente-Web-Dify',
step2: 'Implantar no Vercel',
step2Tip: 'Clique aqui para importar o repositório no Vercel e implantar',
step2Operation: 'Importar repositório',
step3: 'Configurar variáveis de ambiente',
step3: 'Configurar as variáveis de ambiente',
step3Tip: 'Adicione as seguintes variáveis de ambiente no Vercel',
},
way2: {
name: 'Escrever código do lado do cliente para chamar a API e implantá-lo em um servidor',
name: 'Escreva código do lado do cliente para chamar a API e implante-o em um servidor',
operation: 'Documentação',
},
},
},
apiInfo: {
title: 'API do serviço de backend',
explanation: 'Integração fácil em seu aplicativo',
accessibleAddress: 'Endpoint da API de serviço',
title: 'API de Serviço de Back-end',
explanation: 'Facilmente integrado em sua aplicação',
accessibleAddress: 'Endpoint do Serviço API',
doc: 'Referência da API',
},
status: {
running: 'Em serviço',
disable: 'Desativar',
disable: 'Desabilitar',
},
},
analysis: {
@@ -105,32 +105,36 @@ const translation = {
ms: 'ms',
tokenPS: 'Token/s',
totalMessages: {
title: 'Total de mensagens',
explanation: 'Contagem diária de interações de IA; engenharia de prompt/depuração excluída.',
title: 'Total de Mensagens',
explanation: 'Contagem diária de interações AI; engenharia/de depuração excluída.',
},
activeUsers: {
title: 'Usuários ativos',
explanation: 'Usuários únicos envolvidos em perguntas e respostas com IA; engenharia de prompt/depuração excluída.',
title: 'Usuários Ativos',
explanation: 'Usuários únicos engajando em Q&A com AI; engenharia/de depuração excluída.',
},
tokenUsage: {
title: 'Uso de tokens',
explanation: 'Reflete o uso diário de tokens do modelo de linguagem para o aplicativo, útil para fins de controle de custos.',
consumed: 'Consumidos',
title: 'Uso de Token',
explanation: 'Reflete o uso diário do token do modelo de linguagem para o aplicativo, útil para fins de controle de custos.',
consumed: 'Consumido',
},
avgSessionInteractions: {
title: 'Média de interações por sessão',
explanation: 'Contagem contínua de comunicação usuário-IA; para aplicativos baseados em conversas.',
title: 'Média de Interações por Sessão',
explanation: 'Contagem de comunicação contínua entre usuário e AI; para aplicativos baseados em conversação.',
},
avgUserInteractions: {
title: 'Média de Interações por Usuário',
explanation: 'Reflete a frequência de uso diário dos usuários. Essa métrica reflete a fidelidade do usuário.',
},
userSatisfactionRate: {
title: 'Taxa de satisfação do usuário',
title: 'Taxa de Satisfação do Usuário',
explanation: 'O número de curtidas por 1.000 mensagens. Isso indica a proporção de respostas com as quais os usuários estão altamente satisfeitos.',
},
avgResponseTime: {
title: 'Tempo médio de resposta',
explanation: 'Tempo (ms) para o processamento/resposta da IA; para aplicativos baseados em texto.',
title: 'Tempo Médio de Resposta',
explanation: 'Tempo (ms) para o AI processar/responder; para aplicativos baseados em texto.',
},
tps: {
title: 'Velocidade de saída de tokens',
title: 'Velocidade de Saída do Token',
explanation: 'Mede o desempenho do LLM. Conta a velocidade de saída de tokens do LLM desde o início da solicitação até a conclusão da saída.',
},
},

View File

@@ -1,62 +1,90 @@
const translation = {
createApp: 'Criar aplicativo',
createApp: 'CRIAR APLICATIVO',
types: {
all: 'Tudo',
all: 'Todos',
chatbot: 'Chatbot',
agent: 'Agente',
workflow: 'Fluxo de trabalho',
completion: 'Gerador de Texto',
completion: 'Conclusão',
},
duplicate: 'Duplicar',
duplicateTitle: 'Duplicate aplicativo',
duplicateTitle: 'Duplicar Aplicativo',
export: 'Exportar DSL',
createFromConfigFile: 'Criar através do arquivo DSL',
exportFailed: 'Falha ao exportar DSL.',
importDSL: 'Importar arquivo DSL',
createFromConfigFile: 'Criar a partir do arquivo DSL',
deleteAppConfirmTitle: 'Excluir este aplicativo?',
deleteAppConfirmContent:
'A exclusão do aplicativo é irreversível. Os usuários não poderão mais acessar seu aplicativo e todas as configurações de prompt e logs serão excluídas permanentemente.',
'A exclusão do aplicativo é irreversível. Os usuários não poderão mais acessar seu aplicativo e todas as configurações de prompt e logs serão permanentemente excluídas.',
appDeleted: 'Aplicativo excluído',
appDeleteFailed: 'Falha ao excluir o aplicativo',
appDeleteFailed: 'Falha ao excluir aplicativo',
join: 'Participe da comunidade',
communityIntro:
'Discuta com membros da equipe, colaboradores e desenvolvedores em diferentes canais.',
roadmap: 'Veja nosso roteiro',
newApp: {
startFromBlank: 'Começar de um aplicativo em branco',
startFromTemplate: 'Começar a partir de um modelo',
captionAppType: 'Que tipo de aplicativo você deseja?',
chatbotDescription: 'Construir um assistente baseado em chat usando um Modelo de Linguagem de Grande Escala',
agentDescription: 'Construir um Agente inteligente que pode escolher autonomamente ferramentas para completar as tarefas',
workflowDescription: 'Description text here',
captionName: 'Dê um nome ao seu aplicativo',
appNamePlaceholder: 'Dê um nome ao seu aplicativo',
startFromBlank: 'Criar do zero',
startFromTemplate: 'Criar do modelo',
captionAppType: 'Que tipo de aplicativo você deseja criar?',
chatbotDescription: 'Construa um aplicativo baseado em chat. Este aplicativo usa um formato de pergunta e resposta, permitindo várias rodadas de conversa contínua.',
completionDescription: 'Construa um aplicativo que gera texto de alta qualidade com base em prompts, como geração de artigos, resumos, traduções e muito mais.',
completionWarning: 'Este tipo de aplicativo não será mais suportado.',
agentDescription: 'Construa um Agente inteligente que pode escolher ferramentas para completar as tarefas autonomamente',
workflowDescription: 'Construa um aplicativo que gera texto de alta qualidade com base em fluxo de trabalho com alto grau de personalização. É adequado para usuários experientes.',
workflowWarning: 'Atualmente em beta',
chatbotType: 'Método de orquestração do Chatbot',
basic: 'Básico',
basicTip: 'Para iniciantes, pode mudar para o Chatflow mais tarde',
basicFor: 'PARA INICIANTES',
basicDescription: 'A Orquestração Básica permite orquestrar um aplicativo Chatbot usando configurações simples, sem a capacidade de modificar prompts integrados. É adequado para iniciantes.',
advanced: 'Chatflow',
advancedFor: 'Para usuários avançados',
advancedDescription: 'A Orquestração de Fluxo de Trabalho orquestra Chatbots na forma de fluxos de trabalho, oferecendo um alto grau de personalização, incluindo a capacidade de editar prompts integrados. É adequado para usuários experientes.',
captionName: 'Ícone e nome do aplicativo',
appNamePlaceholder: 'Dê um nome para o seu aplicativo',
captionDescription: 'Descrição',
appDescriptionPlaceholder: 'Enter the description of the app',
useTemplate: 'Insira a descrição do aplicativo',
appDescriptionPlaceholder: 'Digite a descrição do aplicativo',
useTemplate: 'Usar este modelo',
previewDemo: 'Visualizar demonstração',
chatApp: 'Usar este modelo',
chatApp: 'Assistente',
chatAppIntro:
'Quero construir um aplicativo baseado em chat. Este aplicativo usa um formato de pergunta e resposta, permitindo várias rodadas de conversa contínua.',
agentAssistant: 'Assistente de novo agente',
'Eu quero construir um aplicativo baseado em chat. Este aplicativo usa um formato de pergunta e resposta, permitindo várias rodadas de conversa contínua.',
agentAssistant: 'Novo Assistente de Agente',
completeApp: 'Gerador de Texto',
completeAppIntro:
'Quero criar um aplicativo que gera texto de alta qualidade com base em prompts, como geração de artigos, resumos, traduções e muito mais.',
showTemplates: 'Quero escolher um modelo',
hideTemplates: 'Voltar para seleção de modo',
'Eu quero criar um aplicativo que gera texto de alta qualidade com base em prompts, como geração de artigos, resumos, traduções e muito mais.',
showTemplates: 'Quero escolher a partir de um modelo',
hideTemplates: 'Voltar para a seleção de modo',
Create: 'Criar',
Cancel: 'Cancelar',
nameNotEmpty: 'O nome não pode estar vazio',
appTemplateNotSelected: 'Por favor, selecione um modelo',
appTypeRequired: 'Por favor, selecione um tipo de aplicativo',
appCreated: 'Aplicativo criado',
appCreateFailed: 'Falha ao criar o aplicativo',
},
editApp: {
startToEdit: 'Editar Aplicativo',
appCreateFailed: 'Falha ao criar aplicativo',
},
editApp: 'Editar Informações',
editAppTitle: 'Editar Informações do Aplicativo',
editDone: 'Informações do aplicativo atualizadas',
editFailed: 'Falha ao atualizar informações do aplicativo',
emoji: {
ok: 'OK',
cancel: 'Cancelar',
},
switch: 'Mudar para Orquestração de Fluxo de Trabalho',
switchTipStart: 'Será criada uma nova cópia do aplicativo para você e a nova cópia mudará para Orquestração de Fluxo de Trabalho. A nova cópia não permitirá a ',
switchTip: 'volta',
switchTipEnd: ' para Orquestração Básica.',
switchLabel: 'A cópia do aplicativo a ser criada',
removeOriginal: 'Excluir o aplicativo original',
switchStart: 'Iniciar mudança',
typeSelector: {
all: 'Todos os Tipos',
chatbot: 'Chatbot',
agent: 'Agente',
workflow: 'Fluxo de trabalho',
completion: 'Conclusão',
},
}
export default translation

View File

@@ -478,6 +478,10 @@ const translation = {
title: 'Variáveis e Ferramentas Externas',
desc: 'Inserir Variáveis e Ferramentas Externas',
},
outputToolDisabledItem: {
title: 'Variáveis',
desc: 'Inserir variáveis',
},
modal: {
add: 'Nova variável',
addTool: 'Nova ferramenta',

View File

@@ -18,7 +18,7 @@ const translation = {
apps: {
title: 'Explorar Aplicações por Dify',
description: 'Use esses aplicativos modelo instantaneamente ou personalize seus próprios aplicativos com base nos modelos.',
allCategories: 'Todas as Categorias',
allCategories: 'Recomendado',
},
appCard: {
addToWorkspace: 'Adicionar ao Espaço de Trabalho',

View File

@@ -1,10 +1,22 @@
const translation = {
result: 'RESULT',
tracing: 'TRACING',
input: 'ENTRADA',
result: 'RESULTADO',
detail: 'DETALHE',
tracing: 'RASTREIO',
resultPanel: {
status: 'STATUS',
time: 'ELAPSED TIME',
tokens: 'TOTAL TOKENS',
time: 'TEMPO DECORRIDO',
tokens: 'TOTAL DE TOKENS',
},
meta: {
title: 'METADADOS',
status: 'Status',
version: 'Versão',
executor: 'Executor',
startTime: 'Hora de Início',
time: 'Tempo Decorrido',
tokens: 'Total de Tokens',
steps: 'Passos de Execução',
},
}

View File

@@ -1,90 +1,332 @@
const translation = {
common: {
editing: 'Editando',
autoSaved: 'Auto-salvo',
unpublished: 'Não publicado',
published: 'Publicado',
publish: 'Publicar',
update: 'Atualizar',
run: 'Executar',
running: 'Executando',
inRunMode: 'No modo de execução',
inPreview: 'Na prévia',
inPreviewMode: 'No modo de prévia',
preview: 'Prévia',
viewRunHistory: 'Ver histórico de execução',
runHistory: 'Histórico de execução',
goBackToEdit: 'Voltar para a edição',
conversationLog: 'Registro de conversa',
features: 'Recursos',
debugAndPreview: 'Depurar e pré-visualizar',
restart: 'Reiniciar',
currentDraft: 'Rascunho atual',
currentDraftUnpublished: 'Rascunho atual não publicado',
latestPublished: 'Último publicado',
publishedAt: 'Publicado em',
restore: 'Restaurar',
runApp: 'Executar aplicativo',
batchRunApp: 'Executar aplicativo em lote',
accessAPIReference: 'Acessar referência da API',
embedIntoSite: 'Incorporar no site',
addTitle: 'Adicionar título...',
addDescription: 'Adicionar descrição...',
noVar: 'Sem variável',
searchVar: 'Buscar variável',
variableNamePlaceholder: 'Nome da variável',
setVarValuePlaceholder: 'Definir variável',
needConnecttip: 'Esta etapa não está conectada a nada',
maxTreeDepth: 'Limite máximo de {{depth}} nós por ramificação',
needEndNode: 'O bloco de fim deve ser adicionado',
needAnswerNode: 'O bloco de resposta deve ser adicionado',
workflowProcess: 'Processo de fluxo de trabalho',
notRunning: 'Ainda não em execução',
previewPlaceholder: 'Digite o conteúdo na caixa abaixo para iniciar a depuração do Chatbot',
effectVarConfirm: {
title: 'Remover variável',
content: 'A variável está sendo usada em outros nós. Deseja removê-la mesmo assim?',
},
insertVarTip: 'Pressione a tecla \'/\' para inserir rapidamente',
},
errorMsg: {
fieldRequired: '{{field}} é obrigatório',
authRequired: 'Autorização é necessária',
invalidJson: '{{field}} é JSON inválido',
fields: {
variable: 'Nome da Variável',
variableValue: 'Valor da Variável',
code: 'Código',
model: 'Modelo',
rerankModel: 'Modelo de Re-ordenação',
},
invalidVariable: 'Variável inválida',
},
singleRun: {
testRun: 'Execução de teste ',
startRun: 'Iniciar execução',
running: 'Executando',
},
tabs: {
'searchBlock': 'Buscar bloco',
'blocks': 'Blocos',
'builtInTool': 'Ferramenta incorporada',
'customTool': 'Ferramenta personalizada',
'question-understand': 'Entendimento da pergunta',
'logic': 'Lógica',
'transform': 'Transformar',
'utilities': 'Utilitários',
'noResult': 'Nenhum resultado encontrado',
},
blocks: {
'start': 'Início',
'end': 'Fim',
'answer': 'Resposta',
'llm': 'LLM',
'knowledge-retrieval': 'Recuperação de conhecimento',
'question-classifier': 'Classificador de perguntas',
'if-else': 'Se/Senão',
'code': 'Código',
'template-transform': 'Modelo',
'http-request': 'Requisição HTTP',
'variable-assigner': 'Atribuidor de variáveis',
},
blocksAbout: {
'start': 'Defina os parâmetros iniciais para iniciar um fluxo de trabalho',
'end': 'Defina o final e o tipo de resultado de um fluxo de trabalho',
'answer': 'Defina o conteúdo da resposta de uma conversa no chat',
'llm': 'Invocar grandes modelos de linguagem para responder perguntas ou processar linguagem natural',
'knowledge-retrieval': 'Permite consultar conteúdo de texto relacionado a perguntas de usuário do conhecimento',
'question-classifier': 'Define as condições de classificação de perguntas do usuário, LLM pode definir como a conversa progride com base na descrição da classificação',
'if-else': 'Permite dividir o fluxo de trabalho em dois ramos com base em condições if/else',
'code': 'Execute um trecho de código Python ou NodeJS para implementar lógica personalizada',
'template-transform': 'Converta dados em string usando a sintaxe do modelo Jinja',
'http-request': 'Permite enviar solicitações de servidor sobre o protocolo HTTP',
'variable-assigner': 'Atribua variáveis em diferentes ramos à mesma variável para alcançar uma configuração unificada de pós-nós',
},
operator: {
zoomIn: 'Aumentar zoom',
zoomOut: 'Diminuir zoom',
zoomTo50: 'Zoom para 50%',
zoomTo100: 'Zoom para 100%',
zoomToFit: 'Zoom para ajustar',
},
panel: {
userInputField: 'Campo de entrada do usuário',
changeBlock: 'Mudar bloco',
helpLink: 'Link de ajuda',
about: 'Sobre',
createdBy: 'Criado por ',
nextStep: 'Próximo passo',
addNextStep: 'Adicionar o próximo bloco neste fluxo de trabalho',
selectNextStep: 'Selecionar próximo bloco',
runThisStep: 'Executar este passo',
checklist: 'Lista de verificação',
checklistTip: 'Certifique-se de resolver todos os problemas antes de publicar',
checklistResolved: 'Todos os problemas estão resolvidos',
organizeBlocks: 'Organizar blocos',
change: 'Mudar',
},
nodes: {
common: {
outputVars: 'Output Variables',
insertVarTip: 'Insert Variable',
},
start: {
required: 'required',
inputField: 'Input Field',
builtInVar: 'Built-in Variables',
outputVars: {
query: 'User input',
memories: {
des: 'Conversation history',
type: 'message type',
content: 'message content',
},
files: 'File list',
outputVars: 'Variáveis de saída',
insertVarTip: 'Pressione a tecla \'/\' para inserir',
memory: {
memory: 'Memória',
memoryTip: 'Configurações de memória do chat',
windowSize: 'Tamanho da janela',
conversationRoleName: 'Nome do papel na conversa',
user: 'Prefixo do usuário',
assistant: 'Prefixo do assistente',
},
memories: {
title: 'Memórias',
tip: 'Memória do chat',
builtIn: 'Incorporada',
},
},
start: {
required: 'obrigatório',
inputField: 'Campo de entrada',
builtInVar: 'Variáveis incorporadas',
outputVars: {
query: 'Entrada do usuário',
memories: {
des: 'Histórico da conversa',
type: 'Tipo de mensagem',
content: 'Conteúdo da mensagem',
},
files: 'Lista de arquivos',
},
noVarTip: 'Defina as entradas que podem ser usadas no fluxo de trabalho',
},
end: {
outputs: 'Outputs',
outputs: 'Saídas',
output: {
type: 'Tipo de saída',
variable: 'Variável de saída',
},
type: {
'none': 'None',
'plain-text': 'Plain Text',
'structured': 'Structured',
'none': 'Nenhum',
'plain-text': 'Texto simples',
'structured': 'Estruturado',
},
},
answer: {
answer: 'Answer',
inputVars: 'Input Variables',
answer: 'Resposta',
outputVars: 'Variáveis de saída',
},
llm: {
model: 'model',
variables: 'variables',
context: 'context',
model: 'modelo',
variables: 'variáveis',
context: 'contexto',
contextTooltip: 'Você pode importar conhecimento como contexto',
notSetContextInPromptTip: 'Para habilitar o recurso de contexto, preencha a variável de contexto em PROMPT.',
prompt: 'prompt',
vision: 'vision',
roleDescription: {
system: 'Dar instruções de alto nível para a conversa',
user: 'Fornecer instruções, consultas ou qualquer entrada baseada em texto para o modelo',
assistant: 'Respostas do modelo com base nas mensagens do usuário',
},
addMessage: 'Adicionar mensagem',
vision: 'visão',
files: 'Arquivos',
resolution: {
name: 'Resolução',
high: 'Alta',
low: 'Baixa',
},
outputVars: {
output: 'Generate content',
usage: 'Model Usage Information',
output: 'Gerar conteúdo',
usage: 'Informações de uso do modelo',
},
singleRun: {
variable: 'Variável',
},
},
knowledgeRetrieval: {
queryVariable: 'Variável de consulta',
knowledge: 'Conhecimento',
outputVars: {
output: 'Dados segmentados de recuperação',
content: 'Conteúdo segmentado',
title: 'Título segmentado',
icon: 'Ícone segmentado',
url: 'URL segmentada',
metadata: 'Outros metadados',
},
},
http: {
inputVars: 'Input Variables',
inputVars: 'Variáveis de entrada',
api: 'API',
headers: 'Headers',
params: 'Params',
body: 'Body',
apiPlaceholder: 'Insira o URL, digite \'/\' para inserir a variável',
notStartWithHttp: 'A API deve começar com http:// ou https://',
key: 'Chave',
value: 'Valor',
bulkEdit: 'Editar em massa',
keyValueEdit: 'Edição de chave-valor',
headers: 'Cabeçalhos',
params: 'Parâmetros',
body: 'Corpo',
outputVars: {
body: 'Response Content',
statusCode: 'Response Status Code',
headers: 'Response Header List JSON',
body: 'Conteúdo da resposta',
statusCode: 'Código de status da resposta',
headers: 'Lista de cabeçalhos de resposta JSON',
files: 'Lista de arquivos',
},
authorization: {
'authorization': 'Autorização',
'authorizationType': 'Tipo de autorização',
'no-auth': 'Nenhum',
'api-key': 'Chave da API',
'auth-type': 'Tipo de autenticação',
'basic': 'Básica',
'bearer': 'Bearer',
'custom': 'Personalizada',
'api-key-title': 'Chave da API',
'header': 'Cabeçalho',
},
insertVarPlaceholder: 'digite \'/\' para inserir variável',
},
code: {
inputVars: 'Input Variables',
outputVars: 'Output Variables',
inputVars: 'Variáveis de entrada',
outputVars: 'Variáveis de saída',
},
templateTransform: {
inputVars: 'Input Variables',
code: 'Code',
codeSupportTip: 'Only supports Jinja2',
inputVars: 'Variáveis de entrada',
code: 'Código',
codeSupportTip: 'Suporta apenas Jinja2',
outputVars: {
output: 'Transformed content',
output: 'Conteúdo transformado',
},
},
ifElse: {
conditions: 'Conditions',
and: 'and',
or: 'or',
if: 'Se',
else: 'Senão',
elseDescription: 'Usado para definir a lógica que deve ser executada quando a condição if não é atendida.',
and: 'e',
or: 'ou',
operator: 'Operador',
notSetVariable: 'Por favor, defina a variável primeiro',
comparisonOperator: {
'contains': 'contains',
'not contains': 'not contains',
'start with': 'start with',
'end with': 'end with',
'is': 'is',
'is not': 'is not',
'empty': 'empty',
'not empty': 'not empty',
'null': 'is null',
'not null': 'is not null',
'contains': 'contém',
'not contains': 'não contém',
'start with': 'começa com',
'end with': 'termina com',
'is': 'é',
'is not': 'não é',
'empty': 'vazio',
'not empty': 'não está vazio',
'null': 'nulo',
'not null': 'não é nulo',
},
enterValue: 'Digite o valor',
addCondition: 'Adicionar condição',
conditionNotSetup: 'Condição NÃO configurada',
},
variableAssigner: {
title: 'Assign variables',
title: 'Atribuir variáveis',
outputType: 'Tipo de saída',
outputVarType: 'Tipo de variável de saída',
varNotSet: 'Variável não definida',
noVarTip: 'Adicione as variáveis a serem atribuídas',
type: {
string: 'String',
number: 'Número',
object: 'Objeto',
array: 'Array',
},
outputVars: {
output: 'Valor da variável atribuída',
},
},
tool: {
toAuthorize: 'Para autorizar',
inputVars: 'Variáveis de entrada',
outputVars: {
text: 'conteúdo gerado pela ferramenta',
files: {
title: 'arquivos gerados pela ferramenta',
type: 'Tipo de suporte. Agora apenas suporte a imagem',
transfer_method: 'Método de transferência. O valor é remote_url ou local_file',
url: 'URL da imagem',
upload_file_id: 'ID de upload do arquivo',
},
},
},
questionClassifiers: {
model: 'modelo',
inputVars: 'Variáveis de entrada',
class: 'Classe',
classNamePlaceholder: 'Escreva o nome da classe',
advancedSetting: 'Configuração avançada',
topicName: 'Nome do tópico',
topicPlaceholder: 'Escreva o nome do tópico',
addClass: 'Adicionar classe',
instruction: 'Instrução',
instructionPlaceholder: 'Escreva sua instrução',
},
},
tracing: {
stopBy: 'Parado por {{user}}',
},
}

View File

@@ -260,19 +260,32 @@ const translation = {
queryNoBeEmpty: 'Запит має бути встановлений у підказці', // Query must be set in the prompt
},
variableConig: {
'addModalTitle': 'Налаштування поля', // Field settings
'description': 'Налаштування для змінної {{varName}}', // Setting for variable {{varName}}
'fieldType': 'Тип поля', // Field type
'string': 'Короткий текст', // Short Text
'text-input': 'Короткий текст', // Short Text
'paragraph': 'Абзац', // Paragraph
'select': 'Вибрати', // Select
'notSet': 'Не налаштовано, спробуйте ввести {{input}} у префіксну підказку', // Not set, try typing {{input}} in the prefix prompt
'stringTitle': 'Опції текстового поля форми', // Form text box options
'maxLength': 'Максимальна довжина', // Max length
'options': 'Опції', // Options
'addOption': 'Додати опцію', // Add option
'apiBasedVar': 'Змінна на основі API', // API-based Variable
'addModalTitle': 'Додати Поле Введення',
'editModalTitle': 'Редагувати Поле Введення',
'description': 'Налаштування для змінної {{varName}}',
'fieldType': 'Тип поля',
'string': 'Короткий текст',
'text-input': 'Короткий текст',
'paragraph': 'Параграф',
'select': 'Вибрати',
'number': 'Число',
'notSet': 'Не встановлено, спробуйте ввести {{input}} у префіксній підказці',
'stringTitle': 'Параметри поля введення форми',
'maxLength': 'Максимальна довжина',
'options': 'Опції',
'addOption': 'Додати опцію',
'apiBasedVar': 'Змінна на основі API',
'varName': 'Назва змінної',
'labelName': 'Назва мітки',
'inputPlaceholder': 'Будь ласка, введіть',
'required': 'Обов\'язково',
'errorMsg': {
varNameRequired: 'Потрібно вказати назву змінної',
labelNameRequired: 'Потрібно вказати назву мітки',
varNameCanBeRepeat: 'Назва змінної не може повторюватися',
atLeastOneOption: 'Потрібно щонайменше одну опцію',
optionRepeat: 'Є повторні опції',
},
},
vision: {
name: 'Зображення', // Vision
@@ -340,6 +353,7 @@ const translation = {
result: 'Вихідний текст', // Output Text
datasetConfig: {
settingTitle: 'Налаштування пошуку', // Retrieval settings
knowledgeTip: 'Клацніть кнопку “+”, щоб додати знання',
retrieveOneWay: {
title: 'Односторонній пошук', // N-to-1 retrieval
description: 'На основі намірів користувача та описів Знань Агент самостійно вибирає найкращі Знання для запитів. Найкраще підходить для застосунків з окремими, обмеженими Знаннями.',

View File

@@ -1,69 +1,83 @@
const translation = {
title: 'Журнали', // Logs
description: 'Журнали фіксують стан роботи застосунку, включаючи введення користувача та відповіді ШІ.', // The logs record...
dateTimeFormat: 'MM/DD/YYYY hh:mm A', // (Keep date format as is)
title: 'Журнали',
description: 'Журнали фіксують робочий статус додатка, включаючи введення користувачів та відповіді штучного інтелекту.',
dateTimeFormat: 'MM/DD/YYYY hh:mm A',
table: {
header: {
time: 'Час', // Time
endUser: 'Кінцевий користувач', // End User
input: 'Введення', // Input
output: 'Виведення', // Output
summary: 'Заголовок', // Title
messageCount: 'Кількість повідомлень', // Message Count
userRate: 'Оцінка користувача', // User Rate
adminRate: 'Оцінка адміністратора', // Op. Rate
time: 'Час',
endUser: 'Кінцевий Користувач',
input: 'Введення',
output: 'Виведення',
summary: 'Заголовок',
messageCount: 'Кількість Повідомлень',
userRate: 'Рейтинг Користувача',
adminRate: 'Рейтинг Оператора',
startTime: 'ЧАС ПОЧАТКУ',
status: 'СТАТУС',
runtime: 'ЧАС ВИКОНАННЯ',
tokens: 'ТОКЕНИ',
user: 'КІНЦЕВИЙ КОРИСТУВАЧ',
version: 'ВЕРСІЯ',
},
pagination: {
previous: 'Назад', // Prev
next: 'Далі', // Next
previous: 'Попередня',
next: 'Наступна',
},
empty: {
noChat: 'Ще немає розмови', // No conversation yet
noOutput: 'Відповіді немає', // No output
noChat: 'Ще немає розмов',
noOutput: 'Немає виводу',
element: {
title: 'Хто-небудь тут є?', // Is anyone there?
content: 'Спостерігайте й коментуйте взаємодію між кінцевими користувачами та програмами штучного інтелекту, щоб постійно покращувати точність ШІ. Ви можете спробувати <shareLink>поділитися</shareLink> або <testLink>тестувати</testLink> веб-програму самостійно, а потім повернутися на цю сторінку.',
title: 'Хтось тут?',
content: 'Спостерігайте та анотуйте взаємодії між кінцевими користувачами та додатками штучного інтелекту тут, щоб постійно покращувати точність штучного інтелекту. Ви можете спробувати <shareLink>поділитися</shareLink> або <testLink>протестувати</testLink> веб-додаток самостійно, а потім повернутися на цю сторінку.',
},
},
},
detail: {
time: 'Час', // Time
conversationId: 'Ідентифікатор розмови', // Conversation ID
promptTemplate: 'Шаблон підказки', // Prompt Template
promptTemplateBeforeChat: 'Шаблон підказки перед чатом · як системне повідомлення', // Prompt Template Before Chat...
annotationTip: 'Покращення, позначені {{user}}', // Improvements Marked by {{user}}
time: 'Час',
conversationId: 'ID Розмови',
promptTemplate: 'Шаблон Запитання',
promptTemplateBeforeChat: 'Шаблон Запитання Перед Чатом · Як Системне Повідомлення',
annotationTip: 'Покращення Позначені Користувачем {{user}}',
timeConsuming: '',
second: 'с', // s (seconds)
tokenCost: 'Витрачені токени', // Token spent
loading: 'Завантаження', // loading
second: 'с',
tokenCost: 'Витрати Токенів',
loading: 'завантаження',
operation: {
like: 'Вподобати', // like
dislike: 'Не вподобати', // dislike
addAnnotation: 'Додати покращення', // Add Improvement
editAnnotation: 'Редагувати покращення', // Edit Improvement
annotationPlaceholder: 'Введіть очікувану відповідь, яку ви хочете, щоб відповів ШІ. ',
like: 'подобається',
dislike: 'не подобається',
addAnnotation: 'Додати Покращення',
editAnnotation: 'Редагувати Покращення',
annotationPlaceholder: 'Введіть очікувану відповідь, яку ви хочете, щоб штучний інтелект повертав, це може бути використано для налаштування моделі та постійного покращення якості генерації тексту в майбутньому.',
},
variables: 'Змінні', // Variables
uploadImages: 'Завантажені зображення', // Uploaded Images
variables: 'Змінні',
uploadImages: 'Завантажені Зображення',
},
filter: {
period: {
today: 'Сьогодні', // Today
last7days: 'Останні 7 днів', // Last 7 Days
last4weeks: 'Останні 4 тижні', // Last 4 weeks
last3months: 'Останні 3 місяці', // Last 3 months
last12months: 'Останні 12 місяців', // Last 12 months
monthToDate: 'Місяць до дати', // Month to date
quarterToDate: 'Квартал до дати', // Quarter to date
yearToDate: 'Рік до дати', // Year to date
allTime: 'Увесь час', // All time
today: 'Сьогодні',
last7days: 'Останні 7 днів',
last4weeks: 'Останні 4 тижні',
last3months: 'Останні 3 місяці',
last12months: 'Останні 12 місяців',
monthToDate: 'Місяць до сьогодні',
quarterToDate: 'Квартал до сьогодні',
yearToDate: 'Рік до сьогодні',
allTime: 'За весь час',
},
annotation: {
all: 'Усі', // All
annotated: 'Анотовані покращення ({{count}} елементів)', // Annotated Improvements...
not_annotated: 'Не анотований', // Not Annotated
all: 'Всі',
annotated: 'Покращення з Анотацією ({{count}} елементів)',
not_annotated: 'Без Анотації',
},
},
workflowTitle: 'Журнали Робочого Процесу',
workflowSubtitle: 'Журнал зареєстрував роботу Автоматизації.',
runDetail: {
title: 'Журнал Розмови',
workflowTitle: 'Деталі Журналу',
},
promptLog: 'Журнал Запитань',
viewLog: 'Переглянути Журнал',
}
export default translation

View File

@@ -1,137 +1,141 @@
const translation = {
welcome: {
firstStepTip: 'Щоб розпочати,',
firstStepTip: 'Для початку,',
enterKeyTip: 'введіть свій ключ API OpenAI нижче',
getKeyTip: 'Отримайте свій ключ API з панелі керування OpenAI',
placeholder: 'Ваш ключ API OpenAI (наприклад, sk-xxxx)',
getKeyTip: 'Отримайте свій ключ API з панелі OpenAI',
placeholder: 'Ваш ключ API OpenAI (напр. sk-xxxx)',
},
apiKeyInfo: {
cloud: {
trial: {
title: 'Ви використовуєте пробну квоту {{providerName}}.', // You are using...
description: 'Пробна квота надається для використання під час тестування. Перш ніж вичерпається пробна квота, будь ласка, налаштуйте свого постачальника моделі або придбайте додаткову квоту.',
title: 'Ви використовуєте квоту пробного періоду {{providerName}}.',
description: 'Квота пробного періоду надається для вашого тестувального використання. Перш ніж будуть вичерпані виклики квоти пробного періоду, налаштуйте свого власного постачальника моделей або придбайте додаткову квоту.',
},
exhausted: {
title: 'Ваша пробна квота використана, будь ласка, налаштуйте свій APIKey.',
description: 'Ваша пробна квота вичерпана. Будь ласка, налаштуйте свого постачальника моделі або придбайте додаткову квоту.',
title: 'Вашу квоту пробного періоду вичерпано, налаштуйте свій ключ API.',
description: 'Вашу квоту пробного періоду вичерпано. Налаштуйте свого власного постачальника моделей або придбайте додаткову квоту.',
},
},
selfHost: {
title: {
row1: 'Щоб розпочати,',
row2: 'спочатку налаштуйте свого постачальника моделі.',
row1: 'Для початку,',
row2: 'спочатку налаштуйте постачальника моделей.',
},
},
callTimes: 'Кількість викликів', // Call times
usedToken: 'Використано токенів', // Used token
setAPIBtn: 'Перейти до налаштування постачальника моделі', // Go to setup model provider
tryCloud: 'Або спробуйте хмарну версію Dify з безкоштовною квотою', // Or try the cloud version ...
callTimes: 'Кількість викликів',
usedToken: 'Використані токени',
setAPIBtn: 'Перейти до налаштування постачальника моделей',
tryCloud: 'Або спробуйте хмарну версію Dify з безкоштовним цитатою',
},
overview: {
title: 'Огляд', // Overview
title: 'Огляд',
appInfo: {
explanation: 'Готовий до використання AI WebApp',
accessibleAddress: 'Публічна URL-адреса', // Public URL
preview: 'Попередній перегляд', // Preview
regenerate: 'Регенерувати', // Regenerate
preUseReminder: 'Будь ласка, увімкніть WebApp, перш ніж продовжувати.', // Please enable WebApp...
explanation: 'Готовий до використання веб-додаток зі штучним інтелектом',
accessibleAddress: 'Публічний URL',
preview: 'Попередній перегляд',
regenerate: 'Відновити',
preUseReminder: 'Будь ласка, активуйте веб-додаток перед продовженням.',
settings: {
entry: 'Налаштування', // Settings
title: 'Налаштування веб-програми', // WebApp Settings
webName: 'Ім’я веб-програми', // WebApp Name
webDesc: 'Опис веб-програми', // WebApp Description
webDescTip: 'Цей текст відображатиметься на стороні клієнта, надаючи базові вказівки щодо використання програми',
webDescPlaceholder: 'Введіть опис WebApp',
language: 'Мова', // Language
entry: 'Налаштування',
title: 'Налаштування веб-додатку',
webName: 'Назва веб-додатку',
webDesc: 'Опис веб-додатку',
webDescTip: 'Цей текст буде відображений на клієнтському боці, надаючи базові вказівки щодо використання додатка',
webDescPlaceholder: 'Введіть опис веб-додатку',
language: 'Мова',
more: {
entry: 'Показати більше налаштувань', // Show more settings
copyright: 'Авторські права', // Copyright
copyRightPlaceholder: 'Введіть імя автора або організації',
privacyPolicy: 'Політика конфіденційності', // Privacy Policy
entry: 'Показати додаткові налаштування',
copyright: 'Авторське право',
copyRightPlaceholder: 'Введіть ім\'я автора або організації',
privacyPolicy: 'Політика конфіденційності',
privacyPolicyPlaceholder: 'Введіть посилання на політику конфіденційності',
privacyPolicyTip: 'Допомагає відвідувачам зрозуміти, які дані збирає програма. Дивіться <privacyPolicyLink>Політику конфіденційності</privacyPolicyLink> Dify.',
privacyPolicyTip: 'Допомагає відвідувачам зрозуміти дані, зібрані додатком, див. <privacyPolicyLink>Політику конфіденційності</privacyPolicyLink> Dify.',
},
},
embedded: {
entry: 'Вбудований', // Embedded
title: 'Вбудувати на веб-сайт', // Embed on website
explanation: 'Виберіть спосіб вбудувати чат-програму на свій веб-сайт',
iframe: 'Щоб додати чат-програму будь-де на своєму веб-сайті, додайте цей iframe у свій код html.',
scripts: 'Щоб додати програму чату у правий нижній кут свого веб-сайту, додайте цей код до свого html.',
chromePlugin: 'Встановити розширення Dify Chatbot для Chrome',
copied: 'Скопійовано', // Copied
copy: 'Копіювати', // Copy
entry: 'Вбудоване',
title: 'Вбудувати на веб-сайт',
explanation: 'Виберіть спосіб вбудування чат-додатка на ваш веб-сайт',
iframe: 'Для додавання чат-додатка в будь-яке місце на вашому веб-сайті, додайте цей iframe до вашого HTML-коду.',
scripts: 'Для додавання чат-додатка в правий нижній кут вашого веб-сайту додайте цей код до вашого HTML.',
chromePlugin: 'Встановити розширення Chrome Dify Chatbot',
copied: 'Скопійовано',
copy: 'Скопіювати',
},
qrcode: {
title: 'QR-код для спільного доступу',
scan: 'Відсканувати програму спільного доступу',
title: 'QR-код для обміну',
scan: 'Сканувати та обмінюватися додатком',
download: 'Завантажити QR-код',
},
customize: {
way: 'спосіб', // way
entry: 'Налаштувати', // Customize
title: 'Налаштувати веб-додаток AI',
explanation: 'Ви можете налаштувати зовнішній вигляд WebApp відповідно до вашого сценарію та стилю.',
way: 'спосіб',
entry: 'Налаштувати',
title: 'Налаштування веб-додатку зі штучним інтелектом',
explanation: 'Ви можете налаштувати інтерфейс користувача веб-додатка, щоб він відповідав вашим потребам у сценаріях та стилі.',
way1: {
name: 'Розгалужити код клієнта, змінити його та розгорнути у Vercel (рекомендовано)',
step1: 'Розгалужити код клієнта та модифікувати його',
step1Tip: 'Натисніть тут, щоб розгалужити вихідний код у свій обліковий запис GitHub і змінити код',
step1Operation: 'Dify-WebClient',
step2: 'Розгорнути у Vercel',
step2Tip: 'Натисніть тут, щоб імпортувати репозиторій до Vercel та виконати розгортання',
step2Operation: 'Імпортувати репозиторій',
step3: 'Налаштувати змінні середовища',
step3Tip: 'Додайте такі змінні середовища у Vercel',
name: 'Склонуйте клієнтський код, відредагуйте його та розгорніть на Vercel (рекомендовано)',
step1: 'Склонуйте клієнтський код та відредагуйте його',
step1Tip: 'Натисніть тут, щоб склонувати вихідний код у ваш обліковий запис GitHub та відредагувати код',
step1Operation: 'Клієнт-Web-Dify',
step2: 'Розгорніть на Vercel',
step2Tip: 'Натисніть тут, щоб імпортувати репозиторій у Vercel та розгорнути',
step2Operation: 'Імпорт репозиторію',
step3: 'Налаштуйте змінні середовища',
step3Tip: 'Додайте наступні змінні середовища у Vercel',
},
way2: {
name: 'Напишіть код на клієнтській стороні, щоб викликати API та розгорнути його на сервері',
name: 'Напишіть клієнтський код для виклику API та розгорніть його на сервері',
operation: 'Документація',
},
},
},
apiInfo: {
title: 'API бекенд-сервісу', // Backend service API
explanation: 'Легко інтегрується в вашу програму',
accessibleAddress: 'Кінцевий ресурс сервісу API', // Service API Endpoint
doc: 'API довідка', // API Reference
title: 'API сервісу Backend',
explanation: 'Легко інтегрований у вашу програму',
accessibleAddress: 'Кінцева точка API сервісу',
doc: 'Довідка з API',
},
status: {
running: 'Працює', // In service
disable: 'Вимкнути', // Disable
running: 'У роботі',
disable: 'Вимкнути',
},
},
analysis: {
title: 'Аналіз', // Analysis
ms: 'мс', // ms
tokenPS: 'Токен/с', // Token/s
title: 'Аналіз',
ms: 'мс',
tokenPS: 'Токени/с',
totalMessages: {
title: 'Загалом повідомлень', // Total Messages
explanation: 'Щоденна кількість взаємодій зі ШІ; виключено проектування запитань або налагодження.',
title: 'Загальна кількість повідомлень',
explanation: 'Щоденна кількість взаємодій з штучним інтелектом; інженерія/налагодження запитів виключено.',
},
activeUsers: {
title: 'Активні користувачі', // Active Users
explanation: 'Унікальні користувачі, які беруть участь у запитах/відповідях зі ШІ; виключено інженерію запитань або налагодження.',
title: 'Активні користувачі',
explanation: 'Унікальні користувачі, які взаємодіють з AI; інженерія/налагодження запитів виключено.',
},
tokenUsage: {
title: 'Використання токенів', // Token Usage
explanation: 'Відображає щоденне використання токенів мовної моделі для програми, корисно для контролю витрат.',
consumed: 'Спожито', // Consumed
title: 'Використання токенів',
explanation: 'Відображає щоденне використання токенів мовної моделі для додатка, корисно для контролю витрат.',
consumed: 'Спожито',
},
avgSessionInteractions: {
title: 'Середня кількість взаємодій під час сеансу', // Avg. Session Interactions
explanation: 'Кількість безперервних комунікацій між користувачем і ШІ; для програм, що базуються на розмовах.',
title: 'Середня кількість взаємодій за сесію',
explanation: 'Кількість продовжуючихся спілкувань користувача з AI; для програм, що базуються на розмові.',
},
avgUserInteractions: {
title: 'Середня кількість взаємодій на користувача',
explanation: 'Відображає щоденну частоту використання користувачами. Ця метрика відображає лояльність користувачів.',
},
userSatisfactionRate: {
title: 'Рівень задоволеності користувачів',
explanation: 'Кількість лайків на 1000 повідомлень. Це означає частку відповідей, якими користувачі дуже задоволені.',
title: 'Показник задоволення користувача',
explanation: 'Кількість лайків на 1000 повідомлень. Це вказує на те, наскільки задоволені користувачі відповідями.',
},
avgResponseTime: {
title: 'Середній час відповіді',
explanation: 'Час (мс) для обробки/відповіді ШІ; для текстових програм.',
explanation: 'Час (мс) для обробки/відповіді AI; для текстових програм.',
},
tps: {
title: 'Швидкість виведення токенів',
explanation: 'Виміряйте продуктивність LLM. Підрахуйте швидкість виведення токенів LLM від початку запиту до завершення виведення.',
explanation: 'Вимірює продуктивність LLM. Підраховує швидкість виведення токенів LLM від початку запиту до завершення виведення.',
},
},
}

Some files were not shown because too many files have changed in this diff Show More