mirror of
https://github.com/langgenius/dify.git
synced 2025-12-25 00:28:54 +00:00
Compare commits
7 Commits
fix/note-n
...
0.7.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d27d15f00 | ||
|
|
ab6499e5b7 | ||
|
|
4ff4859036 | ||
|
|
53cf756207 | ||
|
|
0087afc2e3 | ||
|
|
bd07e1d2fd | ||
|
|
8b06105fa1 |
@@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
||||
|
||||
CURRENT_VERSION: str = Field(
|
||||
description='Dify version',
|
||||
default='0.7.0',
|
||||
default='0.7.1',
|
||||
)
|
||||
|
||||
COMMIT_SHA: str = Field(
|
||||
|
||||
@@ -185,7 +185,7 @@ if you are not sure about the structure.
|
||||
stream=stream,
|
||||
user=user
|
||||
)
|
||||
|
||||
|
||||
model_parameters.pop("response_format")
|
||||
stop = stop or []
|
||||
stop.extend(["\n```", "```\n"])
|
||||
@@ -249,10 +249,10 @@ if you are not sure about the structure.
|
||||
prompt_messages=prompt_messages,
|
||||
input_generator=new_generator()
|
||||
)
|
||||
|
||||
|
||||
return response
|
||||
|
||||
def _code_block_mode_stream_processor(self, model: str, prompt_messages: list[PromptMessage],
|
||||
def _code_block_mode_stream_processor(self, model: str, prompt_messages: list[PromptMessage],
|
||||
input_generator: Generator[LLMResultChunk, None, None]
|
||||
) -> Generator[LLMResultChunk, None, None]:
|
||||
"""
|
||||
@@ -310,7 +310,7 @@ if you are not sure about the structure.
|
||||
)
|
||||
)
|
||||
|
||||
def _code_block_mode_stream_processor_with_backtick(self, model: str, prompt_messages: list,
|
||||
def _code_block_mode_stream_processor_with_backtick(self, model: str, prompt_messages: list,
|
||||
input_generator: Generator[LLMResultChunk, None, None]) \
|
||||
-> Generator[LLMResultChunk, None, None]:
|
||||
"""
|
||||
@@ -470,7 +470,7 @@ if you are not sure about the structure.
|
||||
:return: full response or stream response chunk generator result
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage],
|
||||
tools: Optional[list[PromptMessageTool]] = None) -> int:
|
||||
@@ -792,6 +792,13 @@ if you are not sure about the structure.
|
||||
if not isinstance(parameter_value, str):
|
||||
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
|
||||
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}.")
|
||||
|
||||
@@ -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
|
||||
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
|
||||
@@ -2,7 +2,7 @@ version: '3'
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:0.7.0
|
||||
image: langgenius/dify-api:0.7.1
|
||||
restart: always
|
||||
environment:
|
||||
# Startup mode, 'api' starts the API server.
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:0.7.0
|
||||
image: langgenius/dify-api:0.7.1
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_WEB_URL: ''
|
||||
@@ -400,7 +400,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:0.7.0
|
||||
image: langgenius/dify-web:0.7.1
|
||||
restart: always
|
||||
environment:
|
||||
# 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
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:0.2.1
|
||||
image: langgenius/dify-sandbox:0.2.6
|
||||
restart: always
|
||||
environment:
|
||||
# The DifySandbox configurations
|
||||
|
||||
@@ -188,7 +188,7 @@ x-shared-env: &shared-api-worker-env
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:0.7.0
|
||||
image: langgenius/dify-api:0.7.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -208,7 +208,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:0.7.0
|
||||
image: langgenius/dify-api:0.7.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -227,7 +227,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:0.7.0
|
||||
image: langgenius/dify-web:0.7.1
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
@@ -272,7 +272,7 @@ services:
|
||||
|
||||
# The DifySandbox
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:0.2.1
|
||||
image: langgenius/dify-sandbox:0.2.6
|
||||
restart: always
|
||||
environment:
|
||||
# The DifySandbox configurations
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function ChartView({ appId }: IChartViewProps) {
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const isChatApp = appDetail?.mode !== 'completion' && 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.format(queryDateFormat) } })
|
||||
|
||||
const onSelect = (item: Item) => {
|
||||
if (item.value === 'all') {
|
||||
@@ -37,7 +37,7 @@ export default function ChartView({ appId }: IChartViewProps) {
|
||||
setPeriod({ name: item.name, query: { start: startOfToday, end: endOfToday } })
|
||||
}
|
||||
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.format(queryDateFormat) } })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,9 @@ const RunMode = memo(() => {
|
||||
'hover:bg-state-accent-hover cursor-pointer',
|
||||
isRunning && 'bg-state-accent-hover !cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => handleWorkflowStartRunInWorkflow()}
|
||||
onClick={() => {
|
||||
handleWorkflowStartRunInWorkflow()
|
||||
}}
|
||||
>
|
||||
{
|
||||
isRunning
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
useWorkflowInteractions,
|
||||
useWorkflowRun,
|
||||
} from '../hooks'
|
||||
import { WorkflowRunningStatus } from '../types'
|
||||
import { ControlMode, WorkflowRunningStatus } from '../types'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
@@ -58,6 +58,7 @@ const ViewHistory = ({
|
||||
handleCancelDebugAndPreviewPanel,
|
||||
} = useWorkflowInteractions()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const setControlMode = useStore(s => s.setControlMode)
|
||||
const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({
|
||||
appDetail: state.appDetail,
|
||||
setCurrentLogItem: state.setCurrentLogItem,
|
||||
@@ -173,6 +174,7 @@ const ViewHistory = ({
|
||||
setOpen(false)
|
||||
handleNodesCancelSelected()
|
||||
handleCancelDebugAndPreviewPanel()
|
||||
setControlMode(ControlMode.Hand)
|
||||
}}
|
||||
>
|
||||
{
|
||||
|
||||
@@ -7,11 +7,12 @@ export * from './use-workflow'
|
||||
export * from './use-workflow-run'
|
||||
export * from './use-workflow-template'
|
||||
export * from './use-checklist'
|
||||
export * from './use-workflow-mode'
|
||||
export * from './use-workflow-interactions'
|
||||
export * from './use-selection-interactions'
|
||||
export * from './use-panel-interactions'
|
||||
export * from './use-workflow-start-run'
|
||||
export * from './use-nodes-layout'
|
||||
export * from './use-workflow-history'
|
||||
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 {
|
||||
useNodesReadOnly,
|
||||
useWorkflow,
|
||||
useWorkflowReadOnly,
|
||||
} from './use-workflow'
|
||||
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
||||
|
||||
@@ -62,6 +63,7 @@ export const useNodesInteractions = () => {
|
||||
getAfterNodesInSameBranch,
|
||||
} = useWorkflow()
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
const { getWorkflowReadOnly } = useWorkflowReadOnly()
|
||||
const { handleSetHelpline } = useHelpline()
|
||||
const {
|
||||
handleNodeIterationChildDrag,
|
||||
@@ -1029,14 +1031,7 @@ export const useNodesInteractions = () => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const {
|
||||
setClipboardElements,
|
||||
shortcutsDisabled,
|
||||
showFeaturesPanel,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (shortcutsDisabled || showFeaturesPanel)
|
||||
return
|
||||
const { setClipboardElements } = workflowStore.getState()
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
@@ -1062,14 +1057,9 @@ export const useNodesInteractions = () => {
|
||||
|
||||
const {
|
||||
clipboardElements,
|
||||
shortcutsDisabled,
|
||||
showFeaturesPanel,
|
||||
mousePosition,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (shortcutsDisabled || showFeaturesPanel)
|
||||
return
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
@@ -1107,6 +1097,11 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
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[] = []
|
||||
if (nodeToPaste.data.type === BlockEnum.Iteration) {
|
||||
newNode.data._children = [];
|
||||
@@ -1145,14 +1140,6 @@ export const useNodesInteractions = () => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const {
|
||||
shortcutsDisabled,
|
||||
showFeaturesPanel,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (shortcutsDisabled || showFeaturesPanel)
|
||||
return
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
edges,
|
||||
@@ -1175,7 +1162,7 @@ export const useNodesInteractions = () => {
|
||||
|
||||
if (selectedNode)
|
||||
handleNodeDelete(selectedNode.id)
|
||||
}, [store, workflowStore, getNodesReadOnly, handleNodeDelete])
|
||||
}, [store, getNodesReadOnly, handleNodeDelete])
|
||||
|
||||
const handleNodeResize = useCallback((nodeId: string, params: ResizeParamsWithDirection) => {
|
||||
if (getNodesReadOnly())
|
||||
@@ -1234,14 +1221,7 @@ export const useNodesInteractions = () => {
|
||||
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])
|
||||
|
||||
const handleHistoryBack = useCallback(() => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const {
|
||||
shortcutsDisabled,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (shortcutsDisabled)
|
||||
if (getNodesReadOnly() || getWorkflowReadOnly())
|
||||
return
|
||||
|
||||
const { setEdges, setNodes } = store.getState()
|
||||
@@ -1253,17 +1233,10 @@ export const useNodesInteractions = () => {
|
||||
|
||||
setEdges(edges)
|
||||
setNodes(nodes)
|
||||
}, [store, undo, workflowHistoryStore, workflowStore, getNodesReadOnly])
|
||||
}, [store, undo, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly])
|
||||
|
||||
const handleHistoryForward = useCallback(() => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const {
|
||||
shortcutsDisabled,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (shortcutsDisabled)
|
||||
if (getNodesReadOnly() || getWorkflowReadOnly())
|
||||
return
|
||||
|
||||
const { setEdges, setNodes } = store.getState()
|
||||
@@ -1275,7 +1248,7 @@ export const useNodesInteractions = () => {
|
||||
|
||||
setEdges(edges)
|
||||
setNodes(nodes)
|
||||
}, [redo, store, workflowHistoryStore, workflowStore, getNodesReadOnly])
|
||||
}, [redo, store, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly])
|
||||
|
||||
return {
|
||||
handleNodeDragStart,
|
||||
|
||||
@@ -8,7 +8,9 @@ import {
|
||||
} from '../store'
|
||||
import { BlockEnum } from '../types'
|
||||
import { useWorkflowUpdate } from '../hooks'
|
||||
import { useNodesReadOnly } from './use-workflow'
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
} from './use-workflow'
|
||||
import { syncWorkflowDraft } from '@/service/workflow'
|
||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
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,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useReactFlow } from 'reactflow'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import { DSL_EXPORT_CHECK, WORKFLOW_DATA_UPDATE } from '../constants'
|
||||
import type { WorkflowDataUpdator } from '../types'
|
||||
import { useReactFlow, useStoreApi } from 'reactflow'
|
||||
import produce from 'immer'
|
||||
import { useStore, useWorkflowStore } from '../store'
|
||||
import {
|
||||
CUSTOM_NODE, DSL_EXPORT_CHECK,
|
||||
WORKFLOW_DATA_UPDATE,
|
||||
} from '../constants'
|
||||
import type { Node, WorkflowDataUpdator } from '../types'
|
||||
import { ControlMode } from '../types'
|
||||
import {
|
||||
getLayoutByDagre,
|
||||
initialEdges,
|
||||
initialNodes,
|
||||
} from '../utils'
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
useSelectionInteractions,
|
||||
useWorkflowReadOnly,
|
||||
} from '../hooks'
|
||||
import { useEdgesInteractions } from './use-edges-interactions'
|
||||
import { useNodesInteractions } from './use-nodes-interactions'
|
||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
||||
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||
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 = () => {
|
||||
const reactflow = useReactFlow()
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
@@ -7,19 +7,14 @@ import {
|
||||
import dayjs from 'dayjs'
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import produce from 'immer'
|
||||
import {
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
useReactFlow,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import type {
|
||||
Connection,
|
||||
} from 'reactflow'
|
||||
import {
|
||||
getLayoutByDagre,
|
||||
} from '../utils'
|
||||
import type {
|
||||
Edge,
|
||||
Node,
|
||||
@@ -34,15 +29,12 @@ import {
|
||||
useWorkflowStore,
|
||||
} from '../store'
|
||||
import {
|
||||
CUSTOM_NODE,
|
||||
SUPPORT_OUTPUT_VARS_NODE,
|
||||
} from '../constants'
|
||||
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
|
||||
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
|
||||
import { useNodesExtraData } from './use-nodes-data'
|
||||
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 {
|
||||
fetchNodesDefaultConfigs,
|
||||
@@ -68,68 +60,13 @@ export const useIsChatMode = () => {
|
||||
export const useWorkflow = () => {
|
||||
const { locale } = useContext(I18n)
|
||||
const store = useStoreApi()
|
||||
const reactflow = useReactFlow()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { saveStateToHistory } = useWorkflowHistory()
|
||||
|
||||
const setPanelWidth = useCallback((width: number) => {
|
||||
localStorage.setItem('workflow-node-panel-width', `${width}`)
|
||||
workflowStore.setState({ panelWidth: width })
|
||||
}, [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 {
|
||||
getNodes,
|
||||
@@ -392,19 +329,8 @@ export const useWorkflow = () => {
|
||||
return nodes.find(node => node.id === nodeId) || nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
}, [store])
|
||||
|
||||
const enableShortcuts = useCallback(() => {
|
||||
const { setShortcutsDisabled } = workflowStore.getState()
|
||||
setShortcutsDisabled(false)
|
||||
}, [workflowStore])
|
||||
|
||||
const disableShortcuts = useCallback(() => {
|
||||
const { setShortcutsDisabled } = workflowStore.getState()
|
||||
setShortcutsDisabled(true)
|
||||
}, [workflowStore])
|
||||
|
||||
return {
|
||||
setPanelWidth,
|
||||
handleLayout,
|
||||
getTreeLeafNodes,
|
||||
getBeforeNodesInSameBranch,
|
||||
getBeforeNodesInSameBranchIncludeParent,
|
||||
@@ -418,8 +344,6 @@ export const useWorkflow = () => {
|
||||
getNode,
|
||||
getBeforeNodeById,
|
||||
getIterationNodeChildren,
|
||||
enableShortcuts,
|
||||
disableShortcuts,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
import { setAutoFreeze } from 'immer'
|
||||
import {
|
||||
useEventListener,
|
||||
useKeyPress,
|
||||
} from 'ahooks'
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
@@ -34,6 +33,9 @@ import type {
|
||||
EnvironmentVariable,
|
||||
Node,
|
||||
} from './types'
|
||||
import {
|
||||
ControlMode,
|
||||
} from './types'
|
||||
import { WorkflowContextProvider } from './context'
|
||||
import {
|
||||
useDSL,
|
||||
@@ -43,10 +45,10 @@ import {
|
||||
useNodesSyncDraft,
|
||||
usePanelInteractions,
|
||||
useSelectionInteractions,
|
||||
useShortcuts,
|
||||
useWorkflow,
|
||||
useWorkflowInit,
|
||||
useWorkflowReadOnly,
|
||||
useWorkflowStartRun,
|
||||
useWorkflowUpdate,
|
||||
} from './hooks'
|
||||
import Header from './header'
|
||||
@@ -70,10 +72,8 @@ import {
|
||||
useWorkflowStore,
|
||||
} from './store'
|
||||
import {
|
||||
getKeyboardKeyCodeBySystem,
|
||||
initialEdges,
|
||||
initialNodes,
|
||||
isEventTargetInputArea,
|
||||
} from './utils'
|
||||
import {
|
||||
CUSTOM_NODE,
|
||||
@@ -81,7 +81,7 @@ import {
|
||||
ITERATION_CHILDREN_Z_INDEX,
|
||||
WORKFLOW_DATA_UPDATE,
|
||||
} from './constants'
|
||||
import { WorkflowHistoryProvider, useWorkflowHistoryStore } from './workflow-history-store'
|
||||
import { WorkflowHistoryProvider } from './workflow-history-store'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { FeaturesProvider } from '@/app/components/base/features'
|
||||
import type { Features as FeaturesData } from '@/app/components/base/features/types'
|
||||
@@ -225,17 +225,12 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
handleNodeConnectStart,
|
||||
handleNodeConnectEnd,
|
||||
handleNodeContextMenu,
|
||||
handleNodesCopy,
|
||||
handleNodesPaste,
|
||||
handleNodesDuplicate,
|
||||
handleNodesDelete,
|
||||
handleHistoryBack,
|
||||
handleHistoryForward,
|
||||
} = useNodesInteractions()
|
||||
const {
|
||||
handleEdgeEnter,
|
||||
handleEdgeLeave,
|
||||
handleEdgeDelete,
|
||||
handleEdgesChange,
|
||||
} = useEdgesInteractions()
|
||||
const {
|
||||
@@ -250,7 +245,6 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
const {
|
||||
isValidConnection,
|
||||
} = useWorkflow()
|
||||
const { handleStartWorkflowRun } = useWorkflowStartRun()
|
||||
const {
|
||||
exportCheck,
|
||||
handleExportDSL,
|
||||
@@ -262,41 +256,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
},
|
||||
})
|
||||
|
||||
const { shortcutsEnabled: workflowHistoryShortcutsEnabled } = useWorkflowHistoryStore()
|
||||
|
||||
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 },
|
||||
)
|
||||
useShortcuts()
|
||||
|
||||
const store = useStoreApi()
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
@@ -388,14 +348,14 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
nodesConnectable={!nodesReadOnly}
|
||||
nodesFocusable={!nodesReadOnly}
|
||||
edgesFocusable={!nodesReadOnly}
|
||||
panOnDrag={controlMode === 'hand' && !workflowReadOnly}
|
||||
panOnDrag={controlMode === ControlMode.Hand && !workflowReadOnly}
|
||||
zoomOnPinch={!workflowReadOnly}
|
||||
zoomOnScroll={!workflowReadOnly}
|
||||
zoomOnDoubleClick={!workflowReadOnly}
|
||||
isValidConnection={isValidConnection}
|
||||
selectionKeyCode={null}
|
||||
selectionMode={SelectionMode.Partial}
|
||||
selectionOnDrag={controlMode === 'pointer' && !workflowReadOnly}
|
||||
selectionOnDrag={controlMode === ControlMode.Pointer && !workflowReadOnly}
|
||||
minZoom={0.25}
|
||||
>
|
||||
<Background
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -55,33 +54,14 @@ const NoteNode = ({
|
||||
handleNodeDelete(id)
|
||||
}, [id, handleNodeDelete])
|
||||
|
||||
const preventZoom = (e: WheelEvent) => {
|
||||
if (data.selected && e.ctrlKey) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
useClickAway(() => {
|
||||
handleNodeDataUpdateWithSyncDraft({ id, data: { selected: false } })
|
||||
}, ref)
|
||||
|
||||
useEffect(() => {
|
||||
const node = ref.current
|
||||
if (node)
|
||||
node.addEventListener('wheel', preventZoom, { passive: false })
|
||||
|
||||
return () => {
|
||||
if (node)
|
||||
node.removeEventListener('wheel', preventZoom)
|
||||
}
|
||||
}, [data.selected])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col relative rounded-md shadow-xs border hover:shadow-md',
|
||||
data.selected && 'nodrag nopan nowheel',
|
||||
)}
|
||||
style={{
|
||||
background: THEME_MAP[theme].bg,
|
||||
@@ -122,7 +102,7 @@ const NoteNode = ({
|
||||
}
|
||||
<div className='grow px-3 py-2.5 overflow-y-auto'>
|
||||
<div className={cn(
|
||||
data.selected && 'cursor-text',
|
||||
data.selected && 'nodrag nopan nowheel cursor-text',
|
||||
)}>
|
||||
<NoteEditor
|
||||
containerElement={ref.current}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { MouseEvent } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
@@ -10,13 +9,14 @@ import {
|
||||
RiHand,
|
||||
RiStickyNoteAddLine,
|
||||
} from '@remixicon/react'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
useSelectionInteractions,
|
||||
useWorkflow,
|
||||
useWorkflowMoveMode,
|
||||
useWorkflowOrganize,
|
||||
} from '../hooks'
|
||||
import { getKeyboardKeyCodeBySystem, isEventTargetInputArea } from '../utils'
|
||||
import {
|
||||
ControlMode,
|
||||
} from '../types'
|
||||
import { useStore } from '../store'
|
||||
import AddBlock from './add-block'
|
||||
import TipPopup from './tip-popup'
|
||||
@@ -26,62 +26,13 @@ import cn from '@/utils/classnames'
|
||||
const Control = () => {
|
||||
const { t } = useTranslation()
|
||||
const controlMode = useStore(s => s.controlMode)
|
||||
const setControlMode = useStore(s => s.setControlMode)
|
||||
const { handleLayout } = useWorkflow()
|
||||
const { handleModePointer, handleModeHand } = useWorkflowMoveMode()
|
||||
const { handleLayout } = useWorkflowOrganize()
|
||||
const { handleAddNote } = useOperator()
|
||||
const {
|
||||
nodesReadOnly,
|
||||
getNodesReadOnly,
|
||||
} = 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>) => {
|
||||
if (getNodesReadOnly())
|
||||
@@ -110,7 +61,7 @@ const Control = () => {
|
||||
<div
|
||||
className={cn(
|
||||
'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'}`,
|
||||
)}
|
||||
onClick={handleModePointer}
|
||||
@@ -122,7 +73,7 @@ const Control = () => {
|
||||
<div
|
||||
className={cn(
|
||||
'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'}`,
|
||||
)}
|
||||
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',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
)}
|
||||
onClick={goLayout}
|
||||
onClick={handleLayout}
|
||||
>
|
||||
<RiFunctionAddLine className='w-4 h-4' />
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
RiZoomInLine,
|
||||
RiZoomOutLine,
|
||||
} from '@remixicon/react'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
useReactFlow,
|
||||
@@ -20,9 +19,7 @@ import {
|
||||
useWorkflowReadOnly,
|
||||
} from '../hooks'
|
||||
import {
|
||||
getKeyboardKeyCodeBySystem,
|
||||
getKeyboardKeyNameBySystem,
|
||||
isEventTargetInputArea,
|
||||
} from '../utils'
|
||||
import ShortcutsName from '../shortcuts-name'
|
||||
import TipPopup from './tip-popup'
|
||||
@@ -116,87 +113,6 @@ const ZoomInOut: FC = () => {
|
||||
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(() => {
|
||||
if (getWorkflowReadOnly())
|
||||
return
|
||||
@@ -289,11 +205,6 @@ const ZoomInOut: FC = () => {
|
||||
<ShortcutsName keys={['shift', '1']} />
|
||||
)
|
||||
}
|
||||
{
|
||||
option.key === ZoomType.zoomTo200 && (
|
||||
<ShortcutsName keys={['shift', '2']} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Panel as NodePanel } from '../nodes'
|
||||
import { useStore } from '../store'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
} from '../hooks'
|
||||
import DebugAndPreview from './debug-and-preview'
|
||||
import Record from './record'
|
||||
@@ -28,10 +27,6 @@ const Panel: FC = () => {
|
||||
const showEnvPanel = useStore(s => s.showEnvPanel)
|
||||
const showChatVariablePanel = useStore(s => s.showChatVariablePanel)
|
||||
const isRestoring = useStore(s => s.isRestoring)
|
||||
const {
|
||||
enableShortcuts,
|
||||
disableShortcuts,
|
||||
} = useWorkflow()
|
||||
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
|
||||
currentLogItem: state.currentLogItem,
|
||||
setCurrentLogItem: state.setCurrentLogItem,
|
||||
@@ -44,8 +39,6 @@ const Panel: FC = () => {
|
||||
<div
|
||||
tabIndex={-1}
|
||||
className={cn('absolute top-14 right-0 bottom-2 flex z-10 outline-none')}
|
||||
onFocus={disableShortcuts}
|
||||
onBlur={enableShortcuts}
|
||||
key={`${isRestoring}`}
|
||||
>
|
||||
{
|
||||
|
||||
@@ -99,8 +99,6 @@ type Shape = {
|
||||
setWorkflowTools: (tools: ToolWithProvider[]) => void
|
||||
clipboardElements: Node[]
|
||||
setClipboardElements: (clipboardElements: Node[]) => void
|
||||
shortcutsDisabled: boolean
|
||||
setShortcutsDisabled: (shortcutsDisabled: boolean) => void
|
||||
showDebugAndPreviewPanel: boolean
|
||||
setShowDebugAndPreviewPanel: (showDebugAndPreviewPanel: boolean) => void
|
||||
showEnvPanel: boolean
|
||||
@@ -217,8 +215,6 @@ export const createWorkflowStore = () => {
|
||||
setWorkflowTools: workflowTools => set(() => ({ workflowTools })),
|
||||
clipboardElements: [],
|
||||
setClipboardElements: clipboardElements => set(() => ({ clipboardElements })),
|
||||
shortcutsDisabled: false,
|
||||
setShortcutsDisabled: shortcutsDisabled => set(() => ({ shortcutsDisabled })),
|
||||
showDebugAndPreviewPanel: false,
|
||||
setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })),
|
||||
showEnvPanel: false,
|
||||
|
||||
@@ -29,6 +29,11 @@ export enum BlockEnum {
|
||||
Assigner = 'assigner', // is now named as VariableAssigner
|
||||
}
|
||||
|
||||
export enum ControlMode {
|
||||
Pointer = 'pointer',
|
||||
Hand = 'hand',
|
||||
}
|
||||
|
||||
export type Branch = {
|
||||
id: string
|
||||
name: string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dify-web",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
|
||||
Reference in New Issue
Block a user