Merge branch 'main' into feat/external-knowledge
This commit is contained in:
commit
4fd57929df
@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
|||||||
|
|
||||||
CURRENT_VERSION: str = Field(
|
CURRENT_VERSION: str = Field(
|
||||||
description='Dify version',
|
description='Dify version',
|
||||||
default='0.7.0',
|
default='0.7.1',
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMIT_SHA: str = Field(
|
COMMIT_SHA: str = Field(
|
||||||
|
@ -792,6 +792,13 @@ if you are not sure about the structure.
|
|||||||
if not isinstance(parameter_value, str):
|
if not isinstance(parameter_value, str):
|
||||||
raise ValueError(f"Model Parameter {parameter_name} should be string.")
|
raise ValueError(f"Model Parameter {parameter_name} should be string.")
|
||||||
|
|
||||||
|
# validate options
|
||||||
|
if parameter_rule.options and parameter_value not in parameter_rule.options:
|
||||||
|
raise ValueError(f"Model Parameter {parameter_name} should be one of {parameter_rule.options}.")
|
||||||
|
elif parameter_rule.type == ParameterType.TEXT:
|
||||||
|
if not isinstance(parameter_value, str):
|
||||||
|
raise ValueError(f"Model Parameter {parameter_name} should be text.")
|
||||||
|
|
||||||
# validate options
|
# validate options
|
||||||
if parameter_rule.options and parameter_value not in parameter_rule.options:
|
if parameter_rule.options and parameter_value not in parameter_rule.options:
|
||||||
raise ValueError(f"Model Parameter {parameter_name} should be one of {parameter_rule.options}.")
|
raise ValueError(f"Model Parameter {parameter_name} should be one of {parameter_rule.options}.")
|
||||||
|
@ -70,7 +70,7 @@ class AzureOpenAIText2SpeechModel(_CommonAzureOpenAI, TTSModel):
|
|||||||
# doc: https://platform.openai.com/docs/guides/text-to-speech
|
# doc: https://platform.openai.com/docs/guides/text-to-speech
|
||||||
credentials_kwargs = self._to_credential_kwargs(credentials)
|
credentials_kwargs = self._to_credential_kwargs(credentials)
|
||||||
client = AzureOpenAI(**credentials_kwargs)
|
client = AzureOpenAI(**credentials_kwargs)
|
||||||
# max font is 4096,there is 3500 limit for each request
|
# max length is 4096 characters, there is 3500 limit for each request
|
||||||
max_length = 3500
|
max_length = 3500
|
||||||
if len(content_text) > max_length:
|
if len(content_text) > max_length:
|
||||||
sentences = self._split_text_into_sentences(content_text, max_length=max_length)
|
sentences = self._split_text_into_sentences(content_text, max_length=max_length)
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
model: gpt-4o-2024-08-06
|
||||||
|
label:
|
||||||
|
zh_Hans: gpt-4o-2024-08-06
|
||||||
|
en_US: gpt-4o-2024-08-06
|
||||||
|
model_type: llm
|
||||||
|
features:
|
||||||
|
- multi-tool-call
|
||||||
|
- agent-thought
|
||||||
|
- stream-tool-call
|
||||||
|
- vision
|
||||||
|
model_properties:
|
||||||
|
mode: chat
|
||||||
|
context_size: 128000
|
||||||
|
parameter_rules:
|
||||||
|
- name: temperature
|
||||||
|
use_template: temperature
|
||||||
|
- name: top_p
|
||||||
|
use_template: top_p
|
||||||
|
- name: presence_penalty
|
||||||
|
use_template: presence_penalty
|
||||||
|
- name: frequency_penalty
|
||||||
|
use_template: frequency_penalty
|
||||||
|
- name: max_tokens
|
||||||
|
use_template: max_tokens
|
||||||
|
default: 512
|
||||||
|
min: 1
|
||||||
|
max: 16384
|
||||||
|
- name: response_format
|
||||||
|
label:
|
||||||
|
zh_Hans: 回复格式
|
||||||
|
en_US: response_format
|
||||||
|
type: string
|
||||||
|
help:
|
||||||
|
zh_Hans: 指定模型必须输出的格式
|
||||||
|
en_US: specifying the format that the model must output
|
||||||
|
required: false
|
||||||
|
options:
|
||||||
|
- text
|
||||||
|
- json_object
|
||||||
|
pricing:
|
||||||
|
input: '2.50'
|
||||||
|
output: '10.00'
|
||||||
|
unit: '0.000001'
|
||||||
|
currency: USD
|
@ -118,6 +118,9 @@ class _CommonWenxin:
|
|||||||
'ernie-4.0-turbo-8k-preview': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-4.0-turbo-8k-preview',
|
'ernie-4.0-turbo-8k-preview': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-4.0-turbo-8k-preview',
|
||||||
'yi_34b_chat': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/yi_34b_chat',
|
'yi_34b_chat': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/yi_34b_chat',
|
||||||
'embedding-v1': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/embeddings/embedding-v1',
|
'embedding-v1': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/embeddings/embedding-v1',
|
||||||
|
'bge-large-en': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/embeddings/bge_large_en',
|
||||||
|
'bge-large-zh': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/embeddings/bge_large_zh',
|
||||||
|
'tao-8k': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/embeddings/tao_8k',
|
||||||
}
|
}
|
||||||
|
|
||||||
function_calling_supports = [
|
function_calling_supports = [
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
model: bge-large-en
|
||||||
|
model_type: text-embedding
|
||||||
|
model_properties:
|
||||||
|
context_size: 512
|
||||||
|
max_chunks: 16
|
||||||
|
pricing:
|
||||||
|
input: '0.0005'
|
||||||
|
unit: '0.001'
|
||||||
|
currency: RMB
|
@ -0,0 +1,9 @@
|
|||||||
|
model: bge-large-zh
|
||||||
|
model_type: text-embedding
|
||||||
|
model_properties:
|
||||||
|
context_size: 512
|
||||||
|
max_chunks: 16
|
||||||
|
pricing:
|
||||||
|
input: '0.0005'
|
||||||
|
unit: '0.001'
|
||||||
|
currency: RMB
|
@ -0,0 +1,9 @@
|
|||||||
|
model: tao-8k
|
||||||
|
model_type: text-embedding
|
||||||
|
model_properties:
|
||||||
|
context_size: 8192
|
||||||
|
max_chunks: 1
|
||||||
|
pricing:
|
||||||
|
input: '0.0005'
|
||||||
|
unit: '0.001'
|
||||||
|
currency: RMB
|
@ -85,7 +85,8 @@ class XinferenceAILargeLanguageModel(LargeLanguageModel):
|
|||||||
tools=tools, stop=stop, stream=stream, user=user,
|
tools=tools, stop=stop, stream=stream, user=user,
|
||||||
extra_model_kwargs=XinferenceHelper.get_xinference_extra_parameter(
|
extra_model_kwargs=XinferenceHelper.get_xinference_extra_parameter(
|
||||||
server_url=credentials['server_url'],
|
server_url=credentials['server_url'],
|
||||||
model_uid=credentials['model_uid']
|
model_uid=credentials['model_uid'],
|
||||||
|
api_key=credentials.get('api_key'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -106,7 +107,8 @@ class XinferenceAILargeLanguageModel(LargeLanguageModel):
|
|||||||
|
|
||||||
extra_param = XinferenceHelper.get_xinference_extra_parameter(
|
extra_param = XinferenceHelper.get_xinference_extra_parameter(
|
||||||
server_url=credentials['server_url'],
|
server_url=credentials['server_url'],
|
||||||
model_uid=credentials['model_uid']
|
model_uid=credentials['model_uid'],
|
||||||
|
api_key=credentials.get('api_key')
|
||||||
)
|
)
|
||||||
if 'completion_type' not in credentials:
|
if 'completion_type' not in credentials:
|
||||||
if 'chat' in extra_param.model_ability:
|
if 'chat' in extra_param.model_ability:
|
||||||
@ -396,7 +398,8 @@ class XinferenceAILargeLanguageModel(LargeLanguageModel):
|
|||||||
else:
|
else:
|
||||||
extra_args = XinferenceHelper.get_xinference_extra_parameter(
|
extra_args = XinferenceHelper.get_xinference_extra_parameter(
|
||||||
server_url=credentials['server_url'],
|
server_url=credentials['server_url'],
|
||||||
model_uid=credentials['model_uid']
|
model_uid=credentials['model_uid'],
|
||||||
|
api_key=credentials.get('api_key')
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'chat' in extra_args.model_ability:
|
if 'chat' in extra_args.model_ability:
|
||||||
@ -464,6 +467,7 @@ class XinferenceAILargeLanguageModel(LargeLanguageModel):
|
|||||||
|
|
||||||
xinference_client = Client(
|
xinference_client = Client(
|
||||||
base_url=credentials['server_url'],
|
base_url=credentials['server_url'],
|
||||||
|
api_key=credentials.get('api_key'),
|
||||||
)
|
)
|
||||||
|
|
||||||
xinference_model = xinference_client.get_model(credentials['model_uid'])
|
xinference_model = xinference_client.get_model(credentials['model_uid'])
|
||||||
|
@ -108,7 +108,8 @@ class XinferenceRerankModel(RerankModel):
|
|||||||
|
|
||||||
# initialize client
|
# initialize client
|
||||||
client = Client(
|
client = Client(
|
||||||
base_url=credentials['server_url']
|
base_url=credentials['server_url'],
|
||||||
|
api_key=credentials.get('api_key'),
|
||||||
)
|
)
|
||||||
|
|
||||||
xinference_client = client.get_model(model_uid=credentials['model_uid'])
|
xinference_client = client.get_model(model_uid=credentials['model_uid'])
|
||||||
|
@ -52,7 +52,8 @@ class XinferenceSpeech2TextModel(Speech2TextModel):
|
|||||||
|
|
||||||
# initialize client
|
# initialize client
|
||||||
client = Client(
|
client = Client(
|
||||||
base_url=credentials['server_url']
|
base_url=credentials['server_url'],
|
||||||
|
api_key=credentials.get('api_key'),
|
||||||
)
|
)
|
||||||
|
|
||||||
xinference_client = client.get_model(model_uid=credentials['model_uid'])
|
xinference_client = client.get_model(model_uid=credentials['model_uid'])
|
||||||
|
@ -110,14 +110,22 @@ class XinferenceTextEmbeddingModel(TextEmbeddingModel):
|
|||||||
|
|
||||||
server_url = credentials['server_url']
|
server_url = credentials['server_url']
|
||||||
model_uid = credentials['model_uid']
|
model_uid = credentials['model_uid']
|
||||||
extra_args = XinferenceHelper.get_xinference_extra_parameter(server_url=server_url, model_uid=model_uid)
|
api_key = credentials.get('api_key')
|
||||||
|
extra_args = XinferenceHelper.get_xinference_extra_parameter(
|
||||||
|
server_url=server_url,
|
||||||
|
model_uid=model_uid,
|
||||||
|
api_key=api_key,
|
||||||
|
)
|
||||||
|
|
||||||
if extra_args.max_tokens:
|
if extra_args.max_tokens:
|
||||||
credentials['max_tokens'] = extra_args.max_tokens
|
credentials['max_tokens'] = extra_args.max_tokens
|
||||||
if server_url.endswith('/'):
|
if server_url.endswith('/'):
|
||||||
server_url = server_url[:-1]
|
server_url = server_url[:-1]
|
||||||
|
|
||||||
client = Client(base_url=server_url)
|
client = Client(
|
||||||
|
base_url=server_url,
|
||||||
|
api_key=api_key,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handle = client.get_model(model_uid=model_uid)
|
handle = client.get_model(model_uid=model_uid)
|
||||||
|
@ -81,7 +81,8 @@ class XinferenceText2SpeechModel(TTSModel):
|
|||||||
|
|
||||||
extra_param = XinferenceHelper.get_xinference_extra_parameter(
|
extra_param = XinferenceHelper.get_xinference_extra_parameter(
|
||||||
server_url=credentials['server_url'],
|
server_url=credentials['server_url'],
|
||||||
model_uid=credentials['model_uid']
|
model_uid=credentials['model_uid'],
|
||||||
|
api_key=credentials.get('api_key'),
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'text-to-audio' not in extra_param.model_ability:
|
if 'text-to-audio' not in extra_param.model_ability:
|
||||||
@ -203,7 +204,11 @@ class XinferenceText2SpeechModel(TTSModel):
|
|||||||
credentials['server_url'] = credentials['server_url'][:-1]
|
credentials['server_url'] = credentials['server_url'][:-1]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handle = RESTfulAudioModelHandle(credentials['model_uid'], credentials['server_url'], auth_headers={})
|
api_key = credentials.get('api_key')
|
||||||
|
auth_headers = {'Authorization': f'Bearer {api_key}'} if api_key else {}
|
||||||
|
handle = RESTfulAudioModelHandle(
|
||||||
|
credentials['model_uid'], credentials['server_url'], auth_headers=auth_headers
|
||||||
|
)
|
||||||
|
|
||||||
model_support_voice = [x.get("value") for x in
|
model_support_voice = [x.get("value") for x in
|
||||||
self.get_tts_model_voices(model=model, credentials=credentials)]
|
self.get_tts_model_voices(model=model, credentials=credentials)]
|
||||||
|
@ -35,13 +35,13 @@ cache_lock = Lock()
|
|||||||
|
|
||||||
class XinferenceHelper:
|
class XinferenceHelper:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_xinference_extra_parameter(server_url: str, model_uid: str) -> XinferenceModelExtraParameter:
|
def get_xinference_extra_parameter(server_url: str, model_uid: str, api_key: str) -> XinferenceModelExtraParameter:
|
||||||
XinferenceHelper._clean_cache()
|
XinferenceHelper._clean_cache()
|
||||||
with cache_lock:
|
with cache_lock:
|
||||||
if model_uid not in cache:
|
if model_uid not in cache:
|
||||||
cache[model_uid] = {
|
cache[model_uid] = {
|
||||||
'expires': time() + 300,
|
'expires': time() + 300,
|
||||||
'value': XinferenceHelper._get_xinference_extra_parameter(server_url, model_uid)
|
'value': XinferenceHelper._get_xinference_extra_parameter(server_url, model_uid, api_key)
|
||||||
}
|
}
|
||||||
return cache[model_uid]['value']
|
return cache[model_uid]['value']
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ class XinferenceHelper:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_xinference_extra_parameter(server_url: str, model_uid: str) -> XinferenceModelExtraParameter:
|
def _get_xinference_extra_parameter(server_url: str, model_uid: str, api_key: str) -> XinferenceModelExtraParameter:
|
||||||
"""
|
"""
|
||||||
get xinference model extra parameter like model_format and model_handle_type
|
get xinference model extra parameter like model_format and model_handle_type
|
||||||
"""
|
"""
|
||||||
@ -70,9 +70,10 @@ class XinferenceHelper:
|
|||||||
session = Session()
|
session = Session()
|
||||||
session.mount('http://', HTTPAdapter(max_retries=3))
|
session.mount('http://', HTTPAdapter(max_retries=3))
|
||||||
session.mount('https://', HTTPAdapter(max_retries=3))
|
session.mount('https://', HTTPAdapter(max_retries=3))
|
||||||
|
headers = {'Authorization': f'Bearer {api_key}'} if api_key else {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = session.get(url, timeout=10)
|
response = session.get(url, headers=headers, timeout=10)
|
||||||
except (MissingSchema, ConnectionError, Timeout) as e:
|
except (MissingSchema, ConnectionError, Timeout) as e:
|
||||||
raise RuntimeError(f'get xinference model extra parameter failed, url: {url}, error: {e}')
|
raise RuntimeError(f'get xinference model extra parameter failed, url: {url}, error: {e}')
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
|
@ -152,8 +152,27 @@ class PGVector(BaseVector):
|
|||||||
return docs
|
return docs
|
||||||
|
|
||||||
def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]:
|
def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]:
|
||||||
# do not support bm25 search
|
top_k = kwargs.get("top_k", 5)
|
||||||
return []
|
|
||||||
|
with self._get_cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
f"""SELECT meta, text, ts_rank(to_tsvector(coalesce(text, '')), to_tsquery(%s)) AS score
|
||||||
|
FROM {self.table_name}
|
||||||
|
WHERE to_tsvector(text) @@ plainto_tsquery(%s)
|
||||||
|
ORDER BY score DESC
|
||||||
|
LIMIT {top_k}""",
|
||||||
|
# f"'{query}'" is required in order to account for whitespace in query
|
||||||
|
(f"'{query}'", f"'{query}'"),
|
||||||
|
)
|
||||||
|
|
||||||
|
docs = []
|
||||||
|
|
||||||
|
for record in cur:
|
||||||
|
metadata, text, score = record
|
||||||
|
metadata["score"] = score
|
||||||
|
docs.append(Document(page_content=text, metadata=metadata))
|
||||||
|
|
||||||
|
return docs
|
||||||
|
|
||||||
def delete(self) -> None:
|
def delete(self) -> None:
|
||||||
with self._get_cursor() as cur:
|
with self._get_cursor() as cur:
|
||||||
|
49
api/core/tools/provider/builtin/crossref/_assets/icon.svg
Normal file
49
api/core/tools/provider/builtin/crossref/_assets/icon.svg
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 200 130.2" style="enable-background:new 0 0 200 130.2;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#3EB1C8;}
|
||||||
|
.st1{fill:#D8D2C4;}
|
||||||
|
.st2{fill:#4F5858;}
|
||||||
|
.st3{fill:#FFC72C;}
|
||||||
|
.st4{fill:#EF3340;}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<polygon class="st0" points="111.8,95.5 111.8,66.8 135.4,59 177.2,73.3 "/>
|
||||||
|
<polygon class="st1" points="153.6,36.8 111.8,51.2 135.4,59 177.2,44.6 "/>
|
||||||
|
<polygon class="st2" points="135.4,59 177.2,44.6 177.2,73.3 "/>
|
||||||
|
<polygon class="st3" points="177.2,0.3 177.2,29 153.6,36.8 111.8,22.5 "/>
|
||||||
|
<polygon class="st4" points="153.6,36.8 111.8,51.2 111.8,22.5 "/>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st2" d="M26.3,104.8c-0.5-3.7-4.1-6.5-8.1-6.5c-7.3,0-10.1,6.2-10.1,12.7c0,6.2,2.8,12.4,10.1,12.4
|
||||||
|
c5,0,7.8-3.4,8.4-8.3h7.9c-0.8,9.2-7.2,15.2-16.3,15.2C6.8,130.2,0,121.7,0,111c0-11,6.8-19.6,18.2-19.6c8.2,0,15,4.8,16,13.3
|
||||||
|
H26.3z"/>
|
||||||
|
<path class="st2" d="M37.4,102.5h7v5h0.1c1.4-3.4,5-5.7,8.6-5.7c0.5,0,1.1,0.1,1.6,0.3v6.9c-0.7-0.2-1.8-0.3-2.6-0.3
|
||||||
|
c-5.4,0-7.3,3.9-7.3,8.6v12.1h-7.4V102.5z"/>
|
||||||
|
<path class="st2" d="M68.7,101.8c8.5,0,13.9,5.6,13.9,14.2c0,8.5-5.5,14.1-13.9,14.1c-8.4,0-13.9-5.6-13.9-14.1
|
||||||
|
C54.9,107.4,60.3,101.8,68.7,101.8z M68.7,124.5c5,0,6.5-4.3,6.5-8.6c0-4.3-1.5-8.6-6.5-8.6c-5,0-6.5,4.3-6.5,8.6
|
||||||
|
C62.2,120.2,63.8,124.5,68.7,124.5z"/>
|
||||||
|
<path class="st2" d="M91.2,120.6c0.1,3.2,2.8,4.5,5.7,4.5c2.1,0,4.8-0.8,4.8-3.4c0-2.2-3.1-3-8.4-4.2c-4.3-0.9-8.5-2.4-8.5-7.2
|
||||||
|
c0-6.9,5.9-8.6,11.7-8.6c5.9,0,11.3,2,11.8,8.6h-7c-0.2-2.9-2.4-3.6-5-3.6c-1.7,0-4.1,0.3-4.1,2.5c0,2.6,4.2,3,8.4,4
|
||||||
|
c4.3,1,8.5,2.5,8.5,7.5c0,7.1-6.1,9.3-12.3,9.3c-6.2,0-12.3-2.3-12.6-9.5H91.2z"/>
|
||||||
|
<path class="st2" d="M118.1,120.6c0.1,3.2,2.8,4.5,5.7,4.5c2.1,0,4.8-0.8,4.8-3.4c0-2.2-3.1-3-8.4-4.2
|
||||||
|
c-4.3-0.9-8.5-2.4-8.5-7.2c0-6.9,5.9-8.6,11.7-8.6c5.9,0,11.3,2,11.8,8.6h-7c-0.2-2.9-2.4-3.6-5-3.6c-1.7,0-4.1,0.3-4.1,2.5
|
||||||
|
c0,2.6,4.2,3,8.4,4c4.3,1,8.5,2.5,8.5,7.5c0,7.1-6.1,9.3-12.3,9.3c-6.2,0-12.3-2.3-12.6-9.5H118.1z"/>
|
||||||
|
<path class="st2" d="M138.4,102.5h7v5h0.1c1.4-3.4,5-5.7,8.6-5.7c0.5,0,1.1,0.1,1.6,0.3v6.9c-0.7-0.2-1.8-0.3-2.6-0.3
|
||||||
|
c-5.4,0-7.3,3.9-7.3,8.6v12.1h-7.4V102.5z"/>
|
||||||
|
<path class="st2" d="M163.7,117.7c0.2,4.7,2.5,6.8,6.6,6.8c3,0,5.3-1.8,5.8-3.5h6.5c-2.1,6.3-6.5,9-12.6,9
|
||||||
|
c-8.5,0-13.7-5.8-13.7-14.1c0-8,5.6-14.2,13.7-14.2c9.1,0,13.6,7.7,13,15.9H163.7z M175.7,113.1c-0.7-3.7-2.3-5.7-5.9-5.7
|
||||||
|
c-4.7,0-6,3.6-6.1,5.7H175.7z"/>
|
||||||
|
<path class="st2" d="M187.2,107.5h-4.4v-4.9h4.4v-2.1c0-4.7,3-8.2,9-8.2c1.3,0,2.6,0.2,3.9,0.2V98c-0.9-0.1-1.8-0.2-2.7-0.2
|
||||||
|
c-2,0-2.8,0.8-2.8,3.1v1.6h5.1v4.9h-5.1v21.9h-7.4V107.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
20
api/core/tools/provider/builtin/crossref/crossref.py
Normal file
20
api/core/tools/provider/builtin/crossref/crossref.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from core.tools.errors import ToolProviderCredentialValidationError
|
||||||
|
from core.tools.provider.builtin.crossref.tools.query_doi import CrossRefQueryDOITool
|
||||||
|
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||||
|
|
||||||
|
|
||||||
|
class CrossRefProvider(BuiltinToolProviderController):
|
||||||
|
def _validate_credentials(self, credentials: dict) -> None:
|
||||||
|
try:
|
||||||
|
CrossRefQueryDOITool().fork_tool_runtime(
|
||||||
|
runtime={
|
||||||
|
"credentials": credentials,
|
||||||
|
}
|
||||||
|
).invoke(
|
||||||
|
user_id='',
|
||||||
|
tool_parameters={
|
||||||
|
"doi": '10.1007/s00894-022-05373-8',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise ToolProviderCredentialValidationError(str(e))
|
29
api/core/tools/provider/builtin/crossref/crossref.yaml
Normal file
29
api/core/tools/provider/builtin/crossref/crossref.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
identity:
|
||||||
|
author: Sakura4036
|
||||||
|
name: crossref
|
||||||
|
label:
|
||||||
|
en_US: CrossRef
|
||||||
|
zh_Hans: CrossRef
|
||||||
|
description:
|
||||||
|
en_US: Crossref is a cross-publisher reference linking registration query system using DOI technology created in 2000. Crossref establishes cross-database links between the reference list and citation full text of papers, making it very convenient for readers to access the full text of papers.
|
||||||
|
zh_Hans: Crossref是于2000年创建的使用DOI技术的跨出版商参考文献链接注册查询系统。Crossref建立了在论文的参考文献列表和引文全文之间的跨数据库链接,使得读者能够非常便捷地获取文献全文。
|
||||||
|
icon: icon.svg
|
||||||
|
tags:
|
||||||
|
- search
|
||||||
|
credentials_for_provider:
|
||||||
|
mailto:
|
||||||
|
type: text-input
|
||||||
|
required: true
|
||||||
|
label:
|
||||||
|
en_US: email address
|
||||||
|
zh_Hans: email地址
|
||||||
|
pt_BR: email address
|
||||||
|
placeholder:
|
||||||
|
en_US: Please input your email address
|
||||||
|
zh_Hans: 请输入你的email地址
|
||||||
|
pt_BR: Please input your email address
|
||||||
|
help:
|
||||||
|
en_US: According to the requirements of Crossref, an email address is required
|
||||||
|
zh_Hans: 根据Crossref的要求,需要提供一个邮箱地址
|
||||||
|
pt_BR: According to the requirements of Crossref, an email address is required
|
||||||
|
url: https://api.crossref.org/swagger-ui/index.html
|
25
api/core/tools/provider/builtin/crossref/tools/query_doi.py
Normal file
25
api/core/tools/provider/builtin/crossref/tools/query_doi.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||||
|
from core.tools.errors import ToolParameterValidationError
|
||||||
|
from core.tools.tool.builtin_tool import BuiltinTool
|
||||||
|
|
||||||
|
|
||||||
|
class CrossRefQueryDOITool(BuiltinTool):
|
||||||
|
"""
|
||||||
|
Tool for querying the metadata of a publication using its DOI.
|
||||||
|
"""
|
||||||
|
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||||
|
doi = tool_parameters.get('doi')
|
||||||
|
if not doi:
|
||||||
|
raise ToolParameterValidationError('doi is required.')
|
||||||
|
# doc: https://github.com/CrossRef/rest-api-doc
|
||||||
|
url = f"https://api.crossref.org/works/{doi}"
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
response = response.json()
|
||||||
|
message = response.get('message', {})
|
||||||
|
|
||||||
|
return self.create_json_message(message)
|
@ -0,0 +1,23 @@
|
|||||||
|
identity:
|
||||||
|
name: crossref_query_doi
|
||||||
|
author: Sakura4036
|
||||||
|
label:
|
||||||
|
en_US: CrossRef Query DOI
|
||||||
|
zh_Hans: CrossRef DOI 查询
|
||||||
|
pt_BR: CrossRef Query DOI
|
||||||
|
description:
|
||||||
|
human:
|
||||||
|
en_US: A tool for searching literature information using CrossRef by DOI.
|
||||||
|
zh_Hans: 一个使用CrossRef通过DOI获取文献信息的工具。
|
||||||
|
pt_BR: A tool for searching literature information using CrossRef by DOI.
|
||||||
|
llm: A tool for searching literature information using CrossRef by DOI.
|
||||||
|
parameters:
|
||||||
|
- name: doi
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
label:
|
||||||
|
en_US: DOI
|
||||||
|
zh_Hans: DOI
|
||||||
|
pt_BR: DOI
|
||||||
|
llm_description: DOI for searching in CrossRef
|
||||||
|
form: llm
|
120
api/core/tools/provider/builtin/crossref/tools/query_title.py
Normal file
120
api/core/tools/provider/builtin/crossref/tools/query_title.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import time
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||||
|
from core.tools.tool.builtin_tool import BuiltinTool
|
||||||
|
|
||||||
|
|
||||||
|
def convert_time_str_to_seconds(time_str: str) -> int:
|
||||||
|
"""
|
||||||
|
Convert a time string to seconds.
|
||||||
|
example: 1s -> 1, 1m30s -> 90, 1h30m -> 5400, 1h30m30s -> 5430
|
||||||
|
"""
|
||||||
|
time_str = time_str.lower().strip().replace(' ', '')
|
||||||
|
seconds = 0
|
||||||
|
if 'h' in time_str:
|
||||||
|
hours, time_str = time_str.split('h')
|
||||||
|
seconds += int(hours) * 3600
|
||||||
|
if 'm' in time_str:
|
||||||
|
minutes, time_str = time_str.split('m')
|
||||||
|
seconds += int(minutes) * 60
|
||||||
|
if 's' in time_str:
|
||||||
|
seconds += int(time_str.replace('s', ''))
|
||||||
|
return seconds
|
||||||
|
|
||||||
|
|
||||||
|
class CrossRefQueryTitleAPI:
|
||||||
|
"""
|
||||||
|
Tool for querying the metadata of a publication using its title.
|
||||||
|
Crossref API doc: https://github.com/CrossRef/rest-api-doc
|
||||||
|
"""
|
||||||
|
query_url_template: str = "https://api.crossref.org/works?query.bibliographic={query}&rows={rows}&offset={offset}&sort={sort}&order={order}&mailto={mailto}"
|
||||||
|
rate_limit: int = 50
|
||||||
|
rate_interval: float = 1
|
||||||
|
max_limit: int = 1000
|
||||||
|
|
||||||
|
def __init__(self, mailto: str):
|
||||||
|
self.mailto = mailto
|
||||||
|
|
||||||
|
def _query(self, query: str, rows: int = 5, offset: int = 0, sort: str = 'relevance', order: str = 'desc', fuzzy_query: bool = False) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Query the metadata of a publication using its title.
|
||||||
|
:param query: the title of the publication
|
||||||
|
:param rows: the number of results to return
|
||||||
|
:param sort: the sort field
|
||||||
|
:param order: the sort order
|
||||||
|
:param fuzzy_query: whether to return all items that match the query
|
||||||
|
"""
|
||||||
|
url = self.query_url_template.format(query=query, rows=rows, offset=offset, sort=sort, order=order, mailto=self.mailto)
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
rate_limit = int(response.headers['x-ratelimit-limit'])
|
||||||
|
# convert time string to seconds
|
||||||
|
rate_interval = convert_time_str_to_seconds(response.headers['x-ratelimit-interval'])
|
||||||
|
|
||||||
|
self.rate_limit = rate_limit
|
||||||
|
self.rate_interval = rate_interval
|
||||||
|
|
||||||
|
response = response.json()
|
||||||
|
if response['status'] != 'ok':
|
||||||
|
return []
|
||||||
|
|
||||||
|
message = response['message']
|
||||||
|
if fuzzy_query:
|
||||||
|
# fuzzy query return all items
|
||||||
|
return message['items']
|
||||||
|
else:
|
||||||
|
for paper in message['items']:
|
||||||
|
title = paper['title'][0]
|
||||||
|
if title.lower() != query.lower():
|
||||||
|
continue
|
||||||
|
return [paper]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def query(self, query: str, rows: int = 5, sort: str = 'relevance', order: str = 'desc', fuzzy_query: bool = False) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Query the metadata of a publication using its title.
|
||||||
|
:param query: the title of the publication
|
||||||
|
:param rows: the number of results to return
|
||||||
|
:param sort: the sort field
|
||||||
|
:param order: the sort order
|
||||||
|
:param fuzzy_query: whether to return all items that match the query
|
||||||
|
"""
|
||||||
|
rows = min(rows, self.max_limit)
|
||||||
|
if rows > self.rate_limit:
|
||||||
|
# query multiple times
|
||||||
|
query_times = rows // self.rate_limit + 1
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for i in range(query_times):
|
||||||
|
result = self._query(query, rows=self.rate_limit, offset=i * self.rate_limit, sort=sort, order=order, fuzzy_query=fuzzy_query)
|
||||||
|
if fuzzy_query:
|
||||||
|
results.extend(result)
|
||||||
|
else:
|
||||||
|
# fuzzy_query=False, only one result
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
time.sleep(self.rate_interval)
|
||||||
|
return results
|
||||||
|
else:
|
||||||
|
# query once
|
||||||
|
return self._query(query, rows, sort=sort, order=order, fuzzy_query=fuzzy_query)
|
||||||
|
|
||||||
|
|
||||||
|
class CrossRefQueryTitleTool(BuiltinTool):
|
||||||
|
"""
|
||||||
|
Tool for querying the metadata of a publication using its title.
|
||||||
|
"""
|
||||||
|
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||||
|
query = tool_parameters.get('query')
|
||||||
|
fuzzy_query = tool_parameters.get('fuzzy_query', False)
|
||||||
|
rows = tool_parameters.get('rows', 3)
|
||||||
|
sort = tool_parameters.get('sort', 'relevance')
|
||||||
|
order = tool_parameters.get('order', 'desc')
|
||||||
|
mailto = self.runtime.credentials['mailto']
|
||||||
|
|
||||||
|
result = CrossRefQueryTitleAPI(mailto).query(query, rows, sort, order, fuzzy_query)
|
||||||
|
|
||||||
|
return [self.create_json_message(r) for r in result]
|
105
api/core/tools/provider/builtin/crossref/tools/query_title.yaml
Normal file
105
api/core/tools/provider/builtin/crossref/tools/query_title.yaml
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
identity:
|
||||||
|
name: crossref_query_title
|
||||||
|
author: Sakura4036
|
||||||
|
label:
|
||||||
|
en_US: CrossRef Title Query
|
||||||
|
zh_Hans: CrossRef 标题查询
|
||||||
|
pt_BR: CrossRef Title Query
|
||||||
|
description:
|
||||||
|
human:
|
||||||
|
en_US: A tool for querying literature information using CrossRef by title.
|
||||||
|
zh_Hans: 一个使用CrossRef通过标题搜索文献信息的工具。
|
||||||
|
pt_BR: A tool for querying literature information using CrossRef by title.
|
||||||
|
llm: A tool for querying literature information using CrossRef by title.
|
||||||
|
parameters:
|
||||||
|
- name: query
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
label:
|
||||||
|
en_US: 标题
|
||||||
|
zh_Hans: 查询语句
|
||||||
|
pt_BR: 标题
|
||||||
|
human_description:
|
||||||
|
en_US: Query bibliographic information, useful for citation look up. Includes titles, authors, ISSNs and publication years
|
||||||
|
zh_Hans: 用于搜索文献信息,有助于查找引用。包括标题,作者,ISSN和出版年份
|
||||||
|
pt_BR: Query bibliographic information, useful for citation look up. Includes titles, authors, ISSNs and publication years
|
||||||
|
llm_description: key words for querying in Web of Science
|
||||||
|
form: llm
|
||||||
|
- name: fuzzy_query
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
label:
|
||||||
|
en_US: Whether to fuzzy search
|
||||||
|
zh_Hans: 是否模糊搜索
|
||||||
|
pt_BR: Whether to fuzzy search
|
||||||
|
human_description:
|
||||||
|
en_US: used for selecting the query type, fuzzy query returns more results, precise query returns 1 or none
|
||||||
|
zh_Hans: 用于选择搜索类型,模糊搜索返回更多结果,精确搜索返回1条结果或无
|
||||||
|
pt_BR: used for selecting the query type, fuzzy query returns more results, precise query returns 1 or none
|
||||||
|
form: form
|
||||||
|
- name: limit
|
||||||
|
type: number
|
||||||
|
required: false
|
||||||
|
label:
|
||||||
|
en_US: max query number
|
||||||
|
zh_Hans: 最大搜索数
|
||||||
|
pt_BR: max query number
|
||||||
|
human_description:
|
||||||
|
en_US: max query number(fuzzy search returns the maximum number of results or precise search the maximum number of matches)
|
||||||
|
zh_Hans: 最大搜索数(模糊搜索返回的最大结果数或精确搜索最大匹配数)
|
||||||
|
pt_BR: max query number(fuzzy search returns the maximum number of results or precise search the maximum number of matches)
|
||||||
|
form: llm
|
||||||
|
default: 50
|
||||||
|
- name: sort
|
||||||
|
type: select
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
- value: relevance
|
||||||
|
label:
|
||||||
|
en_US: relevance
|
||||||
|
zh_Hans: 相关性
|
||||||
|
pt_BR: relevance
|
||||||
|
- value: published
|
||||||
|
label:
|
||||||
|
en_US: publication date
|
||||||
|
zh_Hans: 出版日期
|
||||||
|
pt_BR: publication date
|
||||||
|
- value: references-count
|
||||||
|
label:
|
||||||
|
en_US: references-count
|
||||||
|
zh_Hans: 引用次数
|
||||||
|
pt_BR: references-count
|
||||||
|
default: relevance
|
||||||
|
label:
|
||||||
|
en_US: sorting field
|
||||||
|
zh_Hans: 排序字段
|
||||||
|
pt_BR: sorting field
|
||||||
|
human_description:
|
||||||
|
en_US: Sorting of query results
|
||||||
|
zh_Hans: 检索结果的排序字段
|
||||||
|
pt_BR: Sorting of query results
|
||||||
|
form: form
|
||||||
|
- name: order
|
||||||
|
type: select
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
- value: desc
|
||||||
|
label:
|
||||||
|
en_US: descending
|
||||||
|
zh_Hans: 降序
|
||||||
|
pt_BR: descending
|
||||||
|
- value: asc
|
||||||
|
label:
|
||||||
|
en_US: ascending
|
||||||
|
zh_Hans: 升序
|
||||||
|
pt_BR: ascending
|
||||||
|
default: desc
|
||||||
|
label:
|
||||||
|
en_US: Order
|
||||||
|
zh_Hans: 排序
|
||||||
|
pt_BR: Order
|
||||||
|
human_description:
|
||||||
|
en_US: Order of query results
|
||||||
|
zh_Hans: 检索结果的排序方式
|
||||||
|
pt_BR: Order of query results
|
||||||
|
form: form
|
@ -60,5 +60,11 @@ parameters:
|
|||||||
label:
|
label:
|
||||||
en_US: Tokenizer
|
en_US: Tokenizer
|
||||||
human_description:
|
human_description:
|
||||||
en_US: cl100k_base - gpt-4,gpt-3.5-turbo,gpt-3.5; o200k_base - gpt-4o,gpt-4o-mini; p50k_base - text-davinci-003,text-davinci-002
|
en_US: |
|
||||||
|
· cl100k_base --- gpt-4, gpt-3.5-turbo, gpt-3.5
|
||||||
|
· o200k_base --- gpt-4o, gpt-4o-mini
|
||||||
|
· p50k_base --- text-davinci-003, text-davinci-002
|
||||||
|
· r50k_base --- text-davinci-001, text-curie-001
|
||||||
|
· p50k_edit --- text-davinci-edit-001, code-davinci-edit-001
|
||||||
|
· gpt2 --- gpt-2
|
||||||
form: form
|
form: form
|
||||||
|
2
api/poetry.lock
generated
2
api/poetry.lock
generated
@ -9584,4 +9584,4 @@ cffi = ["cffi (>=1.11)"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.10,<3.13"
|
python-versions = ">=3.10,<3.13"
|
||||||
content-hash = "165e4af9cfbce83ee831dd0e82159446ef595d7a7850ee8644c8e2d24dd7040d"
|
content-hash = "a74c7b6a72145d5074aa84581df6e543ea422810caf0ba1561cd2d35497243ca"
|
||||||
|
@ -156,6 +156,7 @@ markdown = "~3.5.1"
|
|||||||
novita-client = "^0.5.6"
|
novita-client = "^0.5.6"
|
||||||
numpy = "~1.26.4"
|
numpy = "~1.26.4"
|
||||||
openai = "~1.29.0"
|
openai = "~1.29.0"
|
||||||
|
openpyxl = "~3.1.5"
|
||||||
oss2 = "2.18.5"
|
oss2 = "2.18.5"
|
||||||
pandas = { version = "~2.2.2", extras = ["performance", "excel"] }
|
pandas = { version = "~2.2.2", extras = ["performance", "excel"] }
|
||||||
psycopg2-binary = "~2.9.6"
|
psycopg2-binary = "~2.9.6"
|
||||||
@ -173,7 +174,6 @@ readabilipy = "0.2.0"
|
|||||||
redis = { version = "~5.0.3", extras = ["hiredis"] }
|
redis = { version = "~5.0.3", extras = ["hiredis"] }
|
||||||
replicate = "~0.22.0"
|
replicate = "~0.22.0"
|
||||||
resend = "~0.7.0"
|
resend = "~0.7.0"
|
||||||
safetensors = "~0.4.3"
|
|
||||||
scikit-learn = "^1.5.1"
|
scikit-learn = "^1.5.1"
|
||||||
sentry-sdk = { version = "~1.44.1", extras = ["flask"] }
|
sentry-sdk = { version = "~1.44.1", extras = ["flask"] }
|
||||||
sqlalchemy = "~2.0.29"
|
sqlalchemy = "~2.0.29"
|
||||||
@ -187,10 +187,16 @@ werkzeug = "~3.0.1"
|
|||||||
xinference-client = "0.13.3"
|
xinference-client = "0.13.3"
|
||||||
yarl = "~1.9.4"
|
yarl = "~1.9.4"
|
||||||
zhipuai = "1.0.7"
|
zhipuai = "1.0.7"
|
||||||
rank-bm25 = "~0.2.2"
|
# Before adding new dependency, consider place it in alphabet order (a-z) and suitable group.
|
||||||
openpyxl = "^3.1.5"
|
|
||||||
|
############################################################
|
||||||
|
# Related transparent dependencies with pinned verion
|
||||||
|
# required by main implementations
|
||||||
|
############################################################
|
||||||
|
[tool.poetry.group.indriect.dependencies]
|
||||||
kaleido = "0.2.1"
|
kaleido = "0.2.1"
|
||||||
elasticsearch = "8.14.0"
|
rank-bm25 = "~0.2.2"
|
||||||
|
safetensors = "~0.4.3"
|
||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
# Tool dependencies required by tool implementations
|
# Tool dependencies required by tool implementations
|
||||||
@ -198,6 +204,7 @@ elasticsearch = "8.14.0"
|
|||||||
|
|
||||||
[tool.poetry.group.tool.dependencies]
|
[tool.poetry.group.tool.dependencies]
|
||||||
arxiv = "2.1.0"
|
arxiv = "2.1.0"
|
||||||
|
cloudscraper = "1.2.71"
|
||||||
matplotlib = "~3.8.2"
|
matplotlib = "~3.8.2"
|
||||||
newspaper3k = "0.2.8"
|
newspaper3k = "0.2.8"
|
||||||
duckduckgo-search = "^6.2.6"
|
duckduckgo-search = "^6.2.6"
|
||||||
@ -209,26 +216,25 @@ twilio = "~9.0.4"
|
|||||||
vanna = { version = "0.5.5", extras = ["postgres", "mysql", "clickhouse", "duckdb"] }
|
vanna = { version = "0.5.5", extras = ["postgres", "mysql", "clickhouse", "duckdb"] }
|
||||||
wikipedia = "1.4.0"
|
wikipedia = "1.4.0"
|
||||||
yfinance = "~0.2.40"
|
yfinance = "~0.2.40"
|
||||||
cloudscraper = "1.2.71"
|
|
||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
# VDB dependencies required by vector store clients
|
# VDB dependencies required by vector store clients
|
||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
[tool.poetry.group.vdb.dependencies]
|
[tool.poetry.group.vdb.dependencies]
|
||||||
|
alibabacloud_gpdb20160503 = "~3.8.0"
|
||||||
|
alibabacloud_tea_openapi = "~0.3.9"
|
||||||
chromadb = "0.5.1"
|
chromadb = "0.5.1"
|
||||||
|
clickhouse-connect = "~0.7.16"
|
||||||
|
elasticsearch = "8.14.0"
|
||||||
oracledb = "~2.2.1"
|
oracledb = "~2.2.1"
|
||||||
pgvecto-rs = { version = "~0.2.1", extras = ['sqlalchemy'] }
|
pgvecto-rs = { version = "~0.2.1", extras = ['sqlalchemy'] }
|
||||||
pgvector = "0.2.5"
|
pgvector = "0.2.5"
|
||||||
pymilvus = "~2.4.4"
|
pymilvus = "~2.4.4"
|
||||||
pymysql = "1.1.1"
|
|
||||||
tcvectordb = "1.3.2"
|
tcvectordb = "1.3.2"
|
||||||
tidb-vector = "0.0.9"
|
tidb-vector = "0.0.9"
|
||||||
qdrant-client = "1.7.3"
|
qdrant-client = "1.7.3"
|
||||||
weaviate-client = "~3.21.0"
|
weaviate-client = "~3.21.0"
|
||||||
alibabacloud_gpdb20160503 = "~3.8.0"
|
|
||||||
alibabacloud_tea_openapi = "~0.3.9"
|
|
||||||
clickhouse-connect = "~0.7.16"
|
|
||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
# Dev dependencies for running tests
|
# Dev dependencies for running tests
|
||||||
@ -252,5 +258,5 @@ pytest-mock = "~3.14.0"
|
|||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[tool.poetry.group.lint.dependencies]
|
[tool.poetry.group.lint.dependencies]
|
||||||
ruff = "~0.6.1"
|
|
||||||
dotenv-linter = "~0.5.0"
|
dotenv-linter = "~0.5.0"
|
||||||
|
ruff = "~0.6.1"
|
||||||
|
@ -5,7 +5,7 @@ from core.model_runtime.entities.text_embedding_entities import TextEmbeddingRes
|
|||||||
from core.model_runtime.model_providers.wenxin.text_embedding.text_embedding import WenxinTextEmbeddingModel
|
from core.model_runtime.model_providers.wenxin.text_embedding.text_embedding import WenxinTextEmbeddingModel
|
||||||
|
|
||||||
|
|
||||||
def test_invoke_embedding_model():
|
def test_invoke_embedding_v1():
|
||||||
sleep(3)
|
sleep(3)
|
||||||
model = WenxinTextEmbeddingModel()
|
model = WenxinTextEmbeddingModel()
|
||||||
|
|
||||||
@ -22,3 +22,60 @@ def test_invoke_embedding_model():
|
|||||||
assert isinstance(response, TextEmbeddingResult)
|
assert isinstance(response, TextEmbeddingResult)
|
||||||
assert len(response.embeddings) == 3
|
assert len(response.embeddings) == 3
|
||||||
assert isinstance(response.embeddings[0], list)
|
assert isinstance(response.embeddings[0], list)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invoke_embedding_bge_large_en():
|
||||||
|
sleep(3)
|
||||||
|
model = WenxinTextEmbeddingModel()
|
||||||
|
|
||||||
|
response = model.invoke(
|
||||||
|
model='bge-large-en',
|
||||||
|
credentials={
|
||||||
|
'api_key': os.environ.get('WENXIN_API_KEY'),
|
||||||
|
'secret_key': os.environ.get('WENXIN_SECRET_KEY')
|
||||||
|
},
|
||||||
|
texts=['hello', '你好', 'xxxxx'],
|
||||||
|
user="abc-123"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(response, TextEmbeddingResult)
|
||||||
|
assert len(response.embeddings) == 3
|
||||||
|
assert isinstance(response.embeddings[0], list)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invoke_embedding_bge_large_zh():
|
||||||
|
sleep(3)
|
||||||
|
model = WenxinTextEmbeddingModel()
|
||||||
|
|
||||||
|
response = model.invoke(
|
||||||
|
model='bge-large-zh',
|
||||||
|
credentials={
|
||||||
|
'api_key': os.environ.get('WENXIN_API_KEY'),
|
||||||
|
'secret_key': os.environ.get('WENXIN_SECRET_KEY')
|
||||||
|
},
|
||||||
|
texts=['hello', '你好', 'xxxxx'],
|
||||||
|
user="abc-123"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(response, TextEmbeddingResult)
|
||||||
|
assert len(response.embeddings) == 3
|
||||||
|
assert isinstance(response.embeddings[0], list)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invoke_embedding_tao_8k():
|
||||||
|
sleep(3)
|
||||||
|
model = WenxinTextEmbeddingModel()
|
||||||
|
|
||||||
|
response = model.invoke(
|
||||||
|
model='tao-8k',
|
||||||
|
credentials={
|
||||||
|
'api_key': os.environ.get('WENXIN_API_KEY'),
|
||||||
|
'secret_key': os.environ.get('WENXIN_SECRET_KEY')
|
||||||
|
},
|
||||||
|
texts=['hello', '你好', 'xxxxx'],
|
||||||
|
user="abc-123"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(response, TextEmbeddingResult)
|
||||||
|
assert len(response.embeddings) == 3
|
||||||
|
assert isinstance(response.embeddings[0], list)
|
||||||
|
@ -21,10 +21,6 @@ class PGVectorTest(AbstractVectorTest):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def search_by_full_text(self):
|
|
||||||
hits_by_full_text: list[Document] = self.vector.search_by_full_text(query=get_example_text())
|
|
||||||
assert len(hits_by_full_text) == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_pgvector(setup_mock_redis):
|
def test_pgvector(setup_mock_redis):
|
||||||
PGVectorTest().run_all_tests()
|
PGVectorTest().run_all_tests()
|
||||||
|
@ -2,7 +2,7 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
# API service
|
# API service
|
||||||
api:
|
api:
|
||||||
image: langgenius/dify-api:0.7.0
|
image: langgenius/dify-api:0.7.1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Startup mode, 'api' starts the API server.
|
# Startup mode, 'api' starts the API server.
|
||||||
@ -229,7 +229,7 @@ services:
|
|||||||
# worker service
|
# worker service
|
||||||
# The Celery worker for processing the queue.
|
# The Celery worker for processing the queue.
|
||||||
worker:
|
worker:
|
||||||
image: langgenius/dify-api:0.7.0
|
image: langgenius/dify-api:0.7.1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
CONSOLE_WEB_URL: ''
|
CONSOLE_WEB_URL: ''
|
||||||
@ -400,7 +400,7 @@ services:
|
|||||||
|
|
||||||
# Frontend web application.
|
# Frontend web application.
|
||||||
web:
|
web:
|
||||||
image: langgenius/dify-web:0.7.0
|
image: langgenius/dify-web:0.7.1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is
|
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is
|
||||||
|
@ -34,7 +34,7 @@ services:
|
|||||||
|
|
||||||
# The DifySandbox
|
# The DifySandbox
|
||||||
sandbox:
|
sandbox:
|
||||||
image: langgenius/dify-sandbox:0.2.1
|
image: langgenius/dify-sandbox:0.2.6
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# The DifySandbox configurations
|
# The DifySandbox configurations
|
||||||
|
@ -188,7 +188,7 @@ x-shared-env: &shared-api-worker-env
|
|||||||
services:
|
services:
|
||||||
# API service
|
# API service
|
||||||
api:
|
api:
|
||||||
image: langgenius/dify-api:0.7.0
|
image: langgenius/dify-api:0.7.1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Use the shared environment variables.
|
# Use the shared environment variables.
|
||||||
@ -208,7 +208,7 @@ services:
|
|||||||
# worker service
|
# worker service
|
||||||
# The Celery worker for processing the queue.
|
# The Celery worker for processing the queue.
|
||||||
worker:
|
worker:
|
||||||
image: langgenius/dify-api:0.7.0
|
image: langgenius/dify-api:0.7.1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Use the shared environment variables.
|
# Use the shared environment variables.
|
||||||
@ -227,7 +227,7 @@ services:
|
|||||||
|
|
||||||
# Frontend web application.
|
# Frontend web application.
|
||||||
web:
|
web:
|
||||||
image: langgenius/dify-web:0.7.0
|
image: langgenius/dify-web:0.7.1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||||
@ -272,7 +272,7 @@ services:
|
|||||||
|
|
||||||
# The DifySandbox
|
# The DifySandbox
|
||||||
sandbox:
|
sandbox:
|
||||||
image: langgenius/dify-sandbox:0.2.1
|
image: langgenius/dify-sandbox:0.2.6
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# The DifySandbox configurations
|
# The DifySandbox configurations
|
||||||
|
@ -25,7 +25,7 @@ export default function ChartView({ appId }: IChartViewProps) {
|
|||||||
const appDetail = useAppStore(state => state.appDetail)
|
const appDetail = useAppStore(state => state.appDetail)
|
||||||
const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow'
|
const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow'
|
||||||
const isWorkflow = appDetail?.mode === 'workflow'
|
const isWorkflow = appDetail?.mode === 'workflow'
|
||||||
const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } })
|
const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').startOf('day').format(queryDateFormat), end: today.endOf('day').format(queryDateFormat) } })
|
||||||
|
|
||||||
const onSelect = (item: Item) => {
|
const onSelect = (item: Item) => {
|
||||||
if (item.value === 'all') {
|
if (item.value === 'all') {
|
||||||
@ -37,7 +37,7 @@ export default function ChartView({ appId }: IChartViewProps) {
|
|||||||
setPeriod({ name: item.name, query: { start: startOfToday, end: endOfToday } })
|
setPeriod({ name: item.name, query: { start: startOfToday, end: endOfToday } })
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setPeriod({ name: item.name, query: { start: today.subtract(item.value as number, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } })
|
setPeriod({ name: item.name, query: { start: today.subtract(item.value as number, 'day').startOf('day').format(queryDateFormat), end: today.endOf('day').format(queryDateFormat) } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => {
|
|||||||
...(queryParams.period !== 'all'
|
...(queryParams.period !== 'all'
|
||||||
? {
|
? {
|
||||||
start: dayjs().subtract(queryParams.period as number, 'day').startOf('day').format('YYYY-MM-DD HH:mm'),
|
start: dayjs().subtract(queryParams.period as number, 'day').startOf('day').format('YYYY-MM-DD HH:mm'),
|
||||||
end: dayjs().format('YYYY-MM-DD HH:mm'),
|
end: dayjs().endOf('day').format('YYYY-MM-DD HH:mm'),
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...omit(queryParams, ['period']),
|
...omit(queryParams, ['period']),
|
||||||
|
@ -90,7 +90,7 @@ const ChatInput: FC<ChatInputProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyUp = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleKeyUp = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (e.code === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
// prevent send message when using input method enter
|
// prevent send message when using input method enter
|
||||||
if (!e.shiftKey && !isUseInputMethod.current)
|
if (!e.shiftKey && !isUseInputMethod.current)
|
||||||
@ -100,7 +100,7 @@ const ChatInput: FC<ChatInputProps> = ({
|
|||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
isUseInputMethod.current = e.nativeEvent.isComposing
|
isUseInputMethod.current = e.nativeEvent.isComposing
|
||||||
if (e.code === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
setQuery(query.replace(/\n$/, ''))
|
setQuery(query.replace(/\n$/, ''))
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import {
|
import {
|
||||||
RiQuestionLine,
|
RiQuestionLine,
|
||||||
@ -70,6 +70,16 @@ const Form: FC<FormProps> = ({
|
|||||||
onChange({ ...value, [key]: val, ...shouldClearVariable })
|
onChange({ ...value, [key]: val, ...shouldClearVariable })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert tooltip '\n' to <br />
|
||||||
|
const renderTooltipContent = (content: string) => {
|
||||||
|
return content.split('\n').map((line, index, array) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
{line}
|
||||||
|
{index < array.length - 1 && <br />}
|
||||||
|
</Fragment>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
const renderField = (formSchema: CredentialFormSchema) => {
|
const renderField = (formSchema: CredentialFormSchema) => {
|
||||||
const tooltip = formSchema.tooltip
|
const tooltip = formSchema.tooltip
|
||||||
const tooltipContent = (tooltip && (
|
const tooltipContent = (tooltip && (
|
||||||
@ -77,7 +87,7 @@ const Form: FC<FormProps> = ({
|
|||||||
<Tooltip popupContent={
|
<Tooltip popupContent={
|
||||||
// w-[100px] caused problem
|
// w-[100px] caused problem
|
||||||
<div className=''>
|
<div className=''>
|
||||||
{tooltip[language] || tooltip.en_US}
|
{renderTooltipContent(tooltip[language] || tooltip.en_US)}
|
||||||
</div>
|
</div>
|
||||||
} >
|
} >
|
||||||
<RiQuestionLine className='w-3 h-3 text-gray-500' />
|
<RiQuestionLine className='w-3 h-3 text-gray-500' />
|
||||||
|
@ -35,7 +35,9 @@ const RunMode = memo(() => {
|
|||||||
'hover:bg-state-accent-hover cursor-pointer',
|
'hover:bg-state-accent-hover cursor-pointer',
|
||||||
isRunning && 'bg-state-accent-hover !cursor-not-allowed',
|
isRunning && 'bg-state-accent-hover !cursor-not-allowed',
|
||||||
)}
|
)}
|
||||||
onClick={() => handleWorkflowStartRunInWorkflow()}
|
onClick={() => {
|
||||||
|
handleWorkflowStartRunInWorkflow()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
isRunning
|
isRunning
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
useWorkflowInteractions,
|
useWorkflowInteractions,
|
||||||
useWorkflowRun,
|
useWorkflowRun,
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
import { WorkflowRunningStatus } from '../types'
|
import { ControlMode, WorkflowRunningStatus } from '../types'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
@ -58,6 +58,7 @@ const ViewHistory = ({
|
|||||||
handleCancelDebugAndPreviewPanel,
|
handleCancelDebugAndPreviewPanel,
|
||||||
} = useWorkflowInteractions()
|
} = useWorkflowInteractions()
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
|
const setControlMode = useStore(s => s.setControlMode)
|
||||||
const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({
|
const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({
|
||||||
appDetail: state.appDetail,
|
appDetail: state.appDetail,
|
||||||
setCurrentLogItem: state.setCurrentLogItem,
|
setCurrentLogItem: state.setCurrentLogItem,
|
||||||
@ -173,6 +174,7 @@ const ViewHistory = ({
|
|||||||
setOpen(false)
|
setOpen(false)
|
||||||
handleNodesCancelSelected()
|
handleNodesCancelSelected()
|
||||||
handleCancelDebugAndPreviewPanel()
|
handleCancelDebugAndPreviewPanel()
|
||||||
|
setControlMode(ControlMode.Hand)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
@ -7,11 +7,12 @@ export * from './use-workflow'
|
|||||||
export * from './use-workflow-run'
|
export * from './use-workflow-run'
|
||||||
export * from './use-workflow-template'
|
export * from './use-workflow-template'
|
||||||
export * from './use-checklist'
|
export * from './use-checklist'
|
||||||
export * from './use-workflow-mode'
|
|
||||||
export * from './use-workflow-interactions'
|
|
||||||
export * from './use-selection-interactions'
|
export * from './use-selection-interactions'
|
||||||
export * from './use-panel-interactions'
|
export * from './use-panel-interactions'
|
||||||
export * from './use-workflow-start-run'
|
export * from './use-workflow-start-run'
|
||||||
export * from './use-nodes-layout'
|
export * from './use-nodes-layout'
|
||||||
export * from './use-workflow-history'
|
export * from './use-workflow-history'
|
||||||
export * from './use-workflow-variables'
|
export * from './use-workflow-variables'
|
||||||
|
export * from './use-shortcuts'
|
||||||
|
export * from './use-workflow-interactions'
|
||||||
|
export * from './use-workflow-mode'
|
||||||
|
@ -48,6 +48,7 @@ import { useHelpline } from './use-helpline'
|
|||||||
import {
|
import {
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
useWorkflow,
|
useWorkflow,
|
||||||
|
useWorkflowReadOnly,
|
||||||
} from './use-workflow'
|
} from './use-workflow'
|
||||||
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
||||||
|
|
||||||
@ -62,6 +63,7 @@ export const useNodesInteractions = () => {
|
|||||||
getAfterNodesInSameBranch,
|
getAfterNodesInSameBranch,
|
||||||
} = useWorkflow()
|
} = useWorkflow()
|
||||||
const { getNodesReadOnly } = useNodesReadOnly()
|
const { getNodesReadOnly } = useNodesReadOnly()
|
||||||
|
const { getWorkflowReadOnly } = useWorkflowReadOnly()
|
||||||
const { handleSetHelpline } = useHelpline()
|
const { handleSetHelpline } = useHelpline()
|
||||||
const {
|
const {
|
||||||
handleNodeIterationChildDrag,
|
handleNodeIterationChildDrag,
|
||||||
@ -1029,14 +1031,7 @@ export const useNodesInteractions = () => {
|
|||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
return
|
return
|
||||||
|
|
||||||
const {
|
const { setClipboardElements } = workflowStore.getState()
|
||||||
setClipboardElements,
|
|
||||||
shortcutsDisabled,
|
|
||||||
showFeaturesPanel,
|
|
||||||
} = workflowStore.getState()
|
|
||||||
|
|
||||||
if (shortcutsDisabled || showFeaturesPanel)
|
|
||||||
return
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getNodes,
|
getNodes,
|
||||||
@ -1062,14 +1057,9 @@ export const useNodesInteractions = () => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
clipboardElements,
|
clipboardElements,
|
||||||
shortcutsDisabled,
|
|
||||||
showFeaturesPanel,
|
|
||||||
mousePosition,
|
mousePosition,
|
||||||
} = workflowStore.getState()
|
} = workflowStore.getState()
|
||||||
|
|
||||||
if (shortcutsDisabled || showFeaturesPanel)
|
|
||||||
return
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getNodes,
|
getNodes,
|
||||||
setNodes,
|
setNodes,
|
||||||
@ -1107,6 +1097,11 @@ export const useNodesInteractions = () => {
|
|||||||
})
|
})
|
||||||
newNode.id = newNode.id + index
|
newNode.id = newNode.id + index
|
||||||
|
|
||||||
|
// If only the iteration start node is copied, remove the isIterationStart flag
|
||||||
|
// This new node is movable and can be placed anywhere
|
||||||
|
if (clipboardElements.length === 1 && newNode.data.isIterationStart)
|
||||||
|
newNode.data.isIterationStart = false
|
||||||
|
|
||||||
let newChildren: Node[] = []
|
let newChildren: Node[] = []
|
||||||
if (nodeToPaste.data.type === BlockEnum.Iteration) {
|
if (nodeToPaste.data.type === BlockEnum.Iteration) {
|
||||||
newNode.data._children = [];
|
newNode.data._children = [];
|
||||||
@ -1145,14 +1140,6 @@ export const useNodesInteractions = () => {
|
|||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
return
|
return
|
||||||
|
|
||||||
const {
|
|
||||||
shortcutsDisabled,
|
|
||||||
showFeaturesPanel,
|
|
||||||
} = workflowStore.getState()
|
|
||||||
|
|
||||||
if (shortcutsDisabled || showFeaturesPanel)
|
|
||||||
return
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getNodes,
|
getNodes,
|
||||||
edges,
|
edges,
|
||||||
@ -1175,7 +1162,7 @@ export const useNodesInteractions = () => {
|
|||||||
|
|
||||||
if (selectedNode)
|
if (selectedNode)
|
||||||
handleNodeDelete(selectedNode.id)
|
handleNodeDelete(selectedNode.id)
|
||||||
}, [store, workflowStore, getNodesReadOnly, handleNodeDelete])
|
}, [store, getNodesReadOnly, handleNodeDelete])
|
||||||
|
|
||||||
const handleNodeResize = useCallback((nodeId: string, params: ResizeParamsWithDirection) => {
|
const handleNodeResize = useCallback((nodeId: string, params: ResizeParamsWithDirection) => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
@ -1234,14 +1221,7 @@ export const useNodesInteractions = () => {
|
|||||||
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])
|
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])
|
||||||
|
|
||||||
const handleHistoryBack = useCallback(() => {
|
const handleHistoryBack = useCallback(() => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly() || getWorkflowReadOnly())
|
||||||
return
|
|
||||||
|
|
||||||
const {
|
|
||||||
shortcutsDisabled,
|
|
||||||
} = workflowStore.getState()
|
|
||||||
|
|
||||||
if (shortcutsDisabled)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
const { setEdges, setNodes } = store.getState()
|
const { setEdges, setNodes } = store.getState()
|
||||||
@ -1253,17 +1233,10 @@ export const useNodesInteractions = () => {
|
|||||||
|
|
||||||
setEdges(edges)
|
setEdges(edges)
|
||||||
setNodes(nodes)
|
setNodes(nodes)
|
||||||
}, [store, undo, workflowHistoryStore, workflowStore, getNodesReadOnly])
|
}, [store, undo, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly])
|
||||||
|
|
||||||
const handleHistoryForward = useCallback(() => {
|
const handleHistoryForward = useCallback(() => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly() || getWorkflowReadOnly())
|
||||||
return
|
|
||||||
|
|
||||||
const {
|
|
||||||
shortcutsDisabled,
|
|
||||||
} = workflowStore.getState()
|
|
||||||
|
|
||||||
if (shortcutsDisabled)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
const { setEdges, setNodes } = store.getState()
|
const { setEdges, setNodes } = store.getState()
|
||||||
@ -1275,7 +1248,7 @@ export const useNodesInteractions = () => {
|
|||||||
|
|
||||||
setEdges(edges)
|
setEdges(edges)
|
||||||
setNodes(nodes)
|
setNodes(nodes)
|
||||||
}, [redo, store, workflowHistoryStore, workflowStore, getNodesReadOnly])
|
}, [redo, store, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleNodeDragStart,
|
handleNodeDragStart,
|
||||||
|
@ -8,7 +8,9 @@ import {
|
|||||||
} from '../store'
|
} from '../store'
|
||||||
import { BlockEnum } from '../types'
|
import { BlockEnum } from '../types'
|
||||||
import { useWorkflowUpdate } from '../hooks'
|
import { useWorkflowUpdate } from '../hooks'
|
||||||
import { useNodesReadOnly } from './use-workflow'
|
import {
|
||||||
|
useNodesReadOnly,
|
||||||
|
} from './use-workflow'
|
||||||
import { syncWorkflowDraft } from '@/service/workflow'
|
import { syncWorkflowDraft } from '@/service/workflow'
|
||||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||||
import { API_PREFIX } from '@/config'
|
import { API_PREFIX } from '@/config'
|
||||||
|
186
web/app/components/workflow/hooks/use-shortcuts.ts
Normal file
186
web/app/components/workflow/hooks/use-shortcuts.ts
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import { useReactFlow } from 'reactflow'
|
||||||
|
import { useKeyPress } from 'ahooks'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import {
|
||||||
|
getKeyboardKeyCodeBySystem,
|
||||||
|
isEventTargetInputArea,
|
||||||
|
} from '../utils'
|
||||||
|
import { useWorkflowHistoryStore } from '../workflow-history-store'
|
||||||
|
import { useWorkflowStore } from '../store'
|
||||||
|
import {
|
||||||
|
useEdgesInteractions,
|
||||||
|
useNodesInteractions,
|
||||||
|
useNodesSyncDraft,
|
||||||
|
useWorkflowMoveMode,
|
||||||
|
useWorkflowOrganize,
|
||||||
|
useWorkflowStartRun,
|
||||||
|
} from '.'
|
||||||
|
|
||||||
|
export const useShortcuts = (): void => {
|
||||||
|
const {
|
||||||
|
handleNodesCopy,
|
||||||
|
handleNodesPaste,
|
||||||
|
handleNodesDuplicate,
|
||||||
|
handleNodesDelete,
|
||||||
|
handleHistoryBack,
|
||||||
|
handleHistoryForward,
|
||||||
|
} = useNodesInteractions()
|
||||||
|
const { handleStartWorkflowRun } = useWorkflowStartRun()
|
||||||
|
const { shortcutsEnabled: workflowHistoryShortcutsEnabled } = useWorkflowHistoryStore()
|
||||||
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
const { handleEdgeDelete } = useEdgesInteractions()
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
|
const {
|
||||||
|
handleModeHand,
|
||||||
|
handleModePointer,
|
||||||
|
} = useWorkflowMoveMode()
|
||||||
|
const { handleLayout } = useWorkflowOrganize()
|
||||||
|
|
||||||
|
const {
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
zoomTo,
|
||||||
|
fitView,
|
||||||
|
} = useReactFlow()
|
||||||
|
|
||||||
|
const shouldHandleShortcut = useCallback((e: KeyboardEvent) => {
|
||||||
|
const { showFeaturesPanel } = workflowStore.getState()
|
||||||
|
return !showFeaturesPanel && !isEventTargetInputArea(e.target as HTMLElement)
|
||||||
|
}, [workflowStore])
|
||||||
|
|
||||||
|
useKeyPress(['delete', 'backspace'], (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
handleNodesDelete()
|
||||||
|
handleEdgeDelete()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.c`, (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
handleNodesCopy()
|
||||||
|
}
|
||||||
|
}, { exactMatch: true, useCapture: true })
|
||||||
|
|
||||||
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.v`, (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
handleNodesPaste()
|
||||||
|
}
|
||||||
|
}, { exactMatch: true, useCapture: true })
|
||||||
|
|
||||||
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.d`, (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
handleNodesDuplicate()
|
||||||
|
}
|
||||||
|
}, { exactMatch: true, useCapture: true })
|
||||||
|
|
||||||
|
useKeyPress(`${getKeyboardKeyCodeBySystem('alt')}.r`, (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
handleStartWorkflowRun()
|
||||||
|
}
|
||||||
|
}, { exactMatch: true, useCapture: true })
|
||||||
|
|
||||||
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.z`, (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
workflowHistoryShortcutsEnabled && handleHistoryBack()
|
||||||
|
}
|
||||||
|
}, { exactMatch: true, useCapture: true })
|
||||||
|
|
||||||
|
useKeyPress(
|
||||||
|
[`${getKeyboardKeyCodeBySystem('ctrl')}.y`, `${getKeyboardKeyCodeBySystem('ctrl')}.shift.z`],
|
||||||
|
(e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
workflowHistoryShortcutsEnabled && handleHistoryForward()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ exactMatch: true, useCapture: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
useKeyPress('h', (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
handleModeHand()
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
exactMatch: true,
|
||||||
|
useCapture: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
useKeyPress('v', (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
handleModePointer()
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
exactMatch: true,
|
||||||
|
useCapture: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.o`, (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
handleLayout()
|
||||||
|
}
|
||||||
|
}, { exactMatch: true, useCapture: true })
|
||||||
|
|
||||||
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.1`, (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
fitView()
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
exactMatch: true,
|
||||||
|
useCapture: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
useKeyPress('shift.1', (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
zoomTo(1)
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
exactMatch: true,
|
||||||
|
useCapture: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
useKeyPress('shift.5', (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
zoomTo(0.5)
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
exactMatch: true,
|
||||||
|
useCapture: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.dash`, (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
zoomOut()
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
exactMatch: true,
|
||||||
|
useCapture: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.equalsign`, (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
zoomIn()
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
exactMatch: true,
|
||||||
|
useCapture: true,
|
||||||
|
})
|
||||||
|
}
|
@ -3,17 +3,29 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useReactFlow } from 'reactflow'
|
import { useReactFlow, useStoreApi } from 'reactflow'
|
||||||
import { useWorkflowStore } from '../store'
|
import produce from 'immer'
|
||||||
import { DSL_EXPORT_CHECK, WORKFLOW_DATA_UPDATE } from '../constants'
|
import { useStore, useWorkflowStore } from '../store'
|
||||||
import type { WorkflowDataUpdator } from '../types'
|
|
||||||
import {
|
import {
|
||||||
|
CUSTOM_NODE, DSL_EXPORT_CHECK,
|
||||||
|
WORKFLOW_DATA_UPDATE,
|
||||||
|
} from '../constants'
|
||||||
|
import type { Node, WorkflowDataUpdator } from '../types'
|
||||||
|
import { ControlMode } from '../types'
|
||||||
|
import {
|
||||||
|
getLayoutByDagre,
|
||||||
initialEdges,
|
initialEdges,
|
||||||
initialNodes,
|
initialNodes,
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
|
import {
|
||||||
|
useNodesReadOnly,
|
||||||
|
useSelectionInteractions,
|
||||||
|
useWorkflowReadOnly,
|
||||||
|
} from '../hooks'
|
||||||
import { useEdgesInteractions } from './use-edges-interactions'
|
import { useEdgesInteractions } from './use-edges-interactions'
|
||||||
import { useNodesInteractions } from './use-nodes-interactions'
|
import { useNodesInteractions } from './use-nodes-interactions'
|
||||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
||||||
|
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
import { fetchWorkflowDraft } from '@/service/workflow'
|
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||||
import { exportAppConfig } from '@/service/apps'
|
import { exportAppConfig } from '@/service/apps'
|
||||||
@ -39,6 +51,158 @@ export const useWorkflowInteractions = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useWorkflowMoveMode = () => {
|
||||||
|
const setControlMode = useStore(s => s.setControlMode)
|
||||||
|
const {
|
||||||
|
getNodesReadOnly,
|
||||||
|
} = useNodesReadOnly()
|
||||||
|
const { handleSelectionCancel } = useSelectionInteractions()
|
||||||
|
|
||||||
|
const handleModePointer = useCallback(() => {
|
||||||
|
if (getNodesReadOnly())
|
||||||
|
return
|
||||||
|
|
||||||
|
setControlMode(ControlMode.Pointer)
|
||||||
|
}, [getNodesReadOnly, setControlMode])
|
||||||
|
|
||||||
|
const handleModeHand = useCallback(() => {
|
||||||
|
if (getNodesReadOnly())
|
||||||
|
return
|
||||||
|
|
||||||
|
setControlMode(ControlMode.Hand)
|
||||||
|
handleSelectionCancel()
|
||||||
|
}, [getNodesReadOnly, setControlMode, handleSelectionCancel])
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleModePointer,
|
||||||
|
handleModeHand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useWorkflowOrganize = () => {
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
|
const store = useStoreApi()
|
||||||
|
const reactflow = useReactFlow()
|
||||||
|
const { getNodesReadOnly } = useNodesReadOnly()
|
||||||
|
const { saveStateToHistory } = useWorkflowHistory()
|
||||||
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
|
||||||
|
const handleLayout = useCallback(async () => {
|
||||||
|
if (getNodesReadOnly())
|
||||||
|
return
|
||||||
|
workflowStore.setState({ nodeAnimation: true })
|
||||||
|
const {
|
||||||
|
getNodes,
|
||||||
|
edges,
|
||||||
|
setNodes,
|
||||||
|
} = store.getState()
|
||||||
|
const { setViewport } = reactflow
|
||||||
|
const nodes = getNodes()
|
||||||
|
const layout = getLayoutByDagre(nodes, edges)
|
||||||
|
const rankMap = {} as Record<string, Node>
|
||||||
|
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
if (!node.parentId && node.type === CUSTOM_NODE) {
|
||||||
|
const rank = layout.node(node.id).rank!
|
||||||
|
|
||||||
|
if (!rankMap[rank]) {
|
||||||
|
rankMap[rank] = node
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (rankMap[rank].position.y > node.position.y)
|
||||||
|
rankMap[rank] = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const newNodes = produce(nodes, (draft) => {
|
||||||
|
draft.forEach((node) => {
|
||||||
|
if (!node.parentId && node.type === CUSTOM_NODE) {
|
||||||
|
const nodeWithPosition = layout.node(node.id)
|
||||||
|
|
||||||
|
node.position = {
|
||||||
|
x: nodeWithPosition.x - node.width! / 2,
|
||||||
|
y: nodeWithPosition.y - node.height! / 2 + rankMap[nodeWithPosition.rank!].height! / 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setNodes(newNodes)
|
||||||
|
const zoom = 0.7
|
||||||
|
setViewport({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
zoom,
|
||||||
|
})
|
||||||
|
saveStateToHistory(WorkflowHistoryEvent.LayoutOrganize)
|
||||||
|
setTimeout(() => {
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
})
|
||||||
|
}, [getNodesReadOnly, store, reactflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory])
|
||||||
|
return {
|
||||||
|
handleLayout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useWorkflowZoom = () => {
|
||||||
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
const { getWorkflowReadOnly } = useWorkflowReadOnly()
|
||||||
|
const {
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
zoomTo,
|
||||||
|
fitView,
|
||||||
|
} = useReactFlow()
|
||||||
|
|
||||||
|
const handleFitView = useCallback(() => {
|
||||||
|
if (getWorkflowReadOnly())
|
||||||
|
return
|
||||||
|
|
||||||
|
fitView()
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
}, [getWorkflowReadOnly, fitView, handleSyncWorkflowDraft])
|
||||||
|
|
||||||
|
const handleBackToOriginalSize = useCallback(() => {
|
||||||
|
if (getWorkflowReadOnly())
|
||||||
|
return
|
||||||
|
|
||||||
|
zoomTo(1)
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
}, [getWorkflowReadOnly, zoomTo, handleSyncWorkflowDraft])
|
||||||
|
|
||||||
|
const handleSizeToHalf = useCallback(() => {
|
||||||
|
if (getWorkflowReadOnly())
|
||||||
|
return
|
||||||
|
|
||||||
|
zoomTo(0.5)
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
}, [getWorkflowReadOnly, zoomTo, handleSyncWorkflowDraft])
|
||||||
|
|
||||||
|
const handleZoomOut = useCallback(() => {
|
||||||
|
if (getWorkflowReadOnly())
|
||||||
|
return
|
||||||
|
|
||||||
|
zoomOut()
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
}, [getWorkflowReadOnly, zoomOut, handleSyncWorkflowDraft])
|
||||||
|
|
||||||
|
const handleZoomIn = useCallback(() => {
|
||||||
|
if (getWorkflowReadOnly())
|
||||||
|
return
|
||||||
|
|
||||||
|
zoomIn()
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
}, [getWorkflowReadOnly, zoomIn, handleSyncWorkflowDraft])
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleFitView,
|
||||||
|
handleBackToOriginalSize,
|
||||||
|
handleSizeToHalf,
|
||||||
|
handleZoomOut,
|
||||||
|
handleZoomIn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const useWorkflowUpdate = () => {
|
export const useWorkflowUpdate = () => {
|
||||||
const reactflow = useReactFlow()
|
const reactflow = useReactFlow()
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
|
@ -7,19 +7,14 @@ import {
|
|||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { uniqBy } from 'lodash-es'
|
import { uniqBy } from 'lodash-es'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import produce from 'immer'
|
|
||||||
import {
|
import {
|
||||||
getIncomers,
|
getIncomers,
|
||||||
getOutgoers,
|
getOutgoers,
|
||||||
useReactFlow,
|
|
||||||
useStoreApi,
|
useStoreApi,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
import type {
|
import type {
|
||||||
Connection,
|
Connection,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
import {
|
|
||||||
getLayoutByDagre,
|
|
||||||
} from '../utils'
|
|
||||||
import type {
|
import type {
|
||||||
Edge,
|
Edge,
|
||||||
Node,
|
Node,
|
||||||
@ -34,15 +29,12 @@ import {
|
|||||||
useWorkflowStore,
|
useWorkflowStore,
|
||||||
} from '../store'
|
} from '../store'
|
||||||
import {
|
import {
|
||||||
CUSTOM_NODE,
|
|
||||||
SUPPORT_OUTPUT_VARS_NODE,
|
SUPPORT_OUTPUT_VARS_NODE,
|
||||||
} from '../constants'
|
} from '../constants'
|
||||||
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
|
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
|
||||||
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
|
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
|
||||||
import { useNodesExtraData } from './use-nodes-data'
|
import { useNodesExtraData } from './use-nodes-data'
|
||||||
import { useWorkflowTemplate } from './use-workflow-template'
|
import { useWorkflowTemplate } from './use-workflow-template'
|
||||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
|
||||||
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import {
|
import {
|
||||||
fetchNodesDefaultConfigs,
|
fetchNodesDefaultConfigs,
|
||||||
@ -68,68 +60,13 @@ export const useIsChatMode = () => {
|
|||||||
export const useWorkflow = () => {
|
export const useWorkflow = () => {
|
||||||
const { locale } = useContext(I18n)
|
const { locale } = useContext(I18n)
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
const reactflow = useReactFlow()
|
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const nodesExtraData = useNodesExtraData()
|
const nodesExtraData = useNodesExtraData()
|
||||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
|
||||||
const { saveStateToHistory } = useWorkflowHistory()
|
|
||||||
|
|
||||||
const setPanelWidth = useCallback((width: number) => {
|
const setPanelWidth = useCallback((width: number) => {
|
||||||
localStorage.setItem('workflow-node-panel-width', `${width}`)
|
localStorage.setItem('workflow-node-panel-width', `${width}`)
|
||||||
workflowStore.setState({ panelWidth: width })
|
workflowStore.setState({ panelWidth: width })
|
||||||
}, [workflowStore])
|
}, [workflowStore])
|
||||||
|
|
||||||
const handleLayout = useCallback(async () => {
|
|
||||||
workflowStore.setState({ nodeAnimation: true })
|
|
||||||
const {
|
|
||||||
getNodes,
|
|
||||||
edges,
|
|
||||||
setNodes,
|
|
||||||
} = store.getState()
|
|
||||||
const { setViewport } = reactflow
|
|
||||||
const nodes = getNodes()
|
|
||||||
const layout = getLayoutByDagre(nodes, edges)
|
|
||||||
const rankMap = {} as Record<string, Node>
|
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
|
||||||
if (!node.parentId && node.type === CUSTOM_NODE) {
|
|
||||||
const rank = layout.node(node.id).rank!
|
|
||||||
|
|
||||||
if (!rankMap[rank]) {
|
|
||||||
rankMap[rank] = node
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (rankMap[rank].position.y > node.position.y)
|
|
||||||
rankMap[rank] = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const newNodes = produce(nodes, (draft) => {
|
|
||||||
draft.forEach((node) => {
|
|
||||||
if (!node.parentId && node.type === CUSTOM_NODE) {
|
|
||||||
const nodeWithPosition = layout.node(node.id)
|
|
||||||
|
|
||||||
node.position = {
|
|
||||||
x: nodeWithPosition.x - node.width! / 2,
|
|
||||||
y: nodeWithPosition.y - node.height! / 2 + rankMap[nodeWithPosition.rank!].height! / 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
setNodes(newNodes)
|
|
||||||
const zoom = 0.7
|
|
||||||
setViewport({
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
zoom,
|
|
||||||
})
|
|
||||||
saveStateToHistory(WorkflowHistoryEvent.LayoutOrganize)
|
|
||||||
setTimeout(() => {
|
|
||||||
handleSyncWorkflowDraft()
|
|
||||||
})
|
|
||||||
}, [workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft])
|
|
||||||
|
|
||||||
const getTreeLeafNodes = useCallback((nodeId: string) => {
|
const getTreeLeafNodes = useCallback((nodeId: string) => {
|
||||||
const {
|
const {
|
||||||
getNodes,
|
getNodes,
|
||||||
@ -392,19 +329,8 @@ export const useWorkflow = () => {
|
|||||||
return nodes.find(node => node.id === nodeId) || nodes.find(node => node.data.type === BlockEnum.Start)
|
return nodes.find(node => node.id === nodeId) || nodes.find(node => node.data.type === BlockEnum.Start)
|
||||||
}, [store])
|
}, [store])
|
||||||
|
|
||||||
const enableShortcuts = useCallback(() => {
|
|
||||||
const { setShortcutsDisabled } = workflowStore.getState()
|
|
||||||
setShortcutsDisabled(false)
|
|
||||||
}, [workflowStore])
|
|
||||||
|
|
||||||
const disableShortcuts = useCallback(() => {
|
|
||||||
const { setShortcutsDisabled } = workflowStore.getState()
|
|
||||||
setShortcutsDisabled(true)
|
|
||||||
}, [workflowStore])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setPanelWidth,
|
setPanelWidth,
|
||||||
handleLayout,
|
|
||||||
getTreeLeafNodes,
|
getTreeLeafNodes,
|
||||||
getBeforeNodesInSameBranch,
|
getBeforeNodesInSameBranch,
|
||||||
getBeforeNodesInSameBranchIncludeParent,
|
getBeforeNodesInSameBranchIncludeParent,
|
||||||
@ -418,8 +344,6 @@ export const useWorkflow = () => {
|
|||||||
getNode,
|
getNode,
|
||||||
getBeforeNodeById,
|
getBeforeNodeById,
|
||||||
getIterationNodeChildren,
|
getIterationNodeChildren,
|
||||||
enableShortcuts,
|
|
||||||
disableShortcuts,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
import { setAutoFreeze } from 'immer'
|
import { setAutoFreeze } from 'immer'
|
||||||
import {
|
import {
|
||||||
useEventListener,
|
useEventListener,
|
||||||
useKeyPress,
|
|
||||||
} from 'ahooks'
|
} from 'ahooks'
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
@ -34,6 +33,9 @@ import type {
|
|||||||
EnvironmentVariable,
|
EnvironmentVariable,
|
||||||
Node,
|
Node,
|
||||||
} from './types'
|
} from './types'
|
||||||
|
import {
|
||||||
|
ControlMode,
|
||||||
|
} from './types'
|
||||||
import { WorkflowContextProvider } from './context'
|
import { WorkflowContextProvider } from './context'
|
||||||
import {
|
import {
|
||||||
useDSL,
|
useDSL,
|
||||||
@ -43,10 +45,10 @@ import {
|
|||||||
useNodesSyncDraft,
|
useNodesSyncDraft,
|
||||||
usePanelInteractions,
|
usePanelInteractions,
|
||||||
useSelectionInteractions,
|
useSelectionInteractions,
|
||||||
|
useShortcuts,
|
||||||
useWorkflow,
|
useWorkflow,
|
||||||
useWorkflowInit,
|
useWorkflowInit,
|
||||||
useWorkflowReadOnly,
|
useWorkflowReadOnly,
|
||||||
useWorkflowStartRun,
|
|
||||||
useWorkflowUpdate,
|
useWorkflowUpdate,
|
||||||
} from './hooks'
|
} from './hooks'
|
||||||
import Header from './header'
|
import Header from './header'
|
||||||
@ -70,10 +72,8 @@ import {
|
|||||||
useWorkflowStore,
|
useWorkflowStore,
|
||||||
} from './store'
|
} from './store'
|
||||||
import {
|
import {
|
||||||
getKeyboardKeyCodeBySystem,
|
|
||||||
initialEdges,
|
initialEdges,
|
||||||
initialNodes,
|
initialNodes,
|
||||||
isEventTargetInputArea,
|
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import {
|
import {
|
||||||
CUSTOM_NODE,
|
CUSTOM_NODE,
|
||||||
@ -81,7 +81,7 @@ import {
|
|||||||
ITERATION_CHILDREN_Z_INDEX,
|
ITERATION_CHILDREN_Z_INDEX,
|
||||||
WORKFLOW_DATA_UPDATE,
|
WORKFLOW_DATA_UPDATE,
|
||||||
} from './constants'
|
} from './constants'
|
||||||
import { WorkflowHistoryProvider, useWorkflowHistoryStore } from './workflow-history-store'
|
import { WorkflowHistoryProvider } from './workflow-history-store'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { FeaturesProvider } from '@/app/components/base/features'
|
import { FeaturesProvider } from '@/app/components/base/features'
|
||||||
import type { Features as FeaturesData } from '@/app/components/base/features/types'
|
import type { Features as FeaturesData } from '@/app/components/base/features/types'
|
||||||
@ -225,17 +225,12 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
handleNodeConnectStart,
|
handleNodeConnectStart,
|
||||||
handleNodeConnectEnd,
|
handleNodeConnectEnd,
|
||||||
handleNodeContextMenu,
|
handleNodeContextMenu,
|
||||||
handleNodesCopy,
|
|
||||||
handleNodesPaste,
|
|
||||||
handleNodesDuplicate,
|
|
||||||
handleNodesDelete,
|
|
||||||
handleHistoryBack,
|
handleHistoryBack,
|
||||||
handleHistoryForward,
|
handleHistoryForward,
|
||||||
} = useNodesInteractions()
|
} = useNodesInteractions()
|
||||||
const {
|
const {
|
||||||
handleEdgeEnter,
|
handleEdgeEnter,
|
||||||
handleEdgeLeave,
|
handleEdgeLeave,
|
||||||
handleEdgeDelete,
|
|
||||||
handleEdgesChange,
|
handleEdgesChange,
|
||||||
} = useEdgesInteractions()
|
} = useEdgesInteractions()
|
||||||
const {
|
const {
|
||||||
@ -250,7 +245,6 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
const {
|
const {
|
||||||
isValidConnection,
|
isValidConnection,
|
||||||
} = useWorkflow()
|
} = useWorkflow()
|
||||||
const { handleStartWorkflowRun } = useWorkflowStartRun()
|
|
||||||
const {
|
const {
|
||||||
exportCheck,
|
exportCheck,
|
||||||
handleExportDSL,
|
handleExportDSL,
|
||||||
@ -262,41 +256,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { shortcutsEnabled: workflowHistoryShortcutsEnabled } = useWorkflowHistoryStore()
|
useShortcuts()
|
||||||
|
|
||||||
useKeyPress(['delete', 'backspace'], (e) => {
|
|
||||||
if (isEventTargetInputArea(e.target as HTMLElement))
|
|
||||||
return
|
|
||||||
|
|
||||||
handleNodesDelete()
|
|
||||||
})
|
|
||||||
useKeyPress(['delete', 'backspace'], handleEdgeDelete)
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.c`, (e) => {
|
|
||||||
if (isEventTargetInputArea(e.target as HTMLElement))
|
|
||||||
return
|
|
||||||
|
|
||||||
handleNodesCopy()
|
|
||||||
}, { exactMatch: true, useCapture: true })
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.v`, (e) => {
|
|
||||||
if (isEventTargetInputArea(e.target as HTMLElement))
|
|
||||||
return
|
|
||||||
|
|
||||||
handleNodesPaste()
|
|
||||||
}, { exactMatch: true, useCapture: true })
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.d`, handleNodesDuplicate, { exactMatch: true, useCapture: true })
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('alt')}.r`, handleStartWorkflowRun, { exactMatch: true, useCapture: true })
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('alt')}.r`, handleStartWorkflowRun, { exactMatch: true, useCapture: true })
|
|
||||||
useKeyPress(
|
|
||||||
`${getKeyboardKeyCodeBySystem('ctrl')}.z`,
|
|
||||||
() => workflowHistoryShortcutsEnabled && handleHistoryBack(),
|
|
||||||
{ exactMatch: true, useCapture: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
useKeyPress(
|
|
||||||
[`${getKeyboardKeyCodeBySystem('ctrl')}.y`, `${getKeyboardKeyCodeBySystem('ctrl')}.shift.z`],
|
|
||||||
() => workflowHistoryShortcutsEnabled && handleHistoryForward(),
|
|
||||||
{ exactMatch: true, useCapture: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
@ -388,14 +348,14 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
nodesConnectable={!nodesReadOnly}
|
nodesConnectable={!nodesReadOnly}
|
||||||
nodesFocusable={!nodesReadOnly}
|
nodesFocusable={!nodesReadOnly}
|
||||||
edgesFocusable={!nodesReadOnly}
|
edgesFocusable={!nodesReadOnly}
|
||||||
panOnDrag={controlMode === 'hand' && !workflowReadOnly}
|
panOnDrag={controlMode === ControlMode.Hand && !workflowReadOnly}
|
||||||
zoomOnPinch={!workflowReadOnly}
|
zoomOnPinch={!workflowReadOnly}
|
||||||
zoomOnScroll={!workflowReadOnly}
|
zoomOnScroll={!workflowReadOnly}
|
||||||
zoomOnDoubleClick={!workflowReadOnly}
|
zoomOnDoubleClick={!workflowReadOnly}
|
||||||
isValidConnection={isValidConnection}
|
isValidConnection={isValidConnection}
|
||||||
selectionKeyCode={null}
|
selectionKeyCode={null}
|
||||||
selectionMode={SelectionMode.Partial}
|
selectionMode={SelectionMode.Partial}
|
||||||
selectionOnDrag={controlMode === 'pointer' && !workflowReadOnly}
|
selectionOnDrag={controlMode === ControlMode.Pointer && !workflowReadOnly}
|
||||||
minZoom={0.25}
|
minZoom={0.25}
|
||||||
>
|
>
|
||||||
<Background
|
<Background
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import type { MouseEvent } from 'react'
|
import type { MouseEvent } from 'react'
|
||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
@ -10,13 +9,14 @@ import {
|
|||||||
RiHand,
|
RiHand,
|
||||||
RiStickyNoteAddLine,
|
RiStickyNoteAddLine,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import { useKeyPress } from 'ahooks'
|
|
||||||
import {
|
import {
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
useSelectionInteractions,
|
useWorkflowMoveMode,
|
||||||
useWorkflow,
|
useWorkflowOrganize,
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
import { getKeyboardKeyCodeBySystem, isEventTargetInputArea } from '../utils'
|
import {
|
||||||
|
ControlMode,
|
||||||
|
} from '../types'
|
||||||
import { useStore } from '../store'
|
import { useStore } from '../store'
|
||||||
import AddBlock from './add-block'
|
import AddBlock from './add-block'
|
||||||
import TipPopup from './tip-popup'
|
import TipPopup from './tip-popup'
|
||||||
@ -26,62 +26,13 @@ import cn from '@/utils/classnames'
|
|||||||
const Control = () => {
|
const Control = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const controlMode = useStore(s => s.controlMode)
|
const controlMode = useStore(s => s.controlMode)
|
||||||
const setControlMode = useStore(s => s.setControlMode)
|
const { handleModePointer, handleModeHand } = useWorkflowMoveMode()
|
||||||
const { handleLayout } = useWorkflow()
|
const { handleLayout } = useWorkflowOrganize()
|
||||||
const { handleAddNote } = useOperator()
|
const { handleAddNote } = useOperator()
|
||||||
const {
|
const {
|
||||||
nodesReadOnly,
|
nodesReadOnly,
|
||||||
getNodesReadOnly,
|
getNodesReadOnly,
|
||||||
} = useNodesReadOnly()
|
} = useNodesReadOnly()
|
||||||
const { handleSelectionCancel } = useSelectionInteractions()
|
|
||||||
|
|
||||||
const handleModePointer = useCallback(() => {
|
|
||||||
if (getNodesReadOnly())
|
|
||||||
return
|
|
||||||
setControlMode('pointer')
|
|
||||||
}, [getNodesReadOnly, setControlMode])
|
|
||||||
const handleModeHand = useCallback(() => {
|
|
||||||
if (getNodesReadOnly())
|
|
||||||
return
|
|
||||||
setControlMode('hand')
|
|
||||||
handleSelectionCancel()
|
|
||||||
}, [getNodesReadOnly, setControlMode, handleSelectionCancel])
|
|
||||||
|
|
||||||
useKeyPress('h', (e) => {
|
|
||||||
if (getNodesReadOnly())
|
|
||||||
return
|
|
||||||
|
|
||||||
if (isEventTargetInputArea(e.target as HTMLElement))
|
|
||||||
return
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
handleModeHand()
|
|
||||||
}, {
|
|
||||||
exactMatch: true,
|
|
||||||
useCapture: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
useKeyPress('v', (e) => {
|
|
||||||
if (isEventTargetInputArea(e.target as HTMLElement))
|
|
||||||
return
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
handleModePointer()
|
|
||||||
}, {
|
|
||||||
exactMatch: true,
|
|
||||||
useCapture: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const goLayout = () => {
|
|
||||||
if (getNodesReadOnly())
|
|
||||||
return
|
|
||||||
handleLayout()
|
|
||||||
}
|
|
||||||
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.o`, (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
goLayout()
|
|
||||||
}, { exactMatch: true, useCapture: true })
|
|
||||||
|
|
||||||
const addNote = (e: MouseEvent<HTMLDivElement>) => {
|
const addNote = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
@ -110,7 +61,7 @@ const Control = () => {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center justify-center mr-[1px] w-8 h-8 rounded-lg cursor-pointer',
|
'flex items-center justify-center mr-[1px] w-8 h-8 rounded-lg cursor-pointer',
|
||||||
controlMode === 'pointer' ? 'bg-primary-50 text-primary-600' : 'hover:bg-black/5 hover:text-gray-700',
|
controlMode === ControlMode.Pointer ? 'bg-primary-50 text-primary-600' : 'hover:bg-black/5 hover:text-gray-700',
|
||||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||||
)}
|
)}
|
||||||
onClick={handleModePointer}
|
onClick={handleModePointer}
|
||||||
@ -122,7 +73,7 @@ const Control = () => {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer',
|
'flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer',
|
||||||
controlMode === 'hand' ? 'bg-primary-50 text-primary-600' : 'hover:bg-black/5 hover:text-gray-700',
|
controlMode === ControlMode.Hand ? 'bg-primary-50 text-primary-600' : 'hover:bg-black/5 hover:text-gray-700',
|
||||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||||
)}
|
)}
|
||||||
onClick={handleModeHand}
|
onClick={handleModeHand}
|
||||||
@ -137,7 +88,7 @@ const Control = () => {
|
|||||||
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
|
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
|
||||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||||
)}
|
)}
|
||||||
onClick={goLayout}
|
onClick={handleLayout}
|
||||||
>
|
>
|
||||||
<RiFunctionAddLine className='w-4 h-4' />
|
<RiFunctionAddLine className='w-4 h-4' />
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
RiZoomInLine,
|
RiZoomInLine,
|
||||||
RiZoomOutLine,
|
RiZoomOutLine,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import { useKeyPress } from 'ahooks'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
useReactFlow,
|
useReactFlow,
|
||||||
@ -20,9 +19,7 @@ import {
|
|||||||
useWorkflowReadOnly,
|
useWorkflowReadOnly,
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
import {
|
import {
|
||||||
getKeyboardKeyCodeBySystem,
|
|
||||||
getKeyboardKeyNameBySystem,
|
getKeyboardKeyNameBySystem,
|
||||||
isEventTargetInputArea,
|
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import ShortcutsName from '../shortcuts-name'
|
import ShortcutsName from '../shortcuts-name'
|
||||||
import TipPopup from './tip-popup'
|
import TipPopup from './tip-popup'
|
||||||
@ -116,87 +113,6 @@ const ZoomInOut: FC = () => {
|
|||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
}
|
}
|
||||||
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.1`, (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (workflowReadOnly)
|
|
||||||
return
|
|
||||||
|
|
||||||
fitView()
|
|
||||||
handleSyncWorkflowDraft()
|
|
||||||
}, {
|
|
||||||
exactMatch: true,
|
|
||||||
useCapture: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
useKeyPress('shift.1', (e) => {
|
|
||||||
if (workflowReadOnly)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (isEventTargetInputArea(e.target as HTMLElement))
|
|
||||||
return
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
zoomTo(1)
|
|
||||||
handleSyncWorkflowDraft()
|
|
||||||
}, {
|
|
||||||
exactMatch: true,
|
|
||||||
useCapture: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
useKeyPress('shift.2', (e) => {
|
|
||||||
if (workflowReadOnly)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (isEventTargetInputArea(e.target as HTMLElement))
|
|
||||||
return
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
zoomTo(2)
|
|
||||||
handleSyncWorkflowDraft()
|
|
||||||
}, {
|
|
||||||
exactMatch: true,
|
|
||||||
useCapture: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
useKeyPress('shift.5', (e) => {
|
|
||||||
if (workflowReadOnly)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (isEventTargetInputArea(e.target as HTMLElement))
|
|
||||||
return
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
zoomTo(0.5)
|
|
||||||
handleSyncWorkflowDraft()
|
|
||||||
}, {
|
|
||||||
exactMatch: true,
|
|
||||||
useCapture: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.dash`, (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (workflowReadOnly)
|
|
||||||
return
|
|
||||||
|
|
||||||
zoomOut()
|
|
||||||
handleSyncWorkflowDraft()
|
|
||||||
}, {
|
|
||||||
exactMatch: true,
|
|
||||||
useCapture: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.equalsign`, (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (workflowReadOnly)
|
|
||||||
return
|
|
||||||
|
|
||||||
zoomIn()
|
|
||||||
handleSyncWorkflowDraft()
|
|
||||||
}, {
|
|
||||||
exactMatch: true,
|
|
||||||
useCapture: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleTrigger = useCallback(() => {
|
const handleTrigger = useCallback(() => {
|
||||||
if (getWorkflowReadOnly())
|
if (getWorkflowReadOnly())
|
||||||
return
|
return
|
||||||
@ -289,11 +205,6 @@ const ZoomInOut: FC = () => {
|
|||||||
<ShortcutsName keys={['shift', '1']} />
|
<ShortcutsName keys={['shift', '1']} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
|
||||||
option.key === ZoomType.zoomTo200 && (
|
|
||||||
<ShortcutsName keys={['shift', '2']} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import { Panel as NodePanel } from '../nodes'
|
|||||||
import { useStore } from '../store'
|
import { useStore } from '../store'
|
||||||
import {
|
import {
|
||||||
useIsChatMode,
|
useIsChatMode,
|
||||||
useWorkflow,
|
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
import DebugAndPreview from './debug-and-preview'
|
import DebugAndPreview from './debug-and-preview'
|
||||||
import Record from './record'
|
import Record from './record'
|
||||||
@ -28,10 +27,6 @@ const Panel: FC = () => {
|
|||||||
const showEnvPanel = useStore(s => s.showEnvPanel)
|
const showEnvPanel = useStore(s => s.showEnvPanel)
|
||||||
const showChatVariablePanel = useStore(s => s.showChatVariablePanel)
|
const showChatVariablePanel = useStore(s => s.showChatVariablePanel)
|
||||||
const isRestoring = useStore(s => s.isRestoring)
|
const isRestoring = useStore(s => s.isRestoring)
|
||||||
const {
|
|
||||||
enableShortcuts,
|
|
||||||
disableShortcuts,
|
|
||||||
} = useWorkflow()
|
|
||||||
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
|
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
|
||||||
currentLogItem: state.currentLogItem,
|
currentLogItem: state.currentLogItem,
|
||||||
setCurrentLogItem: state.setCurrentLogItem,
|
setCurrentLogItem: state.setCurrentLogItem,
|
||||||
@ -44,8 +39,6 @@ const Panel: FC = () => {
|
|||||||
<div
|
<div
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className={cn('absolute top-14 right-0 bottom-2 flex z-10 outline-none')}
|
className={cn('absolute top-14 right-0 bottom-2 flex z-10 outline-none')}
|
||||||
onFocus={disableShortcuts}
|
|
||||||
onBlur={enableShortcuts}
|
|
||||||
key={`${isRestoring}`}
|
key={`${isRestoring}`}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
@ -99,8 +99,6 @@ type Shape = {
|
|||||||
setWorkflowTools: (tools: ToolWithProvider[]) => void
|
setWorkflowTools: (tools: ToolWithProvider[]) => void
|
||||||
clipboardElements: Node[]
|
clipboardElements: Node[]
|
||||||
setClipboardElements: (clipboardElements: Node[]) => void
|
setClipboardElements: (clipboardElements: Node[]) => void
|
||||||
shortcutsDisabled: boolean
|
|
||||||
setShortcutsDisabled: (shortcutsDisabled: boolean) => void
|
|
||||||
showDebugAndPreviewPanel: boolean
|
showDebugAndPreviewPanel: boolean
|
||||||
setShowDebugAndPreviewPanel: (showDebugAndPreviewPanel: boolean) => void
|
setShowDebugAndPreviewPanel: (showDebugAndPreviewPanel: boolean) => void
|
||||||
showEnvPanel: boolean
|
showEnvPanel: boolean
|
||||||
@ -217,8 +215,6 @@ export const createWorkflowStore = () => {
|
|||||||
setWorkflowTools: workflowTools => set(() => ({ workflowTools })),
|
setWorkflowTools: workflowTools => set(() => ({ workflowTools })),
|
||||||
clipboardElements: [],
|
clipboardElements: [],
|
||||||
setClipboardElements: clipboardElements => set(() => ({ clipboardElements })),
|
setClipboardElements: clipboardElements => set(() => ({ clipboardElements })),
|
||||||
shortcutsDisabled: false,
|
|
||||||
setShortcutsDisabled: shortcutsDisabled => set(() => ({ shortcutsDisabled })),
|
|
||||||
showDebugAndPreviewPanel: false,
|
showDebugAndPreviewPanel: false,
|
||||||
setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })),
|
setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })),
|
||||||
showEnvPanel: false,
|
showEnvPanel: false,
|
||||||
|
@ -29,6 +29,11 @@ export enum BlockEnum {
|
|||||||
Assigner = 'assigner', // is now named as VariableAssigner
|
Assigner = 'assigner', // is now named as VariableAssigner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ControlMode {
|
||||||
|
Pointer = 'pointer',
|
||||||
|
Hand = 'hand',
|
||||||
|
}
|
||||||
|
|
||||||
export type Branch = {
|
export type Branch = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
@ -49,6 +49,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
|
emoji: 'Emoji',
|
||||||
|
image: 'Bild',
|
||||||
},
|
},
|
||||||
switch: 'Zu Workflow-Orchestrierung wechseln',
|
switch: 'Zu Workflow-Orchestrierung wechseln',
|
||||||
switchTipStart: 'Eine neue App-Kopie wird für Sie erstellt, und die neue Kopie wird zur Workflow-Orchestrierung wechseln. Die neue Kopie wird ',
|
switchTipStart: 'Eine neue App-Kopie wird für Sie erstellt, und die neue Kopie wird zur Workflow-Orchestrierung wechseln. Die neue Kopie wird ',
|
||||||
|
@ -70,6 +70,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
|
emoji: 'Emoji',
|
||||||
|
image: 'Imagen',
|
||||||
},
|
},
|
||||||
switch: 'Cambiar a Orquestación de Flujo de Trabajo',
|
switch: 'Cambiar a Orquestación de Flujo de Trabajo',
|
||||||
switchTipStart: 'Se creará una nueva copia de la app para ti y la nueva copia cambiará a Orquestación de Flujo de Trabajo. La nueva copia no permitirá',
|
switchTipStart: 'Se creará una nueva copia de la app para ti y la nueva copia cambiará a Orquestación de Flujo de Trabajo. La nueva copia no permitirá',
|
||||||
|
@ -74,6 +74,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'باشه',
|
ok: 'باشه',
|
||||||
cancel: 'لغو',
|
cancel: 'لغو',
|
||||||
|
emoji: 'ایموجی',
|
||||||
|
image: 'تصویر',
|
||||||
},
|
},
|
||||||
switch: 'تغییر به سازماندهی گردش کار',
|
switch: 'تغییر به سازماندهی گردش کار',
|
||||||
switchTipStart: 'یک نسخه جدید از برنامه برای شما ایجاد خواهد شد و نسخه جدید به سازماندهی گردش کار تغییر خواهد کرد. نسخه جدید ',
|
switchTipStart: 'یک نسخه جدید از برنامه برای شما ایجاد خواهد شد و نسخه جدید به سازماندهی گردش کار تغییر خواهد کرد. نسخه جدید ',
|
||||||
|
@ -70,6 +70,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
cancel: 'Annuler',
|
cancel: 'Annuler',
|
||||||
|
emoji: 'Emoji',
|
||||||
|
image: 'Image',
|
||||||
},
|
},
|
||||||
switch: 'Passer à l\'orchestration de flux de travail',
|
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 ',
|
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 ',
|
||||||
|
@ -70,6 +70,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'ठीक है',
|
ok: 'ठीक है',
|
||||||
cancel: 'रद्द करें',
|
cancel: 'रद्द करें',
|
||||||
|
emoji: 'इमोजी',
|
||||||
|
image: 'छवि',
|
||||||
},
|
},
|
||||||
switch: 'वर्कफ़्लो ऑर्केस्ट्रेट पर स्विच करें',
|
switch: 'वर्कफ़्लो ऑर्केस्ट्रेट पर स्विच करें',
|
||||||
switchTipStart: 'आपके लिए एक नई ऐप कॉपी बनाई जाएगी, और नई कॉपी वर्कफ़्लो ऑर्केस्ट्रेट में स्विच हो जाएगी। नई कॉपी ',
|
switchTipStart: 'आपके लिए एक नई ऐप कॉपी बनाई जाएगी, और नई कॉपी वर्कफ़्लो ऑर्केस्ट्रेट में स्विच हो जाएगी। नई कॉपी ',
|
||||||
|
@ -76,6 +76,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
cancel: 'Annulla',
|
cancel: 'Annulla',
|
||||||
|
emoji: 'Emoji',
|
||||||
|
image: 'Immagine',
|
||||||
},
|
},
|
||||||
switch: 'Passa a Orchestrazione del flusso di lavoro',
|
switch: 'Passa a Orchestrazione del flusso di lavoro',
|
||||||
switchTipStart:
|
switchTipStart:
|
||||||
|
@ -75,6 +75,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
cancel: 'キャンセル',
|
cancel: 'キャンセル',
|
||||||
|
emoji: '絵文字',
|
||||||
|
image: '画像',
|
||||||
},
|
},
|
||||||
switch: 'ワークフロー オーケストレートに切り替える',
|
switch: 'ワークフロー オーケストレートに切り替える',
|
||||||
switchTipStart: '新しいアプリのコピーが作成され、新しいコピーがワークフロー オーケストレートに切り替わります。新しいコピーは ',
|
switchTipStart: '新しいアプリのコピーが作成され、新しいコピーがワークフロー オーケストレートに切り替わります。新しいコピーは ',
|
||||||
|
@ -66,6 +66,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: '확인',
|
ok: '확인',
|
||||||
cancel: '취소',
|
cancel: '취소',
|
||||||
|
emoji: '이모지',
|
||||||
|
image: '이미지',
|
||||||
},
|
},
|
||||||
switch: '워크플로우 오케스트레이션으로 전환하기',
|
switch: '워크플로우 오케스트레이션으로 전환하기',
|
||||||
switchTipStart: '새로운 앱의 복사본이 생성되어 새로운 복사본이 워크플로우 오케스트레이션으로 전환됩니다. 새로운 복사본은 ',
|
switchTipStart: '새로운 앱의 복사본이 생성되어 새로운 복사본이 워크플로우 오케스트레이션으로 전환됩니다. 새로운 복사본은 ',
|
||||||
|
@ -76,6 +76,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
cancel: 'Anuluj',
|
cancel: 'Anuluj',
|
||||||
|
emoji: 'Emoji',
|
||||||
|
image: 'Obraz',
|
||||||
},
|
},
|
||||||
switch: 'Przełącz na Orkiestrację Przepływu Pracy',
|
switch: 'Przełącz na Orkiestrację Przepływu Pracy',
|
||||||
switchTipStart:
|
switchTipStart:
|
||||||
|
@ -70,6 +70,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
|
emoji: 'Emoji',
|
||||||
|
image: 'Imagem',
|
||||||
},
|
},
|
||||||
switch: 'Mudar para Orquestração de Fluxo de Trabalho',
|
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 ',
|
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 ',
|
||||||
|
@ -70,6 +70,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
cancel: 'Anulează',
|
cancel: 'Anulează',
|
||||||
|
emoji: 'Emoji',
|
||||||
|
image: 'Imagine',
|
||||||
},
|
},
|
||||||
switch: 'Comută la Orchestrare Flux de Lucru',
|
switch: 'Comută la Orchestrare Flux de Lucru',
|
||||||
switchTipStart: 'O nouă copie a aplicației va fi creată pentru tine, iar noua copie va comuta la Orchestrare Flux de Lucru. Noua copie ',
|
switchTipStart: 'O nouă copie a aplicației va fi creată pentru tine, iar noua copie va comuta la Orchestrare Flux de Lucru. Noua copie ',
|
||||||
|
@ -70,6 +70,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'Tamam',
|
ok: 'Tamam',
|
||||||
cancel: 'İptal',
|
cancel: 'İptal',
|
||||||
|
emoji: 'Emoji',
|
||||||
|
image: 'Görsel',
|
||||||
},
|
},
|
||||||
switch: 'Workflow Orkestrasyonuna Geç',
|
switch: 'Workflow Orkestrasyonuna Geç',
|
||||||
switchTipStart: 'Sizin için yeni bir uygulama kopyası oluşturulacak ve yeni kopya Workflow Orkestrasyonuna geçecektir. Yeni kopya ',
|
switchTipStart: 'Sizin için yeni bir uygulama kopyası oluşturulacak ve yeni kopya Workflow Orkestrasyonuna geçecektir. Yeni kopya ',
|
||||||
|
@ -70,6 +70,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
cancel: 'Скасувати',
|
cancel: 'Скасувати',
|
||||||
|
emoji: 'Емодзі',
|
||||||
|
image: 'Зображення',
|
||||||
},
|
},
|
||||||
switch: 'Перейти до оркестрації робочого процесу',
|
switch: 'Перейти до оркестрації робочого процесу',
|
||||||
switchTipStart: 'Для вас буде створена нова копія додатка, і нова копія перейде до оркестрації робочого процесу. Нова копія не дозволить ',
|
switchTipStart: 'Для вас буде створена нова копія додатка, і нова копія перейде до оркестрації робочого процесу. Нова копія не дозволить ',
|
||||||
|
@ -70,6 +70,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: 'Đồng ý',
|
ok: 'Đồng ý',
|
||||||
cancel: 'Hủy',
|
cancel: 'Hủy',
|
||||||
|
emoji: 'Biểu tượng cảm xúc',
|
||||||
|
image: 'Hình ảnh',
|
||||||
},
|
},
|
||||||
switch: 'Chuyển sang quản lý quy trình',
|
switch: 'Chuyển sang quản lý quy trình',
|
||||||
switchTipStart: 'Một bản sao ứng dụng mới sẽ được tạo và chuyển sang quản lý quy trình. Bản sao mới sẽ ',
|
switchTipStart: 'Một bản sao ứng dụng mới sẽ được tạo và chuyển sang quản lý quy trình. Bản sao mới sẽ ',
|
||||||
|
@ -76,9 +76,9 @@ const translation = {
|
|||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
params: {
|
params: {
|
||||||
temperature: 'Nhiệt độ',
|
temperature: 'Độ sáng tạo',
|
||||||
temperatureTip:
|
temperatureTip:
|
||||||
'Kiểm soát độ ngẫu nhiên: Giảm nhiệt độ dẫn đến ít kết quả ngẫu nhiên hơn. Khi nhiệt độ gần bằng 0, mô hình sẽ trở nên xác định và lặp lại.',
|
'Kiểm soát độ ngẫu nhiên: Giảm độ sáng tạo dẫn đến ít kết quả ngẫu nhiên hơn. Khi độ sáng tạo gần bằng 0, mô hình sẽ trở nên xác định và lặp lại.',
|
||||||
top_p: 'Top P',
|
top_p: 'Top P',
|
||||||
top_pTip:
|
top_pTip:
|
||||||
'Kiểm soát đa dạng thông qua lấy mẫu nhân nhóm: 0.5 có nghĩa là nửa số tùy chọn có khả năng cao được xem xét.',
|
'Kiểm soát đa dạng thông qua lấy mẫu nhân nhóm: 0.5 có nghĩa là nửa số tùy chọn có khả năng cao được xem xét.',
|
||||||
|
@ -69,6 +69,8 @@ const translation = {
|
|||||||
iconPicker: {
|
iconPicker: {
|
||||||
ok: '確認',
|
ok: '確認',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
|
emoji: '表情符號',
|
||||||
|
image: '圖片',
|
||||||
},
|
},
|
||||||
switch: '遷移為工作流編排',
|
switch: '遷移為工作流編排',
|
||||||
switchTipStart: '將為您建立一個使用工作流編排的新應用。新應用將',
|
switchTipStart: '將為您建立一個使用工作流編排的新應用。新應用將',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dify-web",
|
"name": "dify-web",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17.0"
|
"node": ">=18.17.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user