mirror of
https://github.com/langgenius/dify.git
synced 2026-08-28 20:26:50 +08:00
Merge remote-tracking branch 'origin/main' into feat/trigger
This commit is contained in:
commit
29353bd7c2
@ -25,7 +25,7 @@ class FirecrawlApp:
|
|||||||
}
|
}
|
||||||
if params:
|
if params:
|
||||||
json_data.update(params)
|
json_data.update(params)
|
||||||
response = self._post_request(f"{self.base_url}/v1/scrape", json_data, headers)
|
response = self._post_request(f"{self.base_url}/v2/scrape", json_data, headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
data = response_data["data"]
|
data = response_data["data"]
|
||||||
@ -42,7 +42,7 @@ class FirecrawlApp:
|
|||||||
json_data = {"url": url}
|
json_data = {"url": url}
|
||||||
if params:
|
if params:
|
||||||
json_data.update(params)
|
json_data.update(params)
|
||||||
response = self._post_request(f"{self.base_url}/v1/crawl", json_data, headers)
|
response = self._post_request(f"{self.base_url}/v2/crawl", json_data, headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# There's also another two fields in the response: "success" (bool) and "url" (str)
|
# There's also another two fields in the response: "success" (bool) and "url" (str)
|
||||||
job_id = response.json().get("id")
|
job_id = response.json().get("id")
|
||||||
@ -51,9 +51,25 @@ class FirecrawlApp:
|
|||||||
self._handle_error(response, "start crawl job")
|
self._handle_error(response, "start crawl job")
|
||||||
return "" # unreachable
|
return "" # unreachable
|
||||||
|
|
||||||
|
def map(self, url: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||||
|
# Documentation: https://docs.firecrawl.dev/api-reference/endpoint/map
|
||||||
|
headers = self._prepare_headers()
|
||||||
|
json_data: dict[str, Any] = {"url": url, "integration": "dify"}
|
||||||
|
if params:
|
||||||
|
# Pass through provided params, including optional "sitemap": "only" | "include" | "skip"
|
||||||
|
json_data.update(params)
|
||||||
|
response = self._post_request(f"{self.base_url}/v2/map", json_data, headers)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return cast(dict[str, Any], response.json())
|
||||||
|
elif response.status_code in {402, 409, 500, 429, 408}:
|
||||||
|
self._handle_error(response, "start map job")
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
raise Exception(f"Failed to start map job. Status code: {response.status_code}")
|
||||||
|
|
||||||
def check_crawl_status(self, job_id) -> dict[str, Any]:
|
def check_crawl_status(self, job_id) -> dict[str, Any]:
|
||||||
headers = self._prepare_headers()
|
headers = self._prepare_headers()
|
||||||
response = self._get_request(f"{self.base_url}/v1/crawl/{job_id}", headers)
|
response = self._get_request(f"{self.base_url}/v2/crawl/{job_id}", headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
crawl_status_response = response.json()
|
crawl_status_response = response.json()
|
||||||
if crawl_status_response.get("status") == "completed":
|
if crawl_status_response.get("status") == "completed":
|
||||||
@ -135,12 +151,16 @@ class FirecrawlApp:
|
|||||||
"lang": "en",
|
"lang": "en",
|
||||||
"country": "us",
|
"country": "us",
|
||||||
"timeout": 60000,
|
"timeout": 60000,
|
||||||
"ignoreInvalidURLs": False,
|
"ignoreInvalidURLs": True,
|
||||||
"scrapeOptions": {},
|
"scrapeOptions": {},
|
||||||
|
"sources": [
|
||||||
|
{"type": "web"},
|
||||||
|
],
|
||||||
|
"integration": "dify",
|
||||||
}
|
}
|
||||||
if params:
|
if params:
|
||||||
json_data.update(params)
|
json_data.update(params)
|
||||||
response = self._post_request(f"{self.base_url}/v1/search", json_data, headers)
|
response = self._post_request(f"{self.base_url}/v2/search", json_data, headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
if not response_data.get("success"):
|
if not response_data.get("success"):
|
||||||
|
|||||||
@ -41,6 +41,7 @@ class RedisChannel:
|
|||||||
self._redis = redis_client
|
self._redis = redis_client
|
||||||
self._key = channel_key
|
self._key = channel_key
|
||||||
self._command_ttl = command_ttl
|
self._command_ttl = command_ttl
|
||||||
|
self._pending_key = f"{channel_key}:pending"
|
||||||
|
|
||||||
def fetch_commands(self) -> list[GraphEngineCommand]:
|
def fetch_commands(self) -> list[GraphEngineCommand]:
|
||||||
"""
|
"""
|
||||||
@ -49,6 +50,9 @@ class RedisChannel:
|
|||||||
Returns:
|
Returns:
|
||||||
List of pending commands (drains the Redis list)
|
List of pending commands (drains the Redis list)
|
||||||
"""
|
"""
|
||||||
|
if not self._has_pending_commands():
|
||||||
|
return []
|
||||||
|
|
||||||
commands: list[GraphEngineCommand] = []
|
commands: list[GraphEngineCommand] = []
|
||||||
|
|
||||||
# Use pipeline for atomic operations
|
# Use pipeline for atomic operations
|
||||||
@ -85,6 +89,7 @@ class RedisChannel:
|
|||||||
with self._redis.pipeline() as pipe:
|
with self._redis.pipeline() as pipe:
|
||||||
pipe.rpush(self._key, command_json)
|
pipe.rpush(self._key, command_json)
|
||||||
pipe.expire(self._key, self._command_ttl)
|
pipe.expire(self._key, self._command_ttl)
|
||||||
|
pipe.set(self._pending_key, "1", ex=self._command_ttl)
|
||||||
pipe.execute()
|
pipe.execute()
|
||||||
|
|
||||||
def _deserialize_command(self, data: dict[str, Any]) -> GraphEngineCommand | None:
|
def _deserialize_command(self, data: dict[str, Any]) -> GraphEngineCommand | None:
|
||||||
@ -112,3 +117,17 @@ class RedisChannel:
|
|||||||
|
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _has_pending_commands(self) -> bool:
|
||||||
|
"""
|
||||||
|
Check and consume the pending marker to avoid unnecessary list reads.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if commands should be fetched from Redis.
|
||||||
|
"""
|
||||||
|
with self._redis.pipeline() as pipe:
|
||||||
|
pipe.get(self._pending_key)
|
||||||
|
pipe.delete(self._pending_key)
|
||||||
|
pending_value, _ = pipe.execute()
|
||||||
|
|
||||||
|
return pending_value is not None
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from collections.abc import Mapping
|
|||||||
from functools import singledispatchmethod
|
from functools import singledispatchmethod
|
||||||
from typing import TYPE_CHECKING, final
|
from typing import TYPE_CHECKING, final
|
||||||
|
|
||||||
|
from core.model_runtime.entities.llm_entities import LLMUsage
|
||||||
from core.workflow.entities import GraphRuntimeState
|
from core.workflow.entities import GraphRuntimeState
|
||||||
from core.workflow.enums import ErrorStrategy, NodeExecutionType
|
from core.workflow.enums import ErrorStrategy, NodeExecutionType
|
||||||
from core.workflow.graph import Graph
|
from core.workflow.graph import Graph
|
||||||
@ -125,6 +126,7 @@ class EventHandler:
|
|||||||
node_execution = self._graph_execution.get_or_create_node_execution(event.node_id)
|
node_execution = self._graph_execution.get_or_create_node_execution(event.node_id)
|
||||||
is_initial_attempt = node_execution.retry_count == 0
|
is_initial_attempt = node_execution.retry_count == 0
|
||||||
node_execution.mark_started(event.id)
|
node_execution.mark_started(event.id)
|
||||||
|
self._graph_runtime_state.increment_node_run_steps()
|
||||||
|
|
||||||
# Track in response coordinator for stream ordering
|
# Track in response coordinator for stream ordering
|
||||||
self._response_coordinator.track_node_execution(event.node_id, event.id)
|
self._response_coordinator.track_node_execution(event.node_id, event.id)
|
||||||
@ -163,6 +165,8 @@ class EventHandler:
|
|||||||
node_execution = self._graph_execution.get_or_create_node_execution(event.node_id)
|
node_execution = self._graph_execution.get_or_create_node_execution(event.node_id)
|
||||||
node_execution.mark_taken()
|
node_execution.mark_taken()
|
||||||
|
|
||||||
|
self._accumulate_node_usage(event.node_run_result.llm_usage)
|
||||||
|
|
||||||
# Store outputs in variable pool
|
# Store outputs in variable pool
|
||||||
self._store_node_outputs(event.node_id, event.node_run_result.outputs)
|
self._store_node_outputs(event.node_id, event.node_run_result.outputs)
|
||||||
|
|
||||||
@ -212,6 +216,8 @@ class EventHandler:
|
|||||||
node_execution.mark_failed(event.error)
|
node_execution.mark_failed(event.error)
|
||||||
self._graph_execution.record_node_failure()
|
self._graph_execution.record_node_failure()
|
||||||
|
|
||||||
|
self._accumulate_node_usage(event.node_run_result.llm_usage)
|
||||||
|
|
||||||
result = self._error_handler.handle_node_failure(event)
|
result = self._error_handler.handle_node_failure(event)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
@ -235,6 +241,8 @@ class EventHandler:
|
|||||||
node_execution = self._graph_execution.get_or_create_node_execution(event.node_id)
|
node_execution = self._graph_execution.get_or_create_node_execution(event.node_id)
|
||||||
node_execution.mark_taken()
|
node_execution.mark_taken()
|
||||||
|
|
||||||
|
self._accumulate_node_usage(event.node_run_result.llm_usage)
|
||||||
|
|
||||||
# Persist outputs produced by the exception strategy (e.g. default values)
|
# Persist outputs produced by the exception strategy (e.g. default values)
|
||||||
self._store_node_outputs(event.node_id, event.node_run_result.outputs)
|
self._store_node_outputs(event.node_id, event.node_run_result.outputs)
|
||||||
|
|
||||||
@ -286,6 +294,19 @@ class EventHandler:
|
|||||||
self._state_manager.enqueue_node(event.node_id)
|
self._state_manager.enqueue_node(event.node_id)
|
||||||
self._state_manager.start_execution(event.node_id)
|
self._state_manager.start_execution(event.node_id)
|
||||||
|
|
||||||
|
def _accumulate_node_usage(self, usage: LLMUsage) -> None:
|
||||||
|
"""Accumulate token usage into the shared runtime state."""
|
||||||
|
if usage.total_tokens <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._graph_runtime_state.add_tokens(usage.total_tokens)
|
||||||
|
|
||||||
|
current_usage = self._graph_runtime_state.llm_usage
|
||||||
|
if current_usage.total_tokens == 0:
|
||||||
|
self._graph_runtime_state.llm_usage = usage
|
||||||
|
else:
|
||||||
|
self._graph_runtime_state.llm_usage = current_usage.plus(usage)
|
||||||
|
|
||||||
def _store_node_outputs(self, node_id: str, outputs: Mapping[str, object]) -> None:
|
def _store_node_outputs(self, node_id: str, outputs: Mapping[str, object]) -> None:
|
||||||
"""
|
"""
|
||||||
Store node outputs in the variable pool.
|
Store node outputs in the variable pool.
|
||||||
|
|||||||
@ -8,7 +8,12 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, final
|
from typing import TYPE_CHECKING, final
|
||||||
|
|
||||||
from core.workflow.graph_events.base import GraphNodeEventBase
|
from core.workflow.graph_events import (
|
||||||
|
GraphNodeEventBase,
|
||||||
|
NodeRunExceptionEvent,
|
||||||
|
NodeRunFailedEvent,
|
||||||
|
NodeRunSucceededEvent,
|
||||||
|
)
|
||||||
|
|
||||||
from ..event_management import EventManager
|
from ..event_management import EventManager
|
||||||
from .execution_coordinator import ExecutionCoordinator
|
from .execution_coordinator import ExecutionCoordinator
|
||||||
@ -72,13 +77,16 @@ class Dispatcher:
|
|||||||
if self._thread and self._thread.is_alive():
|
if self._thread and self._thread.is_alive():
|
||||||
self._thread.join(timeout=10.0)
|
self._thread.join(timeout=10.0)
|
||||||
|
|
||||||
|
_COMMAND_TRIGGER_EVENTS = (
|
||||||
|
NodeRunSucceededEvent,
|
||||||
|
NodeRunFailedEvent,
|
||||||
|
NodeRunExceptionEvent,
|
||||||
|
)
|
||||||
|
|
||||||
def _dispatcher_loop(self) -> None:
|
def _dispatcher_loop(self) -> None:
|
||||||
"""Main dispatcher loop."""
|
"""Main dispatcher loop."""
|
||||||
try:
|
try:
|
||||||
while not self._stop_event.is_set():
|
while not self._stop_event.is_set():
|
||||||
# Check for commands
|
|
||||||
self._execution_coordinator.check_commands()
|
|
||||||
|
|
||||||
# Check for scaling
|
# Check for scaling
|
||||||
self._execution_coordinator.check_scaling()
|
self._execution_coordinator.check_scaling()
|
||||||
|
|
||||||
@ -87,6 +95,8 @@ class Dispatcher:
|
|||||||
event = self._event_queue.get(timeout=0.1)
|
event = self._event_queue.get(timeout=0.1)
|
||||||
# Route to the event handler
|
# Route to the event handler
|
||||||
self._event_handler.dispatch(event)
|
self._event_handler.dispatch(event)
|
||||||
|
if self._should_check_commands(event):
|
||||||
|
self._execution_coordinator.check_commands()
|
||||||
self._event_queue.task_done()
|
self._event_queue.task_done()
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
# Check if execution is complete
|
# Check if execution is complete
|
||||||
@ -102,3 +112,7 @@ class Dispatcher:
|
|||||||
# Signal the event emitter that execution is complete
|
# Signal the event emitter that execution is complete
|
||||||
if self._event_emitter:
|
if self._event_emitter:
|
||||||
self._event_emitter.mark_complete()
|
self._event_emitter.mark_complete()
|
||||||
|
|
||||||
|
def _should_check_commands(self, event: GraphNodeEventBase) -> bool:
|
||||||
|
"""Return True if the event represents a node completion."""
|
||||||
|
return isinstance(event, self._COMMAND_TRIGGER_EVENTS)
|
||||||
|
|||||||
@ -23,6 +23,7 @@ class CrawlOptions:
|
|||||||
only_main_content: bool = False
|
only_main_content: bool = False
|
||||||
includes: str | None = None
|
includes: str | None = None
|
||||||
excludes: str | None = None
|
excludes: str | None = None
|
||||||
|
prompt: str | None = None
|
||||||
max_depth: int | None = None
|
max_depth: int | None = None
|
||||||
use_sitemap: bool = True
|
use_sitemap: bool = True
|
||||||
|
|
||||||
@ -70,6 +71,7 @@ class WebsiteCrawlApiRequest:
|
|||||||
only_main_content=self.options.get("only_main_content", False),
|
only_main_content=self.options.get("only_main_content", False),
|
||||||
includes=self.options.get("includes"),
|
includes=self.options.get("includes"),
|
||||||
excludes=self.options.get("excludes"),
|
excludes=self.options.get("excludes"),
|
||||||
|
prompt=self.options.get("prompt"),
|
||||||
max_depth=self.options.get("max_depth"),
|
max_depth=self.options.get("max_depth"),
|
||||||
use_sitemap=self.options.get("use_sitemap", True),
|
use_sitemap=self.options.get("use_sitemap", True),
|
||||||
)
|
)
|
||||||
@ -174,6 +176,7 @@ class WebsiteService:
|
|||||||
def _crawl_with_firecrawl(cls, request: CrawlRequest, api_key: str, config: dict) -> dict[str, Any]:
|
def _crawl_with_firecrawl(cls, request: CrawlRequest, api_key: str, config: dict) -> dict[str, Any]:
|
||||||
firecrawl_app = FirecrawlApp(api_key=api_key, base_url=config.get("base_url"))
|
firecrawl_app = FirecrawlApp(api_key=api_key, base_url=config.get("base_url"))
|
||||||
|
|
||||||
|
params: dict[str, Any]
|
||||||
if not request.options.crawl_sub_pages:
|
if not request.options.crawl_sub_pages:
|
||||||
params = {
|
params = {
|
||||||
"includePaths": [],
|
"includePaths": [],
|
||||||
@ -188,8 +191,10 @@ class WebsiteService:
|
|||||||
"limit": request.options.limit,
|
"limit": request.options.limit,
|
||||||
"scrapeOptions": {"onlyMainContent": request.options.only_main_content},
|
"scrapeOptions": {"onlyMainContent": request.options.only_main_content},
|
||||||
}
|
}
|
||||||
if request.options.max_depth:
|
|
||||||
params["maxDepth"] = request.options.max_depth
|
# Add optional prompt for Firecrawl v2 crawl-params compatibility
|
||||||
|
if request.options.prompt:
|
||||||
|
params["prompt"] = request.options.prompt
|
||||||
|
|
||||||
job_id = firecrawl_app.crawl_url(request.url, params)
|
job_id = firecrawl_app.crawl_url(request.url, params)
|
||||||
website_crawl_time_cache_key = f"website_crawl_{job_id}"
|
website_crawl_time_cache_key = f"website_crawl_{job_id}"
|
||||||
|
|||||||
@ -35,11 +35,15 @@ class TestRedisChannel:
|
|||||||
"""Test sending a command to Redis."""
|
"""Test sending a command to Redis."""
|
||||||
mock_redis = MagicMock()
|
mock_redis = MagicMock()
|
||||||
mock_pipe = MagicMock()
|
mock_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe)
|
context = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None)
|
context.__enter__.return_value = mock_pipe
|
||||||
|
context.__exit__.return_value = None
|
||||||
|
mock_redis.pipeline.return_value = context
|
||||||
|
|
||||||
channel = RedisChannel(mock_redis, "test:key", 3600)
|
channel = RedisChannel(mock_redis, "test:key", 3600)
|
||||||
|
|
||||||
|
pending_key = "test:key:pending"
|
||||||
|
|
||||||
# Create a test command
|
# Create a test command
|
||||||
command = GraphEngineCommand(command_type=CommandType.ABORT)
|
command = GraphEngineCommand(command_type=CommandType.ABORT)
|
||||||
|
|
||||||
@ -55,6 +59,7 @@ class TestRedisChannel:
|
|||||||
|
|
||||||
# Verify expire was set
|
# Verify expire was set
|
||||||
mock_pipe.expire.assert_called_once_with("test:key", 3600)
|
mock_pipe.expire.assert_called_once_with("test:key", 3600)
|
||||||
|
mock_pipe.set.assert_called_once_with(pending_key, "1", ex=3600)
|
||||||
|
|
||||||
# Verify execute was called
|
# Verify execute was called
|
||||||
mock_pipe.execute.assert_called_once()
|
mock_pipe.execute.assert_called_once()
|
||||||
@ -62,33 +67,48 @@ class TestRedisChannel:
|
|||||||
def test_fetch_commands_empty(self):
|
def test_fetch_commands_empty(self):
|
||||||
"""Test fetching commands when Redis list is empty."""
|
"""Test fetching commands when Redis list is empty."""
|
||||||
mock_redis = MagicMock()
|
mock_redis = MagicMock()
|
||||||
mock_pipe = MagicMock()
|
pending_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe)
|
fetch_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None)
|
pending_context = MagicMock()
|
||||||
|
fetch_context = MagicMock()
|
||||||
|
pending_context.__enter__.return_value = pending_pipe
|
||||||
|
pending_context.__exit__.return_value = None
|
||||||
|
fetch_context.__enter__.return_value = fetch_pipe
|
||||||
|
fetch_context.__exit__.return_value = None
|
||||||
|
mock_redis.pipeline.side_effect = [pending_context]
|
||||||
|
|
||||||
# Simulate empty list
|
# No pending marker
|
||||||
mock_pipe.execute.return_value = [[], 1] # Empty list, delete successful
|
pending_pipe.execute.return_value = [None, 0]
|
||||||
|
mock_redis.llen.return_value = 0
|
||||||
|
|
||||||
channel = RedisChannel(mock_redis, "test:key")
|
channel = RedisChannel(mock_redis, "test:key")
|
||||||
commands = channel.fetch_commands()
|
commands = channel.fetch_commands()
|
||||||
|
|
||||||
assert commands == []
|
assert commands == []
|
||||||
mock_pipe.lrange.assert_called_once_with("test:key", 0, -1)
|
mock_redis.pipeline.assert_called_once()
|
||||||
mock_pipe.delete.assert_called_once_with("test:key")
|
fetch_pipe.lrange.assert_not_called()
|
||||||
|
fetch_pipe.delete.assert_not_called()
|
||||||
|
|
||||||
def test_fetch_commands_with_abort_command(self):
|
def test_fetch_commands_with_abort_command(self):
|
||||||
"""Test fetching abort commands from Redis."""
|
"""Test fetching abort commands from Redis."""
|
||||||
mock_redis = MagicMock()
|
mock_redis = MagicMock()
|
||||||
mock_pipe = MagicMock()
|
pending_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe)
|
fetch_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None)
|
pending_context = MagicMock()
|
||||||
|
fetch_context = MagicMock()
|
||||||
|
pending_context.__enter__.return_value = pending_pipe
|
||||||
|
pending_context.__exit__.return_value = None
|
||||||
|
fetch_context.__enter__.return_value = fetch_pipe
|
||||||
|
fetch_context.__exit__.return_value = None
|
||||||
|
mock_redis.pipeline.side_effect = [pending_context, fetch_context]
|
||||||
|
|
||||||
# Create abort command data
|
# Create abort command data
|
||||||
abort_command = AbortCommand()
|
abort_command = AbortCommand()
|
||||||
command_json = json.dumps(abort_command.model_dump())
|
command_json = json.dumps(abort_command.model_dump())
|
||||||
|
|
||||||
# Simulate Redis returning one command
|
# Simulate Redis returning one command
|
||||||
mock_pipe.execute.return_value = [[command_json.encode()], 1]
|
pending_pipe.execute.return_value = [b"1", 1]
|
||||||
|
fetch_pipe.execute.return_value = [[command_json.encode()], 1]
|
||||||
|
|
||||||
channel = RedisChannel(mock_redis, "test:key")
|
channel = RedisChannel(mock_redis, "test:key")
|
||||||
commands = channel.fetch_commands()
|
commands = channel.fetch_commands()
|
||||||
@ -100,9 +120,15 @@ class TestRedisChannel:
|
|||||||
def test_fetch_commands_multiple(self):
|
def test_fetch_commands_multiple(self):
|
||||||
"""Test fetching multiple commands from Redis."""
|
"""Test fetching multiple commands from Redis."""
|
||||||
mock_redis = MagicMock()
|
mock_redis = MagicMock()
|
||||||
mock_pipe = MagicMock()
|
pending_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe)
|
fetch_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None)
|
pending_context = MagicMock()
|
||||||
|
fetch_context = MagicMock()
|
||||||
|
pending_context.__enter__.return_value = pending_pipe
|
||||||
|
pending_context.__exit__.return_value = None
|
||||||
|
fetch_context.__enter__.return_value = fetch_pipe
|
||||||
|
fetch_context.__exit__.return_value = None
|
||||||
|
mock_redis.pipeline.side_effect = [pending_context, fetch_context]
|
||||||
|
|
||||||
# Create multiple commands
|
# Create multiple commands
|
||||||
command1 = GraphEngineCommand(command_type=CommandType.ABORT)
|
command1 = GraphEngineCommand(command_type=CommandType.ABORT)
|
||||||
@ -112,7 +138,8 @@ class TestRedisChannel:
|
|||||||
command2_json = json.dumps(command2.model_dump())
|
command2_json = json.dumps(command2.model_dump())
|
||||||
|
|
||||||
# Simulate Redis returning multiple commands
|
# Simulate Redis returning multiple commands
|
||||||
mock_pipe.execute.return_value = [[command1_json.encode(), command2_json.encode()], 1]
|
pending_pipe.execute.return_value = [b"1", 1]
|
||||||
|
fetch_pipe.execute.return_value = [[command1_json.encode(), command2_json.encode()], 1]
|
||||||
|
|
||||||
channel = RedisChannel(mock_redis, "test:key")
|
channel = RedisChannel(mock_redis, "test:key")
|
||||||
commands = channel.fetch_commands()
|
commands = channel.fetch_commands()
|
||||||
@ -124,9 +151,15 @@ class TestRedisChannel:
|
|||||||
def test_fetch_commands_skips_invalid_json(self):
|
def test_fetch_commands_skips_invalid_json(self):
|
||||||
"""Test that invalid JSON commands are skipped."""
|
"""Test that invalid JSON commands are skipped."""
|
||||||
mock_redis = MagicMock()
|
mock_redis = MagicMock()
|
||||||
mock_pipe = MagicMock()
|
pending_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe)
|
fetch_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None)
|
pending_context = MagicMock()
|
||||||
|
fetch_context = MagicMock()
|
||||||
|
pending_context.__enter__.return_value = pending_pipe
|
||||||
|
pending_context.__exit__.return_value = None
|
||||||
|
fetch_context.__enter__.return_value = fetch_pipe
|
||||||
|
fetch_context.__exit__.return_value = None
|
||||||
|
mock_redis.pipeline.side_effect = [pending_context, fetch_context]
|
||||||
|
|
||||||
# Mix valid and invalid JSON
|
# Mix valid and invalid JSON
|
||||||
valid_command = AbortCommand()
|
valid_command = AbortCommand()
|
||||||
@ -134,7 +167,8 @@ class TestRedisChannel:
|
|||||||
invalid_json = b"invalid json {"
|
invalid_json = b"invalid json {"
|
||||||
|
|
||||||
# Simulate Redis returning mixed valid/invalid commands
|
# Simulate Redis returning mixed valid/invalid commands
|
||||||
mock_pipe.execute.return_value = [[invalid_json, valid_json.encode()], 1]
|
pending_pipe.execute.return_value = [b"1", 1]
|
||||||
|
fetch_pipe.execute.return_value = [[invalid_json, valid_json.encode()], 1]
|
||||||
|
|
||||||
channel = RedisChannel(mock_redis, "test:key")
|
channel = RedisChannel(mock_redis, "test:key")
|
||||||
commands = channel.fetch_commands()
|
commands = channel.fetch_commands()
|
||||||
@ -187,13 +221,20 @@ class TestRedisChannel:
|
|||||||
def test_atomic_fetch_and_clear(self):
|
def test_atomic_fetch_and_clear(self):
|
||||||
"""Test that fetch_commands atomically fetches and clears the list."""
|
"""Test that fetch_commands atomically fetches and clears the list."""
|
||||||
mock_redis = MagicMock()
|
mock_redis = MagicMock()
|
||||||
mock_pipe = MagicMock()
|
pending_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe)
|
fetch_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None)
|
pending_context = MagicMock()
|
||||||
|
fetch_context = MagicMock()
|
||||||
|
pending_context.__enter__.return_value = pending_pipe
|
||||||
|
pending_context.__exit__.return_value = None
|
||||||
|
fetch_context.__enter__.return_value = fetch_pipe
|
||||||
|
fetch_context.__exit__.return_value = None
|
||||||
|
mock_redis.pipeline.side_effect = [pending_context, fetch_context]
|
||||||
|
|
||||||
command = AbortCommand()
|
command = AbortCommand()
|
||||||
command_json = json.dumps(command.model_dump())
|
command_json = json.dumps(command.model_dump())
|
||||||
mock_pipe.execute.return_value = [[command_json.encode()], 1]
|
pending_pipe.execute.return_value = [b"1", 1]
|
||||||
|
fetch_pipe.execute.return_value = [[command_json.encode()], 1]
|
||||||
|
|
||||||
channel = RedisChannel(mock_redis, "test:key")
|
channel = RedisChannel(mock_redis, "test:key")
|
||||||
|
|
||||||
@ -202,7 +243,29 @@ class TestRedisChannel:
|
|||||||
assert len(commands) == 1
|
assert len(commands) == 1
|
||||||
|
|
||||||
# Verify both lrange and delete were called in the pipeline
|
# Verify both lrange and delete were called in the pipeline
|
||||||
assert mock_pipe.lrange.call_count == 1
|
assert fetch_pipe.lrange.call_count == 1
|
||||||
assert mock_pipe.delete.call_count == 1
|
assert fetch_pipe.delete.call_count == 1
|
||||||
mock_pipe.lrange.assert_called_with("test:key", 0, -1)
|
fetch_pipe.lrange.assert_called_with("test:key", 0, -1)
|
||||||
mock_pipe.delete.assert_called_with("test:key")
|
fetch_pipe.delete.assert_called_with("test:key")
|
||||||
|
|
||||||
|
def test_fetch_commands_without_pending_marker_returns_empty(self):
|
||||||
|
"""Ensure we avoid unnecessary list reads when pending flag is missing."""
|
||||||
|
mock_redis = MagicMock()
|
||||||
|
pending_pipe = MagicMock()
|
||||||
|
fetch_pipe = MagicMock()
|
||||||
|
pending_context = MagicMock()
|
||||||
|
fetch_context = MagicMock()
|
||||||
|
pending_context.__enter__.return_value = pending_pipe
|
||||||
|
pending_context.__exit__.return_value = None
|
||||||
|
fetch_context.__enter__.return_value = fetch_pipe
|
||||||
|
fetch_context.__exit__.return_value = None
|
||||||
|
mock_redis.pipeline.side_effect = [pending_context, fetch_context]
|
||||||
|
|
||||||
|
# Pending flag absent
|
||||||
|
pending_pipe.execute.return_value = [None, 0]
|
||||||
|
channel = RedisChannel(mock_redis, "test:key")
|
||||||
|
commands = channel.fetch_commands()
|
||||||
|
|
||||||
|
assert commands == []
|
||||||
|
mock_redis.llen.assert_not_called()
|
||||||
|
assert mock_redis.pipeline.call_count == 1
|
||||||
|
|||||||
@ -0,0 +1,104 @@
|
|||||||
|
"""Tests for dispatcher command checking behavior."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import queue
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus
|
||||||
|
from core.workflow.graph_engine.event_management.event_manager import EventManager
|
||||||
|
from core.workflow.graph_engine.orchestration.dispatcher import Dispatcher
|
||||||
|
from core.workflow.graph_events import NodeRunStartedEvent, NodeRunSucceededEvent
|
||||||
|
from core.workflow.node_events import NodeRunResult
|
||||||
|
|
||||||
|
|
||||||
|
class _StubExecutionCoordinator:
|
||||||
|
"""Stub execution coordinator that tracks command checks."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.command_checks = 0
|
||||||
|
self.scaling_checks = 0
|
||||||
|
self._execution_complete = False
|
||||||
|
self.mark_complete_called = False
|
||||||
|
self.failed = False
|
||||||
|
|
||||||
|
def check_commands(self) -> None:
|
||||||
|
self.command_checks += 1
|
||||||
|
|
||||||
|
def check_scaling(self) -> None:
|
||||||
|
self.scaling_checks += 1
|
||||||
|
|
||||||
|
def is_execution_complete(self) -> bool:
|
||||||
|
return self._execution_complete
|
||||||
|
|
||||||
|
def mark_complete(self) -> None:
|
||||||
|
self.mark_complete_called = True
|
||||||
|
|
||||||
|
def mark_failed(self, error: Exception) -> None: # pragma: no cover - defensive, not triggered in tests
|
||||||
|
self.failed = True
|
||||||
|
|
||||||
|
def set_execution_complete(self) -> None:
|
||||||
|
self._execution_complete = True
|
||||||
|
|
||||||
|
|
||||||
|
class _StubEventHandler:
|
||||||
|
"""Minimal event handler that marks execution complete after handling an event."""
|
||||||
|
|
||||||
|
def __init__(self, coordinator: _StubExecutionCoordinator) -> None:
|
||||||
|
self._coordinator = coordinator
|
||||||
|
self.events = []
|
||||||
|
|
||||||
|
def dispatch(self, event) -> None:
|
||||||
|
self.events.append(event)
|
||||||
|
self._coordinator.set_execution_complete()
|
||||||
|
|
||||||
|
|
||||||
|
def _run_dispatcher_for_event(event) -> int:
|
||||||
|
"""Run the dispatcher loop for a single event and return command check count."""
|
||||||
|
event_queue: queue.Queue = queue.Queue()
|
||||||
|
event_queue.put(event)
|
||||||
|
|
||||||
|
coordinator = _StubExecutionCoordinator()
|
||||||
|
event_handler = _StubEventHandler(coordinator)
|
||||||
|
event_manager = EventManager()
|
||||||
|
|
||||||
|
dispatcher = Dispatcher(
|
||||||
|
event_queue=event_queue,
|
||||||
|
event_handler=event_handler,
|
||||||
|
event_collector=event_manager,
|
||||||
|
execution_coordinator=coordinator,
|
||||||
|
)
|
||||||
|
|
||||||
|
dispatcher._dispatcher_loop()
|
||||||
|
|
||||||
|
return coordinator.command_checks
|
||||||
|
|
||||||
|
|
||||||
|
def _make_started_event() -> NodeRunStartedEvent:
|
||||||
|
return NodeRunStartedEvent(
|
||||||
|
id="start-event",
|
||||||
|
node_id="node-1",
|
||||||
|
node_type=NodeType.CODE,
|
||||||
|
node_title="Test Node",
|
||||||
|
start_at=datetime.utcnow(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_succeeded_event() -> NodeRunSucceededEvent:
|
||||||
|
return NodeRunSucceededEvent(
|
||||||
|
id="success-event",
|
||||||
|
node_id="node-1",
|
||||||
|
node_type=NodeType.CODE,
|
||||||
|
node_title="Test Node",
|
||||||
|
start_at=datetime.utcnow(),
|
||||||
|
node_run_result=NodeRunResult(status=WorkflowNodeExecutionStatus.SUCCEEDED),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatcher_checks_commands_after_node_completion() -> None:
|
||||||
|
"""Dispatcher should only check commands after node completion events."""
|
||||||
|
started_checks = _run_dispatcher_for_event(_make_started_event())
|
||||||
|
succeeded_checks = _run_dispatcher_for_event(_make_succeeded_event())
|
||||||
|
|
||||||
|
assert started_checks == 0
|
||||||
|
assert succeeded_checks == 1
|
||||||
@ -132,15 +132,22 @@ class TestRedisStopIntegration:
|
|||||||
"""Test RedisChannel correctly fetches and deserializes commands."""
|
"""Test RedisChannel correctly fetches and deserializes commands."""
|
||||||
# Setup
|
# Setup
|
||||||
mock_redis = MagicMock()
|
mock_redis = MagicMock()
|
||||||
mock_pipeline = MagicMock()
|
pending_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__enter__ = Mock(return_value=mock_pipeline)
|
fetch_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__exit__ = Mock(return_value=None)
|
pending_context = MagicMock()
|
||||||
|
fetch_context = MagicMock()
|
||||||
|
pending_context.__enter__.return_value = pending_pipe
|
||||||
|
pending_context.__exit__.return_value = None
|
||||||
|
fetch_context.__enter__.return_value = fetch_pipe
|
||||||
|
fetch_context.__exit__.return_value = None
|
||||||
|
mock_redis.pipeline.side_effect = [pending_context, fetch_context]
|
||||||
|
|
||||||
# Mock command data
|
# Mock command data
|
||||||
abort_command_json = json.dumps({"command_type": CommandType.ABORT, "reason": "Test abort", "payload": None})
|
abort_command_json = json.dumps({"command_type": CommandType.ABORT, "reason": "Test abort", "payload": None})
|
||||||
|
|
||||||
# Mock pipeline execute to return commands
|
# Mock pipeline execute to return commands
|
||||||
mock_pipeline.execute.return_value = [
|
pending_pipe.execute.return_value = [b"1", 1]
|
||||||
|
fetch_pipe.execute.return_value = [
|
||||||
[abort_command_json.encode()], # lrange result
|
[abort_command_json.encode()], # lrange result
|
||||||
True, # delete result
|
True, # delete result
|
||||||
]
|
]
|
||||||
@ -158,19 +165,29 @@ class TestRedisStopIntegration:
|
|||||||
assert commands[0].reason == "Test abort"
|
assert commands[0].reason == "Test abort"
|
||||||
|
|
||||||
# Verify Redis operations
|
# Verify Redis operations
|
||||||
mock_pipeline.lrange.assert_called_once_with(channel_key, 0, -1)
|
pending_pipe.get.assert_called_once_with(f"{channel_key}:pending")
|
||||||
mock_pipeline.delete.assert_called_once_with(channel_key)
|
pending_pipe.delete.assert_called_once_with(f"{channel_key}:pending")
|
||||||
|
fetch_pipe.lrange.assert_called_once_with(channel_key, 0, -1)
|
||||||
|
fetch_pipe.delete.assert_called_once_with(channel_key)
|
||||||
|
assert mock_redis.pipeline.call_count == 2
|
||||||
|
|
||||||
def test_redis_channel_fetch_commands_handles_invalid_json(self):
|
def test_redis_channel_fetch_commands_handles_invalid_json(self):
|
||||||
"""Test RedisChannel gracefully handles invalid JSON in commands."""
|
"""Test RedisChannel gracefully handles invalid JSON in commands."""
|
||||||
# Setup
|
# Setup
|
||||||
mock_redis = MagicMock()
|
mock_redis = MagicMock()
|
||||||
mock_pipeline = MagicMock()
|
pending_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__enter__ = Mock(return_value=mock_pipeline)
|
fetch_pipe = MagicMock()
|
||||||
mock_redis.pipeline.return_value.__exit__ = Mock(return_value=None)
|
pending_context = MagicMock()
|
||||||
|
fetch_context = MagicMock()
|
||||||
|
pending_context.__enter__.return_value = pending_pipe
|
||||||
|
pending_context.__exit__.return_value = None
|
||||||
|
fetch_context.__enter__.return_value = fetch_pipe
|
||||||
|
fetch_context.__exit__.return_value = None
|
||||||
|
mock_redis.pipeline.side_effect = [pending_context, fetch_context]
|
||||||
|
|
||||||
# Mock invalid command data
|
# Mock invalid command data
|
||||||
mock_pipeline.execute.return_value = [
|
pending_pipe.execute.return_value = [b"1", 1]
|
||||||
|
fetch_pipe.execute.return_value = [
|
||||||
[b"invalid json", b'{"command_type": "invalid_type"}'], # lrange result
|
[b"invalid json", b'{"command_type": "invalid_type"}'], # lrange result
|
||||||
True, # delete result
|
True, # delete result
|
||||||
]
|
]
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import MenuItem from './menu-item'
|
import MenuItem from './menu-item'
|
||||||
import { RiDeleteBinLine, RiEditLine, RiFileDownloadLine } from '@remixicon/react'
|
import { RiDeleteBinLine, RiEditLine, RiFileDownloadLine } from '@remixicon/react'
|
||||||
import Divider from '../../base/divider'
|
import Divider from '../../base/divider'
|
||||||
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
|
|
||||||
type MenuProps = {
|
type MenuProps = {
|
||||||
showDelete: boolean
|
showDelete: boolean
|
||||||
@ -18,6 +19,7 @@ const Menu = ({
|
|||||||
detectIsUsedByApp,
|
detectIsUsedByApp,
|
||||||
}: MenuProps) => {
|
}: MenuProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const runtimeMode = useDatasetDetailContextWithSelector(state => state.dataset?.runtime_mode)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex w-[200px] flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]'>
|
<div className='flex w-[200px] flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]'>
|
||||||
@ -27,11 +29,13 @@ const Menu = ({
|
|||||||
name={t('common.operation.edit')}
|
name={t('common.operation.edit')}
|
||||||
handleClick={openRenameModal}
|
handleClick={openRenameModal}
|
||||||
/>
|
/>
|
||||||
|
{runtimeMode === 'rag_pipeline' && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Icon={RiFileDownloadLine}
|
Icon={RiFileDownloadLine}
|
||||||
name={t('datasetPipeline.operations.exportPipeline')}
|
name={t('datasetPipeline.operations.exportPipeline')}
|
||||||
handleClick={handleExportPipeline}
|
handleClick={handleExportPipeline}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{showDelete && (
|
{showDelete && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import type { SVGProps } from 'react'
|
|
||||||
|
|
||||||
const CitationIcon = (props: SVGProps<SVGSVGElement>) => (
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
aria-hidden="true"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M7 6h10M7 12h6M7 18h10"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="1.5"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M5 6c0-1.105.895-2 2-2h10c1.105 0 2 .895 2 2v12c0 1.105-.895 2-2 2H9l-4 3v-3H7"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="1.5"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default CitationIcon
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
const MoreLikeThisIcon: FC = () => {
|
|
||||||
return (
|
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M5.83914 0.666748H10.1609C10.6975 0.666741 11.1404 0.666734 11.5012 0.696212C11.8759 0.726829 12.2204 0.792538 12.544 0.957399C13.0457 1.21306 13.4537 1.62101 13.7093 2.12277C13.8742 2.44633 13.9399 2.7908 13.9705 3.16553C14 3.52633 14 3.96923 14 4.50587V7.41171C14 7.62908 14 7.73776 13.9652 7.80784C13.9303 7.87806 13.8939 7.91566 13.8249 7.95288C13.756 7.99003 13.6262 7.99438 13.3665 8.00307C12.8879 8.01909 12.4204 8.14633 11.997 8.36429C10.9478 7.82388 9.62021 7.82912 8.53296 8.73228C7.15064 9.88056 6.92784 11.8645 8.0466 13.2641C8.36602 13.6637 8.91519 14.1949 9.40533 14.6492C9.49781 14.7349 9.54405 14.7777 9.5632 14.8041C9.70784 15.003 9.5994 15.2795 9.35808 15.3271C9.32614 15.3334 9.26453 15.3334 9.14129 15.3334H5.83912C5.30248 15.3334 4.85958 15.3334 4.49878 15.304C4.12405 15.2733 3.77958 15.2076 3.45603 15.0428C2.95426 14.7871 2.54631 14.3792 2.29065 13.8774C2.12579 13.5538 2.06008 13.2094 2.02946 12.8346C1.99999 12.4738 1.99999 12.0309 2 11.4943V4.50587C1.99999 3.96924 1.99999 3.52632 2.02946 3.16553C2.06008 2.7908 2.12579 2.44633 2.29065 2.12277C2.54631 1.62101 2.95426 1.21306 3.45603 0.957399C3.77958 0.792538 4.12405 0.726829 4.49878 0.696212C4.85957 0.666734 5.3025 0.666741 5.83914 0.666748ZM4.66667 5.33342C4.29848 5.33342 4 5.63189 4 6.00008C4 6.36827 4.29848 6.66675 4.66667 6.66675H8.66667C9.03486 6.66675 9.33333 6.36827 9.33333 6.00008C9.33333 5.63189 9.03486 5.33342 8.66667 5.33342H4.66667ZM4 8.66675C4 8.29856 4.29848 8.00008 4.66667 8.00008H6C6.36819 8.00008 6.66667 8.29856 6.66667 8.66675C6.66667 9.03494 6.36819 9.33342 6 9.33342H4.66667C4.29848 9.33342 4 9.03494 4 8.66675ZM4.66667 2.66675C4.29848 2.66675 4 2.96523 4 3.33342C4 3.7016 4.29848 4.00008 4.66667 4.00008H10.6667C11.0349 4.00008 11.3333 3.7016 11.3333 3.33342C11.3333 2.96523 11.0349 2.66675 10.6667 2.66675H4.66667Z" fill="#DD2590" />
|
|
||||||
<path d="M11.9977 10.0256C11.3313 9.26808 10.2199 9.06432 9.3849 9.75796C8.54988 10.4516 8.43232 11.6113 9.08807 12.4317C9.58479 13.0531 10.9986 14.3025 11.655 14.8719C11.7744 14.9754 11.8341 15.0272 11.9037 15.0477C11.9642 15.0654 12.0312 15.0654 12.0917 15.0477C12.1613 15.0272 12.221 14.9754 12.3404 14.8719C12.9968 14.3025 14.4106 13.0531 14.9074 12.4317C15.5631 11.6113 15.4599 10.4443 14.6105 9.75796C13.7612 9.07161 12.6642 9.26808 11.9977 10.0256Z" fill="#DD2590" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(MoreLikeThisIcon)
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
const SuggestedQuestionsAfterAnswerIcon: FC = () => {
|
|
||||||
return (
|
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M10.8275 1.33325H5.17245C4.63581 1.33324 4.19289 1.33324 3.8321 1.36272C3.45737 1.39333 3.1129 1.45904 2.78934 1.6239C2.28758 1.87956 1.87963 2.28751 1.62397 2.78928C1.45911 3.11284 1.3934 3.4573 1.36278 3.83204C1.3333 4.19283 1.33331 4.63574 1.33332 5.17239L1.33328 9.42497C1.333 9.95523 1.33278 10.349 1.42418 10.6901C1.67076 11.6103 2.38955 12.3291 3.3098 12.5757C3.51478 12.6306 3.73878 12.6525 3.99998 12.6611L3.99998 13.5806C3.99995 13.7374 3.99992 13.8973 4.01182 14.0283C4.0232 14.1536 4.05333 14.3901 4.21844 14.5969C4.40843 14.8349 4.69652 14.9734 5.00106 14.973C5.26572 14.9728 5.46921 14.8486 5.57416 14.7792C5.6839 14.7066 5.80872 14.6067 5.93117 14.5087L7.53992 13.2217C7.88564 12.9451 7.98829 12.8671 8.09494 12.8126C8.20192 12.7579 8.3158 12.718 8.43349 12.6938C8.55081 12.6697 8.67974 12.6666 9.12248 12.6666H10.8275C11.3642 12.6666 11.8071 12.6666 12.1679 12.6371C12.5426 12.6065 12.8871 12.5408 13.2106 12.3759C13.7124 12.1203 14.1203 11.7123 14.376 11.2106C14.5409 10.887 14.6066 10.5425 14.6372 10.1678C14.6667 9.80701 14.6667 9.36411 14.6667 8.82747V5.17237C14.6667 4.63573 14.6667 4.19283 14.6372 3.83204C14.6066 3.4573 14.5409 3.11284 14.376 2.78928C14.1203 2.28751 13.7124 1.87956 13.2106 1.6239C12.8871 1.45904 12.5426 1.39333 12.1679 1.36272C11.8071 1.33324 11.3642 1.33324 10.8275 1.33325ZM8.99504 4.99992C8.99504 4.44763 9.44275 3.99992 9.99504 3.99992C10.5473 3.99992 10.995 4.44763 10.995 4.99992C10.995 5.5522 10.5473 5.99992 9.99504 5.99992C9.44275 5.99992 8.99504 5.5522 8.99504 4.99992ZM4.92837 7.79996C5.222 7.57974 5.63816 7.63837 5.85961 7.93051C5.90071 7.98295 5.94593 8.03229 5.99199 8.08035C6.09019 8.18282 6.23775 8.32184 6.42882 8.4608C6.81353 8.74059 7.3454 8.99996 7.99504 8.99996C8.64469 8.99996 9.17655 8.74059 9.56126 8.4608C9.75233 8.32184 9.89989 8.18282 9.99809 8.08035C10.0441 8.0323 10.0894 7.98294 10.1305 7.93051C10.3519 7.63837 10.7681 7.57974 11.0617 7.79996C11.3563 8.02087 11.416 8.43874 11.195 8.73329C11.1967 8.73112 11.1928 8.7361 11.186 8.74466C11.1697 8.7651 11.1372 8.80597 11.1261 8.81916C11.087 8.86575 11.0317 8.92884 10.9607 9.00289C10.8194 9.15043 10.6128 9.34474 10.3455 9.53912C9.81353 9.92599 9.01206 10.3333 7.99504 10.3333C6.97802 10.3333 6.17655 9.92599 5.64459 9.53912C5.37733 9.34474 5.17072 9.15043 5.02934 9.00289C4.95837 8.92884 4.90305 8.86575 4.86395 8.81916C4.84438 8.79585 4.82881 8.77659 4.81731 8.76207C4.58702 8.46455 4.61798 8.03275 4.92837 7.79996ZM5.99504 3.99992C5.44275 3.99992 4.99504 4.44763 4.99504 4.99992C4.99504 5.5522 5.44275 5.99992 5.99504 5.99992C6.54732 5.99992 6.99504 5.5522 6.99504 4.99992C6.99504 4.44763 6.54732 3.99992 5.99504 3.99992Z" fill="#06AED4" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(SuggestedQuestionsAfterAnswerIcon)
|
|
||||||
@ -1,10 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useState } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
|
import { ReactSortable } from 'react-sortablejs'
|
||||||
import Panel from '../base/feature-panel'
|
import Panel from '../base/feature-panel'
|
||||||
import EditModal from './config-modal'
|
import EditModal from './config-modal'
|
||||||
import VarItem from './var-item'
|
import VarItem from './var-item'
|
||||||
@ -22,6 +23,7 @@ import { useModalContext } from '@/context/modal-context'
|
|||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
import type { InputVar } from '@/app/components/workflow/types'
|
import type { InputVar } from '@/app/components/workflow/types'
|
||||||
import { InputVarType } from '@/app/components/workflow/types'
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
export const ADD_EXTERNAL_DATA_TOOL = 'ADD_EXTERNAL_DATA_TOOL'
|
export const ADD_EXTERNAL_DATA_TOOL = 'ADD_EXTERNAL_DATA_TOOL'
|
||||||
|
|
||||||
@ -218,6 +220,16 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||||||
|
|
||||||
showEditModal()
|
showEditModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const promptVariablesWithIds = useMemo(() => promptVariables.map((item) => {
|
||||||
|
return {
|
||||||
|
id: item.key,
|
||||||
|
variable: { ...item },
|
||||||
|
}
|
||||||
|
}), [promptVariables])
|
||||||
|
|
||||||
|
const canDrag = !readonly && promptVariables.length > 1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
@ -245,9 +257,20 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||||||
)}
|
)}
|
||||||
{hasVar && (
|
{hasVar && (
|
||||||
<div className='mt-1 px-3 pb-3'>
|
<div className='mt-1 px-3 pb-3'>
|
||||||
{promptVariables.map(({ key, name, type, required, config, icon, icon_background }, index) => (
|
<ReactSortable
|
||||||
|
className='space-y-1'
|
||||||
|
list={promptVariablesWithIds}
|
||||||
|
setList={(list) => { onPromptVariablesChange?.(list.map(item => item.variable)) }}
|
||||||
|
handle='.handle'
|
||||||
|
ghostClass='opacity-50'
|
||||||
|
animation={150}
|
||||||
|
>
|
||||||
|
{promptVariablesWithIds.map((item, index) => {
|
||||||
|
const { key, name, type, required, config, icon, icon_background } = item.variable
|
||||||
|
return (
|
||||||
<VarItem
|
<VarItem
|
||||||
key={index}
|
className={cn(canDrag && 'handle')}
|
||||||
|
key={key}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
name={key}
|
name={key}
|
||||||
label={name}
|
label={name}
|
||||||
@ -255,8 +278,11 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||||||
type={type}
|
type={type}
|
||||||
onEdit={() => handleConfig({ type, key, index, name, config, icon, icon_background })}
|
onEdit={() => handleConfig({ type, key, index, name, config, icon, icon_background })}
|
||||||
onRemove={() => handleRemoveVar(index)}
|
onRemove={() => handleRemoveVar(index)}
|
||||||
|
canDrag={canDrag}
|
||||||
/>
|
/>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
|
</ReactSortable>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type { FC } from 'react'
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
RiDeleteBinLine,
|
RiDeleteBinLine,
|
||||||
|
RiDraggable,
|
||||||
RiEditLine,
|
RiEditLine,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import type { IInputTypeIconProps } from './input-type-icon'
|
import type { IInputTypeIconProps } from './input-type-icon'
|
||||||
@ -12,6 +13,7 @@ import Badge from '@/app/components/base/badge'
|
|||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type ItemProps = {
|
type ItemProps = {
|
||||||
|
className?: string
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
name: string
|
name: string
|
||||||
label: string
|
label: string
|
||||||
@ -19,9 +21,11 @@ type ItemProps = {
|
|||||||
type: string
|
type: string
|
||||||
onEdit: () => void
|
onEdit: () => void
|
||||||
onRemove: () => void
|
onRemove: () => void
|
||||||
|
canDrag?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const VarItem: FC<ItemProps> = ({
|
const VarItem: FC<ItemProps> = ({
|
||||||
|
className,
|
||||||
readonly,
|
readonly,
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
@ -29,12 +33,16 @@ const VarItem: FC<ItemProps> = ({
|
|||||||
type,
|
type,
|
||||||
onEdit,
|
onEdit,
|
||||||
onRemove,
|
onRemove,
|
||||||
|
canDrag,
|
||||||
}) => {
|
}) => {
|
||||||
const [isDeleting, setIsDeleting] = useState(false)
|
const [isDeleting, setIsDeleting] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('group relative mb-1 flex h-[34px] w-full items-center rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg pl-2.5 pr-3 shadow-xs last-of-type:mb-0 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm', isDeleting && 'border-state-destructive-border hover:bg-state-destructive-hover', readonly && 'cursor-not-allowed opacity-30')}>
|
<div className={cn('group relative mb-1 flex h-[34px] w-full items-center rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg pl-2.5 pr-3 shadow-xs last-of-type:mb-0 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm', isDeleting && 'border-state-destructive-border hover:bg-state-destructive-hover', readonly && 'cursor-not-allowed opacity-30', className)}>
|
||||||
<VarIcon className='mr-1 h-4 w-4 shrink-0 text-text-accent' />
|
<VarIcon className={cn('mr-1 h-4 w-4 shrink-0 text-text-accent', canDrag && 'group-hover:opacity-0')} />
|
||||||
|
{canDrag && (
|
||||||
|
<RiDraggable className='absolute left-3 top-3 hidden h-3 w-3 cursor-pointer text-text-tertiary group-hover:block' />
|
||||||
|
)}
|
||||||
<div className='flex w-0 grow items-center'>
|
<div className='flex w-0 grow items-center'>
|
||||||
<div className='truncate' title={`${name} · ${label}`}>
|
<div className='truncate' title={`${name} · ${label}`}>
|
||||||
<span className='system-sm-medium text-text-secondary'>{name}</span>
|
<span className='system-sm-medium text-text-secondary'>{name}</span>
|
||||||
|
|||||||
@ -1,96 +0,0 @@
|
|||||||
import React, { useEffect } from 'react'
|
|
||||||
|
|
||||||
function useFeature({
|
|
||||||
introduction,
|
|
||||||
setIntroduction,
|
|
||||||
moreLikeThis,
|
|
||||||
setMoreLikeThis,
|
|
||||||
suggestedQuestionsAfterAnswer,
|
|
||||||
setSuggestedQuestionsAfterAnswer,
|
|
||||||
speechToText,
|
|
||||||
setSpeechToText,
|
|
||||||
textToSpeech,
|
|
||||||
setTextToSpeech,
|
|
||||||
citation,
|
|
||||||
setCitation,
|
|
||||||
annotation,
|
|
||||||
setAnnotation,
|
|
||||||
moderation,
|
|
||||||
setModeration,
|
|
||||||
}: {
|
|
||||||
introduction: string
|
|
||||||
setIntroduction: (introduction: string) => void
|
|
||||||
moreLikeThis: boolean
|
|
||||||
setMoreLikeThis: (moreLikeThis: boolean) => void
|
|
||||||
suggestedQuestionsAfterAnswer: boolean
|
|
||||||
setSuggestedQuestionsAfterAnswer: (suggestedQuestionsAfterAnswer: boolean) => void
|
|
||||||
speechToText: boolean
|
|
||||||
setSpeechToText: (speechToText: boolean) => void
|
|
||||||
textToSpeech: boolean
|
|
||||||
setTextToSpeech: (textToSpeech: boolean) => void
|
|
||||||
citation: boolean
|
|
||||||
setCitation: (citation: boolean) => void
|
|
||||||
annotation: boolean
|
|
||||||
setAnnotation: (annotation: boolean) => void
|
|
||||||
moderation: boolean
|
|
||||||
setModeration: (moderation: boolean) => void
|
|
||||||
}) {
|
|
||||||
const [tempShowOpeningStatement, setTempShowOpeningStatement] = React.useState(!!introduction)
|
|
||||||
useEffect(() => {
|
|
||||||
// wait to api data back
|
|
||||||
if (introduction)
|
|
||||||
setTempShowOpeningStatement(true)
|
|
||||||
}, [introduction])
|
|
||||||
|
|
||||||
// const [tempMoreLikeThis, setTempMoreLikeThis] = React.useState(moreLikeThis)
|
|
||||||
// useEffect(() => {
|
|
||||||
// setTempMoreLikeThis(moreLikeThis)
|
|
||||||
// }, [moreLikeThis])
|
|
||||||
|
|
||||||
const featureConfig = {
|
|
||||||
openingStatement: tempShowOpeningStatement,
|
|
||||||
moreLikeThis,
|
|
||||||
suggestedQuestionsAfterAnswer,
|
|
||||||
speechToText,
|
|
||||||
textToSpeech,
|
|
||||||
citation,
|
|
||||||
annotation,
|
|
||||||
moderation,
|
|
||||||
}
|
|
||||||
const handleFeatureChange = (key: string, value: boolean) => {
|
|
||||||
switch (key) {
|
|
||||||
case 'openingStatement':
|
|
||||||
if (!value)
|
|
||||||
setIntroduction('')
|
|
||||||
|
|
||||||
setTempShowOpeningStatement(value)
|
|
||||||
break
|
|
||||||
case 'moreLikeThis':
|
|
||||||
setMoreLikeThis(value)
|
|
||||||
break
|
|
||||||
case 'suggestedQuestionsAfterAnswer':
|
|
||||||
setSuggestedQuestionsAfterAnswer(value)
|
|
||||||
break
|
|
||||||
case 'speechToText':
|
|
||||||
setSpeechToText(value)
|
|
||||||
break
|
|
||||||
case 'textToSpeech':
|
|
||||||
setTextToSpeech(value)
|
|
||||||
break
|
|
||||||
case 'citation':
|
|
||||||
setCitation(value)
|
|
||||||
break
|
|
||||||
case 'annotation':
|
|
||||||
setAnnotation(value)
|
|
||||||
break
|
|
||||||
case 'moderation':
|
|
||||||
setModeration(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
featureConfig,
|
|
||||||
handleFeatureChange,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useFeature
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { useDocLink } from '@/context/i18n'
|
|
||||||
type Props = {
|
|
||||||
onReturnToSimpleMode: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const AdvancedModeWarning: FC<Props> = ({
|
|
||||||
onReturnToSimpleMode,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const docLink = useDocLink()
|
|
||||||
const [show, setShow] = React.useState(true)
|
|
||||||
if (!show)
|
|
||||||
return null
|
|
||||||
return (
|
|
||||||
<div className='mb-3 rounded-xl border border-[#FEF0C7] bg-[#FFFAEB] px-4 py-3' >
|
|
||||||
<div className='mb-2 text-xs font-bold leading-[18px] text-[#DC6803]'>{t('appDebug.promptMode.advancedWarning.title')}</div>
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div className='text-xs leading-[18px] '>
|
|
||||||
<span className='text-gray-700'>{t('appDebug.promptMode.advancedWarning.description')}</span>
|
|
||||||
<a
|
|
||||||
className='font-medium text-[#155EEF]'
|
|
||||||
href={docLink('/guides/features/prompt-engineering')}
|
|
||||||
target='_blank' rel='noopener noreferrer'
|
|
||||||
>
|
|
||||||
{t('appDebug.promptMode.advancedWarning.learnMore')}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-center space-x-1'>
|
|
||||||
<div
|
|
||||||
onClick={onReturnToSimpleMode}
|
|
||||||
className='flex h-6 shrink-0 cursor-pointer items-center space-x-1 rounded-lg border border-gray-200 bg-indigo-600 px-2 text-xs font-semibold text-white shadow-xs'
|
|
||||||
>
|
|
||||||
<div className='text-xs font-semibold uppercase'>{t('appDebug.promptMode.switchBack')}</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className='flex h-6 cursor-pointer items-center rounded-md border border-gray-200 bg-[#fff] px-2 text-xs font-medium text-primary-600 shadow-xs'
|
|
||||||
onClick={() => setShow(false)}
|
|
||||||
>{t('appDebug.promptMode.advancedWarning.ok')}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(AdvancedModeWarning)
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
|
||||||
import cn from '@/utils/classnames'
|
|
||||||
|
|
||||||
type AutoHeightTextareaProps
|
|
||||||
= & React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>
|
|
||||||
& { outerClassName?: string }
|
|
||||||
|
|
||||||
const AutoHeightTextarea = (
|
|
||||||
{
|
|
||||||
ref: outRef,
|
|
||||||
outerClassName,
|
|
||||||
value,
|
|
||||||
className,
|
|
||||||
placeholder,
|
|
||||||
autoFocus,
|
|
||||||
disabled,
|
|
||||||
...rest
|
|
||||||
}: AutoHeightTextareaProps & {
|
|
||||||
ref: React.RefObject<HTMLTextAreaElement>;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
const innerRef = useRef<HTMLTextAreaElement>(null)
|
|
||||||
const ref = outRef || innerRef
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (autoFocus && !disabled && value) {
|
|
||||||
if (typeof ref !== 'function') {
|
|
||||||
ref.current?.setSelectionRange(`${value}`.length, `${value}`.length)
|
|
||||||
ref.current?.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [autoFocus, disabled, ref])
|
|
||||||
return (
|
|
||||||
(<div className={outerClassName}>
|
|
||||||
<div className='relative'>
|
|
||||||
<div className={cn(className, 'invisible whitespace-pre-wrap break-all')}>
|
|
||||||
{!value ? placeholder : `${value}`.replace(/\n$/, '\n ')}
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
ref={ref}
|
|
||||||
placeholder={placeholder}
|
|
||||||
className={cn(className, 'absolute inset-0 h-full w-full resize-none appearance-none border-none outline-none disabled:bg-transparent')}
|
|
||||||
value={value}
|
|
||||||
disabled={disabled}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoHeightTextarea.displayName = 'AutoHeightTextarea'
|
|
||||||
|
|
||||||
export default AutoHeightTextarea
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isRequest: boolean
|
|
||||||
toolName: string
|
|
||||||
content: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const Panel: FC<Props> = ({
|
|
||||||
isRequest,
|
|
||||||
toolName,
|
|
||||||
content,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='overflow-hidden rounded-md border border-black/5 bg-gray-100'>
|
|
||||||
<div className='flex items-center bg-gray-50 px-2 py-1 text-xs font-medium uppercase leading-[18px] text-gray-500'>
|
|
||||||
{t(`tools.thought.${isRequest ? 'requestTitle' : 'responseTitle'}`)} {toolName}
|
|
||||||
</div>
|
|
||||||
<div className='border-t border-black/5 p-2 text-xs leading-4 text-gray-700'>{content}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(Panel)
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React, { useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
import {
|
|
||||||
RiArrowDownSLine,
|
|
||||||
RiLoader2Line,
|
|
||||||
} from '@remixicon/react'
|
|
||||||
import type { ToolInfoInThought } from '../type'
|
|
||||||
import Panel from './panel'
|
|
||||||
import cn from '@/utils/classnames'
|
|
||||||
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
|
||||||
import { DataSet as DataSetIcon } from '@/app/components/base/icons/src/public/thought'
|
|
||||||
import type { Emoji } from '@/app/components/tools/types'
|
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
payload: ToolInfoInThought
|
|
||||||
allToolIcons?: Record<string, string | Emoji>
|
|
||||||
}
|
|
||||||
|
|
||||||
const getIcon = (toolName: string, allToolIcons: Record<string, string | Emoji>) => {
|
|
||||||
if (toolName.startsWith('dataset_'))
|
|
||||||
return <DataSetIcon className='shrink-0'></DataSetIcon>
|
|
||||||
const icon = allToolIcons[toolName]
|
|
||||||
if (!icon)
|
|
||||||
return null
|
|
||||||
return (
|
|
||||||
typeof icon === 'string'
|
|
||||||
? (
|
|
||||||
<div
|
|
||||||
className='h-3 w-3 shrink-0 rounded-[3px] bg-cover bg-center'
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url(${icon})`,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<AppIcon
|
|
||||||
className='shrink-0 rounded-[3px]'
|
|
||||||
size='xs'
|
|
||||||
icon={icon?.content}
|
|
||||||
background={icon?.background}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
const Tool: FC<Props> = ({
|
|
||||||
payload,
|
|
||||||
allToolIcons = {},
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { name, label, input, isFinished, output } = payload
|
|
||||||
const toolName = name.startsWith('dataset_') ? t('dataset.knowledge') : name
|
|
||||||
const toolLabel = name.startsWith('dataset_') ? t('dataset.knowledge') : label
|
|
||||||
const [isShowDetail, setIsShowDetail] = useState(false)
|
|
||||||
const icon = getIcon(name, allToolIcons) as any
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className={cn(!isShowDetail && 'shadow-sm', !isShowDetail && 'inline-block', 'max-w-full overflow-x-auto rounded-md bg-white')}>
|
|
||||||
<div
|
|
||||||
className={cn('flex h-7 cursor-pointer items-center px-2')}
|
|
||||||
onClick={() => setIsShowDetail(!isShowDetail)}
|
|
||||||
>
|
|
||||||
{!isFinished && (
|
|
||||||
<RiLoader2Line className='h-3 w-3 shrink-0 animate-spin text-gray-500' />
|
|
||||||
)}
|
|
||||||
{isFinished && !isShowDetail && (
|
|
||||||
<CheckCircle className='h-3 w-3 shrink-0 text-[#12B76A]' />
|
|
||||||
)}
|
|
||||||
{isFinished && isShowDetail && (
|
|
||||||
icon
|
|
||||||
)}
|
|
||||||
<span className='mx-1 shrink-0 text-xs font-medium text-gray-500'>
|
|
||||||
{t(`tools.thought.${isFinished ? 'used' : 'using'}`)}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className='truncate text-xs font-medium text-gray-700'
|
|
||||||
title={toolLabel}
|
|
||||||
>
|
|
||||||
{toolLabel}
|
|
||||||
</span>
|
|
||||||
<RiArrowDownSLine
|
|
||||||
className={cn(isShowDetail && 'rotate-180', 'ml-1 h-3 w-3 shrink-0 cursor-pointer select-none text-gray-500')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{isShowDetail && (
|
|
||||||
<div className='space-y-2 border-t border-black/5 p-2 '>
|
|
||||||
<Panel
|
|
||||||
isRequest={true}
|
|
||||||
toolName={toolName}
|
|
||||||
content={input} />
|
|
||||||
{output && (
|
|
||||||
<Panel
|
|
||||||
isRequest={false}
|
|
||||||
toolName={toolName}
|
|
||||||
content={output as string} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(Tool)
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { useState } from 'react'
|
|
||||||
import { t } from 'i18next'
|
|
||||||
import { debounce } from 'lodash-es'
|
|
||||||
import copy from 'copy-to-clipboard'
|
|
||||||
import s from './style.module.css'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
|
||||||
|
|
||||||
type ICopyBtnProps = {
|
|
||||||
value: string
|
|
||||||
className?: string
|
|
||||||
isPlain?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const CopyBtn = ({
|
|
||||||
value,
|
|
||||||
className,
|
|
||||||
isPlain,
|
|
||||||
}: ICopyBtnProps) => {
|
|
||||||
const [isCopied, setIsCopied] = useState(false)
|
|
||||||
|
|
||||||
const onClickCopy = debounce(() => {
|
|
||||||
copy(value)
|
|
||||||
setIsCopied(true)
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
const onMouseLeave = debounce(() => {
|
|
||||||
setIsCopied(false)
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${className}`}>
|
|
||||||
<Tooltip
|
|
||||||
popupContent={(isCopied ? t('appApi.copied') : t('appApi.copy'))}
|
|
||||||
asChild={false}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
className={'box-border flex cursor-pointer items-center justify-center rounded-md bg-components-button-secondary-bg p-0.5'}
|
|
||||||
style={!isPlain
|
|
||||||
? {
|
|
||||||
boxShadow: '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)',
|
|
||||||
}
|
|
||||||
: {}}
|
|
||||||
onClick={onClickCopy}
|
|
||||||
>
|
|
||||||
<div className={`h-6 w-6 rounded-md hover:bg-components-button-secondary-bg-hover ${s.copyIcon} ${isCopied ? s.copied : ''}`}></div>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CopyBtn
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
.copyIcon {
|
|
||||||
background-image: url(~@/app/components/develop/secret-key/assets/copy.svg);
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copyIcon:hover {
|
|
||||||
background-image: url(~@/app/components/develop/secret-key/assets/copy-hover.svg);
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copyIcon.copied {
|
|
||||||
background-image: url(~@/app/components/develop/secret-key/assets/copied.svg);
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
type IconProps = {
|
|
||||||
icon: any
|
|
||||||
className?: string
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
const Icon: FC<IconProps> = ({ icon, className, ...other }) => {
|
|
||||||
return (
|
|
||||||
<img src={icon} className={`h-3 w-3 ${className}`} {...other} alt="icon" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Icon
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import type { FC } from 'react'
|
|
||||||
import type { DividerProps } from '.'
|
|
||||||
import Divider from '.'
|
|
||||||
import classNames from '@/utils/classnames'
|
|
||||||
|
|
||||||
export type DividerWithLabelProps = DividerProps & {
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DividerWithLabel: FC<DividerWithLabelProps> = (props) => {
|
|
||||||
const { label, className, ...rest } = props
|
|
||||||
return <div
|
|
||||||
className="my-2 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<Divider {...rest} className={classNames('flex-1', className)} />
|
|
||||||
<span className="text-xs text-text-tertiary">
|
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
<Divider {...rest} className={classNames('flex-1', className)} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DividerWithLabel
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import {
|
|
||||||
PortalToFollowElem,
|
|
||||||
PortalToFollowElemContent,
|
|
||||||
PortalToFollowElemTrigger,
|
|
||||||
} from '@/app/components/base/portal-to-follow-elem'
|
|
||||||
import type { PortalToFollowElemOptions } from '@/app/components/base/portal-to-follow-elem'
|
|
||||||
|
|
||||||
type IFloatRightContainerProps = {
|
|
||||||
isMobile: boolean
|
|
||||||
open: boolean
|
|
||||||
toggle: () => void
|
|
||||||
triggerElement?: React.ReactNode
|
|
||||||
children?: React.ReactNode
|
|
||||||
} & PortalToFollowElemOptions
|
|
||||||
|
|
||||||
const FloatRightContainer = ({ open, toggle, triggerElement, isMobile, children, ...portalProps }: IFloatRightContainerProps) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{isMobile && (
|
|
||||||
<PortalToFollowElem open={open} {...portalProps}>
|
|
||||||
<PortalToFollowElemTrigger onClick={toggle}>
|
|
||||||
{triggerElement}
|
|
||||||
</PortalToFollowElemTrigger>
|
|
||||||
<PortalToFollowElemContent>
|
|
||||||
{children}
|
|
||||||
</PortalToFollowElemContent>
|
|
||||||
</PortalToFollowElem>
|
|
||||||
)}
|
|
||||||
{!isMobile && open && (
|
|
||||||
<>{children}</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FloatRightContainer
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import Button from '../button'
|
|
||||||
import { RiInstallLine, RiLoader2Line } from '@remixicon/react'
|
|
||||||
|
|
||||||
type InstallButtonProps = {
|
|
||||||
loading: boolean
|
|
||||||
onInstall: (e: React.MouseEvent) => void
|
|
||||||
t: any
|
|
||||||
}
|
|
||||||
|
|
||||||
const InstallButton = ({ loading, onInstall, t }: InstallButtonProps) => {
|
|
||||||
return (
|
|
||||||
<Button size='small' className='z-[100]' onClick={onInstall}>
|
|
||||||
<div className={`flex items-center justify-center gap-1 px-[3px]
|
|
||||||
${loading ? 'text-components-button-secondary-text-disabled' : 'text-components-button-secondary-text'}
|
|
||||||
system-xs-medium`}
|
|
||||||
>
|
|
||||||
{loading ? t('workflow.nodes.agent.pluginInstaller.installing') : t('workflow.nodes.agent.pluginInstaller.install')}
|
|
||||||
</div>
|
|
||||||
{loading
|
|
||||||
? <RiLoader2Line className='h-3.5 w-3.5 animate-spin text-text-quaternary' />
|
|
||||||
: <RiInstallLine className='h-3.5 w-3.5 text-text-secondary' />
|
|
||||||
}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InstallButton
|
|
||||||
@ -118,8 +118,20 @@ const FormItem: FC<Props> = ({
|
|||||||
<div className={cn(className)}>
|
<div className={cn(className)}>
|
||||||
{!isArrayLikeType && !isBooleanType && (
|
{!isArrayLikeType && !isBooleanType && (
|
||||||
<div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'>
|
<div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'>
|
||||||
<div className='truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div>
|
<div className='truncate'>
|
||||||
{!payload.required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
|
{typeof payload.label === 'object' ? nodeKey : payload.label}
|
||||||
|
</div>
|
||||||
|
{payload.hide === true ? (
|
||||||
|
<span className='system-xs-regular text-text-tertiary'>
|
||||||
|
{t('workflow.panel.optional_and_hidden')}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
!payload.required && (
|
||||||
|
<span className='system-xs-regular text-text-tertiary'>
|
||||||
|
{t('workflow.panel.optional')}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className='grow'>
|
<div className='grow'>
|
||||||
|
|||||||
@ -61,7 +61,6 @@ const VarList: FC<Props> = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (list.some(item => item.variable?.trim() === newKey.trim())) {
|
if (list.some(item => item.variable?.trim() === newKey.trim())) {
|
||||||
console.log('new key', newKey.trim())
|
|
||||||
setToastHandle(Toast.notify({
|
setToastHandle(Toast.notify({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }),
|
message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }),
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import produce from 'immer'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import VarItem from './var-item'
|
import VarItem from './var-item'
|
||||||
import { ChangeType, type InputVar, type MoreInfo } from '@/app/components/workflow/types'
|
import { ChangeType, type InputVar, type MoreInfo } from '@/app/components/workflow/types'
|
||||||
import { v4 as uuid4 } from 'uuid'
|
|
||||||
import { ReactSortable } from 'react-sortablejs'
|
import { ReactSortable } from 'react-sortablejs'
|
||||||
import { RiDraggable } from '@remixicon/react'
|
import { RiDraggable } from '@remixicon/react'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
@ -71,9 +70,8 @@ const VarList: FC<Props> = ({
|
|||||||
}, [list, onChange])
|
}, [list, onChange])
|
||||||
|
|
||||||
const listWithIds = useMemo(() => list.map((item) => {
|
const listWithIds = useMemo(() => list.map((item) => {
|
||||||
const id = uuid4()
|
|
||||||
return {
|
return {
|
||||||
id,
|
id: item.variable,
|
||||||
variable: { ...item },
|
variable: { ...item },
|
||||||
}
|
}
|
||||||
}), [list])
|
}), [list])
|
||||||
@ -88,6 +86,8 @@ const VarList: FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canDrag = !readonly && varCount > 1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactSortable
|
<ReactSortable
|
||||||
className='space-y-1'
|
className='space-y-1'
|
||||||
@ -97,18 +97,12 @@ const VarList: FC<Props> = ({
|
|||||||
ghostClass='opacity-50'
|
ghostClass='opacity-50'
|
||||||
animation={150}
|
animation={150}
|
||||||
>
|
>
|
||||||
{list.map((item, index) => {
|
{listWithIds.map((itemWithId, index) => (
|
||||||
const canDrag = (() => {
|
<div key={itemWithId.id} className='group relative'>
|
||||||
if (readonly)
|
|
||||||
return false
|
|
||||||
return varCount > 1
|
|
||||||
})()
|
|
||||||
return (
|
|
||||||
<div key={index} className='group relative'>
|
|
||||||
<VarItem
|
<VarItem
|
||||||
className={cn(canDrag && 'handle')}
|
className={cn(canDrag && 'handle')}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
payload={item}
|
payload={itemWithId.variable}
|
||||||
onChange={handleVarChange(index)}
|
onChange={handleVarChange(index)}
|
||||||
onRemove={handleVarRemove(index)}
|
onRemove={handleVarRemove(index)}
|
||||||
varKeys={list.map(item => item.variable)}
|
varKeys={list.map(item => item.variable)}
|
||||||
@ -119,8 +113,7 @@ const VarList: FC<Props> = ({
|
|||||||
'group-hover:block',
|
'group-hover:block',
|
||||||
)} />}
|
)} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
))}
|
||||||
})}
|
|
||||||
</ReactSortable>
|
</ReactSortable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const DebugAndPreview = () => {
|
|||||||
const selectedNode = nodes.find(node => node.data.selected)
|
const selectedNode = nodes.find(node => node.data.selected)
|
||||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||||
const variables = startNode?.data.variables || []
|
const variables = startNode?.data.variables || []
|
||||||
const visibleVariables = variables.filter(v => v.hide !== true)
|
const visibleVariables = variables
|
||||||
|
|
||||||
const [showConversationVariableModal, setShowConversationVariableModal] = useState(false)
|
const [showConversationVariableModal, setShowConversationVariableModal] = useState(false)
|
||||||
|
|
||||||
|
|||||||
@ -14,10 +14,11 @@ import cn from '@/utils/classnames'
|
|||||||
const UserInput = () => {
|
const UserInput = () => {
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const inputs = useStore(s => s.inputs)
|
const inputs = useStore(s => s.inputs)
|
||||||
|
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
|
||||||
const nodes = useNodes<StartNodeType>()
|
const nodes = useNodes<StartNodeType>()
|
||||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||||
const variables = startNode?.data.variables || []
|
const variables = startNode?.data.variables || []
|
||||||
const visibleVariables = variables.filter(v => v.hide !== true)
|
const visibleVariables = showDebugAndPreviewPanel ? variables : variables.filter(v => v.hide !== true)
|
||||||
|
|
||||||
const handleValueChange = (variable: string, v: string) => {
|
const handleValueChange = (variable: string, v: string) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
updated: 'Aktualisierte',
|
updated: 'Aktualisierte',
|
||||||
externalKnowledgeBase: 'Externe Wissensdatenbank',
|
externalKnowledgeBase: 'Externe Wissensdatenbank',
|
||||||
createFromPipeline: 'Aus Wissenspipeline erstellen',
|
createFromPipeline: 'Aus Wissenspipeline erstellen',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
title: 'Backend-Dienst-API',
|
||||||
|
apiReference: 'API Referenz',
|
||||||
|
apiKey: 'API-Schlüssel',
|
||||||
|
endpoint: 'Service-API-Endpunkt',
|
||||||
|
},
|
||||||
|
title: 'Service-API',
|
||||||
|
enabled: 'Im Dienst',
|
||||||
|
disabled: 'Behindert',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
maximize: 'Maximiere die Leinwand',
|
maximize: 'Maximiere die Leinwand',
|
||||||
minimize: 'Vollbildmodus beenden',
|
minimize: 'Vollbildmodus beenden',
|
||||||
scrollToSelectedNode: 'Zum ausgewählten Knoten scrollen',
|
scrollToSelectedNode: 'Zum ausgewählten Knoten scrollen',
|
||||||
|
optional_and_hidden: '(optional & hidden)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -90,8 +90,8 @@ const translation = {
|
|||||||
intro2: 'as a context',
|
intro2: 'as a context',
|
||||||
intro3: ',',
|
intro3: ',',
|
||||||
intro4: 'or it ',
|
intro4: 'or it ',
|
||||||
intro5: 'can be created',
|
intro5: 'can be published',
|
||||||
intro6: ' as a standalone ChatGPT index plug-in to publish',
|
intro6: ' as an independent service.',
|
||||||
unavailable: 'Unavailable',
|
unavailable: 'Unavailable',
|
||||||
unavailableTip: 'Embedding model is not available, the default embedding model needs to be configured',
|
unavailableTip: 'Embedding model is not available, the default embedding model needs to be configured',
|
||||||
datasets: 'KNOWLEDGE',
|
datasets: 'KNOWLEDGE',
|
||||||
|
|||||||
@ -371,6 +371,7 @@ const translation = {
|
|||||||
maximize: 'Maximize Canvas',
|
maximize: 'Maximize Canvas',
|
||||||
minimize: 'Exit Full Screen',
|
minimize: 'Exit Full Screen',
|
||||||
scrollToSelectedNode: 'Scroll to selected node',
|
scrollToSelectedNode: 'Scroll to selected node',
|
||||||
|
optional_and_hidden: '(optional & hidden)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
externalKnowledgeBase: 'Base de conocimientos externa',
|
externalKnowledgeBase: 'Base de conocimientos externa',
|
||||||
createFromPipeline: 'Crear desde Knowledge Pipeline',
|
createFromPipeline: 'Crear desde Knowledge Pipeline',
|
||||||
updated: 'Actualizado',
|
updated: 'Actualizado',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiReference: 'Referencia de la API',
|
||||||
|
apiKey: 'Clave API',
|
||||||
|
endpoint: 'Punto de enlace de la API de servicio',
|
||||||
|
title: 'API del servicio de backend',
|
||||||
|
},
|
||||||
|
enabled: 'En servicio',
|
||||||
|
title: 'API de servicios',
|
||||||
|
disabled: 'Discapacitado',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
maximize: 'Maximizar Canvas',
|
maximize: 'Maximizar Canvas',
|
||||||
minimize: 'Salir de pantalla completa',
|
minimize: 'Salir de pantalla completa',
|
||||||
scrollToSelectedNode: 'Desplácese hasta el nodo seleccionado',
|
scrollToSelectedNode: 'Desplácese hasta el nodo seleccionado',
|
||||||
|
optional_and_hidden: '(opcional y oculto)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
updated: 'بروز رسانی',
|
updated: 'بروز رسانی',
|
||||||
createFromPipeline: 'ایجاد از پایپ لاین دانش',
|
createFromPipeline: 'ایجاد از پایپ لاین دانش',
|
||||||
externalKnowledgeBase: 'پایگاه دانش خارجی',
|
externalKnowledgeBase: 'پایگاه دانش خارجی',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiKey: 'کلید API',
|
||||||
|
title: 'رابط برنامهنویسی سرویس پشتیبان',
|
||||||
|
apiReference: 'مرجع API',
|
||||||
|
endpoint: 'نقطه انتهایی رابط برنامهنویسی سرویس',
|
||||||
|
},
|
||||||
|
disabled: 'معلول',
|
||||||
|
enabled: 'در حال خدمت',
|
||||||
|
title: 'رابط برنامهنویسی سرویس',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
minimize: 'خروج از حالت تمام صفحه',
|
minimize: 'خروج از حالت تمام صفحه',
|
||||||
maximize: 'بیشینهسازی بوم',
|
maximize: 'بیشینهسازی بوم',
|
||||||
scrollToSelectedNode: 'به گره انتخاب شده بروید',
|
scrollToSelectedNode: 'به گره انتخاب شده بروید',
|
||||||
|
optional_and_hidden: '(اختیاری و پنهان)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
updated: 'Actualisé',
|
updated: 'Actualisé',
|
||||||
createFromPipeline: 'Créer à partir du pipeline de connaissances',
|
createFromPipeline: 'Créer à partir du pipeline de connaissances',
|
||||||
externalKnowledgeBase: 'Base de connaissances externe',
|
externalKnowledgeBase: 'Base de connaissances externe',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiKey: 'Clé API',
|
||||||
|
apiReference: 'Référence API',
|
||||||
|
title: 'API du service backend',
|
||||||
|
endpoint: 'Point de terminaison de l\'API',
|
||||||
|
},
|
||||||
|
enabled: 'En service',
|
||||||
|
title: 'API de service',
|
||||||
|
disabled: 'désactivé',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
maximize: 'Maximiser le Canvas',
|
maximize: 'Maximiser le Canvas',
|
||||||
minimize: 'Sortir du mode plein écran',
|
minimize: 'Sortir du mode plein écran',
|
||||||
scrollToSelectedNode: 'Faites défiler jusqu’au nœud sélectionné',
|
scrollToSelectedNode: 'Faites défiler jusqu’au nœud sélectionné',
|
||||||
|
optional_and_hidden: '(optionnel et caché)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -233,6 +233,17 @@ const translation = {
|
|||||||
updated: 'अपडेट किया गया',
|
updated: 'अपडेट किया गया',
|
||||||
externalKnowledgeBase: 'बाहरी ज्ञान आधार',
|
externalKnowledgeBase: 'बाहरी ज्ञान आधार',
|
||||||
createFromPipeline: 'ज्ञान पाइपलाइन से बनाएं',
|
createFromPipeline: 'ज्ञान पाइपलाइन से बनाएं',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiReference: 'एपीआई संदर्भ',
|
||||||
|
apiKey: 'एपीआई कुंजी',
|
||||||
|
title: 'बैकएंड सेवा एपीआई',
|
||||||
|
endpoint: 'सेवा एपीआई एंडपॉइंट',
|
||||||
|
},
|
||||||
|
enabled: 'सेवा में',
|
||||||
|
disabled: 'अक्षम',
|
||||||
|
title: 'सेवा एपीआई',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -338,6 +338,7 @@ const translation = {
|
|||||||
minimize: 'पूर्ण स्क्रीन से बाहर निकलें',
|
minimize: 'पूर्ण स्क्रीन से बाहर निकलें',
|
||||||
maximize: 'कैनवास का अधिकतम लाभ उठाएँ',
|
maximize: 'कैनवास का अधिकतम लाभ उठाएँ',
|
||||||
scrollToSelectedNode: 'चुने गए नोड पर स्क्रॉल करें',
|
scrollToSelectedNode: 'चुने गए नोड पर स्क्रॉल करें',
|
||||||
|
optional_and_hidden: '(वैकल्पिक और छिपा हुआ)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -219,6 +219,17 @@ const translation = {
|
|||||||
updated: 'Diperbarui',
|
updated: 'Diperbarui',
|
||||||
createFromPipeline: 'Membuat dari Knowledge Pipeline',
|
createFromPipeline: 'Membuat dari Knowledge Pipeline',
|
||||||
externalKnowledgeBase: 'Basis Pengetahuan Eksternal',
|
externalKnowledgeBase: 'Basis Pengetahuan Eksternal',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiKey: 'Kunci API',
|
||||||
|
apiReference: 'Referensi API',
|
||||||
|
title: 'API layanan backend',
|
||||||
|
endpoint: 'Titik Akhir API Layanan',
|
||||||
|
},
|
||||||
|
title: 'API Layanan',
|
||||||
|
enabled: 'Sedang Beroperasi',
|
||||||
|
disabled: 'Dinonaktifkan',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -325,6 +325,7 @@ const translation = {
|
|||||||
changeBlock: 'Ubah Node',
|
changeBlock: 'Ubah Node',
|
||||||
runThisStep: 'Jalankan langkah ini',
|
runThisStep: 'Jalankan langkah ini',
|
||||||
maximize: 'Maksimalkan Kanvas',
|
maximize: 'Maksimalkan Kanvas',
|
||||||
|
optional_and_hidden: '(opsional & tersembunyi)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -233,6 +233,17 @@ const translation = {
|
|||||||
updated: 'Aggiornato',
|
updated: 'Aggiornato',
|
||||||
externalKnowledgeBase: 'Base di conoscenza esterna',
|
externalKnowledgeBase: 'Base di conoscenza esterna',
|
||||||
createFromPipeline: 'Creazione da pipeline di conoscenza',
|
createFromPipeline: 'Creazione da pipeline di conoscenza',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
endpoint: 'Endpoint dell\'API di servizio',
|
||||||
|
apiKey: 'Chiave API',
|
||||||
|
title: 'API del servizio backend',
|
||||||
|
apiReference: 'Riferimento API',
|
||||||
|
},
|
||||||
|
disabled: 'Disabilitato',
|
||||||
|
title: 'API di servizio',
|
||||||
|
enabled: 'In servizio',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -341,6 +341,7 @@ const translation = {
|
|||||||
minimize: 'Esci dalla modalità schermo intero',
|
minimize: 'Esci dalla modalità schermo intero',
|
||||||
maximize: 'Massimizza Canvas',
|
maximize: 'Massimizza Canvas',
|
||||||
scrollToSelectedNode: 'Scorri fino al nodo selezionato',
|
scrollToSelectedNode: 'Scorri fino al nodo selezionato',
|
||||||
|
optional_and_hidden: '(opzionale e nascosto)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -87,8 +87,8 @@ const translation = {
|
|||||||
intro2: 'コンテキストとして',
|
intro2: 'コンテキストとして',
|
||||||
intro3: '、',
|
intro3: '、',
|
||||||
intro4: 'または',
|
intro4: 'または',
|
||||||
intro5: '作成することができます',
|
intro5: '公開することができます',
|
||||||
intro6: '単体の ChatGPT インデックスプラグインとして公開するために',
|
intro6: '独立したサービスとして',
|
||||||
unavailable: '利用不可',
|
unavailable: '利用不可',
|
||||||
unavailableTip: '埋め込みモデルが利用できません。デフォルトの埋め込みモデルを設定する必要があります',
|
unavailableTip: '埋め込みモデルが利用できません。デフォルトの埋め込みモデルを設定する必要があります',
|
||||||
datasets: 'ナレッジベース',
|
datasets: 'ナレッジベース',
|
||||||
|
|||||||
@ -367,6 +367,7 @@ const translation = {
|
|||||||
maximize: 'キャンバスを最大化する',
|
maximize: 'キャンバスを最大化する',
|
||||||
minimize: '全画面を終了する',
|
minimize: '全画面を終了する',
|
||||||
scrollToSelectedNode: '選択したノードまでスクロール',
|
scrollToSelectedNode: '選択したノードまでスクロール',
|
||||||
|
optional_and_hidden: '(オプションおよび非表示)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -225,6 +225,17 @@ const translation = {
|
|||||||
updated: '업데이트',
|
updated: '업데이트',
|
||||||
externalKnowledgeBase: '외부 기술 자료',
|
externalKnowledgeBase: '외부 기술 자료',
|
||||||
createFromPipeline: '지식 파이프라인에서 만들기',
|
createFromPipeline: '지식 파이프라인에서 만들기',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiReference: 'API 참고',
|
||||||
|
endpoint: '서비스 API 엔드포인트',
|
||||||
|
apiKey: 'API 키',
|
||||||
|
title: '백엔드 서비스 API',
|
||||||
|
},
|
||||||
|
enabled: '서비스 중',
|
||||||
|
title: '서비스 API',
|
||||||
|
disabled: '장애인',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -347,6 +347,7 @@ const translation = {
|
|||||||
minimize: '전체 화면 종료',
|
minimize: '전체 화면 종료',
|
||||||
maximize: '캔버스 전체 화면',
|
maximize: '캔버스 전체 화면',
|
||||||
scrollToSelectedNode: '선택한 노드로 스크롤',
|
scrollToSelectedNode: '선택한 노드로 스크롤',
|
||||||
|
optional_and_hidden: '(선택 사항 및 숨김)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -232,6 +232,17 @@ const translation = {
|
|||||||
updated: 'Aktualizowano',
|
updated: 'Aktualizowano',
|
||||||
createFromPipeline: 'Tworzenie na podstawie potoku wiedzy',
|
createFromPipeline: 'Tworzenie na podstawie potoku wiedzy',
|
||||||
externalKnowledgeBase: 'Zewnętrzna baza wiedzy',
|
externalKnowledgeBase: 'Zewnętrzna baza wiedzy',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiKey: 'Klucz API',
|
||||||
|
title: 'Usługa backendowa API',
|
||||||
|
apiReference: 'Dokumentacja API',
|
||||||
|
endpoint: 'Punkt końcowy API usługi',
|
||||||
|
},
|
||||||
|
title: 'Interfejs API usługi',
|
||||||
|
disabled: 'Niepełnosprawny',
|
||||||
|
enabled: 'W serwisie',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
minimize: 'Wyjdź z trybu pełnoekranowego',
|
minimize: 'Wyjdź z trybu pełnoekranowego',
|
||||||
maximize: 'Maksymalizuj płótno',
|
maximize: 'Maksymalizuj płótno',
|
||||||
scrollToSelectedNode: 'Przewiń do wybranego węzła',
|
scrollToSelectedNode: 'Przewiń do wybranego węzła',
|
||||||
|
optional_and_hidden: '(opcjonalne i ukryte)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
updated: 'Atualizado',
|
updated: 'Atualizado',
|
||||||
externalKnowledgeBase: 'Base de conhecimento externa',
|
externalKnowledgeBase: 'Base de conhecimento externa',
|
||||||
createFromPipeline: 'Criar a partir do pipeline de conhecimento',
|
createFromPipeline: 'Criar a partir do pipeline de conhecimento',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiKey: 'Chave de API',
|
||||||
|
apiReference: 'Referência da API',
|
||||||
|
title: 'API de serviço de backend',
|
||||||
|
endpoint: 'Endpoint da API de Serviço',
|
||||||
|
},
|
||||||
|
enabled: 'Em serviço',
|
||||||
|
title: 'API de Serviço',
|
||||||
|
disabled: 'Desativado',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
maximize: 'Maximize Canvas',
|
maximize: 'Maximize Canvas',
|
||||||
minimize: 'Sair do Modo Tela Cheia',
|
minimize: 'Sair do Modo Tela Cheia',
|
||||||
scrollToSelectedNode: 'Role até o nó selecionado',
|
scrollToSelectedNode: 'Role até o nó selecionado',
|
||||||
|
optional_and_hidden: '(opcional & oculto)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
updated: 'Actualizat',
|
updated: 'Actualizat',
|
||||||
externalKnowledgeBase: 'Baza de cunoștințe externă',
|
externalKnowledgeBase: 'Baza de cunoștințe externă',
|
||||||
createFromPipeline: 'Crearea din Knowledge Pipeline',
|
createFromPipeline: 'Crearea din Knowledge Pipeline',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
title: 'API pentru serviciul backend',
|
||||||
|
apiReference: 'Referință API',
|
||||||
|
endpoint: 'Punct final API de servicii',
|
||||||
|
apiKey: 'Cheie API',
|
||||||
|
},
|
||||||
|
disabled: 'Dezactivat',
|
||||||
|
enabled: 'În serviciu',
|
||||||
|
title: 'API de servicii',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
maximize: 'Maximize Canvas',
|
maximize: 'Maximize Canvas',
|
||||||
minimize: 'Iesi din modul pe tot ecranul',
|
minimize: 'Iesi din modul pe tot ecranul',
|
||||||
scrollToSelectedNode: 'Derulați la nodul selectat',
|
scrollToSelectedNode: 'Derulați la nodul selectat',
|
||||||
|
optional_and_hidden: '(opțional și ascuns)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
updated: 'Обновлено',
|
updated: 'Обновлено',
|
||||||
externalKnowledgeBase: 'Внешняя база знаний',
|
externalKnowledgeBase: 'Внешняя база знаний',
|
||||||
createFromPipeline: 'Создание из конвейера знаний',
|
createFromPipeline: 'Создание из конвейера знаний',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiReference: 'Справочник API',
|
||||||
|
title: 'API бэкенд-сервиса',
|
||||||
|
apiKey: 'API ключ',
|
||||||
|
endpoint: 'Конечная точка API сервиса',
|
||||||
|
},
|
||||||
|
enabled: 'На службе',
|
||||||
|
title: 'Сервисный API',
|
||||||
|
disabled: 'Отключено',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
minimize: 'Выйти из полноэкранного режима',
|
minimize: 'Выйти из полноэкранного режима',
|
||||||
maximize: 'Максимизировать холст',
|
maximize: 'Максимизировать холст',
|
||||||
scrollToSelectedNode: 'Прокрутите до выбранного узла',
|
scrollToSelectedNode: 'Прокрутите до выбранного узла',
|
||||||
|
optional_and_hidden: '(необязательно и скрыто)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
createFromPipeline: 'Ustvarjanje iz cevovoda znanja',
|
createFromPipeline: 'Ustvarjanje iz cevovoda znanja',
|
||||||
updated: 'Posodobljene',
|
updated: 'Posodobljene',
|
||||||
externalKnowledgeBase: 'Zunanja baza znanja',
|
externalKnowledgeBase: 'Zunanja baza znanja',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiKey: 'API ključ',
|
||||||
|
endpoint: 'Vhodna točka API storitve',
|
||||||
|
title: 'API storitev za zaledje',
|
||||||
|
apiReference: 'API Referenca',
|
||||||
|
},
|
||||||
|
title: 'Storitveni API',
|
||||||
|
disabled: 'Onemogočeno',
|
||||||
|
enabled: 'V storitvi',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -333,6 +333,7 @@ const translation = {
|
|||||||
maximize: 'Maksimiziraj platno',
|
maximize: 'Maksimiziraj platno',
|
||||||
optional: '(neobvezno)',
|
optional: '(neobvezno)',
|
||||||
scrollToSelectedNode: 'Pomaknite se do izbranega vozlišča',
|
scrollToSelectedNode: 'Pomaknite se do izbranega vozlišča',
|
||||||
|
optional_and_hidden: '(neobvezno in skrito)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -225,6 +225,17 @@ const translation = {
|
|||||||
updated: 'ปรับ ปรุง',
|
updated: 'ปรับ ปรุง',
|
||||||
externalKnowledgeBase: 'ฐานความรู้ภายนอก',
|
externalKnowledgeBase: 'ฐานความรู้ภายนอก',
|
||||||
createFromPipeline: 'สร้างจากไปป์ไลน์ความรู้',
|
createFromPipeline: 'สร้างจากไปป์ไลน์ความรู้',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
title: 'บริการแบ็กเอนด์ API',
|
||||||
|
apiReference: 'เอกสารอ้างอิง API',
|
||||||
|
apiKey: 'กุญแจ API',
|
||||||
|
endpoint: 'จุดเชื่อมต่อ API บริการ',
|
||||||
|
},
|
||||||
|
enabled: 'ให้บริการ',
|
||||||
|
disabled: 'ถูกปิดใช้งาน',
|
||||||
|
title: 'บริการ API',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
minimize: 'ออกจากโหมดเต็มหน้าจอ',
|
minimize: 'ออกจากโหมดเต็มหน้าจอ',
|
||||||
maximize: 'เพิ่มประสิทธิภาพผ้าใบ',
|
maximize: 'เพิ่มประสิทธิภาพผ้าใบ',
|
||||||
scrollToSelectedNode: 'เลื่อนไปยังโหนดที่เลือก',
|
scrollToSelectedNode: 'เลื่อนไปยังโหนดที่เลือก',
|
||||||
|
optional_and_hidden: '(ตัวเลือก & ซ่อน)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
updated: 'Güncel -leştirilmiş',
|
updated: 'Güncel -leştirilmiş',
|
||||||
createFromPipeline: 'Bilgi İşlem Hattından Oluşturun',
|
createFromPipeline: 'Bilgi İşlem Hattından Oluşturun',
|
||||||
externalKnowledgeBase: 'Harici Bilgi Bankası',
|
externalKnowledgeBase: 'Harici Bilgi Bankası',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
apiReference: 'API Referansı',
|
||||||
|
title: 'Backend servis api',
|
||||||
|
apiKey: 'API Anahtarı',
|
||||||
|
endpoint: 'Hizmet API Uç Noktası',
|
||||||
|
},
|
||||||
|
disabled: 'Engelli',
|
||||||
|
enabled: 'Hizmette',
|
||||||
|
title: 'Servis API\'si',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
minimize: 'Tam Ekrandan Çık',
|
minimize: 'Tam Ekrandan Çık',
|
||||||
maximize: 'Kanvası Maksimize Et',
|
maximize: 'Kanvası Maksimize Et',
|
||||||
scrollToSelectedNode: 'Seçili düğüme kaydırma',
|
scrollToSelectedNode: 'Seçili düğüme kaydırma',
|
||||||
|
optional_and_hidden: '(isteğe bağlı ve gizli)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -227,6 +227,17 @@ const translation = {
|
|||||||
updated: 'Оновлено',
|
updated: 'Оновлено',
|
||||||
createFromPipeline: 'Створюйте на основі Knowledge Pipeline',
|
createFromPipeline: 'Створюйте на основі Knowledge Pipeline',
|
||||||
externalKnowledgeBase: 'Зовнішня база знань',
|
externalKnowledgeBase: 'Зовнішня база знань',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
title: 'API бекенд-сервіс',
|
||||||
|
apiReference: 'Посилання на API',
|
||||||
|
apiKey: 'Ключ API',
|
||||||
|
endpoint: 'Кінцева точка API сервісу',
|
||||||
|
},
|
||||||
|
disabled: 'Вимкнено',
|
||||||
|
enabled: 'У службі',
|
||||||
|
title: 'Сервісний API',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
minimize: 'Вийти з повноекранного режиму',
|
minimize: 'Вийти з повноекранного режиму',
|
||||||
maximize: 'Максимізувати полотно',
|
maximize: 'Максимізувати полотно',
|
||||||
scrollToSelectedNode: 'Прокрутіть до вибраного вузла',
|
scrollToSelectedNode: 'Прокрутіть до вибраного вузла',
|
||||||
|
optional_and_hidden: '(необов\'язково & приховано)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
updated: 'Cập nhật',
|
updated: 'Cập nhật',
|
||||||
createFromPipeline: 'Tạo từ quy trình kiến thức',
|
createFromPipeline: 'Tạo từ quy trình kiến thức',
|
||||||
externalKnowledgeBase: 'Cơ sở kiến thức bên ngoài',
|
externalKnowledgeBase: 'Cơ sở kiến thức bên ngoài',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
title: 'API dịch vụ backend',
|
||||||
|
endpoint: 'Điểm cuối API dịch vụ',
|
||||||
|
apiKey: 'Khóa API',
|
||||||
|
apiReference: 'Tham chiếu API',
|
||||||
|
},
|
||||||
|
enabled: 'Đang phục vụ',
|
||||||
|
disabled: 'Vô hiệu hóa',
|
||||||
|
title: 'Giao diện lập trình dịch vụ',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
maximize: 'Tối đa hóa Canvas',
|
maximize: 'Tối đa hóa Canvas',
|
||||||
minimize: 'Thoát chế độ toàn màn hình',
|
minimize: 'Thoát chế độ toàn màn hình',
|
||||||
scrollToSelectedNode: 'Cuộn đến nút đã chọn',
|
scrollToSelectedNode: 'Cuộn đến nút đã chọn',
|
||||||
|
optional_and_hidden: '(tùy chọn & ẩn)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -90,8 +90,8 @@ const translation = {
|
|||||||
intro2: '作为上下文',
|
intro2: '作为上下文',
|
||||||
intro3: ',',
|
intro3: ',',
|
||||||
intro4: '或可以',
|
intro4: '或可以',
|
||||||
intro5: '创建',
|
intro5: '发布',
|
||||||
intro6: '为独立的 ChatGPT 插件发布使用',
|
intro6: '为独立的服务',
|
||||||
unavailable: '不可用',
|
unavailable: '不可用',
|
||||||
unavailableTip: '由于 embedding 模型不可用,需要配置默认 embedding 模型',
|
unavailableTip: '由于 embedding 模型不可用,需要配置默认 embedding 模型',
|
||||||
datasets: '知识库',
|
datasets: '知识库',
|
||||||
|
|||||||
@ -370,6 +370,7 @@ const translation = {
|
|||||||
maximize: '最大化画布',
|
maximize: '最大化画布',
|
||||||
minimize: '退出最大化',
|
minimize: '退出最大化',
|
||||||
scrollToSelectedNode: '滚动至选中节点',
|
scrollToSelectedNode: '滚动至选中节点',
|
||||||
|
optional_and_hidden: '(选填 & 隐藏)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -226,6 +226,17 @@ const translation = {
|
|||||||
externalKnowledgeBase: '外部知識庫',
|
externalKnowledgeBase: '外部知識庫',
|
||||||
createFromPipeline: '從知識管線建立',
|
createFromPipeline: '從知識管線建立',
|
||||||
updated: '更新時間',
|
updated: '更新時間',
|
||||||
|
serviceApi: {
|
||||||
|
card: {
|
||||||
|
title: '後端服務 API',
|
||||||
|
apiReference: 'API 參考',
|
||||||
|
endpoint: '服務 API 端點',
|
||||||
|
apiKey: 'API 金鑰',
|
||||||
|
},
|
||||||
|
enabled: '使用中',
|
||||||
|
title: '服務 API',
|
||||||
|
disabled: '已停用',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -326,6 +326,7 @@ const translation = {
|
|||||||
minimize: '退出全螢幕',
|
minimize: '退出全螢幕',
|
||||||
maximize: '最大化畫布',
|
maximize: '最大化畫布',
|
||||||
scrollToSelectedNode: '捲動至選取的節點',
|
scrollToSelectedNode: '捲動至選取的節點',
|
||||||
|
optional_and_hidden: '(可選且隱藏)',
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
common: {
|
common: {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "dify-web",
|
"name": "dify-web",
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@10.18.2",
|
"packageManager": "pnpm@10.18.3+sha512.bbd16e6d7286fd7e01f6b3c0b3c932cda2965c06a908328f74663f10a9aea51f1129eea615134bf992831b009eabe167ecb7008b597f40ff9bc75946aadfb08d",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=v22.11.0"
|
"node": ">=v22.11.0"
|
||||||
},
|
},
|
||||||
@ -27,6 +27,7 @@
|
|||||||
"lint:fix": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix",
|
"lint:fix": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix",
|
||||||
"lint:quiet": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --quiet",
|
"lint:quiet": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --quiet",
|
||||||
"lint:complexity": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --rule 'complexity: [error, {max: 15}]' --quiet",
|
"lint:complexity": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --rule 'complexity: [error, {max: 15}]' --quiet",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
"prepare": "cd ../ && node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky ./web/.husky",
|
"prepare": "cd ../ && node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky ./web/.husky",
|
||||||
"gen-icons": "node ./app/components/base/icons/script.mjs",
|
"gen-icons": "node ./app/components/base/icons/script.mjs",
|
||||||
"uglify-embed": "node ./bin/uglify-embed",
|
"uglify-embed": "node ./bin/uglify-embed",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user