Merge branch 'main' into feat/grouping-branching

This commit is contained in:
zhsama 2026-01-04 21:56:13 +08:00
commit 225b13da93
88 changed files with 26441 additions and 1931 deletions

View File

@ -3,6 +3,7 @@
"feature-dev@claude-plugins-official": true,
"context7@claude-plugins-official": true,
"typescript-lsp@claude-plugins-official": true,
"pyright-lsp@claude-plugins-official": true
"pyright-lsp@claude-plugins-official": true,
"ralph-wiggum@claude-plugins-official": true
}
}

View File

@ -0,0 +1,73 @@
---
name: frontend-code-review
description: "Trigger when the user requests a review of frontend files (e.g., `.tsx`, `.ts`, `.js`). Support both pending-change reviews and focused file reviews while applying the checklist rules."
---
# Frontend Code Review
## Intent
Use this skill whenever the user asks to review frontend code (especially `.tsx`, `.ts`, or `.js` files). Support two review modes:
1. **Pending-change review** inspect staged/working-tree files slated for commit and flag checklist violations before submission.
2. **File-targeted review** review the specific file(s) the user names and report the relevant checklist findings.
Stick to the checklist below for every applicable file and mode.
## Checklist
See [references/code-quality.md](references/code-quality.md), [references/performance.md](references/performance.md), [references/business-logic.md](references/business-logic.md) for the living checklist split by category—treat it as the canonical set of rules to follow.
Flag each rule violation with urgency metadata so future reviewers can prioritize fixes.
## Review Process
1. Open the relevant component/module. Gather lines that relate to class names, React Flow hooks, prop memoization, and styling.
2. For each rule in the review point, note where the code deviates and capture a representative snippet.
3. Compose the review section per the template below. Group violations first by **Urgent** flag, then by category order (Code Quality, Performance, Business Logic).
## Required output
When invoked, the response must exactly follow one of the two templates:
### Template A (any findings)
```
# Code review
Found <N> urgent issues need to be fixed:
## 1 <brief description of bug>
FilePath: <path> line <line>
<relevant code snippet or pointer>
### Suggested fix
<brief description of suggested fix>
---
... (repeat for each urgent issue) ...
Found <M> suggestions for improvement:
## 1 <brief description of suggestion>
FilePath: <path> line <line>
<relevant code snippet or pointer>
### Suggested fix
<brief description of suggested fix>
---
... (repeat for each suggestion) ...
```
If there are no urgent issues, omit that section. If there are no suggestions, omit that section.
If the issue number is more than 10, summarize as "10+ urgent issues" or "10+ suggestions" and just output the first 10 issues.
Don't compress the blank lines between sections; keep them as-is for readability.
If you use Template A (i.e., there are issues to fix) and at least one issue requires code changes, append a brief follow-up question after the structured output asking whether the user wants you to apply the suggested fix(es). For example: "Would you like me to use the Suggested fix section to address these issues?"
### Template B (no issues)
```
## Code review
No issues found.
```

View File

@ -0,0 +1,15 @@
# Rule Catalog — Business Logic
## Can't use workflowStore in Node components
IsUrgent: True
### Description
File path pattern of node components: `web/app/components/workflow/nodes/[nodeName]/node.tsx`
Node components are also used when creating a RAG Pipe from a template, but in that context there is no workflowStore Provider, which results in a blank screen. [This Issue](https://github.com/langgenius/dify/issues/29168) was caused by exactly this reason.
### Suggested Fix
Use `import { useNodes } from 'reactflow'` instead of `import useNodes from '@/app/components/workflow/store/workflow/use-nodes'`.

View File

@ -0,0 +1,44 @@
# Rule Catalog — Code Quality
## Conditional class names use utility function
IsUrgent: True
Category: Code Quality
### Description
Ensure conditional CSS is handled via the shared `classNames` instead of custom ternaries, string concatenation, or template strings. Centralizing class logic keeps components consistent and easier to maintain.
### Suggested Fix
```ts
import { cn } from '@/utils/classnames'
const classNames = cn(isActive ? 'text-primary-600' : 'text-gray-500')
```
## Tailwind-first styling
IsUrgent: True
Category: Code Quality
### Description
Favor Tailwind CSS utility classes instead of adding new `.module.css` files unless a Tailwind combination cannot achieve the required styling. Keeping styles in Tailwind improves consistency and reduces maintenance overhead.
Update this file when adding, editing, or removing Code Quality rules so the catalog remains accurate.
## Classname ordering for easy overrides
### Description
When writing components, always place the incoming `className` prop after the components own class values so that downstream consumers can override or extend the styling. This keeps your components defaults but still lets external callers change or remove specific styles.
Example:
```tsx
import { cn } from '@/utils/classnames'
const Button = ({ className }) => {
return <div className={cn('bg-primary-600', className)}></div>
}
```

View File

@ -0,0 +1,45 @@
# Rule Catalog — Performance
## React Flow data usage
IsUrgent: True
Category: Performance
### Description
When rendering React Flow, prefer `useNodes`/`useEdges` for UI consumption and rely on `useStoreApi` inside callbacks that mutate or read node/edge state. Avoid manually pulling Flow data outside of these hooks.
## Complex prop memoization
IsUrgent: True
Category: Performance
### Description
Wrap complex prop values (objects, arrays, maps) in `useMemo` prior to passing them into child components to guarantee stable references and prevent unnecessary renders.
Update this file when adding, editing, or removing Performance rules so the catalog remains accurate.
Wrong:
```tsx
<HeavyComp
config={{
provider: ...,
detail: ...
}}
/>
```
Right:
```tsx
const config = useMemo(() => ({
provider: ...,
detail: ...
}), [provider, detail]);
<HeavyComp
config={config}
/>
```

1
.gitignore vendored
View File

@ -236,3 +236,4 @@ scripts/stress-test/reports/
# settings
*.local.json
*.local.md

View File

@ -515,6 +515,7 @@ class DatasetRetrieval:
0
].embedding_model_provider
weights["vector_setting"]["embedding_model_name"] = available_datasets[0].embedding_model
dataset_count = len(available_datasets)
with measure_time() as timer:
cancel_event = threading.Event()
thread_exceptions: list[Exception] = []
@ -537,6 +538,7 @@ class DatasetRetrieval:
"score_threshold": score_threshold,
"query": query,
"attachment_id": None,
"dataset_count": dataset_count,
"cancel_event": cancel_event,
"thread_exceptions": thread_exceptions,
},
@ -562,6 +564,7 @@ class DatasetRetrieval:
"score_threshold": score_threshold,
"query": None,
"attachment_id": attachment_id,
"dataset_count": dataset_count,
"cancel_event": cancel_event,
"thread_exceptions": thread_exceptions,
},
@ -1422,6 +1425,7 @@ class DatasetRetrieval:
score_threshold: float,
query: str | None,
attachment_id: str | None,
dataset_count: int,
cancel_event: threading.Event | None = None,
thread_exceptions: list[Exception] | None = None,
):
@ -1470,7 +1474,8 @@ class DatasetRetrieval:
if cancel_event and cancel_event.is_set():
break
if reranking_enable:
# Skip second reranking when there is only one dataset
if reranking_enable and dataset_count > 1:
# do rerank for searched documents
data_post_processor = DataPostProcessor(tenant_id, reranking_mode, reranking_model, weights, False)
if query:

View File

@ -1,8 +1,7 @@
from collections.abc import Mapping, Sequence
from decimal import Decimal
from typing import Any, cast
from typing import TYPE_CHECKING, Any, ClassVar, cast
from configs import dify_config
from core.helper.code_executor.code_executor import CodeExecutionError, CodeExecutor, CodeLanguage
from core.helper.code_executor.code_node_provider import CodeNodeProvider
from core.helper.code_executor.javascript.javascript_code_provider import JavascriptCodeProvider
@ -13,6 +12,7 @@ from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus
from core.workflow.node_events import NodeRunResult
from core.workflow.nodes.base.node import Node
from core.workflow.nodes.code.entities import CodeNodeData
from core.workflow.nodes.code.limits import CodeNodeLimits
from .exc import (
CodeNodeError,
@ -20,9 +20,41 @@ from .exc import (
OutputValidationError,
)
if TYPE_CHECKING:
from core.workflow.entities import GraphInitParams
from core.workflow.runtime import GraphRuntimeState
class CodeNode(Node[CodeNodeData]):
node_type = NodeType.CODE
_DEFAULT_CODE_PROVIDERS: ClassVar[tuple[type[CodeNodeProvider], ...]] = (
Python3CodeProvider,
JavascriptCodeProvider,
)
_limits: CodeNodeLimits
def __init__(
self,
id: str,
config: Mapping[str, Any],
graph_init_params: "GraphInitParams",
graph_runtime_state: "GraphRuntimeState",
*,
code_executor: type[CodeExecutor] | None = None,
code_providers: Sequence[type[CodeNodeProvider]] | None = None,
code_limits: CodeNodeLimits,
) -> None:
super().__init__(
id=id,
config=config,
graph_init_params=graph_init_params,
graph_runtime_state=graph_runtime_state,
)
self._code_executor: type[CodeExecutor] = code_executor or CodeExecutor
self._code_providers: tuple[type[CodeNodeProvider], ...] = (
tuple(code_providers) if code_providers else self._DEFAULT_CODE_PROVIDERS
)
self._limits = code_limits
@classmethod
def get_default_config(cls, filters: Mapping[str, object] | None = None) -> Mapping[str, object]:
@ -35,11 +67,16 @@ class CodeNode(Node[CodeNodeData]):
if filters:
code_language = cast(CodeLanguage, filters.get("code_language", CodeLanguage.PYTHON3))
providers: list[type[CodeNodeProvider]] = [Python3CodeProvider, JavascriptCodeProvider]
code_provider: type[CodeNodeProvider] = next(p for p in providers if p.is_accept_language(code_language))
code_provider: type[CodeNodeProvider] = next(
provider for provider in cls._DEFAULT_CODE_PROVIDERS if provider.is_accept_language(code_language)
)
return code_provider.get_default_config()
@classmethod
def default_code_providers(cls) -> tuple[type[CodeNodeProvider], ...]:
return cls._DEFAULT_CODE_PROVIDERS
@classmethod
def version(cls) -> str:
return "1"
@ -60,7 +97,8 @@ class CodeNode(Node[CodeNodeData]):
variables[variable_name] = variable.to_object() if variable else None
# Run code
try:
result = CodeExecutor.execute_workflow_code_template(
_ = self._select_code_provider(code_language)
result = self._code_executor.execute_workflow_code_template(
language=code_language,
code=code,
inputs=variables,
@ -75,6 +113,12 @@ class CodeNode(Node[CodeNodeData]):
return NodeRunResult(status=WorkflowNodeExecutionStatus.SUCCEEDED, inputs=variables, outputs=result)
def _select_code_provider(self, code_language: CodeLanguage) -> type[CodeNodeProvider]:
for provider in self._code_providers:
if provider.is_accept_language(code_language):
return provider
raise CodeNodeError(f"Unsupported code language: {code_language}")
def _check_string(self, value: str | None, variable: str) -> str | None:
"""
Check string
@ -85,10 +129,10 @@ class CodeNode(Node[CodeNodeData]):
if value is None:
return None
if len(value) > dify_config.CODE_MAX_STRING_LENGTH:
if len(value) > self._limits.max_string_length:
raise OutputValidationError(
f"The length of output variable `{variable}` must be"
f" less than {dify_config.CODE_MAX_STRING_LENGTH} characters"
f" less than {self._limits.max_string_length} characters"
)
return value.replace("\x00", "")
@ -109,20 +153,20 @@ class CodeNode(Node[CodeNodeData]):
if value is None:
return None
if value > dify_config.CODE_MAX_NUMBER or value < dify_config.CODE_MIN_NUMBER:
if value > self._limits.max_number or value < self._limits.min_number:
raise OutputValidationError(
f"Output variable `{variable}` is out of range,"
f" it must be between {dify_config.CODE_MIN_NUMBER} and {dify_config.CODE_MAX_NUMBER}."
f" it must be between {self._limits.min_number} and {self._limits.max_number}."
)
if isinstance(value, float):
decimal_value = Decimal(str(value)).normalize()
precision = -decimal_value.as_tuple().exponent if decimal_value.as_tuple().exponent < 0 else 0 # type: ignore[operator]
# raise error if precision is too high
if precision > dify_config.CODE_MAX_PRECISION:
if precision > self._limits.max_precision:
raise OutputValidationError(
f"Output variable `{variable}` has too high precision,"
f" it must be less than {dify_config.CODE_MAX_PRECISION} digits."
f" it must be less than {self._limits.max_precision} digits."
)
return value
@ -137,8 +181,8 @@ class CodeNode(Node[CodeNodeData]):
# TODO(QuantumGhost): Replace native Python lists with `Array*Segment` classes.
# Note that `_transform_result` may produce lists containing `None` values,
# which don't conform to the type requirements of `Array*Segment` classes.
if depth > dify_config.CODE_MAX_DEPTH:
raise DepthLimitError(f"Depth limit {dify_config.CODE_MAX_DEPTH} reached, object too deep.")
if depth > self._limits.max_depth:
raise DepthLimitError(f"Depth limit {self._limits.max_depth} reached, object too deep.")
transformed_result: dict[str, Any] = {}
if output_schema is None:
@ -272,10 +316,10 @@ class CodeNode(Node[CodeNodeData]):
f"Output {prefix}{dot}{output_name} is not an array, got {type(value)} instead."
)
else:
if len(value) > dify_config.CODE_MAX_NUMBER_ARRAY_LENGTH:
if len(value) > self._limits.max_number_array_length:
raise OutputValidationError(
f"The length of output variable `{prefix}{dot}{output_name}` must be"
f" less than {dify_config.CODE_MAX_NUMBER_ARRAY_LENGTH} elements."
f" less than {self._limits.max_number_array_length} elements."
)
for i, inner_value in enumerate(value):
@ -305,10 +349,10 @@ class CodeNode(Node[CodeNodeData]):
f" got {type(result.get(output_name))} instead."
)
else:
if len(result[output_name]) > dify_config.CODE_MAX_STRING_ARRAY_LENGTH:
if len(result[output_name]) > self._limits.max_string_array_length:
raise OutputValidationError(
f"The length of output variable `{prefix}{dot}{output_name}` must be"
f" less than {dify_config.CODE_MAX_STRING_ARRAY_LENGTH} elements."
f" less than {self._limits.max_string_array_length} elements."
)
transformed_result[output_name] = [
@ -326,10 +370,10 @@ class CodeNode(Node[CodeNodeData]):
f" got {type(result.get(output_name))} instead."
)
else:
if len(result[output_name]) > dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH:
if len(result[output_name]) > self._limits.max_object_array_length:
raise OutputValidationError(
f"The length of output variable `{prefix}{dot}{output_name}` must be"
f" less than {dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH} elements."
f" less than {self._limits.max_object_array_length} elements."
)
for i, value in enumerate(result[output_name]):

View File

@ -0,0 +1,13 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class CodeNodeLimits:
max_string_length: int
max_number: int | float
min_number: int | float
max_precision: int
max_depth: int
max_number_array_length: int
max_string_array_length: int
max_object_array_length: int

View File

@ -1,10 +1,16 @@
from collections.abc import Sequence
from typing import TYPE_CHECKING, final
from typing_extensions import override
from configs import dify_config
from core.helper.code_executor.code_executor import CodeExecutor
from core.helper.code_executor.code_node_provider import CodeNodeProvider
from core.workflow.enums import NodeType
from core.workflow.graph import NodeFactory
from core.workflow.nodes.base.node import Node
from core.workflow.nodes.code.code_node import CodeNode
from core.workflow.nodes.code.limits import CodeNodeLimits
from libs.typing import is_str, is_str_dict
from .node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING
@ -27,9 +33,27 @@ class DifyNodeFactory(NodeFactory):
self,
graph_init_params: "GraphInitParams",
graph_runtime_state: "GraphRuntimeState",
*,
code_executor: type[CodeExecutor] | None = None,
code_providers: Sequence[type[CodeNodeProvider]] | None = None,
code_limits: CodeNodeLimits | None = None,
) -> None:
self.graph_init_params = graph_init_params
self.graph_runtime_state = graph_runtime_state
self._code_executor: type[CodeExecutor] = code_executor or CodeExecutor
self._code_providers: tuple[type[CodeNodeProvider], ...] = (
tuple(code_providers) if code_providers else CodeNode.default_code_providers()
)
self._code_limits = code_limits or CodeNodeLimits(
max_string_length=dify_config.CODE_MAX_STRING_LENGTH,
max_number=dify_config.CODE_MAX_NUMBER,
min_number=dify_config.CODE_MIN_NUMBER,
max_precision=dify_config.CODE_MAX_PRECISION,
max_depth=dify_config.CODE_MAX_DEPTH,
max_number_array_length=dify_config.CODE_MAX_NUMBER_ARRAY_LENGTH,
max_string_array_length=dify_config.CODE_MAX_STRING_ARRAY_LENGTH,
max_object_array_length=dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH,
)
@override
def create_node(self, node_config: dict[str, object]) -> Node:
@ -72,6 +96,17 @@ class DifyNodeFactory(NodeFactory):
raise ValueError(f"No latest version class found for node type: {node_type}")
# Create node instance
if node_type == NodeType.CODE:
return CodeNode(
id=node_id,
config=node_config,
graph_init_params=self.graph_init_params,
graph_runtime_state=self.graph_runtime_state,
code_executor=self._code_executor,
code_providers=self._code_providers,
code_limits=self._code_limits,
)
return node_class(
id=node_id,
config=node_config,

View File

@ -10,6 +10,7 @@ from core.workflow.enums import WorkflowNodeExecutionStatus
from core.workflow.graph import Graph
from core.workflow.node_events import NodeRunResult
from core.workflow.nodes.code.code_node import CodeNode
from core.workflow.nodes.code.limits import CodeNodeLimits
from core.workflow.nodes.node_factory import DifyNodeFactory
from core.workflow.runtime import GraphRuntimeState, VariablePool
from core.workflow.system_variable import SystemVariable
@ -67,6 +68,16 @@ def init_code_node(code_config: dict):
config=code_config,
graph_init_params=init_params,
graph_runtime_state=graph_runtime_state,
code_limits=CodeNodeLimits(
max_string_length=dify_config.CODE_MAX_STRING_LENGTH,
max_number=dify_config.CODE_MAX_NUMBER,
min_number=dify_config.CODE_MIN_NUMBER,
max_precision=dify_config.CODE_MAX_PRECISION,
max_depth=dify_config.CODE_MAX_DEPTH,
max_number_array_length=dify_config.CODE_MAX_NUMBER_ARRAY_LENGTH,
max_string_array_length=dify_config.CODE_MAX_STRING_ARRAY_LENGTH,
max_object_array_length=dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH,
),
)
return node

View File

@ -73,6 +73,7 @@ import pytest
from core.rag.datasource.retrieval_service import RetrievalService
from core.rag.models.document import Document
from core.rag.retrieval.dataset_retrieval import DatasetRetrieval
from core.rag.retrieval.retrieval_methods import RetrievalMethod
from models.dataset import Dataset
@ -1518,6 +1519,282 @@ class TestRetrievalService:
call_kwargs = mock_retrieve.call_args.kwargs
assert call_kwargs["reranking_model"] == reranking_model
# ==================== Multiple Retrieve Thread Tests ====================
@patch("core.rag.retrieval.dataset_retrieval.DataPostProcessor")
@patch("core.rag.retrieval.dataset_retrieval.DatasetRetrieval._retriever")
def test_multiple_retrieve_thread_skips_second_reranking_with_single_dataset(
self, mock_retriever, mock_data_processor_class, mock_flask_app, mock_dataset
):
"""
Test that _multiple_retrieve_thread skips second reranking when dataset_count is 1.
When there is only one dataset, the second reranking is unnecessary
because the documents are already ranked from the first retrieval.
This optimization avoids the overhead of reranking when it won't
provide any benefit.
Verifies:
- DataPostProcessor is NOT called when dataset_count == 1
- Documents are still added to all_documents
- Standard scoring logic is applied instead
"""
# Arrange
dataset_retrieval = DatasetRetrieval()
tenant_id = str(uuid4())
# Create test documents
doc1 = Document(
page_content="Test content 1",
metadata={"doc_id": "doc1", "score": 0.9, "document_id": str(uuid4()), "dataset_id": mock_dataset.id},
provider="dify",
)
doc2 = Document(
page_content="Test content 2",
metadata={"doc_id": "doc2", "score": 0.8, "document_id": str(uuid4()), "dataset_id": mock_dataset.id},
provider="dify",
)
# Mock _retriever to return documents
def side_effect_retriever(
flask_app, dataset_id, query, top_k, all_documents, document_ids_filter, metadata_condition, attachment_ids
):
all_documents.extend([doc1, doc2])
mock_retriever.side_effect = side_effect_retriever
# Set up dataset with high_quality indexing
mock_dataset.indexing_technique = "high_quality"
all_documents = []
# Act - Call with dataset_count = 1
dataset_retrieval._multiple_retrieve_thread(
flask_app=mock_flask_app,
available_datasets=[mock_dataset],
metadata_condition=None,
metadata_filter_document_ids=None,
all_documents=all_documents,
tenant_id=tenant_id,
reranking_enable=True,
reranking_mode="reranking_model",
reranking_model={"reranking_provider_name": "cohere", "reranking_model_name": "rerank-v2"},
weights=None,
top_k=5,
score_threshold=0.5,
query="test query",
attachment_id=None,
dataset_count=1, # Single dataset - should skip second reranking
)
# Assert
# DataPostProcessor should NOT be called (second reranking skipped)
mock_data_processor_class.assert_not_called()
# Documents should still be added to all_documents
assert len(all_documents) == 2
assert all_documents[0].page_content == "Test content 1"
assert all_documents[1].page_content == "Test content 2"
@patch("core.rag.retrieval.dataset_retrieval.DataPostProcessor")
@patch("core.rag.retrieval.dataset_retrieval.DatasetRetrieval._retriever")
@patch("core.rag.retrieval.dataset_retrieval.DatasetRetrieval.calculate_vector_score")
def test_multiple_retrieve_thread_performs_second_reranking_with_multiple_datasets(
self, mock_calculate_vector_score, mock_retriever, mock_data_processor_class, mock_flask_app, mock_dataset
):
"""
Test that _multiple_retrieve_thread performs second reranking when dataset_count > 1.
When there are multiple datasets, the second reranking is necessary
to merge and re-rank results from different datasets. This ensures
the most relevant documents across all datasets are returned.
Verifies:
- DataPostProcessor IS called when dataset_count > 1
- Reranking is applied with correct parameters
- Documents are processed correctly
"""
# Arrange
dataset_retrieval = DatasetRetrieval()
tenant_id = str(uuid4())
# Create test documents
doc1 = Document(
page_content="Test content 1",
metadata={"doc_id": "doc1", "score": 0.7, "document_id": str(uuid4()), "dataset_id": mock_dataset.id},
provider="dify",
)
doc2 = Document(
page_content="Test content 2",
metadata={"doc_id": "doc2", "score": 0.6, "document_id": str(uuid4()), "dataset_id": mock_dataset.id},
provider="dify",
)
# Mock _retriever to return documents
def side_effect_retriever(
flask_app, dataset_id, query, top_k, all_documents, document_ids_filter, metadata_condition, attachment_ids
):
all_documents.extend([doc1, doc2])
mock_retriever.side_effect = side_effect_retriever
# Set up dataset with high_quality indexing
mock_dataset.indexing_technique = "high_quality"
# Mock DataPostProcessor instance and its invoke method
mock_processor_instance = Mock()
# Simulate reranking - return documents in reversed order with updated scores
reranked_docs = [
Document(
page_content="Test content 2",
metadata={"doc_id": "doc2", "score": 0.95, "document_id": str(uuid4()), "dataset_id": mock_dataset.id},
provider="dify",
),
Document(
page_content="Test content 1",
metadata={"doc_id": "doc1", "score": 0.85, "document_id": str(uuid4()), "dataset_id": mock_dataset.id},
provider="dify",
),
]
mock_processor_instance.invoke.return_value = reranked_docs
mock_data_processor_class.return_value = mock_processor_instance
all_documents = []
# Create second dataset
mock_dataset2 = Mock(spec=Dataset)
mock_dataset2.id = str(uuid4())
mock_dataset2.indexing_technique = "high_quality"
mock_dataset2.provider = "dify"
# Act - Call with dataset_count = 2
dataset_retrieval._multiple_retrieve_thread(
flask_app=mock_flask_app,
available_datasets=[mock_dataset, mock_dataset2],
metadata_condition=None,
metadata_filter_document_ids=None,
all_documents=all_documents,
tenant_id=tenant_id,
reranking_enable=True,
reranking_mode="reranking_model",
reranking_model={"reranking_provider_name": "cohere", "reranking_model_name": "rerank-v2"},
weights=None,
top_k=5,
score_threshold=0.5,
query="test query",
attachment_id=None,
dataset_count=2, # Multiple datasets - should perform second reranking
)
# Assert
# DataPostProcessor SHOULD be called (second reranking performed)
mock_data_processor_class.assert_called_once_with(
tenant_id,
"reranking_model",
{"reranking_provider_name": "cohere", "reranking_model_name": "rerank-v2"},
None,
False,
)
# Verify invoke was called with correct parameters
mock_processor_instance.invoke.assert_called_once()
# Documents should be added to all_documents after reranking
assert len(all_documents) == 2
# The reranked order should be reflected
assert all_documents[0].page_content == "Test content 2"
assert all_documents[1].page_content == "Test content 1"
@patch("core.rag.retrieval.dataset_retrieval.DataPostProcessor")
@patch("core.rag.retrieval.dataset_retrieval.DatasetRetrieval._retriever")
@patch("core.rag.retrieval.dataset_retrieval.DatasetRetrieval.calculate_vector_score")
def test_multiple_retrieve_thread_single_dataset_uses_standard_scoring(
self, mock_calculate_vector_score, mock_retriever, mock_data_processor_class, mock_flask_app, mock_dataset
):
"""
Test that _multiple_retrieve_thread uses standard scoring when dataset_count is 1
and reranking is enabled.
When there's only one dataset, instead of using DataPostProcessor,
the method should fall through to the standard scoring logic
(calculate_vector_score for high_quality datasets).
Verifies:
- DataPostProcessor is NOT called
- calculate_vector_score IS called for high_quality indexing
- Documents are scored correctly
"""
# Arrange
dataset_retrieval = DatasetRetrieval()
tenant_id = str(uuid4())
# Create test documents
doc1 = Document(
page_content="Test content 1",
metadata={"doc_id": "doc1", "score": 0.9, "document_id": str(uuid4()), "dataset_id": mock_dataset.id},
provider="dify",
)
doc2 = Document(
page_content="Test content 2",
metadata={"doc_id": "doc2", "score": 0.8, "document_id": str(uuid4()), "dataset_id": mock_dataset.id},
provider="dify",
)
# Mock _retriever to return documents
def side_effect_retriever(
flask_app, dataset_id, query, top_k, all_documents, document_ids_filter, metadata_condition, attachment_ids
):
all_documents.extend([doc1, doc2])
mock_retriever.side_effect = side_effect_retriever
# Set up dataset with high_quality indexing
mock_dataset.indexing_technique = "high_quality"
# Mock calculate_vector_score to return scored documents
scored_docs = [
Document(
page_content="Test content 1",
metadata={"doc_id": "doc1", "score": 0.95, "document_id": str(uuid4()), "dataset_id": mock_dataset.id},
provider="dify",
),
]
mock_calculate_vector_score.return_value = scored_docs
all_documents = []
# Act - Call with dataset_count = 1
dataset_retrieval._multiple_retrieve_thread(
flask_app=mock_flask_app,
available_datasets=[mock_dataset],
metadata_condition=None,
metadata_filter_document_ids=None,
all_documents=all_documents,
tenant_id=tenant_id,
reranking_enable=True, # Reranking enabled but should be skipped for single dataset
reranking_mode="reranking_model",
reranking_model={"reranking_provider_name": "cohere", "reranking_model_name": "rerank-v2"},
weights=None,
top_k=5,
score_threshold=0.5,
query="test query",
attachment_id=None,
dataset_count=1,
)
# Assert
# DataPostProcessor should NOT be called
mock_data_processor_class.assert_not_called()
# calculate_vector_score SHOULD be called for high_quality datasets
mock_calculate_vector_score.assert_called_once()
call_args = mock_calculate_vector_score.call_args
assert call_args[0][1] == 5 # top_k
# Documents should be added after standard scoring
assert len(all_documents) == 1
assert all_documents[0].page_content == "Test content 1"
class TestRetrievalMethods:
"""

View File

@ -103,13 +103,25 @@ class MockNodeFactory(DifyNodeFactory):
# Create mock node instance
mock_class = self._mock_node_types[node_type]
mock_instance = mock_class(
id=node_id,
config=node_config,
graph_init_params=self.graph_init_params,
graph_runtime_state=self.graph_runtime_state,
mock_config=self.mock_config,
)
if node_type == NodeType.CODE:
mock_instance = mock_class(
id=node_id,
config=node_config,
graph_init_params=self.graph_init_params,
graph_runtime_state=self.graph_runtime_state,
mock_config=self.mock_config,
code_executor=self._code_executor,
code_providers=self._code_providers,
code_limits=self._code_limits,
)
else:
mock_instance = mock_class(
id=node_id,
config=node_config,
graph_init_params=self.graph_init_params,
graph_runtime_state=self.graph_runtime_state,
mock_config=self.mock_config,
)
return mock_instance

View File

@ -40,12 +40,14 @@ class MockNodeMixin:
graph_init_params: "GraphInitParams",
graph_runtime_state: "GraphRuntimeState",
mock_config: Optional["MockConfig"] = None,
**kwargs: Any,
):
super().__init__(
id=id,
config=config,
graph_init_params=graph_init_params,
graph_runtime_state=graph_runtime_state,
**kwargs,
)
self.mock_config = mock_config

View File

@ -5,11 +5,24 @@ This module tests the functionality of MockTemplateTransformNode and MockCodeNod
to ensure they work correctly with the TableTestRunner.
"""
from configs import dify_config
from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus
from core.workflow.nodes.code.limits import CodeNodeLimits
from tests.unit_tests.core.workflow.graph_engine.test_mock_config import MockConfig, MockConfigBuilder, NodeMockConfig
from tests.unit_tests.core.workflow.graph_engine.test_mock_factory import MockNodeFactory
from tests.unit_tests.core.workflow.graph_engine.test_mock_nodes import MockCodeNode, MockTemplateTransformNode
DEFAULT_CODE_LIMITS = CodeNodeLimits(
max_string_length=dify_config.CODE_MAX_STRING_LENGTH,
max_number=dify_config.CODE_MAX_NUMBER,
min_number=dify_config.CODE_MIN_NUMBER,
max_precision=dify_config.CODE_MAX_PRECISION,
max_depth=dify_config.CODE_MAX_DEPTH,
max_number_array_length=dify_config.CODE_MAX_NUMBER_ARRAY_LENGTH,
max_string_array_length=dify_config.CODE_MAX_STRING_ARRAY_LENGTH,
max_object_array_length=dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH,
)
class TestMockTemplateTransformNode:
"""Test cases for MockTemplateTransformNode."""
@ -306,6 +319,7 @@ class TestMockCodeNode:
graph_init_params=graph_init_params,
graph_runtime_state=graph_runtime_state,
mock_config=mock_config,
code_limits=DEFAULT_CODE_LIMITS,
)
# Run the node
@ -370,6 +384,7 @@ class TestMockCodeNode:
graph_init_params=graph_init_params,
graph_runtime_state=graph_runtime_state,
mock_config=mock_config,
code_limits=DEFAULT_CODE_LIMITS,
)
# Run the node
@ -438,6 +453,7 @@ class TestMockCodeNode:
graph_init_params=graph_init_params,
graph_runtime_state=graph_runtime_state,
mock_config=mock_config,
code_limits=DEFAULT_CODE_LIMITS,
)
# Run the node

View File

@ -1,3 +1,4 @@
from configs import dify_config
from core.helper.code_executor.code_executor import CodeLanguage
from core.variables.types import SegmentType
from core.workflow.nodes.code.code_node import CodeNode
@ -7,6 +8,18 @@ from core.workflow.nodes.code.exc import (
DepthLimitError,
OutputValidationError,
)
from core.workflow.nodes.code.limits import CodeNodeLimits
CodeNode._limits = CodeNodeLimits(
max_string_length=dify_config.CODE_MAX_STRING_LENGTH,
max_number=dify_config.CODE_MAX_NUMBER,
min_number=dify_config.CODE_MIN_NUMBER,
max_precision=dify_config.CODE_MAX_PRECISION,
max_depth=dify_config.CODE_MAX_DEPTH,
max_number_array_length=dify_config.CODE_MAX_NUMBER_ARRAY_LENGTH,
max_string_array_length=dify_config.CODE_MAX_STRING_ARRAY_LENGTH,
max_object_array_length=dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH,
)
class TestCodeNodeExceptions:

View File

@ -233,7 +233,7 @@ NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX=false
# You can adjust the database configuration according to your needs.
# ------------------------------
# Database type, supported values are `postgresql` and `mysql`
# Database type, supported values are `postgresql`, `mysql`, `oceanbase`, `seekdb`
DB_TYPE=postgresql
# For MySQL, only `root` user is supported for now
DB_USERNAME=postgres
@ -533,7 +533,7 @@ SUPABASE_URL=your-server-url
# ------------------------------
# The type of vector store to use.
# Supported values are `weaviate`, `oceanbase`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `pgvecto-rs`, `chroma`, `opensearch`, `oracle`, `tencent`, `elasticsearch`, `elasticsearch-ja`, `analyticdb`, `couchbase`, `vikingdb`, `opengauss`, `tablestore`,`vastbase`,`tidb`,`tidb_on_qdrant`,`baidu`,`lindorm`,`huawei_cloud`,`upstash`, `matrixone`, `clickzetta`, `alibabacloud_mysql`, `iris`.
# Supported values are `weaviate`, `oceanbase`, `seekdb`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `pgvecto-rs`, `chroma`, `opensearch`, `oracle`, `tencent`, `elasticsearch`, `elasticsearch-ja`, `analyticdb`, `couchbase`, `vikingdb`, `opengauss`, `tablestore`, `vastbase`, `tidb`, `tidb_on_qdrant`, `baidu`, `lindorm`, `huawei_cloud`, `upstash`, `matrixone`, `clickzetta`, `alibabacloud_mysql`, `iris`.
VECTOR_STORE=weaviate
# Prefix used to create collection name in vector database
VECTOR_INDEX_NAME_PREFIX=Vector_index
@ -544,9 +544,9 @@ WEAVIATE_API_KEY=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
WEAVIATE_GRPC_ENDPOINT=grpc://weaviate:50051
WEAVIATE_TOKENIZATION=word
# For OceanBase metadata database configuration, available when `DB_TYPE` is `mysql` and `COMPOSE_PROFILES` includes `oceanbase`.
# For OceanBase metadata database configuration, available when `DB_TYPE` is `oceanbase`.
# For OceanBase vector database configuration, available when `VECTOR_STORE` is `oceanbase`
# If you want to use OceanBase as both vector database and metadata database, you need to set `DB_TYPE` to `mysql`, `COMPOSE_PROFILES` is `oceanbase`, and set Database Configuration is the same as the vector database.
# If you want to use OceanBase as both vector database and metadata database, you need to set both `DB_TYPE` and `VECTOR_STORE` to `oceanbase`, and set Database Configuration is the same as the vector database.
# seekdb is the lite version of OceanBase and shares the connection configuration with OceanBase.
OCEANBASE_VECTOR_HOST=oceanbase
OCEANBASE_VECTOR_PORT=2881

View File

@ -54,3 +54,52 @@ http_access allow src_all
# Unless the option's size is increased, an error will occur when uploading more than two files.
client_request_buffer_max_size 100 MB
################################## Performance & Concurrency ###############################
# Increase file descriptor limit for high concurrency
max_filedescriptors 65536
# Timeout configurations for image requests
connect_timeout 30 seconds
request_timeout 2 minutes
read_timeout 2 minutes
client_lifetime 5 minutes
shutdown_lifetime 30 seconds
# Persistent connections - improve performance for multiple requests
server_persistent_connections on
client_persistent_connections on
persistent_request_timeout 30 seconds
pconn_timeout 1 minute
# Connection pool and concurrency limits
client_db on
server_idle_pconn_timeout 2 minutes
client_idle_pconn_timeout 2 minutes
# Quick abort settings - don't abort requests that are mostly done
quick_abort_min 16 KB
quick_abort_max 16 MB
quick_abort_pct 95
# Memory and cache optimization
memory_cache_mode disk
cache_mem 256 MB
maximum_object_size_in_memory 512 KB
# DNS resolver settings for better performance
dns_timeout 30 seconds
dns_retransmit_interval 5 seconds
# By default, Squid uses the system's configured DNS resolvers.
# If you need to override them, set dns_nameservers to appropriate servers
# for your environment (for example, internal/corporate DNS). The following
# is an example using public DNS and SHOULD be customized before use:
# dns_nameservers 8.8.8.8 8.8.4.4
# Logging format for better debugging
logformat dify_log %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
access_log daemon:/var/log/squid/access.log dify_log
# Access log to track concurrent requests and timeouts
logfile_rotate 10

View File

@ -1,17 +0,0 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g clip-path="url(#clip0_6305_73327)">
<path d="M0.5 12.5C0.5 8.77247 0.5 6.9087 1.10896 5.43853C1.92092 3.47831 3.47831 1.92092 5.43853 1.10896C6.9087 0.5 8.77247 0.5 12.5 0.5C16.2275 0.5 18.0913 0.5 19.5615 1.10896C21.5217 1.92092 23.0791 3.47831 23.891 5.43853C24.5 6.9087 24.5 8.77247 24.5 12.5C24.5 16.2275 24.5 18.0913 23.891 19.5615C23.0791 21.5217 21.5217 23.0791 19.5615 23.891C18.0913 24.5 16.2275 24.5 12.5 24.5C8.77247 24.5 6.9087 24.5 5.43853 23.891C3.47831 23.0791 1.92092 21.5217 1.10896 19.5615C0.5 18.0913 0.5 16.2275 0.5 12.5Z" fill="white"/>
<rect width="24" height="24" transform="translate(0.5 0.5)" fill="url(#pattern0_6305_73327)"/>
<rect width="24" height="24" transform="translate(0.5 0.5)" fill="white" fill-opacity="0.01"/>
</g>
<path d="M12.5 0.25C14.3603 0.25 15.7684 0.250313 16.8945 0.327148C18.0228 0.404144 18.8867 0.558755 19.6572 0.87793C21.6787 1.71525 23.2847 3.32133 24.1221 5.34277C24.4412 6.11333 24.5959 6.97723 24.6729 8.10547C24.7497 9.23161 24.75 10.6397 24.75 12.5C24.75 14.3603 24.7497 15.7684 24.6729 16.8945C24.5959 18.0228 24.4412 18.8867 24.1221 19.6572C23.2847 21.6787 21.6787 23.2847 19.6572 24.1221C18.8867 24.4412 18.0228 24.5959 16.8945 24.6729C15.7684 24.7497 14.3603 24.75 12.5 24.75C10.6397 24.75 9.23161 24.7497 8.10547 24.6729C6.97723 24.5959 6.11333 24.4412 5.34277 24.1221C3.32133 23.2847 1.71525 21.6787 0.87793 19.6572C0.558755 18.8867 0.404144 18.0228 0.327148 16.8945C0.250313 15.7684 0.25 14.3603 0.25 12.5C0.25 10.6397 0.250313 9.23161 0.327148 8.10547C0.404144 6.97723 0.558755 6.11333 0.87793 5.34277C1.71525 3.32133 3.32133 1.71525 5.34277 0.87793C6.11333 0.558755 6.97723 0.404144 8.10547 0.327148C9.23161 0.250313 10.6397 0.25 12.5 0.25Z" stroke="#101828" stroke-opacity="0.08" stroke-width="0.5"/>
<defs>
<pattern id="pattern0_6305_73327" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_6305_73327" transform="scale(0.00625)"/>
</pattern>
<clipPath id="clip0_6305_73327">
<path d="M0.5 12.5C0.5 8.77247 0.5 6.9087 1.10896 5.43853C1.92092 3.47831 3.47831 1.92092 5.43853 1.10896C6.9087 0.5 8.77247 0.5 12.5 0.5C16.2275 0.5 18.0913 0.5 19.5615 1.10896C21.5217 1.92092 23.0791 3.47831 23.891 5.43853C24.5 6.9087 24.5 8.77247 24.5 12.5C24.5 16.2275 24.5 18.0913 23.891 19.5615C23.0791 21.5217 21.5217 23.0791 19.5615 23.891C18.0913 24.5 16.2275 24.5 12.5 24.5C8.77247 24.5 6.9087 24.5 5.43853 23.891C3.47831 23.0791 1.92092 21.5217 1.10896 19.5615C0.5 18.0913 0.5 16.2275 0.5 12.5Z" fill="white"/>
</clipPath>
<image id="image0_6305_73327" width="160" height="160" preserveAspectRatio="none" xlink:href="data:image/png;base64,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"/>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -1,4 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" fill="white"/>
<path d="M25.7926 10.1311H21.5089L29.3208 29.869H33.6045L25.7926 10.1311ZM13.4164 10.1311L5.60449 29.869H9.97273L11.5703 25.724H19.743L21.3405 29.869H25.7087L17.8969 10.1311H13.4164ZM12.9834 22.0583L15.6566 15.1217L18.3299 22.0583H12.9834Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 403 B

View File

@ -1,4 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" fill="white"/>
<path d="M36.6676 11.2917C36.3316 11.1277 36.1871 11.4402 35.9906 11.599C35.9242 11.6511 35.8668 11.7188 35.8108 11.7787C35.3199 12.3048 34.747 12.6485 33.9996 12.6068C32.9046 12.5469 31.971 12.8907 31.1455 13.7293C30.9696 12.6954 30.3863 12.0782 29.4996 11.6824C29.0348 11.4766 28.5647 11.2709 28.2406 10.823C28.0127 10.5053 27.9515 10.1511 27.8368 9.80214C27.7652 9.59121 27.6923 9.37506 27.4502 9.33861C27.1871 9.29694 27.0843 9.51829 26.9814 9.70318C26.5674 10.4584 26.4084 11.2917 26.4228 12.1355C26.4592 14.0313 27.26 15.5417 28.8486 16.6173C29.0296 16.7397 29.0764 16.8646 29.0191 17.0443C28.9111 17.4141 28.7822 17.7735 28.6676 18.1433C28.596 18.3803 28.4879 18.4323 28.2354 18.3282C27.363 17.9637 26.609 17.4246 25.9436 16.7709C24.8135 15.6771 23.7914 14.4689 22.5166 13.5235C22.2171 13.3021 21.919 13.0964 21.609 12.9011C20.3082 11.6355 21.7796 10.5964 22.1194 10.474C22.4762 10.3464 22.2431 9.9037 21.092 9.90891C19.9423 9.91413 18.889 10.2995 17.5478 10.8126C17.3512 10.8907 17.1455 10.948 16.9332 10.9922C15.7158 10.7631 14.4515 10.711 13.1298 10.8594C10.6428 11.1381 8.65587 12.3152 7.19493 14.3255C5.44102 16.7397 5.02826 19.4845 5.53347 22.349C6.06473 25.3646 7.60249 27.8646 9.96707 29.8178C12.4176 31.8413 15.2406 32.8334 18.4606 32.6433C20.4163 32.5313 22.5947 32.2683 25.0504 30.1875C25.6702 30.4949 26.3199 30.6173 27.3994 30.711C28.2302 30.7891 29.0296 30.6694 29.6494 30.5417C30.6194 30.3361 30.5518 29.4375 30.2015 29.2709C27.3578 27.9454 27.9814 28.4845 27.4136 28.0495C28.859 26.3361 31.0374 24.5574 31.889 18.797C31.9554 18.3386 31.898 18.0522 31.889 17.6798C31.8838 17.4558 31.9346 17.3673 32.1923 17.3413C32.9046 17.2605 33.596 17.0651 34.2314 16.7137C36.0739 15.7058 36.816 14.0522 36.9918 12.0678C37.0179 11.7657 36.9866 11.4506 36.6676 11.2917ZM20.613 29.1485C17.8564 26.9793 16.5204 26.2657 15.9684 26.297C15.4527 26.3255 15.5452 26.9167 15.6584 27.3022C15.777 27.6823 15.9319 27.9454 16.1494 28.2787C16.2991 28.5001 16.402 28.8307 15.9996 29.0755C15.1116 29.6277 13.5687 28.8907 13.4958 28.8542C11.7001 27.797 10.1988 26.3985 9.14025 24.487C8.11941 22.6459 7.52566 20.6719 7.42801 18.5651C7.40197 18.0547 7.5517 17.875 8.05691 17.7839C8.72227 17.6615 9.40978 17.6355 10.0751 17.7318C12.8876 18.1433 15.2822 19.4037 17.2887 21.3959C18.4346 22.5339 19.3018 23.8907 20.195 25.2162C21.1442 26.6251 22.1663 27.9662 23.4671 29.0651C23.9254 29.4506 24.2926 29.7449 24.6428 29.961C23.5856 30.0782 21.8199 30.1042 20.613 29.1485ZM21.9332 20.6407C21.9332 20.4141 22.1142 20.2345 22.342 20.2345C22.3928 20.2345 22.4398 20.2449 22.4814 20.2605C22.5374 20.2813 22.5895 20.3126 22.6299 20.3594C22.7027 20.4298 22.7444 20.5339 22.7444 20.6407C22.7444 20.8673 22.5635 21.047 22.3368 21.047C22.109 21.047 21.9332 20.8673 21.9332 20.6407ZM26.036 22.7501C25.7731 22.8569 25.51 22.9506 25.2575 22.961C24.8655 22.9793 24.4371 22.8203 24.204 22.6251C23.8434 22.323 23.5856 22.1537 23.4762 21.6225C23.4306 21.3959 23.4567 21.047 23.497 20.8465C23.5908 20.4141 23.4866 20.1381 23.1832 19.8855C22.9346 19.6798 22.6207 19.6251 22.2744 19.6251C22.1455 19.6251 22.027 19.5678 21.9384 19.5209C21.7939 19.4479 21.6754 19.2683 21.7887 19.047C21.8251 18.9766 22.001 18.8022 22.0426 18.7709C22.5114 18.5027 23.053 18.5913 23.5543 18.7918C24.0191 18.9818 24.3694 19.3307 24.8746 19.823C25.3915 20.4194 25.484 20.5861 25.7783 21.0313C26.01 21.3829 26.2223 21.7422 26.3668 22.1537C26.454 22.4089 26.3408 22.6198 26.036 22.7501Z" fill="#4D6BFE"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,105 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" fill="white"/>
<mask id="mask0_3892_95663" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="6" y="6" width="28" height="29">
<path d="M20 6C20.2936 6 20.5488 6.2005 20.6205 6.48556C20.8393 7.3566 21.1277 8.20866 21.4828 9.03356C22.4116 11.191 23.6854 13.0791 25.3032 14.6968C26.9218 16.3146 28.8095 17.5888 30.9664 18.5172C31.7941 18.8735 32.6436 19.16 33.5149 19.3795C33.6533 19.4143 33.7762 19.4942 33.8641 19.6067C33.9519 19.7192 33.9998 19.8578 34 20.0005C34 20.2941 33.7995 20.5492 33.5149 20.621C32.6437 20.8399 31.7915 21.1282 30.9664 21.4833C28.8095 22.4121 26.9209 23.6859 25.3032 25.3036C23.6854 26.9223 22.4116 28.8099 21.4828 30.9669C21.1278 31.7919 20.8394 32.6439 20.6205 33.5149C20.586 33.6534 20.5062 33.7764 20.3937 33.8644C20.2813 33.9524 20.1427 34.0003 20 34.0005C19.8572 34.0003 19.7186 33.9525 19.6062 33.8645C19.4937 33.7765 19.414 33.6535 19.3795 33.5149C19.1605 32.6439 18.872 31.7918 18.5167 30.9669C17.5884 28.8099 16.3151 26.9214 14.6964 25.3036C13.0782 23.6859 11.1906 22.4121 9.03309 21.4833C8.20814 21.1283 7.35608 20.8399 6.48509 20.621C6.34667 20.5864 6.22377 20.5065 6.13589 20.3941C6.04801 20.2817 6.00018 20.1432 6 20.0005C6.00024 19.8578 6.04808 19.7192 6.13594 19.6067C6.2238 19.4942 6.34667 19.4143 6.48509 19.3795C7.35612 19.1607 8.20819 18.8723 9.03309 18.5172C11.1906 17.5888 13.0786 16.3146 14.6964 14.6968C16.3141 13.0791 17.5884 11.191 18.5167 9.03356C18.8719 8.20862 19.1604 7.35656 19.3795 6.48556C19.4508 6.2005 19.7064 6 20 6Z" fill="black"/>
<path d="M20 6C20.2936 6 20.5488 6.2005 20.6205 6.48556C20.8393 7.3566 21.1277 8.20866 21.4828 9.03356C22.4116 11.191 23.6854 13.0791 25.3032 14.6968C26.9218 16.3146 28.8095 17.5888 30.9664 18.5172C31.7941 18.8735 32.6436 19.16 33.5149 19.3795C33.6533 19.4143 33.7762 19.4942 33.8641 19.6067C33.9519 19.7192 33.9998 19.8578 34 20.0005C34 20.2941 33.7995 20.5492 33.5149 20.621C32.6437 20.8399 31.7915 21.1282 30.9664 21.4833C28.8095 22.4121 26.9209 23.6859 25.3032 25.3036C23.6854 26.9223 22.4116 28.8099 21.4828 30.9669C21.1278 31.7919 20.8394 32.6439 20.6205 33.5149C20.586 33.6534 20.5062 33.7764 20.3937 33.8644C20.2813 33.9524 20.1427 34.0003 20 34.0005C19.8572 34.0003 19.7186 33.9525 19.6062 33.8645C19.4937 33.7765 19.414 33.6535 19.3795 33.5149C19.1605 32.6439 18.872 31.7918 18.5167 30.9669C17.5884 28.8099 16.3151 26.9214 14.6964 25.3036C13.0782 23.6859 11.1906 22.4121 9.03309 21.4833C8.20814 21.1283 7.35608 20.8399 6.48509 20.621C6.34667 20.5864 6.22377 20.5065 6.13589 20.3941C6.04801 20.2817 6.00018 20.1432 6 20.0005C6.00024 19.8578 6.04808 19.7192 6.13594 19.6067C6.2238 19.4942 6.34667 19.4143 6.48509 19.3795C7.35612 19.1607 8.20819 18.8723 9.03309 18.5172C11.1906 17.5888 13.0786 16.3146 14.6964 14.6968C16.3141 13.0791 17.5884 11.191 18.5167 9.03356C18.8719 8.20862 19.1604 7.35656 19.3795 6.48556C19.4508 6.2005 19.7064 6 20 6Z" fill="url(#paint0_linear_3892_95663)"/>
</mask>
<g mask="url(#mask0_3892_95663)">
<g filter="url(#filter0_f_3892_95663)">
<path d="M3.47232 27.8921C6.70753 29.0411 10.426 26.8868 11.7778 23.0804C13.1296 19.274 11.6028 15.2569 8.36763 14.108C5.13242 12.959 1.41391 15.1133 0.06211 18.9197C-1.28969 22.7261 0.23711 26.7432 3.47232 27.8921Z" fill="#FFE432"/>
</g>
<g filter="url(#filter1_f_3892_95663)">
<path d="M17.8359 15.341C22.2806 15.341 25.8838 11.6588 25.8838 7.11644C25.8838 2.57412 22.2806 -1.10815 17.8359 -1.10815C13.3912 -1.10815 9.78809 2.57412 9.78809 7.11644C9.78809 11.6588 13.3912 15.341 17.8359 15.341Z" fill="#FC413D"/>
</g>
<g filter="url(#filter2_f_3892_95663)">
<path d="M14.7081 41.6431C19.3478 41.4163 22.8707 36.3599 22.5768 30.3493C22.283 24.3387 18.2836 19.65 13.644 19.8769C9.00433 20.1037 5.48139 25.1601 5.77525 31.1707C6.06911 37.1813 10.0685 41.87 14.7081 41.6431Z" fill="#00B95C"/>
</g>
<g filter="url(#filter3_f_3892_95663)">
<path d="M14.7081 41.6431C19.3478 41.4163 22.8707 36.3599 22.5768 30.3493C22.283 24.3387 18.2836 19.65 13.644 19.8769C9.00433 20.1037 5.48139 25.1601 5.77525 31.1707C6.06911 37.1813 10.0685 41.87 14.7081 41.6431Z" fill="#00B95C"/>
</g>
<g filter="url(#filter4_f_3892_95663)">
<path d="M19.355 38.0071C23.2447 35.6405 24.2857 30.2506 21.6803 25.9684C19.0748 21.6862 13.8095 20.1334 9.91983 22.5C6.03016 24.8666 4.98909 30.2565 7.59454 34.5387C10.2 38.8209 15.4653 40.3738 19.355 38.0071Z" fill="#00B95C"/>
</g>
<g filter="url(#filter5_f_3892_95663)">
<path d="M35.0759 24.5504C39.4477 24.5504 42.9917 21.1377 42.9917 16.9278C42.9917 12.7179 39.4477 9.30518 35.0759 9.30518C30.7042 9.30518 27.1602 12.7179 27.1602 16.9278C27.1602 21.1377 30.7042 24.5504 35.0759 24.5504Z" fill="#3186FF"/>
</g>
<g filter="url(#filter6_f_3892_95663)">
<path d="M0.362818 23.6667C4.3882 26.7279 10.2688 25.7676 13.4976 21.5219C16.7264 17.2762 16.0806 11.3528 12.0552 8.29156C8.02982 5.23037 2.14917 6.19062 -1.07959 10.4364C-4.30835 14.6821 -3.66256 20.6055 0.362818 23.6667Z" fill="#FBBC04"/>
</g>
<g filter="url(#filter7_f_3892_95663)">
<path d="M20.9877 28.1903C25.7924 31.4936 32.1612 30.5732 35.2128 26.1346C38.2644 21.696 36.8432 15.4199 32.0385 12.1166C27.2338 8.81334 20.865 9.73372 17.8134 14.1723C14.7618 18.611 16.183 24.887 20.9877 28.1903Z" fill="#3186FF"/>
</g>
<g filter="url(#filter8_f_3892_95663)">
<path d="M29.7231 4.99175C30.9455 6.65415 29.3748 9.88535 26.2149 12.2096C23.0549 14.5338 19.5026 15.0707 18.2801 13.4088C17.0576 11.7468 18.6284 8.51514 21.7883 6.19092C24.9482 3.86717 28.5006 3.32982 29.7231 4.99175Z" fill="#749BFF"/>
</g>
<g filter="url(#filter9_f_3892_95663)">
<path d="M19.6891 12.9486C24.5759 8.41581 26.2531 2.27858 23.4354 -0.759249C20.6176 -3.79708 14.3718 -2.58516 9.485 1.94765C4.59823 6.48046 2.92099 12.6177 5.73879 15.6555C8.55658 18.6933 14.8024 17.4814 19.6891 12.9486Z" fill="#FC413D"/>
</g>
<g filter="url(#filter10_f_3892_95663)">
<path d="M9.6712 29.23C12.5757 31.3088 15.9102 31.6247 17.1191 29.9356C18.328 28.2465 16.9535 25.1921 14.049 23.1133C11.1446 21.0345 7.81003 20.7186 6.60113 22.4077C5.39223 24.0968 6.76675 27.1512 9.6712 29.23Z" fill="#FFEE48"/>
</g>
</g>
<defs>
<filter id="filter0_f_3892_95663" x="-3.44095" y="10.7885" width="18.7217" height="20.4229" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.50514" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<filter id="filter1_f_3892_95663" x="-4.76352" y="-15.6598" width="45.1989" height="45.5524" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="7.2758" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<filter id="filter2_f_3892_95663" x="-6.61209" y="7.49899" width="41.5757" height="46.522" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="6.18495" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<filter id="filter3_f_3892_95663" x="-6.61209" y="7.49899" width="41.5757" height="46.522" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="6.18495" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<filter id="filter4_f_3892_95663" x="-6.21073" y="9.02316" width="41.6959" height="42.4608" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="6.18495" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<filter id="filter5_f_3892_95663" x="15.405" y="-2.44994" width="39.3423" height="38.7556" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="5.87756" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<filter id="filter6_f_3892_95663" x="-13.7886" y="-4.15284" width="39.9951" height="40.2639" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="5.32691" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<filter id="filter7_f_3892_95663" x="6.6925" y="0.620963" width="39.6414" height="39.065" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="4.75678" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<filter id="filter8_f_3892_95663" x="9.35225" y="-4.48661" width="29.2984" height="27.3739" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="4.25649" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<filter id="filter9_f_3892_95663" x="-2.81919" y="-9.62339" width="34.8122" height="34.143" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="3.59514" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<filter id="filter10_f_3892_95663" x="-2.73761" y="12.4221" width="29.1949" height="27.4994" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="4.44986" result="effect1_foregroundBlur_3892_95663"/>
</filter>
<linearGradient id="paint0_linear_3892_95663" x1="13.9595" y1="24.7349" x2="28.5025" y2="12.4738" gradientUnits="userSpaceOnUse">
<stop stop-color="#4893FC"/>
<stop offset="0.27" stop-color="#4893FC"/>
<stop offset="0.777" stop-color="#969DFF"/>
<stop offset="1" stop-color="#BD99FE"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,11 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" fill="white"/>
<g clip-path="url(#clip0_3892_95659)">
<path d="M15.745 24.54L26.715 16.35C27.254 15.95 28.022 16.106 28.279 16.73C29.628 20.018 29.025 23.971 26.341 26.685C23.658 29.399 19.924 29.995 16.511 28.639L12.783 30.384C18.13 34.081 24.623 33.166 28.681 29.06C31.9 25.805 32.897 21.368 31.965 17.367L31.973 17.376C30.622 11.498 32.305 9.149 35.755 4.345L36 4L31.46 8.59V8.576L15.743 24.544M13.48 26.531C9.643 22.824 10.305 17.085 13.58 13.776C16 11.327 19.968 10.328 23.432 11.797L27.152 10.06C26.482 9.57 25.622 9.043 24.637 8.673C20.182 6.819 14.848 7.742 11.227 11.401C7.744 14.924 6.648 20.341 8.53 24.962C9.935 28.416 7.631 30.86 5.31 33.326C4.49 34.2 3.666 35.074 3 36L13.478 26.534" fill="black"/>
</g>
<defs>
<clipPath id="clip0_3892_95659">
<rect width="33" height="32" fill="white" transform="translate(3 4)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 981 B

View File

@ -1,17 +0,0 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g clip-path="url(#clip0_3892_83671)">
<path d="M1 13C1 9.27247 1 7.4087 1.60896 5.93853C2.42092 3.97831 3.97831 2.42092 5.93853 1.60896C7.4087 1 9.27247 1 13 1C16.7275 1 18.5913 1 20.0615 1.60896C22.0217 2.42092 23.5791 3.97831 24.391 5.93853C25 7.4087 25 9.27247 25 13C25 16.7275 25 18.5913 24.391 20.0615C23.5791 22.0217 22.0217 23.5791 20.0615 24.391C18.5913 25 16.7275 25 13 25C9.27247 25 7.4087 25 5.93853 24.391C3.97831 23.5791 2.42092 22.0217 1.60896 20.0615C1 18.5913 1 16.7275 1 13Z" fill="white"/>
<rect width="24" height="24" transform="translate(1 1)" fill="url(#pattern0_3892_83671)"/>
<rect width="24" height="24" transform="translate(1 1)" fill="white" fill-opacity="0.01"/>
</g>
<path d="M13 0.75C14.8603 0.75 16.2684 0.750313 17.3945 0.827148C18.5228 0.904144 19.3867 1.05876 20.1572 1.37793C22.1787 2.21525 23.7847 3.82133 24.6221 5.84277C24.9412 6.61333 25.0959 7.47723 25.1729 8.60547C25.2497 9.73161 25.25 11.1397 25.25 13C25.25 14.8603 25.2497 16.2684 25.1729 17.3945C25.0959 18.5228 24.9412 19.3867 24.6221 20.1572C23.7847 22.1787 22.1787 23.7847 20.1572 24.6221C19.3867 24.9412 18.5228 25.0959 17.3945 25.1729C16.2684 25.2497 14.8603 25.25 13 25.25C11.1397 25.25 9.73161 25.2497 8.60547 25.1729C7.47723 25.0959 6.61333 24.9412 5.84277 24.6221C3.82133 23.7847 2.21525 22.1787 1.37793 20.1572C1.05876 19.3867 0.904144 18.5228 0.827148 17.3945C0.750313 16.2684 0.75 14.8603 0.75 13C0.75 11.1397 0.750313 9.73161 0.827148 8.60547C0.904144 7.47723 1.05876 6.61333 1.37793 5.84277C2.21525 3.82133 3.82133 2.21525 5.84277 1.37793C6.61333 1.05876 7.47723 0.904144 8.60547 0.827148C9.73161 0.750313 11.1397 0.75 13 0.75Z" stroke="#101828" stroke-opacity="0.08" stroke-width="0.5"/>
<defs>
<pattern id="pattern0_3892_83671" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_3892_83671" transform="scale(0.00625)"/>
</pattern>
<clipPath id="clip0_3892_83671">
<path d="M1 13C1 9.27247 1 7.4087 1.60896 5.93853C2.42092 3.97831 3.97831 2.42092 5.93853 1.60896C7.4087 1 9.27247 1 13 1C16.7275 1 18.5913 1 20.0615 1.60896C22.0217 2.42092 23.5791 3.97831 24.391 5.93853C25 7.4087 25 9.27247 25 13C25 16.7275 25 18.5913 24.391 20.0615C23.5791 22.0217 22.0217 23.5791 20.0615 24.391C18.5913 25 16.7275 25 13 25C9.27247 25 7.4087 25 5.93853 24.391C3.97831 23.5791 2.42092 22.0217 1.60896 20.0615C1 18.5913 1 16.7275 1 13Z" fill="white"/>
</clipPath>
<image id="image0_3892_83671" width="160" height="160" preserveAspectRatio="none" xlink:href="data:image/png;base64,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"/>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -1,36 +0,0 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "40",
"height": "40",
"viewBox": "0 0 40 40",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "40",
"height": "40",
"fill": "white"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M25.7926 10.1311H21.5089L29.3208 29.869H33.6045L25.7926 10.1311ZM13.4164 10.1311L5.60449 29.869H9.97273L11.5703 25.724H19.743L21.3405 29.869H25.7087L17.8969 10.1311H13.4164ZM12.9834 22.0583L15.6566 15.1217L18.3299 22.0583H12.9834Z",
"fill": "black"
},
"children": []
}
]
},
"name": "AnthropicShortLight"
}

View File

@ -1,20 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './AnthropicShortLight.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'AnthropicShortLight'
export default Icon

View File

@ -1,36 +0,0 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "40",
"height": "40",
"viewBox": "0 0 40 40",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "40",
"height": "40",
"fill": "white"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M36.6676 11.2917C36.3316 11.1277 36.1871 11.4402 35.9906 11.599C35.9242 11.6511 35.8668 11.7188 35.8108 11.7787C35.3199 12.3048 34.747 12.6485 33.9996 12.6068C32.9046 12.5469 31.971 12.8907 31.1455 13.7293C30.9696 12.6954 30.3863 12.0782 29.4996 11.6824C29.0348 11.4766 28.5647 11.2709 28.2406 10.823C28.0127 10.5053 27.9515 10.1511 27.8368 9.80214C27.7652 9.59121 27.6923 9.37506 27.4502 9.33861C27.1871 9.29694 27.0843 9.51829 26.9814 9.70318C26.5674 10.4584 26.4084 11.2917 26.4228 12.1355C26.4592 14.0313 27.26 15.5417 28.8486 16.6173C29.0296 16.7397 29.0764 16.8646 29.0191 17.0443C28.9111 17.4141 28.7822 17.7735 28.6676 18.1433C28.596 18.3803 28.4879 18.4323 28.2354 18.3282C27.363 17.9637 26.609 17.4246 25.9436 16.7709C24.8135 15.6771 23.7914 14.4689 22.5166 13.5235C22.2171 13.3021 21.919 13.0964 21.609 12.9011C20.3082 11.6355 21.7796 10.5964 22.1194 10.474C22.4762 10.3464 22.2431 9.9037 21.092 9.90891C19.9423 9.91413 18.889 10.2995 17.5478 10.8126C17.3512 10.8907 17.1455 10.948 16.9332 10.9922C15.7158 10.7631 14.4515 10.711 13.1298 10.8594C10.6428 11.1381 8.65587 12.3152 7.19493 14.3255C5.44102 16.7397 5.02826 19.4845 5.53347 22.349C6.06473 25.3646 7.60249 27.8646 9.96707 29.8178C12.4176 31.8413 15.2406 32.8334 18.4606 32.6433C20.4163 32.5313 22.5947 32.2683 25.0504 30.1875C25.6702 30.4949 26.3199 30.6173 27.3994 30.711C28.2302 30.7891 29.0296 30.6694 29.6494 30.5417C30.6194 30.3361 30.5518 29.4375 30.2015 29.2709C27.3578 27.9454 27.9814 28.4845 27.4136 28.0495C28.859 26.3361 31.0374 24.5574 31.889 18.797C31.9554 18.3386 31.898 18.0522 31.889 17.6798C31.8838 17.4558 31.9346 17.3673 32.1923 17.3413C32.9046 17.2605 33.596 17.0651 34.2314 16.7137C36.0739 15.7058 36.816 14.0522 36.9918 12.0678C37.0179 11.7657 36.9866 11.4506 36.6676 11.2917ZM20.613 29.1485C17.8564 26.9793 16.5204 26.2657 15.9684 26.297C15.4527 26.3255 15.5452 26.9167 15.6584 27.3022C15.777 27.6823 15.9319 27.9454 16.1494 28.2787C16.2991 28.5001 16.402 28.8307 15.9996 29.0755C15.1116 29.6277 13.5687 28.8907 13.4958 28.8542C11.7001 27.797 10.1988 26.3985 9.14025 24.487C8.11941 22.6459 7.52566 20.6719 7.42801 18.5651C7.40197 18.0547 7.5517 17.875 8.05691 17.7839C8.72227 17.6615 9.40978 17.6355 10.0751 17.7318C12.8876 18.1433 15.2822 19.4037 17.2887 21.3959C18.4346 22.5339 19.3018 23.8907 20.195 25.2162C21.1442 26.6251 22.1663 27.9662 23.4671 29.0651C23.9254 29.4506 24.2926 29.7449 24.6428 29.961C23.5856 30.0782 21.8199 30.1042 20.613 29.1485ZM21.9332 20.6407C21.9332 20.4141 22.1142 20.2345 22.342 20.2345C22.3928 20.2345 22.4398 20.2449 22.4814 20.2605C22.5374 20.2813 22.5895 20.3126 22.6299 20.3594C22.7027 20.4298 22.7444 20.5339 22.7444 20.6407C22.7444 20.8673 22.5635 21.047 22.3368 21.047C22.109 21.047 21.9332 20.8673 21.9332 20.6407ZM26.036 22.7501C25.7731 22.8569 25.51 22.9506 25.2575 22.961C24.8655 22.9793 24.4371 22.8203 24.204 22.6251C23.8434 22.323 23.5856 22.1537 23.4762 21.6225C23.4306 21.3959 23.4567 21.047 23.497 20.8465C23.5908 20.4141 23.4866 20.1381 23.1832 19.8855C22.9346 19.6798 22.6207 19.6251 22.2744 19.6251C22.1455 19.6251 22.027 19.5678 21.9384 19.5209C21.7939 19.4479 21.6754 19.2683 21.7887 19.047C21.8251 18.9766 22.001 18.8022 22.0426 18.7709C22.5114 18.5027 23.053 18.5913 23.5543 18.7918C24.0191 18.9818 24.3694 19.3307 24.8746 19.823C25.3915 20.4194 25.484 20.5861 25.7783 21.0313C26.01 21.3829 26.2223 21.7422 26.3668 22.1537C26.454 22.4089 26.3408 22.6198 26.036 22.7501Z",
"fill": "#4D6BFE"
},
"children": []
}
]
},
"name": "Deepseek"
}

View File

@ -1,20 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './Deepseek.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Deepseek'
export default Icon

View File

@ -1,807 +0,0 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "40",
"height": "40",
"viewBox": "0 0 40 40",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "40",
"height": "40",
"fill": "white"
},
"children": []
},
{
"type": "element",
"name": "mask",
"attributes": {
"id": "mask0_3892_95663",
"style": "mask-type:alpha",
"maskUnits": "userSpaceOnUse",
"x": "6",
"y": "6",
"width": "28",
"height": "29"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M20 6C20.2936 6 20.5488 6.2005 20.6205 6.48556C20.8393 7.3566 21.1277 8.20866 21.4828 9.03356C22.4116 11.191 23.6854 13.0791 25.3032 14.6968C26.9218 16.3146 28.8095 17.5888 30.9664 18.5172C31.7941 18.8735 32.6436 19.16 33.5149 19.3795C33.6533 19.4143 33.7762 19.4942 33.8641 19.6067C33.9519 19.7192 33.9998 19.8578 34 20.0005C34 20.2941 33.7995 20.5492 33.5149 20.621C32.6437 20.8399 31.7915 21.1282 30.9664 21.4833C28.8095 22.4121 26.9209 23.6859 25.3032 25.3036C23.6854 26.9223 22.4116 28.8099 21.4828 30.9669C21.1278 31.7919 20.8394 32.6439 20.6205 33.5149C20.586 33.6534 20.5062 33.7764 20.3937 33.8644C20.2813 33.9524 20.1427 34.0003 20 34.0005C19.8572 34.0003 19.7186 33.9525 19.6062 33.8645C19.4937 33.7765 19.414 33.6535 19.3795 33.5149C19.1605 32.6439 18.872 31.7918 18.5167 30.9669C17.5884 28.8099 16.3151 26.9214 14.6964 25.3036C13.0782 23.6859 11.1906 22.4121 9.03309 21.4833C8.20814 21.1283 7.35608 20.8399 6.48509 20.621C6.34667 20.5864 6.22377 20.5065 6.13589 20.3941C6.04801 20.2817 6.00018 20.1432 6 20.0005C6.00024 19.8578 6.04808 19.7192 6.13594 19.6067C6.2238 19.4942 6.34667 19.4143 6.48509 19.3795C7.35612 19.1607 8.20819 18.8723 9.03309 18.5172C11.1906 17.5888 13.0786 16.3146 14.6964 14.6968C16.3141 13.0791 17.5884 11.191 18.5167 9.03356C18.8719 8.20862 19.1604 7.35656 19.3795 6.48556C19.4508 6.2005 19.7064 6 20 6Z",
"fill": "black"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M20 6C20.2936 6 20.5488 6.2005 20.6205 6.48556C20.8393 7.3566 21.1277 8.20866 21.4828 9.03356C22.4116 11.191 23.6854 13.0791 25.3032 14.6968C26.9218 16.3146 28.8095 17.5888 30.9664 18.5172C31.7941 18.8735 32.6436 19.16 33.5149 19.3795C33.6533 19.4143 33.7762 19.4942 33.8641 19.6067C33.9519 19.7192 33.9998 19.8578 34 20.0005C34 20.2941 33.7995 20.5492 33.5149 20.621C32.6437 20.8399 31.7915 21.1282 30.9664 21.4833C28.8095 22.4121 26.9209 23.6859 25.3032 25.3036C23.6854 26.9223 22.4116 28.8099 21.4828 30.9669C21.1278 31.7919 20.8394 32.6439 20.6205 33.5149C20.586 33.6534 20.5062 33.7764 20.3937 33.8644C20.2813 33.9524 20.1427 34.0003 20 34.0005C19.8572 34.0003 19.7186 33.9525 19.6062 33.8645C19.4937 33.7765 19.414 33.6535 19.3795 33.5149C19.1605 32.6439 18.872 31.7918 18.5167 30.9669C17.5884 28.8099 16.3151 26.9214 14.6964 25.3036C13.0782 23.6859 11.1906 22.4121 9.03309 21.4833C8.20814 21.1283 7.35608 20.8399 6.48509 20.621C6.34667 20.5864 6.22377 20.5065 6.13589 20.3941C6.04801 20.2817 6.00018 20.1432 6 20.0005C6.00024 19.8578 6.04808 19.7192 6.13594 19.6067C6.2238 19.4942 6.34667 19.4143 6.48509 19.3795C7.35612 19.1607 8.20819 18.8723 9.03309 18.5172C11.1906 17.5888 13.0786 16.3146 14.6964 14.6968C16.3141 13.0791 17.5884 11.191 18.5167 9.03356C18.8719 8.20862 19.1604 7.35656 19.3795 6.48556C19.4508 6.2005 19.7064 6 20 6Z",
"fill": "url(#paint0_linear_3892_95663)"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"mask": "url(#mask0_3892_95663)"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter0_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M3.47232 27.8921C6.70753 29.0411 10.426 26.8868 11.7778 23.0804C13.1296 19.274 11.6028 15.2569 8.36763 14.108C5.13242 12.959 1.41391 15.1133 0.06211 18.9197C-1.28969 22.7261 0.23711 26.7432 3.47232 27.8921Z",
"fill": "#FFE432"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter1_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M17.8359 15.341C22.2806 15.341 25.8838 11.6588 25.8838 7.11644C25.8838 2.57412 22.2806 -1.10815 17.8359 -1.10815C13.3912 -1.10815 9.78809 2.57412 9.78809 7.11644C9.78809 11.6588 13.3912 15.341 17.8359 15.341Z",
"fill": "#FC413D"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter2_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M14.7081 41.6431C19.3478 41.4163 22.8707 36.3599 22.5768 30.3493C22.283 24.3387 18.2836 19.65 13.644 19.8769C9.00433 20.1037 5.48139 25.1601 5.77525 31.1707C6.06911 37.1813 10.0685 41.87 14.7081 41.6431Z",
"fill": "#00B95C"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter3_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M14.7081 41.6431C19.3478 41.4163 22.8707 36.3599 22.5768 30.3493C22.283 24.3387 18.2836 19.65 13.644 19.8769C9.00433 20.1037 5.48139 25.1601 5.77525 31.1707C6.06911 37.1813 10.0685 41.87 14.7081 41.6431Z",
"fill": "#00B95C"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter4_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M19.355 38.0071C23.2447 35.6405 24.2857 30.2506 21.6803 25.9684C19.0748 21.6862 13.8095 20.1334 9.91983 22.5C6.03016 24.8666 4.98909 30.2565 7.59454 34.5387C10.2 38.8209 15.4653 40.3738 19.355 38.0071Z",
"fill": "#00B95C"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter5_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M35.0759 24.5504C39.4477 24.5504 42.9917 21.1377 42.9917 16.9278C42.9917 12.7179 39.4477 9.30518 35.0759 9.30518C30.7042 9.30518 27.1602 12.7179 27.1602 16.9278C27.1602 21.1377 30.7042 24.5504 35.0759 24.5504Z",
"fill": "#3186FF"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter6_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M0.362818 23.6667C4.3882 26.7279 10.2688 25.7676 13.4976 21.5219C16.7264 17.2762 16.0806 11.3528 12.0552 8.29156C8.02982 5.23037 2.14917 6.19062 -1.07959 10.4364C-4.30835 14.6821 -3.66256 20.6055 0.362818 23.6667Z",
"fill": "#FBBC04"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter7_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M20.9877 28.1903C25.7924 31.4936 32.1612 30.5732 35.2128 26.1346C38.2644 21.696 36.8432 15.4199 32.0385 12.1166C27.2338 8.81334 20.865 9.73372 17.8134 14.1723C14.7618 18.611 16.183 24.887 20.9877 28.1903Z",
"fill": "#3186FF"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter8_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M29.7231 4.99175C30.9455 6.65415 29.3748 9.88535 26.2149 12.2096C23.0549 14.5338 19.5026 15.0707 18.2801 13.4088C17.0576 11.7468 18.6284 8.51514 21.7883 6.19092C24.9482 3.86717 28.5006 3.32982 29.7231 4.99175Z",
"fill": "#749BFF"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter9_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M19.6891 12.9486C24.5759 8.41581 26.2531 2.27858 23.4354 -0.759249C20.6176 -3.79708 14.3718 -2.58516 9.485 1.94765C4.59823 6.48046 2.92099 12.6177 5.73879 15.6555C8.55658 18.6933 14.8024 17.4814 19.6891 12.9486Z",
"fill": "#FC413D"
},
"children": []
}
]
},
{
"type": "element",
"name": "g",
"attributes": {
"filter": "url(#filter10_f_3892_95663)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M9.6712 29.23C12.5757 31.3088 15.9102 31.6247 17.1191 29.9356C18.328 28.2465 16.9535 25.1921 14.049 23.1133C11.1446 21.0345 7.81003 20.7186 6.60113 22.4077C5.39223 24.0968 6.76675 27.1512 9.6712 29.23Z",
"fill": "#FFEE48"
},
"children": []
}
]
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter0_f_3892_95663",
"x": "-3.44095",
"y": "10.7885",
"width": "18.7217",
"height": "20.4229",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "1.50514",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter1_f_3892_95663",
"x": "-4.76352",
"y": "-15.6598",
"width": "45.1989",
"height": "45.5524",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "7.2758",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter2_f_3892_95663",
"x": "-6.61209",
"y": "7.49899",
"width": "41.5757",
"height": "46.522",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "6.18495",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter3_f_3892_95663",
"x": "-6.61209",
"y": "7.49899",
"width": "41.5757",
"height": "46.522",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "6.18495",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter4_f_3892_95663",
"x": "-6.21073",
"y": "9.02316",
"width": "41.6959",
"height": "42.4608",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "6.18495",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter5_f_3892_95663",
"x": "15.405",
"y": "-2.44994",
"width": "39.3423",
"height": "38.7556",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "5.87756",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter6_f_3892_95663",
"x": "-13.7886",
"y": "-4.15284",
"width": "39.9951",
"height": "40.2639",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "5.32691",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter7_f_3892_95663",
"x": "6.6925",
"y": "0.620963",
"width": "39.6414",
"height": "39.065",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "4.75678",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter8_f_3892_95663",
"x": "9.35225",
"y": "-4.48661",
"width": "29.2984",
"height": "27.3739",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "4.25649",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter9_f_3892_95663",
"x": "-2.81919",
"y": "-9.62339",
"width": "34.8122",
"height": "34.143",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "3.59514",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "filter",
"attributes": {
"id": "filter10_f_3892_95663",
"x": "-2.73761",
"y": "12.4221",
"width": "29.1949",
"height": "27.4994",
"filterUnits": "userSpaceOnUse",
"color-interpolation-filters": "sRGB"
},
"children": [
{
"type": "element",
"name": "feFlood",
"attributes": {
"flood-opacity": "0",
"result": "BackgroundImageFix"
},
"children": []
},
{
"type": "element",
"name": "feBlend",
"attributes": {
"mode": "normal",
"in": "SourceGraphic",
"in2": "BackgroundImageFix",
"result": "shape"
},
"children": []
},
{
"type": "element",
"name": "feGaussianBlur",
"attributes": {
"stdDeviation": "4.44986",
"result": "effect1_foregroundBlur_3892_95663"
},
"children": []
}
]
},
{
"type": "element",
"name": "linearGradient",
"attributes": {
"id": "paint0_linear_3892_95663",
"x1": "13.9595",
"y1": "24.7349",
"x2": "28.5025",
"y2": "12.4738",
"gradientUnits": "userSpaceOnUse"
},
"children": [
{
"type": "element",
"name": "stop",
"attributes": {
"stop-color": "#4893FC"
},
"children": []
},
{
"type": "element",
"name": "stop",
"attributes": {
"offset": "0.27",
"stop-color": "#4893FC"
},
"children": []
},
{
"type": "element",
"name": "stop",
"attributes": {
"offset": "0.777",
"stop-color": "#969DFF"
},
"children": []
},
{
"type": "element",
"name": "stop",
"attributes": {
"offset": "1",
"stop-color": "#BD99FE"
},
"children": []
}
]
}
]
}
]
},
"name": "Gemini"
}

View File

@ -1,20 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './Gemini.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Gemini'
export default Icon

View File

@ -1,72 +0,0 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "40",
"height": "40",
"viewBox": "0 0 40 40",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "40",
"height": "40",
"fill": "white"
},
"children": []
},
{
"type": "element",
"name": "g",
"attributes": {
"clip-path": "url(#clip0_3892_95659)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M15.745 24.54L26.715 16.35C27.254 15.95 28.022 16.106 28.279 16.73C29.628 20.018 29.025 23.971 26.341 26.685C23.658 29.399 19.924 29.995 16.511 28.639L12.783 30.384C18.13 34.081 24.623 33.166 28.681 29.06C31.9 25.805 32.897 21.368 31.965 17.367L31.973 17.376C30.622 11.498 32.305 9.149 35.755 4.345L36 4L31.46 8.59V8.576L15.743 24.544M13.48 26.531C9.643 22.824 10.305 17.085 13.58 13.776C16 11.327 19.968 10.328 23.432 11.797L27.152 10.06C26.482 9.57 25.622 9.043 24.637 8.673C20.182 6.819 14.848 7.742 11.227 11.401C7.744 14.924 6.648 20.341 8.53 24.962C9.935 28.416 7.631 30.86 5.31 33.326C4.49 34.2 3.666 35.074 3 36L13.478 26.534",
"fill": "black"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_3892_95659"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "33",
"height": "32",
"fill": "white",
"transform": "translate(3 4)"
},
"children": []
}
]
}
]
}
]
},
"name": "Grok"
}

View File

@ -1,20 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './Grok.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Grok'
export default Icon

File diff suppressed because one or more lines are too long

View File

@ -1,20 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './OpenaiBlue.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'OpenaiBlue'
export default Icon

View File

@ -1,128 +0,0 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "26",
"height": "26",
"viewBox": "0 0 26 26",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"clip-path": "url(#clip0_3892_83671)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M1 13C1 9.27247 1 7.4087 1.60896 5.93853C2.42092 3.97831 3.97831 2.42092 5.93853 1.60896C7.4087 1 9.27247 1 13 1C16.7275 1 18.5913 1 20.0615 1.60896C22.0217 2.42092 23.5791 3.97831 24.391 5.93853C25 7.4087 25 9.27247 25 13C25 16.7275 25 18.5913 24.391 20.0615C23.5791 22.0217 22.0217 23.5791 20.0615 24.391C18.5913 25 16.7275 25 13 25C9.27247 25 7.4087 25 5.93853 24.391C3.97831 23.5791 2.42092 22.0217 1.60896 20.0615C1 18.5913 1 16.7275 1 13Z",
"fill": "white"
},
"children": []
},
{
"type": "element",
"name": "rect",
"attributes": {
"width": "24",
"height": "24",
"transform": "translate(1 1)",
"fill": "url(#pattern0_3892_83671)"
},
"children": []
},
{
"type": "element",
"name": "rect",
"attributes": {
"width": "24",
"height": "24",
"transform": "translate(1 1)",
"fill": "white",
"fill-opacity": "0.01"
},
"children": []
}
]
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M13 0.75C14.8603 0.75 16.2684 0.750313 17.3945 0.827148C18.5228 0.904144 19.3867 1.05876 20.1572 1.37793C22.1787 2.21525 23.7847 3.82133 24.6221 5.84277C24.9412 6.61333 25.0959 7.47723 25.1729 8.60547C25.2497 9.73161 25.25 11.1397 25.25 13C25.25 14.8603 25.2497 16.2684 25.1729 17.3945C25.0959 18.5228 24.9412 19.3867 24.6221 20.1572C23.7847 22.1787 22.1787 23.7847 20.1572 24.6221C19.3867 24.9412 18.5228 25.0959 17.3945 25.1729C16.2684 25.2497 14.8603 25.25 13 25.25C11.1397 25.25 9.73161 25.2497 8.60547 25.1729C7.47723 25.0959 6.61333 24.9412 5.84277 24.6221C3.82133 23.7847 2.21525 22.1787 1.37793 20.1572C1.05876 19.3867 0.904144 18.5228 0.827148 17.3945C0.750313 16.2684 0.75 14.8603 0.75 13C0.75 11.1397 0.750313 9.73161 0.827148 8.60547C0.904144 7.47723 1.05876 6.61333 1.37793 5.84277C2.21525 3.82133 3.82133 2.21525 5.84277 1.37793C6.61333 1.05876 7.47723 0.904144 8.60547 0.827148C9.73161 0.750313 11.1397 0.75 13 0.75Z",
"stroke": "#101828",
"stroke-opacity": "0.08",
"stroke-width": "0.5"
},
"children": []
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "pattern",
"attributes": {
"id": "pattern0_3892_83671",
"patternContentUnits": "objectBoundingBox",
"width": "1",
"height": "1"
},
"children": [
{
"type": "element",
"name": "use",
"attributes": {
"xlink:href": "#image0_3892_83671",
"transform": "scale(0.00625)"
},
"children": []
}
]
},
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_3892_83671"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M1 13C1 9.27247 1 7.4087 1.60896 5.93853C2.42092 3.97831 3.97831 2.42092 5.93853 1.60896C7.4087 1 9.27247 1 13 1C16.7275 1 18.5913 1 20.0615 1.60896C22.0217 2.42092 23.5791 3.97831 24.391 5.93853C25 7.4087 25 9.27247 25 13C25 16.7275 25 18.5913 24.391 20.0615C23.5791 22.0217 22.0217 23.5791 20.0615 24.391C18.5913 25 16.7275 25 13 25C9.27247 25 7.4087 25 5.93853 24.391C3.97831 23.5791 2.42092 22.0217 1.60896 20.0615C1 18.5913 1 16.7275 1 13Z",
"fill": "white"
},
"children": []
}
]
},
{
"type": "element",
"name": "image",
"attributes": {
"id": "image0_3892_83671",
"width": "160",
"height": "160",
"preserveAspectRatio": "none",
"xlink:href": "data:image/png;base64,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"
},
"children": []
}
]
}
]
},
"name": "OpenaiSmall"
}

View File

@ -1,20 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './OpenaiSmall.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'OpenaiSmall'
export default Icon

File diff suppressed because one or more lines are too long

View File

@ -1,20 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './OpenaiTeal.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'OpenaiTeal'
export default Icon

File diff suppressed because one or more lines are too long

View File

@ -1,20 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './OpenaiViolet.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'OpenaiViolet'
export default Icon

View File

@ -1,128 +0,0 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "25",
"height": "25",
"viewBox": "0 0 25 25",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"clip-path": "url(#clip0_6305_73327)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M0.5 12.5C0.5 8.77247 0.5 6.9087 1.10896 5.43853C1.92092 3.47831 3.47831 1.92092 5.43853 1.10896C6.9087 0.5 8.77247 0.5 12.5 0.5C16.2275 0.5 18.0913 0.5 19.5615 1.10896C21.5217 1.92092 23.0791 3.47831 23.891 5.43853C24.5 6.9087 24.5 8.77247 24.5 12.5C24.5 16.2275 24.5 18.0913 23.891 19.5615C23.0791 21.5217 21.5217 23.0791 19.5615 23.891C18.0913 24.5 16.2275 24.5 12.5 24.5C8.77247 24.5 6.9087 24.5 5.43853 23.891C3.47831 23.0791 1.92092 21.5217 1.10896 19.5615C0.5 18.0913 0.5 16.2275 0.5 12.5Z",
"fill": "white"
},
"children": []
},
{
"type": "element",
"name": "rect",
"attributes": {
"width": "24",
"height": "24",
"transform": "translate(0.5 0.5)",
"fill": "url(#pattern0_6305_73327)"
},
"children": []
},
{
"type": "element",
"name": "rect",
"attributes": {
"width": "24",
"height": "24",
"transform": "translate(0.5 0.5)",
"fill": "white",
"fill-opacity": "0.01"
},
"children": []
}
]
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M12.5 0.25C14.3603 0.25 15.7684 0.250313 16.8945 0.327148C18.0228 0.404144 18.8867 0.558755 19.6572 0.87793C21.6787 1.71525 23.2847 3.32133 24.1221 5.34277C24.4412 6.11333 24.5959 6.97723 24.6729 8.10547C24.7497 9.23161 24.75 10.6397 24.75 12.5C24.75 14.3603 24.7497 15.7684 24.6729 16.8945C24.5959 18.0228 24.4412 18.8867 24.1221 19.6572C23.2847 21.6787 21.6787 23.2847 19.6572 24.1221C18.8867 24.4412 18.0228 24.5959 16.8945 24.6729C15.7684 24.7497 14.3603 24.75 12.5 24.75C10.6397 24.75 9.23161 24.7497 8.10547 24.6729C6.97723 24.5959 6.11333 24.4412 5.34277 24.1221C3.32133 23.2847 1.71525 21.6787 0.87793 19.6572C0.558755 18.8867 0.404144 18.0228 0.327148 16.8945C0.250313 15.7684 0.25 14.3603 0.25 12.5C0.25 10.6397 0.250313 9.23161 0.327148 8.10547C0.404144 6.97723 0.558755 6.11333 0.87793 5.34277C1.71525 3.32133 3.32133 1.71525 5.34277 0.87793C6.11333 0.558755 6.97723 0.404144 8.10547 0.327148C9.23161 0.250313 10.6397 0.25 12.5 0.25Z",
"stroke": "#101828",
"stroke-opacity": "0.08",
"stroke-width": "0.5"
},
"children": []
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "pattern",
"attributes": {
"id": "pattern0_6305_73327",
"patternContentUnits": "objectBoundingBox",
"width": "1",
"height": "1"
},
"children": [
{
"type": "element",
"name": "use",
"attributes": {
"xlink:href": "#image0_6305_73327",
"transform": "scale(0.00625)"
},
"children": []
}
]
},
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_6305_73327"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M0.5 12.5C0.5 8.77247 0.5 6.9087 1.10896 5.43853C1.92092 3.47831 3.47831 1.92092 5.43853 1.10896C6.9087 0.5 8.77247 0.5 12.5 0.5C16.2275 0.5 18.0913 0.5 19.5615 1.10896C21.5217 1.92092 23.0791 3.47831 23.891 5.43853C24.5 6.9087 24.5 8.77247 24.5 12.5C24.5 16.2275 24.5 18.0913 23.891 19.5615C23.0791 21.5217 21.5217 23.0791 19.5615 23.891C18.0913 24.5 16.2275 24.5 12.5 24.5C8.77247 24.5 6.9087 24.5 5.43853 23.891C3.47831 23.0791 1.92092 21.5217 1.10896 19.5615C0.5 18.0913 0.5 16.2275 0.5 12.5Z",
"fill": "white"
},
"children": []
}
]
},
{
"type": "element",
"name": "image",
"attributes": {
"id": "image0_6305_73327",
"width": "160",
"height": "160",
"preserveAspectRatio": "none",
"xlink:href": "data:image/png;base64,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"
},
"children": []
}
]
}
]
},
"name": "Tongyi"
}

View File

@ -1,20 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './Tongyi.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Tongyi'
export default Icon

View File

@ -1,7 +1,6 @@
export { default as Anthropic } from './Anthropic'
export { default as AnthropicDark } from './AnthropicDark'
export { default as AnthropicLight } from './AnthropicLight'
export { default as AnthropicShortLight } from './AnthropicShortLight'
export { default as AnthropicText } from './AnthropicText'
export { default as Azureai } from './Azureai'
export { default as AzureaiText } from './AzureaiText'
@ -13,11 +12,8 @@ export { default as Chatglm } from './Chatglm'
export { default as ChatglmText } from './ChatglmText'
export { default as Cohere } from './Cohere'
export { default as CohereText } from './CohereText'
export { default as Deepseek } from './Deepseek'
export { default as Gemini } from './Gemini'
export { default as Gpt3 } from './Gpt3'
export { default as Gpt4 } from './Gpt4'
export { default as Grok } from './Grok'
export { default as Huggingface } from './Huggingface'
export { default as HuggingfaceText } from './HuggingfaceText'
export { default as HuggingfaceTextHub } from './HuggingfaceTextHub'
@ -30,19 +26,14 @@ export { default as Localai } from './Localai'
export { default as LocalaiText } from './LocalaiText'
export { default as Microsoft } from './Microsoft'
export { default as OpenaiBlack } from './OpenaiBlack'
export { default as OpenaiBlue } from './OpenaiBlue'
export { default as OpenaiGreen } from './OpenaiGreen'
export { default as OpenaiSmall } from './OpenaiSmall'
export { default as OpenaiTeal } from './OpenaiTeal'
export { default as OpenaiText } from './OpenaiText'
export { default as OpenaiTransparent } from './OpenaiTransparent'
export { default as OpenaiViolet } from './OpenaiViolet'
export { default as OpenaiYellow } from './OpenaiYellow'
export { default as Openllm } from './Openllm'
export { default as OpenllmText } from './OpenllmText'
export { default as Replicate } from './Replicate'
export { default as ReplicateText } from './ReplicateText'
export { default as Tongyi } from './Tongyi'
export { default as XorbitsInference } from './XorbitsInference'
export { default as XorbitsInferenceText } from './XorbitsInferenceText'
export { default as Zhipuai } from './Zhipuai'

View File

@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@ -1,16 +1,14 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "80px",
"height": "18px",
"viewBox": "0 0 80 18",
"version": "1.1",
"xmlns": "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink"
"version": "1.1"
},
"isRootNode": true,
"children": [
{
"type": "element",

View File

@ -1,16 +1,14 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "120px",
"height": "27px",
"width": "80px",
"height": "18px",
"viewBox": "0 0 80 18",
"version": "1.1",
"xmlns": "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink"
"version": "1.1"
},
"isRootNode": true,
"children": [
{
"type": "element",

View File

@ -75,9 +75,6 @@ const buildAppContext = (overrides: Partial<AppContextValue> = {}): AppContextVa
created_at: 0,
role: 'normal',
providers: [],
trial_credits: 200,
trial_credits_used: 0,
next_credit_reset_date: 0,
}
const langGeniusVersionInfo: LangGeniusVersionResponse = {
current_env: '',
@ -99,7 +96,6 @@ const buildAppContext = (overrides: Partial<AppContextValue> = {}): AppContextVa
mutateCurrentWorkspace: vi.fn(),
langGeniusVersionInfo,
isLoadingCurrentWorkspace: false,
isValidatingCurrentWorkspace: false,
}
const useSelector: AppContextValue['useSelector'] = selector => selector({ ...base, useSelector })
return {

View File

@ -6,10 +6,8 @@ import {
RiBrainLine,
} from '@remixicon/react'
import { useDebounce } from 'ahooks'
import { useEffect, useMemo } from 'react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { IS_CLOUD_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useProviderContext } from '@/context/provider-context'
import { cn } from '@/utils/classnames'
@ -22,7 +20,6 @@ import {
} from './hooks'
import InstallFromMarketplace from './install-from-marketplace'
import ProviderAddedCard from './provider-added-card'
import QuotaPanel from './provider-added-card/quota-panel'
import SystemModelSelector from './system-model-selector'
type Props = {
@ -34,7 +31,6 @@ const FixedModelProvider = ['langgenius/openai/openai', 'langgenius/anthropic/an
const ModelProviderPage = ({ searchText }: Props) => {
const debouncedSearchText = useDebounce(searchText, { wait: 500 })
const { t } = useTranslation()
const { mutateCurrentWorkspace, isValidatingCurrentWorkspace } = useAppContext()
const { data: textGenerationDefaultModel } = useDefaultModel(ModelTypeEnum.textGeneration)
const { data: embeddingsDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding)
const { data: rerankDefaultModel } = useDefaultModel(ModelTypeEnum.rerank)
@ -43,7 +39,6 @@ const ModelProviderPage = ({ searchText }: Props) => {
const { modelProviders: providers } = useProviderContext()
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel && !ttsDefaultModel
const [configuredProviders, notConfiguredProviders] = useMemo(() => {
const configuredProviders: ModelProvider[] = []
const notConfiguredProviders: ModelProvider[] = []
@ -88,10 +83,6 @@ const ModelProviderPage = ({ searchText }: Props) => {
return [filteredConfiguredProviders, filteredNotConfiguredProviders]
}, [configuredProviders, debouncedSearchText, notConfiguredProviders])
useEffect(() => {
mutateCurrentWorkspace()
}, [mutateCurrentWorkspace])
return (
<div className="relative -mt-2 pt-1">
<div className={cn('mb-2 flex items-center')}>
@ -118,7 +109,6 @@ const ModelProviderPage = ({ searchText }: Props) => {
/>
</div>
</div>
{IS_CLOUD_EDITION && <QuotaPanel providers={providers} isLoading={isValidatingCurrentWorkspace} />}
{!filteredConfiguredProviders?.length && (
<div className="mb-2 rounded-[10px] bg-workflow-process-bg p-4">
<div className="flex h-10 w-10 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur">

View File

@ -7,7 +7,6 @@ import { useToastContext } from '@/app/components/base/toast'
import { ConfigProvider } from '@/app/components/header/account-setting/model-provider-page/model-auth'
import { useCredentialStatus } from '@/app/components/header/account-setting/model-provider-page/model-auth/hooks'
import Indicator from '@/app/components/header/indicator'
import { IS_CLOUD_EDITION } from '@/config'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { changeModelProviderPriority } from '@/service/common'
import { cn } from '@/utils/classnames'
@ -115,7 +114,7 @@ const CredentialPanel = ({
provider={provider}
/>
{
systemConfig.enabled && isCustomConfigured && IS_CLOUD_EDITION && (
systemConfig.enabled && isCustomConfigured && (
<PrioritySelector
value={priorityUseType}
onSelect={handleChangePriority}
@ -132,7 +131,7 @@ const CredentialPanel = ({
)
}
{
systemConfig.enabled && isCustomConfigured && !provider.provider_credential_schema && IS_CLOUD_EDITION && (
systemConfig.enabled && isCustomConfigured && !provider.provider_credential_schema && (
<div className="ml-1">
<PrioritySelector
value={priorityUseType}

View File

@ -3,7 +3,6 @@ import type {
ModelItem,
ModelProvider,
} from '../declarations'
import type { ModelProviderQuotaGetPaid } from '../utils'
import {
RiArrowRightSLine,
RiInformation2Fill,
@ -29,6 +28,7 @@ import {
} from '../utils'
import CredentialPanel from './credential-panel'
import ModelList from './model-list'
import QuotaPanel from './quota-panel'
export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
type ProviderAddedCardProps = {
@ -49,7 +49,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
const systemConfig = provider.system_configuration
const hasModelList = fetched && !!modelList.length
const { isCurrentWorkspaceManager } = useAppContext()
const showModelProvider = systemConfig.enabled && [...MODEL_PROVIDER_QUOTA_GET_PAID].includes(provider.provider as ModelProviderQuotaGetPaid) && !IS_CE_EDITION
const showQuota = systemConfig.enabled && [...MODEL_PROVIDER_QUOTA_GET_PAID].includes(provider.provider) && !IS_CE_EDITION
const showCredential = configurationMethods.includes(ConfigurationMethodEnum.predefinedModel) && isCurrentWorkspaceManager
const getModelList = async (providerName: string) => {
@ -104,6 +104,13 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
}
</div>
</div>
{
showQuota && (
<QuotaPanel
provider={provider}
/>
)
}
{
showCredential && (
<CredentialPanel
@ -115,7 +122,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
{
collapsed && (
<div className="system-xs-medium group flex items-center justify-between border-t border-t-divider-subtle py-1.5 pl-2 pr-[11px] text-text-tertiary">
{(showModelProvider || !notConfigured) && (
{(showQuota || !notConfigured) && (
<>
<div className="flex h-6 items-center pl-1 pr-1.5 leading-6 group-hover:hidden">
{
@ -143,7 +150,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
</div>
</>
)}
{!showModelProvider && notConfigured && (
{!showQuota && notConfigured && (
<div className="flex h-6 items-center pl-1 pr-1.5">
<RiInformation2Fill className="mr-1 h-4 w-4 text-text-accent" />
<span className="system-xs-medium text-text-secondary">{t('modelProvider.configureTip', { ns: 'common' })}</span>

View File

@ -1,163 +1,66 @@
import type { FC } from 'react'
import type { ModelProvider } from '../declarations'
import type { Plugin } from '@/app/components/plugins/types'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AnthropicShortLight, Deepseek, Gemini, Grok, OpenaiSmall, Tongyi } from '@/app/components/base/icons/src/public/llm'
import Loading from '@/app/components/base/loading'
import Tooltip from '@/app/components/base/tooltip'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import { useAppContext } from '@/context/app-context'
import useTimestamp from '@/hooks/use-timestamp'
import { cn } from '@/utils/classnames'
import { formatNumber } from '@/utils/format'
import { PreferredProviderTypeEnum } from '../declarations'
import { useMarketplaceAllPlugins } from '../hooks'
import { modelNameMap, ModelProviderQuotaGetPaid } from '../utils'
const allProviders = [
{ key: ModelProviderQuotaGetPaid.OPENAI, Icon: OpenaiSmall },
{ key: ModelProviderQuotaGetPaid.ANTHROPIC, Icon: AnthropicShortLight },
{ key: ModelProviderQuotaGetPaid.GEMINI, Icon: Gemini },
{ key: ModelProviderQuotaGetPaid.X, Icon: Grok },
{ key: ModelProviderQuotaGetPaid.DEEPSEEK, Icon: Deepseek },
{ key: ModelProviderQuotaGetPaid.TONGYI, Icon: Tongyi },
] as const
// Map provider key to plugin ID
// provider key format: langgenius/provider/model, plugin ID format: langgenius/provider
const providerKeyToPluginId: Record<string, string> = {
[ModelProviderQuotaGetPaid.OPENAI]: 'langgenius/openai',
[ModelProviderQuotaGetPaid.ANTHROPIC]: 'langgenius/anthropic',
[ModelProviderQuotaGetPaid.GEMINI]: 'langgenius/gemini',
[ModelProviderQuotaGetPaid.X]: 'langgenius/x',
[ModelProviderQuotaGetPaid.DEEPSEEK]: 'langgenius/deepseek',
[ModelProviderQuotaGetPaid.TONGYI]: 'langgenius/tongyi',
}
import {
CustomConfigurationStatusEnum,
PreferredProviderTypeEnum,
QuotaUnitEnum,
} from '../declarations'
import {
MODEL_PROVIDER_QUOTA_GET_PAID,
} from '../utils'
import PriorityUseTip from './priority-use-tip'
type QuotaPanelProps = {
providers: ModelProvider[]
isLoading?: boolean
provider: ModelProvider
}
const QuotaPanel: FC<QuotaPanelProps> = ({
providers,
isLoading = false,
provider,
}) => {
const { t } = useTranslation()
const { currentWorkspace } = useAppContext()
const credits = Math.max((currentWorkspace.trial_credits - currentWorkspace.trial_credits_used) || 0, 0)
const providerMap = useMemo(() => new Map(
providers.map(p => [p.provider, p.preferred_provider_type]),
), [providers])
const { formatTime } = useTimestamp()
const {
plugins: allPlugins,
} = useMarketplaceAllPlugins(providers, '')
const [selectedPlugin, setSelectedPlugin] = useState<Plugin | null>(null)
const [isShowInstallModal, {
setTrue: showInstallFromMarketplace,
setFalse: hideInstallFromMarketplace,
}] = useBoolean(false)
const selectedPluginIdRef = useRef<string | null>(null)
const handleIconClick = useCallback((key: string) => {
const providerType = providerMap.get(key)
if (!providerType && allPlugins) {
const pluginId = providerKeyToPluginId[key]
const plugin = allPlugins.find(p => p.plugin_id === pluginId)
if (plugin) {
setSelectedPlugin(plugin)
selectedPluginIdRef.current = pluginId
showInstallFromMarketplace()
}
}
}, [allPlugins, providerMap, showInstallFromMarketplace])
useEffect(() => {
if (isShowInstallModal && selectedPluginIdRef.current) {
const isInstalled = providers.some(p => p.provider.startsWith(selectedPluginIdRef.current!))
if (isInstalled) {
hideInstallFromMarketplace()
selectedPluginIdRef.current = null
}
}
}, [providers, isShowInstallModal, hideInstallFromMarketplace])
if (isLoading) {
return (
<div className="my-2 flex min-h-[72px] items-center justify-center rounded-xl border-[0.5px] border-components-panel-border bg-third-party-model-bg-default shadow-xs">
<Loading />
</div>
)
}
const customConfig = provider.custom_configuration
const priorityUseType = provider.preferred_provider_type
const systemConfig = provider.system_configuration
const currentQuota = systemConfig.enabled && systemConfig.quota_configurations.find(item => item.quota_type === systemConfig.current_quota_type)
const openaiOrAnthropic = MODEL_PROVIDER_QUOTA_GET_PAID.includes(provider.provider)
return (
<div className={cn('my-2 min-w-[72px] shrink-0 rounded-xl border-[0.5px] pb-2.5 pl-4 pr-2.5 pt-3 shadow-xs', credits <= 0 ? 'border-state-destructive-border hover:bg-state-destructive-hover' : 'border-components-panel-border bg-third-party-model-bg-default')}>
<div className="group relative min-w-[112px] shrink-0 rounded-lg border-[0.5px] border-components-panel-border bg-white/[0.18] px-3 py-2 shadow-xs">
<div className="system-xs-medium-uppercase mb-2 flex h-4 items-center text-text-tertiary">
{t('modelProvider.quota', { ns: 'common' })}
<Tooltip popupContent={t('modelProvider.card.tip', { ns: 'common' })} />
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-1 text-xs text-text-tertiary">
<span className="system-md-semibold-uppercase mr-0.5 text-text-secondary">{formatNumber(credits)}</span>
<span>{t('modelProvider.credits', { ns: 'common' })}</span>
{currentWorkspace.next_credit_reset_date
? (
<>
<span>·</span>
<span>
{t('modelProvider.resetDate', {
ns: 'common',
date: formatTime(currentWorkspace.next_credit_reset_date, t('dateFormat', { ns: 'appLog' })),
interpolation: { escapeValue: false },
})}
</span>
</>
)
: null}
</div>
<div className="flex items-center gap-1">
{allProviders.map(({ key, Icon }) => {
const providerType = providerMap.get(key)
const usingQuota = providerType === PreferredProviderTypeEnum.system
const getTooltipKey = () => {
if (usingQuota)
return 'modelProvider.card.modelSupported'
if (providerType === PreferredProviderTypeEnum.custom)
return 'modelProvider.card.modelAPI'
return 'modelProvider.card.modelNotSupported'
}
return (
<Tooltip
key={key}
popupContent={t(getTooltipKey(), { modelName: modelNameMap[key], ns: 'common' })}
>
<div
className={cn('relative h-6 w-6', !providerType && 'cursor-pointer hover:opacity-80')}
onClick={() => handleIconClick(key)}
>
<Icon className="h-6 w-6 rounded-lg" />
{!usingQuota && (
<div className="absolute inset-0 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge opacity-30" />
)}
</div>
</Tooltip>
)
})}
</div>
</div>
{isShowInstallModal && selectedPlugin && (
<InstallFromMarketplace
manifest={selectedPlugin}
uniqueIdentifier={selectedPlugin.latest_package_identifier}
onClose={hideInstallFromMarketplace}
onSuccess={hideInstallFromMarketplace}
<Tooltip popupContent={
openaiOrAnthropic
? t('modelProvider.card.tip', { ns: 'common' })
: t('modelProvider.quotaTip', { ns: 'common' })
}
/>
)}
</div>
{
currentQuota && (
<div className="flex h-4 items-center text-xs text-text-tertiary">
<span className="system-md-semibold-uppercase mr-0.5 text-text-secondary">{formatNumber(Math.max((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0), 0))}</span>
{
currentQuota?.quota_unit === QuotaUnitEnum.tokens && 'Tokens'
}
{
currentQuota?.quota_unit === QuotaUnitEnum.times && t('modelProvider.callTimes', { ns: 'common' })
}
{
currentQuota?.quota_unit === QuotaUnitEnum.credits && t('modelProvider.credits', { ns: 'common' })
}
</div>
)
}
{
priorityUseType === PreferredProviderTypeEnum.system && customConfig.status === CustomConfigurationStatusEnum.active && (
<PriorityUseTip />
)
}
</div>
)
}
export default React.memo(QuotaPanel)
export default QuotaPanel

View File

@ -17,25 +17,7 @@ import {
ModelTypeEnum,
} from './declarations'
export enum ModelProviderQuotaGetPaid {
ANTHROPIC = 'langgenius/anthropic/anthropic',
OPENAI = 'langgenius/openai/openai',
// AZURE_OPENAI = 'langgenius/azure_openai/azure_openai',
GEMINI = 'langgenius/gemini/google',
X = 'langgenius/x/x',
DEEPSEEK = 'langgenius/deepseek/deepseek',
TONGYI = 'langgenius/tongyi/tongyi',
}
export const MODEL_PROVIDER_QUOTA_GET_PAID = [ModelProviderQuotaGetPaid.ANTHROPIC, ModelProviderQuotaGetPaid.OPENAI, ModelProviderQuotaGetPaid.GEMINI, ModelProviderQuotaGetPaid.X, ModelProviderQuotaGetPaid.DEEPSEEK, ModelProviderQuotaGetPaid.TONGYI]
export const modelNameMap = {
[ModelProviderQuotaGetPaid.OPENAI]: 'OpenAI',
[ModelProviderQuotaGetPaid.ANTHROPIC]: 'Anthropic',
[ModelProviderQuotaGetPaid.GEMINI]: 'Gemini',
[ModelProviderQuotaGetPaid.X]: 'xAI',
[ModelProviderQuotaGetPaid.DEEPSEEK]: 'DeepSeek',
[ModelProviderQuotaGetPaid.TONGYI]: 'TONGYI',
}
export const MODEL_PROVIDER_QUOTA_GET_PAID = ['langgenius/anthropic/anthropic', 'langgenius/openai/openai', 'langgenius/azure_openai/azure_openai']
export const isNullOrUndefined = (value: any) => {
return value === undefined || value === null

View File

@ -92,7 +92,7 @@ const ProviderCardComponent: FC<Props> = ({
manifest={payload}
uniqueIdentifier={payload.latest_package_identifier}
onClose={hideInstallFromMarketplace}
onSuccess={hideInstallFromMarketplace}
onSuccess={() => hideInstallFromMarketplace()}
/>
)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,971 @@
import type { PanelProps } from '@/app/components/workflow/panel'
import { render, screen, waitFor } from '@testing-library/react'
import * as React from 'react'
import RagPipelinePanel from './index'
// ============================================================================
// Mock External Dependencies
// ============================================================================
// Type definitions for dynamic module
type DynamicModule = {
default?: React.ComponentType<Record<string, unknown>>
}
type PromiseOrModule = Promise<DynamicModule> | DynamicModule
// Mock next/dynamic to return synchronous components immediately
vi.mock('next/dynamic', () => ({
default: (loader: () => PromiseOrModule, _options?: Record<string, unknown>) => {
let Component: React.ComponentType<Record<string, unknown>> | null = null
// Try to resolve the loader synchronously for mocked modules
try {
const result = loader() as PromiseOrModule
if (result && typeof (result as Promise<DynamicModule>).then === 'function') {
// For async modules, we need to handle them specially
// This will work with vi.mock since mocks resolve synchronously
(result as Promise<DynamicModule>).then((mod: DynamicModule) => {
Component = (mod.default || mod) as React.ComponentType<Record<string, unknown>>
})
}
else if (result) {
Component = ((result as DynamicModule).default || result) as React.ComponentType<Record<string, unknown>>
}
}
catch {
// If the module can't be resolved, Component stays null
}
// Return a simple wrapper that renders the component or null
const DynamicComponent = React.forwardRef((props: Record<string, unknown>, ref: React.Ref<unknown>) => {
// For mocked modules, Component should already be set
if (Component)
return <Component {...props} ref={ref} />
return null
})
DynamicComponent.displayName = 'DynamicComponent'
return DynamicComponent
},
}))
// Mock workflow store
let mockHistoryWorkflowData: Record<string, unknown> | null = null
let mockShowDebugAndPreviewPanel = false
let mockShowGlobalVariablePanel = false
let mockShowInputFieldPanel = false
let mockShowInputFieldPreviewPanel = false
let mockInputFieldEditPanelProps: Record<string, unknown> | null = null
let mockPipelineId = 'test-pipeline-123'
type MockStoreState = {
historyWorkflowData: Record<string, unknown> | null
showDebugAndPreviewPanel: boolean
showGlobalVariablePanel: boolean
showInputFieldPanel: boolean
showInputFieldPreviewPanel: boolean
inputFieldEditPanelProps: Record<string, unknown> | null
pipelineId: string
}
vi.mock('@/app/components/workflow/store', () => ({
useStore: (selector: (state: MockStoreState) => unknown) => {
const state: MockStoreState = {
historyWorkflowData: mockHistoryWorkflowData,
showDebugAndPreviewPanel: mockShowDebugAndPreviewPanel,
showGlobalVariablePanel: mockShowGlobalVariablePanel,
showInputFieldPanel: mockShowInputFieldPanel,
showInputFieldPreviewPanel: mockShowInputFieldPreviewPanel,
inputFieldEditPanelProps: mockInputFieldEditPanelProps,
pipelineId: mockPipelineId,
}
return selector(state)
},
}))
// Mock Panel component to capture props and render children
let capturedPanelProps: PanelProps | null = null
vi.mock('@/app/components/workflow/panel', () => ({
default: (props: PanelProps) => {
capturedPanelProps = props
return (
<div data-testid="workflow-panel">
<div data-testid="panel-left">{props.components?.left}</div>
<div data-testid="panel-right">{props.components?.right}</div>
</div>
)
},
}))
// Mock Record component
vi.mock('@/app/components/workflow/panel/record', () => ({
default: () => <div data-testid="record-panel">Record Panel</div>,
}))
// Mock TestRunPanel component
vi.mock('@/app/components/rag-pipeline/components/panel/test-run', () => ({
default: () => <div data-testid="test-run-panel">Test Run Panel</div>,
}))
// Mock InputFieldPanel component
vi.mock('./input-field', () => ({
default: () => <div data-testid="input-field-panel">Input Field Panel</div>,
}))
// Mock InputFieldEditorPanel component
const mockInputFieldEditorProps = vi.fn()
vi.mock('./input-field/editor', () => ({
default: (props: Record<string, unknown>) => {
mockInputFieldEditorProps(props)
return <div data-testid="input-field-editor-panel">Input Field Editor Panel</div>
},
}))
// Mock PreviewPanel component
vi.mock('./input-field/preview', () => ({
default: () => <div data-testid="preview-panel">Preview Panel</div>,
}))
// Mock GlobalVariablePanel component
vi.mock('@/app/components/workflow/panel/global-variable-panel', () => ({
default: () => <div data-testid="global-variable-panel">Global Variable Panel</div>,
}))
// ============================================================================
// Helper Functions
// ============================================================================
type SetupMockOptions = {
historyWorkflowData?: Record<string, unknown> | null
showDebugAndPreviewPanel?: boolean
showGlobalVariablePanel?: boolean
showInputFieldPanel?: boolean
showInputFieldPreviewPanel?: boolean
inputFieldEditPanelProps?: Record<string, unknown> | null
pipelineId?: string
}
const setupMocks = (options?: SetupMockOptions) => {
mockHistoryWorkflowData = options?.historyWorkflowData ?? null
mockShowDebugAndPreviewPanel = options?.showDebugAndPreviewPanel ?? false
mockShowGlobalVariablePanel = options?.showGlobalVariablePanel ?? false
mockShowInputFieldPanel = options?.showInputFieldPanel ?? false
mockShowInputFieldPreviewPanel = options?.showInputFieldPreviewPanel ?? false
mockInputFieldEditPanelProps = options?.inputFieldEditPanelProps ?? null
mockPipelineId = options?.pipelineId ?? 'test-pipeline-123'
capturedPanelProps = null
}
// ============================================================================
// RagPipelinePanel Component Tests
// ============================================================================
describe('RagPipelinePanel', () => {
beforeEach(() => {
vi.clearAllMocks()
setupMocks()
})
// -------------------------------------------------------------------------
// Rendering Tests
// -------------------------------------------------------------------------
describe('Rendering', () => {
it('should render without crashing', async () => {
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('workflow-panel')).toBeInTheDocument()
})
})
it('should render Panel component with correct structure', async () => {
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('panel-left')).toBeInTheDocument()
expect(screen.getByTestId('panel-right')).toBeInTheDocument()
})
})
it('should pass versionHistoryPanelProps to Panel', async () => {
// Arrange
setupMocks({ pipelineId: 'my-pipeline-456' })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(capturedPanelProps?.versionHistoryPanelProps).toBeDefined()
expect(capturedPanelProps?.versionHistoryPanelProps?.getVersionListUrl).toBe(
'/rag/pipelines/my-pipeline-456/workflows',
)
})
})
})
// -------------------------------------------------------------------------
// Memoization Tests - versionHistoryPanelProps
// -------------------------------------------------------------------------
describe('Memoization - versionHistoryPanelProps', () => {
it('should compute correct getVersionListUrl based on pipelineId', async () => {
// Arrange
setupMocks({ pipelineId: 'pipeline-abc' })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(capturedPanelProps?.versionHistoryPanelProps?.getVersionListUrl).toBe(
'/rag/pipelines/pipeline-abc/workflows',
)
})
})
it('should compute correct deleteVersionUrl function', async () => {
// Arrange
setupMocks({ pipelineId: 'pipeline-xyz' })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
const deleteUrl = capturedPanelProps?.versionHistoryPanelProps?.deleteVersionUrl?.('version-1')
expect(deleteUrl).toBe('/rag/pipelines/pipeline-xyz/workflows/version-1')
})
})
it('should compute correct updateVersionUrl function', async () => {
// Arrange
setupMocks({ pipelineId: 'pipeline-def' })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
const updateUrl = capturedPanelProps?.versionHistoryPanelProps?.updateVersionUrl?.('version-2')
expect(updateUrl).toBe('/rag/pipelines/pipeline-def/workflows/version-2')
})
})
it('should set latestVersionId to empty string', async () => {
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(capturedPanelProps?.versionHistoryPanelProps?.latestVersionId).toBe('')
})
})
})
// -------------------------------------------------------------------------
// Memoization Tests - panelProps
// -------------------------------------------------------------------------
describe('Memoization - panelProps', () => {
it('should pass components.left to Panel', async () => {
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(capturedPanelProps?.components?.left).toBeDefined()
})
})
it('should pass components.right to Panel', async () => {
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(capturedPanelProps?.components?.right).toBeDefined()
})
})
it('should pass versionHistoryPanelProps to panelProps', async () => {
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(capturedPanelProps?.versionHistoryPanelProps).toBeDefined()
})
})
})
// -------------------------------------------------------------------------
// Component Memoization Tests (React.memo)
// -------------------------------------------------------------------------
describe('Component Memoization', () => {
it('should be wrapped with React.memo', async () => {
// The component should not break when re-rendered
const { rerender } = render(<RagPipelinePanel />)
// Act - rerender without prop changes
rerender(<RagPipelinePanel />)
// Assert - component should still render correctly
await waitFor(() => {
expect(screen.getByTestId('workflow-panel')).toBeInTheDocument()
})
})
})
})
// ============================================================================
// RagPipelinePanelOnRight Component Tests
// ============================================================================
describe('RagPipelinePanelOnRight', () => {
beforeEach(() => {
vi.clearAllMocks()
setupMocks()
})
// -------------------------------------------------------------------------
// Conditional Rendering - Record Panel
// -------------------------------------------------------------------------
describe('Record Panel Conditional Rendering', () => {
it('should render Record panel when historyWorkflowData exists', async () => {
// Arrange
setupMocks({ historyWorkflowData: { id: 'history-1' } })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('record-panel')).toBeInTheDocument()
})
})
it('should not render Record panel when historyWorkflowData is null', async () => {
// Arrange
setupMocks({ historyWorkflowData: null })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.queryByTestId('record-panel')).not.toBeInTheDocument()
})
})
it('should not render Record panel when historyWorkflowData is undefined', async () => {
// Arrange
setupMocks({ historyWorkflowData: undefined })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.queryByTestId('record-panel')).not.toBeInTheDocument()
})
})
})
// -------------------------------------------------------------------------
// Conditional Rendering - TestRun Panel
// -------------------------------------------------------------------------
describe('TestRun Panel Conditional Rendering', () => {
it('should render TestRun panel when showDebugAndPreviewPanel is true', async () => {
// Arrange
setupMocks({ showDebugAndPreviewPanel: true })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('test-run-panel')).toBeInTheDocument()
})
})
it('should not render TestRun panel when showDebugAndPreviewPanel is false', async () => {
// Arrange
setupMocks({ showDebugAndPreviewPanel: false })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.queryByTestId('test-run-panel')).not.toBeInTheDocument()
})
})
})
// -------------------------------------------------------------------------
// Conditional Rendering - GlobalVariable Panel
// -------------------------------------------------------------------------
describe('GlobalVariable Panel Conditional Rendering', () => {
it('should render GlobalVariable panel when showGlobalVariablePanel is true', async () => {
// Arrange
setupMocks({ showGlobalVariablePanel: true })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('global-variable-panel')).toBeInTheDocument()
})
})
it('should not render GlobalVariable panel when showGlobalVariablePanel is false', async () => {
// Arrange
setupMocks({ showGlobalVariablePanel: false })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.queryByTestId('global-variable-panel')).not.toBeInTheDocument()
})
})
})
// -------------------------------------------------------------------------
// Multiple Panels Rendering
// -------------------------------------------------------------------------
describe('Multiple Panels Rendering', () => {
it('should render all right panels when all conditions are true', async () => {
// Arrange
setupMocks({
historyWorkflowData: { id: 'history-1' },
showDebugAndPreviewPanel: true,
showGlobalVariablePanel: true,
})
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('record-panel')).toBeInTheDocument()
expect(screen.getByTestId('test-run-panel')).toBeInTheDocument()
expect(screen.getByTestId('global-variable-panel')).toBeInTheDocument()
})
})
it('should render no right panels when all conditions are false', async () => {
// Arrange
setupMocks({
historyWorkflowData: null,
showDebugAndPreviewPanel: false,
showGlobalVariablePanel: false,
})
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.queryByTestId('record-panel')).not.toBeInTheDocument()
expect(screen.queryByTestId('test-run-panel')).not.toBeInTheDocument()
expect(screen.queryByTestId('global-variable-panel')).not.toBeInTheDocument()
})
})
it('should render only Record and TestRun panels', async () => {
// Arrange
setupMocks({
historyWorkflowData: { id: 'history-1' },
showDebugAndPreviewPanel: true,
showGlobalVariablePanel: false,
})
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('record-panel')).toBeInTheDocument()
expect(screen.getByTestId('test-run-panel')).toBeInTheDocument()
expect(screen.queryByTestId('global-variable-panel')).not.toBeInTheDocument()
})
})
})
})
// ============================================================================
// RagPipelinePanelOnLeft Component Tests
// ============================================================================
describe('RagPipelinePanelOnLeft', () => {
beforeEach(() => {
vi.clearAllMocks()
setupMocks()
})
// -------------------------------------------------------------------------
// Conditional Rendering - Preview Panel
// -------------------------------------------------------------------------
describe('Preview Panel Conditional Rendering', () => {
it('should render Preview panel when showInputFieldPreviewPanel is true', async () => {
// Arrange
setupMocks({ showInputFieldPreviewPanel: true })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('preview-panel')).toBeInTheDocument()
})
})
it('should not render Preview panel when showInputFieldPreviewPanel is false', async () => {
// Arrange
setupMocks({ showInputFieldPreviewPanel: false })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.queryByTestId('preview-panel')).not.toBeInTheDocument()
})
})
})
// -------------------------------------------------------------------------
// Conditional Rendering - InputFieldEditor Panel
// -------------------------------------------------------------------------
describe('InputFieldEditor Panel Conditional Rendering', () => {
it('should render InputFieldEditor panel when inputFieldEditPanelProps is provided', async () => {
// Arrange
const editProps = {
onClose: vi.fn(),
onSubmit: vi.fn(),
initialData: { variable: 'test' },
}
setupMocks({ inputFieldEditPanelProps: editProps })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('input-field-editor-panel')).toBeInTheDocument()
})
})
it('should not render InputFieldEditor panel when inputFieldEditPanelProps is null', async () => {
// Arrange
setupMocks({ inputFieldEditPanelProps: null })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.queryByTestId('input-field-editor-panel')).not.toBeInTheDocument()
})
})
it('should pass props to InputFieldEditor panel', async () => {
// Arrange
const editProps = {
onClose: vi.fn(),
onSubmit: vi.fn(),
initialData: { variable: 'test_var', label: 'Test Label' },
}
setupMocks({ inputFieldEditPanelProps: editProps })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(mockInputFieldEditorProps).toHaveBeenCalledWith(
expect.objectContaining({
onClose: editProps.onClose,
onSubmit: editProps.onSubmit,
initialData: editProps.initialData,
}),
)
})
})
})
// -------------------------------------------------------------------------
// Conditional Rendering - InputField Panel
// -------------------------------------------------------------------------
describe('InputField Panel Conditional Rendering', () => {
it('should render InputField panel when showInputFieldPanel is true', async () => {
// Arrange
setupMocks({ showInputFieldPanel: true })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('input-field-panel')).toBeInTheDocument()
})
})
it('should not render InputField panel when showInputFieldPanel is false', async () => {
// Arrange
setupMocks({ showInputFieldPanel: false })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.queryByTestId('input-field-panel')).not.toBeInTheDocument()
})
})
})
// -------------------------------------------------------------------------
// Multiple Panels Rendering
// -------------------------------------------------------------------------
describe('Multiple Left Panels Rendering', () => {
it('should render all left panels when all conditions are true', async () => {
// Arrange
setupMocks({
showInputFieldPreviewPanel: true,
inputFieldEditPanelProps: { onClose: vi.fn(), onSubmit: vi.fn() },
showInputFieldPanel: true,
})
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('preview-panel')).toBeInTheDocument()
expect(screen.getByTestId('input-field-editor-panel')).toBeInTheDocument()
expect(screen.getByTestId('input-field-panel')).toBeInTheDocument()
})
})
it('should render no left panels when all conditions are false', async () => {
// Arrange
setupMocks({
showInputFieldPreviewPanel: false,
inputFieldEditPanelProps: null,
showInputFieldPanel: false,
})
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.queryByTestId('preview-panel')).not.toBeInTheDocument()
expect(screen.queryByTestId('input-field-editor-panel')).not.toBeInTheDocument()
expect(screen.queryByTestId('input-field-panel')).not.toBeInTheDocument()
})
})
it('should render only Preview and InputField panels', async () => {
// Arrange
setupMocks({
showInputFieldPreviewPanel: true,
inputFieldEditPanelProps: null,
showInputFieldPanel: true,
})
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(screen.getByTestId('preview-panel')).toBeInTheDocument()
expect(screen.queryByTestId('input-field-editor-panel')).not.toBeInTheDocument()
expect(screen.getByTestId('input-field-panel')).toBeInTheDocument()
})
})
})
})
// ============================================================================
// Edge Cases Tests
// ============================================================================
describe('Edge Cases', () => {
beforeEach(() => {
vi.clearAllMocks()
setupMocks()
})
// -------------------------------------------------------------------------
// Empty/Undefined Values
// -------------------------------------------------------------------------
describe('Empty/Undefined Values', () => {
it('should handle empty pipelineId gracefully', async () => {
// Arrange
setupMocks({ pipelineId: '' })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(capturedPanelProps?.versionHistoryPanelProps?.getVersionListUrl).toBe(
'/rag/pipelines//workflows',
)
})
})
it('should handle special characters in pipelineId', async () => {
// Arrange
setupMocks({ pipelineId: 'pipeline-with-special_chars.123' })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(capturedPanelProps?.versionHistoryPanelProps?.getVersionListUrl).toBe(
'/rag/pipelines/pipeline-with-special_chars.123/workflows',
)
})
})
})
// -------------------------------------------------------------------------
// Props Spreading Tests
// -------------------------------------------------------------------------
describe('Props Spreading', () => {
it('should correctly spread inputFieldEditPanelProps to editor component', async () => {
// Arrange
const customProps = {
onClose: vi.fn(),
onSubmit: vi.fn(),
initialData: {
variable: 'custom_var',
label: 'Custom Label',
type: 'text',
},
extraProp: 'extra-value',
}
setupMocks({ inputFieldEditPanelProps: customProps })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(mockInputFieldEditorProps).toHaveBeenCalledWith(
expect.objectContaining({
extraProp: 'extra-value',
}),
)
})
})
})
// -------------------------------------------------------------------------
// State Combinations
// -------------------------------------------------------------------------
describe('State Combinations', () => {
it('should handle all panels visible simultaneously', async () => {
// Arrange
setupMocks({
historyWorkflowData: { id: 'h1' },
showDebugAndPreviewPanel: true,
showGlobalVariablePanel: true,
showInputFieldPreviewPanel: true,
inputFieldEditPanelProps: { onClose: vi.fn(), onSubmit: vi.fn() },
showInputFieldPanel: true,
})
// Act
render(<RagPipelinePanel />)
// Assert - All panels should be visible
await waitFor(() => {
expect(screen.getByTestId('record-panel')).toBeInTheDocument()
expect(screen.getByTestId('test-run-panel')).toBeInTheDocument()
expect(screen.getByTestId('global-variable-panel')).toBeInTheDocument()
expect(screen.getByTestId('preview-panel')).toBeInTheDocument()
expect(screen.getByTestId('input-field-editor-panel')).toBeInTheDocument()
expect(screen.getByTestId('input-field-panel')).toBeInTheDocument()
})
})
})
})
// ============================================================================
// URL Generator Functions Tests
// ============================================================================
describe('URL Generator Functions', () => {
beforeEach(() => {
vi.clearAllMocks()
setupMocks()
})
it('should return consistent URLs for same versionId', async () => {
// Arrange
setupMocks({ pipelineId: 'stable-pipeline' })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
const deleteUrl1 = capturedPanelProps?.versionHistoryPanelProps?.deleteVersionUrl?.('version-x')
const deleteUrl2 = capturedPanelProps?.versionHistoryPanelProps?.deleteVersionUrl?.('version-x')
expect(deleteUrl1).toBe(deleteUrl2)
})
})
it('should return different URLs for different versionIds', async () => {
// Arrange
setupMocks({ pipelineId: 'stable-pipeline' })
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
const deleteUrl1 = capturedPanelProps?.versionHistoryPanelProps?.deleteVersionUrl?.('version-1')
const deleteUrl2 = capturedPanelProps?.versionHistoryPanelProps?.deleteVersionUrl?.('version-2')
expect(deleteUrl1).not.toBe(deleteUrl2)
expect(deleteUrl1).toBe('/rag/pipelines/stable-pipeline/workflows/version-1')
expect(deleteUrl2).toBe('/rag/pipelines/stable-pipeline/workflows/version-2')
})
})
})
// ============================================================================
// Type Safety Tests
// ============================================================================
describe('Type Safety', () => {
beforeEach(() => {
vi.clearAllMocks()
setupMocks()
})
it('should pass correct PanelProps structure', async () => {
// Act
render(<RagPipelinePanel />)
// Assert - Check structure matches PanelProps
await waitFor(() => {
expect(capturedPanelProps).toHaveProperty('components')
expect(capturedPanelProps).toHaveProperty('versionHistoryPanelProps')
expect(capturedPanelProps?.components).toHaveProperty('left')
expect(capturedPanelProps?.components).toHaveProperty('right')
})
})
it('should pass correct versionHistoryPanelProps structure', async () => {
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(capturedPanelProps?.versionHistoryPanelProps).toHaveProperty('getVersionListUrl')
expect(capturedPanelProps?.versionHistoryPanelProps).toHaveProperty('deleteVersionUrl')
expect(capturedPanelProps?.versionHistoryPanelProps).toHaveProperty('updateVersionUrl')
expect(capturedPanelProps?.versionHistoryPanelProps).toHaveProperty('latestVersionId')
})
})
})
// ============================================================================
// Performance Tests
// ============================================================================
describe('Performance', () => {
beforeEach(() => {
vi.clearAllMocks()
setupMocks()
})
it('should handle multiple rerenders without issues', async () => {
// Arrange
const { rerender } = render(<RagPipelinePanel />)
// Act - Multiple rerenders
for (let i = 0; i < 10; i++)
rerender(<RagPipelinePanel />)
// Assert - Component should still work
await waitFor(() => {
expect(screen.getByTestId('workflow-panel')).toBeInTheDocument()
})
})
})
// ============================================================================
// Integration Tests
// ============================================================================
describe('Integration Tests', () => {
beforeEach(() => {
vi.clearAllMocks()
setupMocks()
})
it('should pass correct components to Panel', async () => {
// Arrange
setupMocks({
historyWorkflowData: { id: 'h1' },
showInputFieldPanel: true,
})
// Act
render(<RagPipelinePanel />)
// Assert
await waitFor(() => {
expect(capturedPanelProps?.components?.left).toBeDefined()
expect(capturedPanelProps?.components?.right).toBeDefined()
// Check that the components are React elements
expect(React.isValidElement(capturedPanelProps?.components?.left)).toBe(true)
expect(React.isValidElement(capturedPanelProps?.components?.right)).toBe(true)
})
})
it('should correctly consume all store selectors', async () => {
// Arrange
setupMocks({
historyWorkflowData: { id: 'test-history' },
showDebugAndPreviewPanel: true,
showGlobalVariablePanel: true,
showInputFieldPanel: true,
showInputFieldPreviewPanel: true,
inputFieldEditPanelProps: { onClose: vi.fn(), onSubmit: vi.fn() },
pipelineId: 'integration-test-pipeline',
})
// Act
render(<RagPipelinePanel />)
// Assert - All store-dependent rendering should work
await waitFor(() => {
expect(screen.getByTestId('record-panel')).toBeInTheDocument()
expect(screen.getByTestId('test-run-panel')).toBeInTheDocument()
expect(screen.getByTestId('global-variable-panel')).toBeInTheDocument()
expect(screen.getByTestId('input-field-panel')).toBeInTheDocument()
expect(screen.getByTestId('preview-panel')).toBeInTheDocument()
expect(screen.getByTestId('input-field-editor-panel')).toBeInTheDocument()
expect(capturedPanelProps?.versionHistoryPanelProps?.getVersionListUrl).toBe(
'/rag/pipelines/integration-test-pipeline/workflows',
)
})
})
})

View File

@ -49,6 +49,7 @@ const InputFieldEditorPanel = ({
</div>
<button
type="button"
data-testid="input-field-editor-close-btn"
className="absolute right-2.5 top-2.5 flex size-8 items-center justify-center"
onClick={onClose}
>

View File

@ -53,6 +53,7 @@ const FieldList = ({
{LabelRightContent}
</div>
<ActionButton
data-testid="field-list-add-btn"
onClick={() => handleOpenInputFieldEditor()}
disabled={readonly}
className={cn(readonly && 'cursor-not-allowed')}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,937 @@
import type { WorkflowRunningData } from '@/app/components/workflow/types'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { ChunkingMode } from '@/models/datasets'
import Header from './header'
// Import components after mocks
import TestRunPanel from './index'
// ============================================================================
// Mocks
// ============================================================================
// Mock workflow store
const mockIsPreparingDataSource = vi.fn(() => true)
const mockSetIsPreparingDataSource = vi.fn()
const mockWorkflowRunningData = vi.fn<() => WorkflowRunningData | undefined>(() => undefined)
const mockPipelineId = 'test-pipeline-id'
vi.mock('@/app/components/workflow/store', () => ({
useStore: (selector: (state: Record<string, unknown>) => unknown) => {
const state = {
isPreparingDataSource: mockIsPreparingDataSource(),
workflowRunningData: mockWorkflowRunningData(),
pipelineId: mockPipelineId,
}
return selector(state)
},
useWorkflowStore: () => ({
getState: () => ({
isPreparingDataSource: mockIsPreparingDataSource(),
setIsPreparingDataSource: mockSetIsPreparingDataSource,
}),
}),
}))
// Mock workflow interactions
const mockHandleCancelDebugAndPreviewPanel = vi.fn()
vi.mock('@/app/components/workflow/hooks', () => ({
useWorkflowInteractions: () => ({
handleCancelDebugAndPreviewPanel: mockHandleCancelDebugAndPreviewPanel,
}),
useWorkflowRun: () => ({
handleRun: vi.fn(),
}),
useToolIcon: () => 'mock-tool-icon',
}))
// Mock data source provider
vi.mock('@/app/components/datasets/documents/create-from-pipeline/data-source/store/provider', () => ({
default: ({ children }: { children: React.ReactNode }) => <div data-testid="data-source-provider">{children}</div>,
}))
// Mock Preparation component
vi.mock('./preparation', () => ({
default: () => <div data-testid="preparation-component">Preparation</div>,
}))
// Mock Result component (for TestRunPanel tests only)
vi.mock('./result', () => ({
default: () => <div data-testid="result-component">Result</div>,
}))
// Mock ResultPanel from workflow
vi.mock('@/app/components/workflow/run/result-panel', () => ({
default: (props: Record<string, unknown>) => (
<div data-testid="result-panel">
ResultPanel -
{' '}
{props.status as string}
</div>
),
}))
// Mock TracingPanel from workflow
vi.mock('@/app/components/workflow/run/tracing-panel', () => ({
default: (props: { list: unknown[] }) => (
<div data-testid="tracing-panel">
TracingPanel -
{' '}
{props.list?.length ?? 0}
{' '}
items
</div>
),
}))
// Mock Loading component
vi.mock('@/app/components/base/loading', () => ({
default: () => <div data-testid="loading">Loading...</div>,
}))
// Mock config
vi.mock('@/config', () => ({
RAG_PIPELINE_PREVIEW_CHUNK_NUM: 5,
}))
// ============================================================================
// Test Data Factories
// ============================================================================
const createMockWorkflowRunningData = (overrides: Partial<WorkflowRunningData> = {}): WorkflowRunningData => ({
result: {
status: WorkflowRunningStatus.Succeeded,
outputs: '{"test": "output"}',
outputs_truncated: false,
inputs: '{"test": "input"}',
inputs_truncated: false,
process_data_truncated: false,
error: undefined,
elapsed_time: 1000,
total_tokens: 100,
created_at: Date.now(),
created_by: 'Test User',
total_steps: 5,
exceptions_count: 0,
},
tracing: [],
...overrides,
})
const createMockGeneralOutputs = (chunkContents: string[] = ['chunk1', 'chunk2']) => ({
chunk_structure: ChunkingMode.text,
preview: chunkContents.map(content => ({ content })),
})
const createMockParentChildOutputs = (parentMode: 'paragraph' | 'full-doc' = 'paragraph') => ({
chunk_structure: ChunkingMode.parentChild,
parent_mode: parentMode,
preview: [
{ content: 'parent1', child_chunks: ['child1', 'child2'] },
{ content: 'parent2', child_chunks: ['child3', 'child4'] },
],
})
const createMockQAOutputs = () => ({
chunk_structure: ChunkingMode.qa,
qa_preview: [
{ question: 'Q1', answer: 'A1' },
{ question: 'Q2', answer: 'A2' },
],
})
// ============================================================================
// TestRunPanel Component Tests
// ============================================================================
describe('TestRunPanel', () => {
beforeEach(() => {
vi.clearAllMocks()
mockIsPreparingDataSource.mockReturnValue(true)
mockWorkflowRunningData.mockReturnValue(undefined)
})
// Basic rendering tests
describe('Rendering', () => {
it('should render with correct container styles', () => {
const { container } = render(<TestRunPanel />)
const panelDiv = container.firstChild as HTMLElement
expect(panelDiv).toHaveClass('relative', 'flex', 'h-full', 'w-[480px]', 'flex-col')
})
it('should render Header component', () => {
render(<TestRunPanel />)
expect(screen.getByText('datasetPipeline.testRun.title')).toBeInTheDocument()
})
})
// Conditional rendering based on isPreparingDataSource
describe('Conditional Content Rendering', () => {
it('should render Preparation inside DataSourceProvider when isPreparingDataSource is true', () => {
mockIsPreparingDataSource.mockReturnValue(true)
render(<TestRunPanel />)
expect(screen.getByTestId('data-source-provider')).toBeInTheDocument()
expect(screen.getByTestId('preparation-component')).toBeInTheDocument()
expect(screen.queryByTestId('result-component')).not.toBeInTheDocument()
})
it('should render Result when isPreparingDataSource is false', () => {
mockIsPreparingDataSource.mockReturnValue(false)
render(<TestRunPanel />)
expect(screen.getByTestId('result-component')).toBeInTheDocument()
expect(screen.queryByTestId('data-source-provider')).not.toBeInTheDocument()
expect(screen.queryByTestId('preparation-component')).not.toBeInTheDocument()
})
})
})
// ============================================================================
// Header Component Tests
// ============================================================================
describe('Header', () => {
beforeEach(() => {
vi.clearAllMocks()
mockIsPreparingDataSource.mockReturnValue(true)
})
// Rendering tests
describe('Rendering', () => {
it('should render title with correct translation key', () => {
render(<Header />)
expect(screen.getByText('datasetPipeline.testRun.title')).toBeInTheDocument()
})
it('should render close button', () => {
render(<Header />)
const closeButton = screen.getByRole('button')
expect(closeButton).toBeInTheDocument()
})
it('should have correct layout classes', () => {
const { container } = render(<Header />)
const headerDiv = container.firstChild as HTMLElement
expect(headerDiv).toHaveClass('flex', 'items-center', 'gap-x-2', 'pl-4', 'pr-3', 'pt-4')
})
})
// Close button interactions
describe('Close Button Interaction', () => {
it('should call setIsPreparingDataSource(false) and handleCancelDebugAndPreviewPanel when clicked and isPreparingDataSource is true', () => {
mockIsPreparingDataSource.mockReturnValue(true)
render(<Header />)
const closeButton = screen.getByRole('button')
fireEvent.click(closeButton)
expect(mockSetIsPreparingDataSource).toHaveBeenCalledWith(false)
expect(mockHandleCancelDebugAndPreviewPanel).toHaveBeenCalledTimes(1)
})
it('should only call handleCancelDebugAndPreviewPanel when isPreparingDataSource is false', () => {
mockIsPreparingDataSource.mockReturnValue(false)
render(<Header />)
const closeButton = screen.getByRole('button')
fireEvent.click(closeButton)
expect(mockSetIsPreparingDataSource).not.toHaveBeenCalled()
expect(mockHandleCancelDebugAndPreviewPanel).toHaveBeenCalledTimes(1)
})
})
})
// ============================================================================
// Result Component Tests (Real Implementation)
// ============================================================================
// Unmock Result for these tests
vi.doUnmock('./result')
describe('Result', () => {
// Dynamically import Result to get real implementation
let Result: typeof import('./result').default
beforeAll(async () => {
const resultModule = await import('./result')
Result = resultModule.default
})
beforeEach(() => {
vi.clearAllMocks()
mockWorkflowRunningData.mockReturnValue(undefined)
})
// Rendering tests
describe('Rendering', () => {
it('should render with RESULT tab active by default', async () => {
render(<Result />)
await waitFor(() => {
const resultTab = screen.getByRole('button', { name: /runLog\.result/i })
expect(resultTab).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
})
})
it('should render all three tabs', () => {
render(<Result />)
expect(screen.getByRole('button', { name: /runLog\.result/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /runLog\.detail/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /runLog\.tracing/i })).toBeInTheDocument()
})
})
// Tab switching tests
describe('Tab Switching', () => {
it('should switch to DETAIL tab when clicked', async () => {
mockWorkflowRunningData.mockReturnValue(createMockWorkflowRunningData())
render(<Result />)
const detailTab = screen.getByRole('button', { name: /runLog\.detail/i })
fireEvent.click(detailTab)
await waitFor(() => {
expect(screen.getByTestId('result-panel')).toBeInTheDocument()
})
})
it('should switch to TRACING tab when clicked', async () => {
mockWorkflowRunningData.mockReturnValue(createMockWorkflowRunningData({ tracing: [{ id: '1' }] as unknown as WorkflowRunningData['tracing'] }))
render(<Result />)
const tracingTab = screen.getByRole('button', { name: /runLog\.tracing/i })
fireEvent.click(tracingTab)
await waitFor(() => {
expect(screen.getByTestId('tracing-panel')).toBeInTheDocument()
})
})
})
// Loading states
describe('Loading States', () => {
it('should show loading in DETAIL tab when no result data', async () => {
mockWorkflowRunningData.mockReturnValue({
result: undefined as unknown as WorkflowRunningData['result'],
tracing: [],
})
render(<Result />)
const detailTab = screen.getByRole('button', { name: /runLog\.detail/i })
fireEvent.click(detailTab)
await waitFor(() => {
expect(screen.getByTestId('loading')).toBeInTheDocument()
})
})
it('should show loading in TRACING tab when no tracing data', async () => {
mockWorkflowRunningData.mockReturnValue(createMockWorkflowRunningData({ tracing: [] }))
render(<Result />)
const tracingTab = screen.getByRole('button', { name: /runLog\.tracing/i })
fireEvent.click(tracingTab)
await waitFor(() => {
expect(screen.getByTestId('loading')).toBeInTheDocument()
})
})
})
})
// ============================================================================
// ResultPreview Component Tests
// ============================================================================
// We need to import ResultPreview directly
vi.doUnmock('./result/result-preview')
describe('ResultPreview', () => {
let ResultPreview: typeof import('./result/result-preview').default
beforeAll(async () => {
const previewModule = await import('./result/result-preview')
ResultPreview = previewModule.default
})
const mockOnSwitchToDetail = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
// Loading state
describe('Loading State', () => {
it('should show loading spinner when isRunning is true and no outputs', () => {
render(
<ResultPreview
isRunning={true}
outputs={undefined}
error={undefined}
onSwitchToDetail={mockOnSwitchToDetail}
/>,
)
expect(screen.getByText('pipeline.result.resultPreview.loading')).toBeInTheDocument()
})
it('should not show loading when outputs are available', () => {
render(
<ResultPreview
isRunning={true}
outputs={createMockGeneralOutputs()}
error={undefined}
onSwitchToDetail={mockOnSwitchToDetail}
/>,
)
expect(screen.queryByText('pipeline.result.resultPreview.loading')).not.toBeInTheDocument()
})
})
// Error state
describe('Error State', () => {
it('should show error message when not running and has error', () => {
render(
<ResultPreview
isRunning={false}
outputs={undefined}
error="Test error message"
onSwitchToDetail={mockOnSwitchToDetail}
/>,
)
expect(screen.getByText('pipeline.result.resultPreview.error')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'pipeline.result.resultPreview.viewDetails' })).toBeInTheDocument()
})
it('should call onSwitchToDetail when View Details button is clicked', () => {
render(
<ResultPreview
isRunning={false}
outputs={undefined}
error="Test error message"
onSwitchToDetail={mockOnSwitchToDetail}
/>,
)
const viewDetailsButton = screen.getByRole('button', { name: 'pipeline.result.resultPreview.viewDetails' })
fireEvent.click(viewDetailsButton)
expect(mockOnSwitchToDetail).toHaveBeenCalledTimes(1)
})
it('should not show error when still running', () => {
render(
<ResultPreview
isRunning={true}
outputs={undefined}
error="Test error message"
onSwitchToDetail={mockOnSwitchToDetail}
/>,
)
expect(screen.queryByText('pipeline.result.resultPreview.error')).not.toBeInTheDocument()
})
})
// Success state with outputs
describe('Success State with Outputs', () => {
it('should render chunk content when outputs are available', () => {
render(
<ResultPreview
isRunning={false}
outputs={createMockGeneralOutputs(['test chunk content'])}
error={undefined}
onSwitchToDetail={mockOnSwitchToDetail}
/>,
)
// Check that chunk content is rendered (the real ChunkCardList renders the content)
expect(screen.getByText('test chunk content')).toBeInTheDocument()
})
it('should render multiple chunks when provided', () => {
render(
<ResultPreview
isRunning={false}
outputs={createMockGeneralOutputs(['chunk one', 'chunk two'])}
error={undefined}
onSwitchToDetail={mockOnSwitchToDetail}
/>,
)
expect(screen.getByText('chunk one')).toBeInTheDocument()
expect(screen.getByText('chunk two')).toBeInTheDocument()
})
it('should show footer tip', () => {
render(
<ResultPreview
isRunning={false}
outputs={createMockGeneralOutputs()}
error={undefined}
onSwitchToDetail={mockOnSwitchToDetail}
/>,
)
expect(screen.getByText(/pipeline\.result\.resultPreview\.footerTip/)).toBeInTheDocument()
})
})
// Edge cases
describe('Edge Cases', () => {
it('should handle empty outputs gracefully', () => {
render(
<ResultPreview
isRunning={false}
outputs={null}
error={undefined}
onSwitchToDetail={mockOnSwitchToDetail}
/>,
)
// Should not crash and should not show chunk card list
expect(screen.queryByTestId('chunk-card-list')).not.toBeInTheDocument()
})
it('should handle undefined outputs', () => {
render(
<ResultPreview
isRunning={false}
outputs={undefined}
error={undefined}
onSwitchToDetail={mockOnSwitchToDetail}
/>,
)
expect(screen.queryByTestId('chunk-card-list')).not.toBeInTheDocument()
})
})
})
// ============================================================================
// Tabs Component Tests
// ============================================================================
vi.doUnmock('./result/tabs')
describe('Tabs', () => {
let Tabs: typeof import('./result/tabs').default
beforeAll(async () => {
const tabsModule = await import('./result/tabs')
Tabs = tabsModule.default
})
const mockSwitchTab = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
// Rendering tests
describe('Rendering', () => {
it('should render all three tabs', () => {
render(
<Tabs
currentTab="RESULT"
workflowRunningData={createMockWorkflowRunningData()}
switchTab={mockSwitchTab}
/>,
)
expect(screen.getByRole('button', { name: /runLog\.result/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /runLog\.detail/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /runLog\.tracing/i })).toBeInTheDocument()
})
})
// Active tab styling
describe('Active Tab Styling', () => {
it('should highlight RESULT tab when currentTab is RESULT', () => {
render(
<Tabs
currentTab="RESULT"
workflowRunningData={createMockWorkflowRunningData()}
switchTab={mockSwitchTab}
/>,
)
const resultTab = screen.getByRole('button', { name: /runLog\.result/i })
expect(resultTab).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
})
it('should highlight DETAIL tab when currentTab is DETAIL', () => {
render(
<Tabs
currentTab="DETAIL"
workflowRunningData={createMockWorkflowRunningData()}
switchTab={mockSwitchTab}
/>,
)
const detailTab = screen.getByRole('button', { name: /runLog\.detail/i })
expect(detailTab).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
})
})
// Tab click handling
describe('Tab Click Handling', () => {
it('should call switchTab with RESULT when RESULT tab is clicked', () => {
render(
<Tabs
currentTab="DETAIL"
workflowRunningData={createMockWorkflowRunningData()}
switchTab={mockSwitchTab}
/>,
)
fireEvent.click(screen.getByRole('button', { name: /runLog\.result/i }))
expect(mockSwitchTab).toHaveBeenCalledWith('RESULT')
})
it('should call switchTab with DETAIL when DETAIL tab is clicked', () => {
render(
<Tabs
currentTab="RESULT"
workflowRunningData={createMockWorkflowRunningData()}
switchTab={mockSwitchTab}
/>,
)
fireEvent.click(screen.getByRole('button', { name: /runLog\.detail/i }))
expect(mockSwitchTab).toHaveBeenCalledWith('DETAIL')
})
it('should call switchTab with TRACING when TRACING tab is clicked', () => {
render(
<Tabs
currentTab="RESULT"
workflowRunningData={createMockWorkflowRunningData()}
switchTab={mockSwitchTab}
/>,
)
fireEvent.click(screen.getByRole('button', { name: /runLog\.tracing/i }))
expect(mockSwitchTab).toHaveBeenCalledWith('TRACING')
})
})
// Disabled state when no data
describe('Disabled State', () => {
it('should disable tabs when workflowRunningData is undefined', () => {
render(
<Tabs
currentTab="RESULT"
workflowRunningData={undefined}
switchTab={mockSwitchTab}
/>,
)
const resultTab = screen.getByRole('button', { name: /runLog\.result/i })
expect(resultTab).toBeDisabled()
})
})
})
// ============================================================================
// Tab Component Tests
// ============================================================================
vi.doUnmock('./result/tabs/tab')
describe('Tab', () => {
let Tab: typeof import('./result/tabs/tab').default
beforeAll(async () => {
const tabModule = await import('./result/tabs/tab')
Tab = tabModule.default
})
const mockOnClick = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
// Rendering tests
describe('Rendering', () => {
it('should render tab with label', () => {
render(
<Tab
isActive={false}
label="Test Tab"
value="TEST"
workflowRunningData={createMockWorkflowRunningData()}
onClick={mockOnClick}
/>,
)
expect(screen.getByRole('button', { name: 'Test Tab' })).toBeInTheDocument()
})
})
// Active state styling
describe('Active State', () => {
it('should have active styles when isActive is true', () => {
render(
<Tab
isActive={true}
label="Active Tab"
value="TEST"
workflowRunningData={createMockWorkflowRunningData()}
onClick={mockOnClick}
/>,
)
const tab = screen.getByRole('button')
expect(tab).toHaveClass('border-util-colors-blue-brand-blue-brand-600', 'text-text-primary')
})
it('should have inactive styles when isActive is false', () => {
render(
<Tab
isActive={false}
label="Inactive Tab"
value="TEST"
workflowRunningData={createMockWorkflowRunningData()}
onClick={mockOnClick}
/>,
)
const tab = screen.getByRole('button')
expect(tab).toHaveClass('border-transparent', 'text-text-tertiary')
})
})
// Click handling
describe('Click Handling', () => {
it('should call onClick with value when clicked', () => {
render(
<Tab
isActive={false}
label="Test Tab"
value="MY_VALUE"
workflowRunningData={createMockWorkflowRunningData()}
onClick={mockOnClick}
/>,
)
fireEvent.click(screen.getByRole('button'))
expect(mockOnClick).toHaveBeenCalledWith('MY_VALUE')
})
it('should not call onClick when disabled (no workflowRunningData)', () => {
render(
<Tab
isActive={false}
label="Test Tab"
value="MY_VALUE"
workflowRunningData={undefined}
onClick={mockOnClick}
/>,
)
const tab = screen.getByRole('button')
fireEvent.click(tab)
// The click handler is still called, but button is disabled
expect(tab).toBeDisabled()
})
})
// Disabled state
describe('Disabled State', () => {
it('should be disabled when workflowRunningData is undefined', () => {
render(
<Tab
isActive={false}
label="Test Tab"
value="TEST"
workflowRunningData={undefined}
onClick={mockOnClick}
/>,
)
const tab = screen.getByRole('button')
expect(tab).toBeDisabled()
expect(tab).toHaveClass('opacity-30')
})
it('should not be disabled when workflowRunningData is provided', () => {
render(
<Tab
isActive={false}
label="Test Tab"
value="TEST"
workflowRunningData={createMockWorkflowRunningData()}
onClick={mockOnClick}
/>,
)
const tab = screen.getByRole('button')
expect(tab).not.toBeDisabled()
})
})
})
// ============================================================================
// formatPreviewChunks Utility Tests
// ============================================================================
describe('formatPreviewChunks', () => {
let formatPreviewChunks: typeof import('./result/result-preview/utils').formatPreviewChunks
beforeAll(async () => {
const utilsModule = await import('./result/result-preview/utils')
formatPreviewChunks = utilsModule.formatPreviewChunks
})
// Edge cases
describe('Edge Cases', () => {
it('should return undefined for null outputs', () => {
expect(formatPreviewChunks(null)).toBeUndefined()
})
it('should return undefined for undefined outputs', () => {
expect(formatPreviewChunks(undefined)).toBeUndefined()
})
it('should return undefined for unknown chunk structure', () => {
const outputs = {
chunk_structure: 'unknown_mode',
preview: [],
}
expect(formatPreviewChunks(outputs)).toBeUndefined()
})
})
// General (text) chunks
describe('General Chunks (ChunkingMode.text)', () => {
it('should format general chunks correctly', () => {
const outputs = createMockGeneralOutputs(['content1', 'content2', 'content3'])
const result = formatPreviewChunks(outputs)
expect(result).toEqual(['content1', 'content2', 'content3'])
})
it('should limit to RAG_PIPELINE_PREVIEW_CHUNK_NUM chunks', () => {
const manyChunks = Array.from({ length: 10 }, (_, i) => `chunk${i}`)
const outputs = createMockGeneralOutputs(manyChunks)
const result = formatPreviewChunks(outputs) as string[]
// RAG_PIPELINE_PREVIEW_CHUNK_NUM is mocked to 5
expect(result).toHaveLength(5)
expect(result).toEqual(['chunk0', 'chunk1', 'chunk2', 'chunk3', 'chunk4'])
})
it('should handle empty preview array', () => {
const outputs = createMockGeneralOutputs([])
const result = formatPreviewChunks(outputs)
expect(result).toEqual([])
})
})
// Parent-child chunks
describe('Parent-Child Chunks (ChunkingMode.parentChild)', () => {
it('should format paragraph mode parent-child chunks correctly', () => {
const outputs = createMockParentChildOutputs('paragraph')
const result = formatPreviewChunks(outputs)
expect(result).toEqual({
parent_child_chunks: [
{ parent_content: 'parent1', child_contents: ['child1', 'child2'], parent_mode: 'paragraph' },
{ parent_content: 'parent2', child_contents: ['child3', 'child4'], parent_mode: 'paragraph' },
],
parent_mode: 'paragraph',
})
})
it('should format full-doc mode parent-child chunks and limit child chunks', () => {
const outputs = {
chunk_structure: ChunkingMode.parentChild,
parent_mode: 'full-doc' as const,
preview: [
{
content: 'full-doc-parent',
child_chunks: Array.from({ length: 10 }, (_, i) => `child${i}`),
},
],
}
const result = formatPreviewChunks(outputs)
expect(result).toEqual({
parent_child_chunks: [
{
parent_content: 'full-doc-parent',
child_contents: ['child0', 'child1', 'child2', 'child3', 'child4'], // Limited to 5
parent_mode: 'full-doc',
},
],
parent_mode: 'full-doc',
})
})
})
// QA chunks
describe('QA Chunks (ChunkingMode.qa)', () => {
it('should format QA chunks correctly', () => {
const outputs = createMockQAOutputs()
const result = formatPreviewChunks(outputs)
expect(result).toEqual({
qa_chunks: [
{ question: 'Q1', answer: 'A1' },
{ question: 'Q2', answer: 'A2' },
],
})
})
it('should limit QA chunks to RAG_PIPELINE_PREVIEW_CHUNK_NUM', () => {
const outputs = {
chunk_structure: ChunkingMode.qa,
qa_preview: Array.from({ length: 10 }, (_, i) => ({
question: `Q${i}`,
answer: `A${i}`,
})),
}
const result = formatPreviewChunks(outputs) as { qa_chunks: Array<{ question: string, answer: string }> }
expect(result.qa_chunks).toHaveLength(5)
})
})
})
// ============================================================================
// Types Tests
// ============================================================================
describe('Types', () => {
describe('TestRunStep Enum', () => {
it('should have correct enum values', async () => {
const { TestRunStep } = await import('./types')
expect(TestRunStep.dataSource).toBe('dataSource')
expect(TestRunStep.documentProcessing).toBe('documentProcessing')
})
})
})

View File

@ -0,0 +1,549 @@
import { fireEvent, render, screen } from '@testing-library/react'
import Actions from './index'
// ============================================================================
// Actions Component Tests
// ============================================================================
describe('Actions', () => {
beforeEach(() => {
vi.clearAllMocks()
})
// -------------------------------------------------------------------------
// Rendering Tests
// -------------------------------------------------------------------------
describe('Rendering', () => {
it('should render without crashing', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions handleNextStep={handleNextStep} />)
// Assert
expect(screen.getByRole('button')).toBeInTheDocument()
})
it('should render button with translated text', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions handleNextStep={handleNextStep} />)
// Assert - Translation mock returns key with namespace prefix
expect(screen.getByText('datasetCreation.stepOne.button')).toBeInTheDocument()
})
it('should render with correct container structure', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
const { container } = render(<Actions handleNextStep={handleNextStep} />)
// Assert
const wrapper = container.firstChild as HTMLElement
expect(wrapper.className).toContain('flex')
expect(wrapper.className).toContain('justify-end')
expect(wrapper.className).toContain('p-4')
expect(wrapper.className).toContain('pt-2')
})
it('should render span with px-0.5 class around text', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
const { container } = render(<Actions handleNextStep={handleNextStep} />)
// Assert
const span = container.querySelector('span')
expect(span).toBeInTheDocument()
expect(span?.className).toContain('px-0.5')
})
})
// -------------------------------------------------------------------------
// Props Variations Tests
// -------------------------------------------------------------------------
describe('Props Variations', () => {
it('should pass disabled=true to button when disabled prop is true', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions disabled={true} handleNextStep={handleNextStep} />)
// Assert
expect(screen.getByRole('button')).toBeDisabled()
})
it('should pass disabled=false to button when disabled prop is false', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions disabled={false} handleNextStep={handleNextStep} />)
// Assert
expect(screen.getByRole('button')).not.toBeDisabled()
})
it('should not disable button when disabled prop is undefined', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions handleNextStep={handleNextStep} />)
// Assert
expect(screen.getByRole('button')).not.toBeDisabled()
})
it('should handle disabled switching from true to false', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
const { rerender } = render(
<Actions disabled={true} handleNextStep={handleNextStep} />,
)
// Assert - Initially disabled
expect(screen.getByRole('button')).toBeDisabled()
// Act - Rerender with disabled=false
rerender(<Actions disabled={false} handleNextStep={handleNextStep} />)
// Assert - Now enabled
expect(screen.getByRole('button')).not.toBeDisabled()
})
it('should handle disabled switching from false to true', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
const { rerender } = render(
<Actions disabled={false} handleNextStep={handleNextStep} />,
)
// Assert - Initially enabled
expect(screen.getByRole('button')).not.toBeDisabled()
// Act - Rerender with disabled=true
rerender(<Actions disabled={true} handleNextStep={handleNextStep} />)
// Assert - Now disabled
expect(screen.getByRole('button')).toBeDisabled()
})
it('should handle undefined disabled becoming true', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
const { rerender } = render(
<Actions handleNextStep={handleNextStep} />,
)
// Assert - Initially not disabled (undefined)
expect(screen.getByRole('button')).not.toBeDisabled()
// Act - Rerender with disabled=true
rerender(<Actions disabled={true} handleNextStep={handleNextStep} />)
// Assert - Now disabled
expect(screen.getByRole('button')).toBeDisabled()
})
})
// -------------------------------------------------------------------------
// User Interaction Tests
// -------------------------------------------------------------------------
describe('User Interactions', () => {
it('should call handleNextStep when button is clicked', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions handleNextStep={handleNextStep} />)
fireEvent.click(screen.getByRole('button'))
// Assert
expect(handleNextStep).toHaveBeenCalledTimes(1)
})
it('should call handleNextStep exactly once per click', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions handleNextStep={handleNextStep} />)
fireEvent.click(screen.getByRole('button'))
// Assert
expect(handleNextStep).toHaveBeenCalled()
expect(handleNextStep.mock.calls).toHaveLength(1)
})
it('should call handleNextStep multiple times on multiple clicks', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions handleNextStep={handleNextStep} />)
const button = screen.getByRole('button')
fireEvent.click(button)
fireEvent.click(button)
fireEvent.click(button)
// Assert
expect(handleNextStep).toHaveBeenCalledTimes(3)
})
it('should not call handleNextStep when button is disabled and clicked', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions disabled={true} handleNextStep={handleNextStep} />)
fireEvent.click(screen.getByRole('button'))
// Assert - Disabled button should not trigger onClick
expect(handleNextStep).not.toHaveBeenCalled()
})
it('should handle rapid clicks when not disabled', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions handleNextStep={handleNextStep} />)
const button = screen.getByRole('button')
// Simulate rapid clicks
for (let i = 0; i < 10; i++)
fireEvent.click(button)
// Assert
expect(handleNextStep).toHaveBeenCalledTimes(10)
})
})
// -------------------------------------------------------------------------
// Callback Stability Tests
// -------------------------------------------------------------------------
describe('Callback Stability', () => {
it('should use the new handleNextStep when prop changes', () => {
// Arrange
const handleNextStep1 = vi.fn()
const handleNextStep2 = vi.fn()
// Act
const { rerender } = render(
<Actions handleNextStep={handleNextStep1} />,
)
fireEvent.click(screen.getByRole('button'))
rerender(<Actions handleNextStep={handleNextStep2} />)
fireEvent.click(screen.getByRole('button'))
// Assert
expect(handleNextStep1).toHaveBeenCalledTimes(1)
expect(handleNextStep2).toHaveBeenCalledTimes(1)
})
it('should maintain functionality after rerender with same props', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
const { rerender } = render(
<Actions handleNextStep={handleNextStep} />,
)
fireEvent.click(screen.getByRole('button'))
rerender(<Actions handleNextStep={handleNextStep} />)
fireEvent.click(screen.getByRole('button'))
// Assert
expect(handleNextStep).toHaveBeenCalledTimes(2)
})
it('should work correctly when handleNextStep changes multiple times', () => {
// Arrange
const handleNextStep1 = vi.fn()
const handleNextStep2 = vi.fn()
const handleNextStep3 = vi.fn()
// Act
const { rerender } = render(
<Actions handleNextStep={handleNextStep1} />,
)
fireEvent.click(screen.getByRole('button'))
rerender(<Actions handleNextStep={handleNextStep2} />)
fireEvent.click(screen.getByRole('button'))
rerender(<Actions handleNextStep={handleNextStep3} />)
fireEvent.click(screen.getByRole('button'))
// Assert
expect(handleNextStep1).toHaveBeenCalledTimes(1)
expect(handleNextStep2).toHaveBeenCalledTimes(1)
expect(handleNextStep3).toHaveBeenCalledTimes(1)
})
})
// -------------------------------------------------------------------------
// Memoization Tests
// -------------------------------------------------------------------------
describe('Memoization', () => {
it('should be wrapped with React.memo', () => {
// Arrange
const handleNextStep = vi.fn()
// Act - Verify component is memoized by checking display name pattern
const { rerender } = render(
<Actions handleNextStep={handleNextStep} />,
)
// Rerender with same props should work without issues
rerender(<Actions handleNextStep={handleNextStep} />)
// Assert - Component should render correctly after rerender
expect(screen.getByRole('button')).toBeInTheDocument()
})
it('should not break when props remain the same across rerenders', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
const { rerender } = render(
<Actions disabled={false} handleNextStep={handleNextStep} />,
)
// Multiple rerenders with same props
for (let i = 0; i < 5; i++) {
rerender(<Actions disabled={false} handleNextStep={handleNextStep} />)
}
// Assert - Should still function correctly
fireEvent.click(screen.getByRole('button'))
expect(handleNextStep).toHaveBeenCalledTimes(1)
})
it('should update correctly when only disabled prop changes', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
const { rerender } = render(
<Actions disabled={false} handleNextStep={handleNextStep} />,
)
// Assert - Initially not disabled
expect(screen.getByRole('button')).not.toBeDisabled()
// Act - Change only disabled prop
rerender(<Actions disabled={true} handleNextStep={handleNextStep} />)
// Assert - Should reflect the new disabled state
expect(screen.getByRole('button')).toBeDisabled()
})
it('should update correctly when only handleNextStep prop changes', () => {
// Arrange
const handleNextStep1 = vi.fn()
const handleNextStep2 = vi.fn()
// Act
const { rerender } = render(
<Actions disabled={false} handleNextStep={handleNextStep1} />,
)
fireEvent.click(screen.getByRole('button'))
expect(handleNextStep1).toHaveBeenCalledTimes(1)
// Act - Change only handleNextStep prop
rerender(<Actions disabled={false} handleNextStep={handleNextStep2} />)
fireEvent.click(screen.getByRole('button'))
// Assert - New callback should be used
expect(handleNextStep1).toHaveBeenCalledTimes(1)
expect(handleNextStep2).toHaveBeenCalledTimes(1)
})
})
// -------------------------------------------------------------------------
// Edge Cases Tests
// -------------------------------------------------------------------------
describe('Edge Cases', () => {
it('should call handleNextStep even if it has side effects', () => {
// Arrange
let sideEffectValue = 0
const handleNextStep = vi.fn(() => {
sideEffectValue = 42
})
// Act
render(<Actions handleNextStep={handleNextStep} />)
fireEvent.click(screen.getByRole('button'))
// Assert
expect(handleNextStep).toHaveBeenCalledTimes(1)
expect(sideEffectValue).toBe(42)
})
it('should handle handleNextStep that returns a value', () => {
// Arrange
const handleNextStep = vi.fn(() => 'return value')
// Act
render(<Actions handleNextStep={handleNextStep} />)
fireEvent.click(screen.getByRole('button'))
// Assert
expect(handleNextStep).toHaveBeenCalledTimes(1)
expect(handleNextStep).toHaveReturnedWith('return value')
})
it('should handle handleNextStep that is async', async () => {
// Arrange
const handleNextStep = vi.fn().mockResolvedValue(undefined)
// Act
render(<Actions handleNextStep={handleNextStep} />)
fireEvent.click(screen.getByRole('button'))
// Assert
expect(handleNextStep).toHaveBeenCalledTimes(1)
})
it('should render correctly with both disabled=true and handleNextStep', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions disabled={true} handleNextStep={handleNextStep} />)
// Assert
const button = screen.getByRole('button')
expect(button).toBeDisabled()
})
it('should handle component unmount gracefully', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
const { unmount } = render(<Actions handleNextStep={handleNextStep} />)
// Assert - Unmount should not throw
expect(() => unmount()).not.toThrow()
})
it('should handle disabled as boolean-like falsy value', () => {
// Arrange
const handleNextStep = vi.fn()
// Act - Test with explicit false
render(<Actions disabled={false} handleNextStep={handleNextStep} />)
// Assert
expect(screen.getByRole('button')).not.toBeDisabled()
})
})
// -------------------------------------------------------------------------
// Accessibility Tests
// -------------------------------------------------------------------------
describe('Accessibility', () => {
it('should have button element that can receive focus', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions handleNextStep={handleNextStep} />)
const button = screen.getByRole('button')
// Assert - Button should be focusable (not disabled by default)
expect(button).not.toBeDisabled()
})
it('should indicate disabled state correctly', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
render(<Actions disabled={true} handleNextStep={handleNextStep} />)
// Assert
expect(screen.getByRole('button')).toHaveAttribute('disabled')
})
})
// -------------------------------------------------------------------------
// Integration Tests
// -------------------------------------------------------------------------
describe('Integration', () => {
it('should work in a typical workflow: enable -> click -> disable', () => {
// Arrange
const handleNextStep = vi.fn()
// Act - Start enabled
const { rerender } = render(
<Actions disabled={false} handleNextStep={handleNextStep} />,
)
// Assert - Can click when enabled
expect(screen.getByRole('button')).not.toBeDisabled()
fireEvent.click(screen.getByRole('button'))
expect(handleNextStep).toHaveBeenCalledTimes(1)
// Act - Disable after click (simulating loading state)
rerender(<Actions disabled={true} handleNextStep={handleNextStep} />)
// Assert - Cannot click when disabled
expect(screen.getByRole('button')).toBeDisabled()
fireEvent.click(screen.getByRole('button'))
expect(handleNextStep).toHaveBeenCalledTimes(1) // Still 1, not 2
// Act - Re-enable
rerender(<Actions disabled={false} handleNextStep={handleNextStep} />)
// Assert - Can click again
expect(screen.getByRole('button')).not.toBeDisabled()
fireEvent.click(screen.getByRole('button'))
expect(handleNextStep).toHaveBeenCalledTimes(2)
})
it('should maintain consistent rendering across multiple state changes', () => {
// Arrange
const handleNextStep = vi.fn()
// Act
const { rerender } = render(
<Actions disabled={false} handleNextStep={handleNextStep} />,
)
// Toggle disabled state multiple times
const states = [true, false, true, false, true]
states.forEach((disabled) => {
rerender(<Actions disabled={disabled} handleNextStep={handleNextStep} />)
if (disabled)
expect(screen.getByRole('button')).toBeDisabled()
else
expect(screen.getByRole('button')).not.toBeDisabled()
})
// Assert - Button should still render correctly
expect(screen.getByRole('button')).toBeInTheDocument()
expect(screen.getByText('datasetCreation.stepOne.button')).toBeInTheDocument()
})
})
})

File diff suppressed because it is too large Load Diff

View File

@ -85,7 +85,11 @@ const PublishAsKnowledgePipelineModal = ({
>
<div className="title-2xl-semi-bold relative flex items-center p-6 pb-3 pr-14 text-text-primary">
{t('common.publishAs', { ns: 'pipeline' })}
<div className="absolute right-5 top-5 flex h-8 w-8 cursor-pointer items-center justify-center" onClick={onCancel}>
<div
data-testid="publish-modal-close-btn"
className="absolute right-5 top-5 flex h-8 w-8 cursor-pointer items-center justify-center"
onClick={onCancel}
>
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ import { useDocLink } from '@/context/i18n'
import useDocumentTitle from '@/hooks/use-document-title'
import { fetchInitValidateStatus, fetchSetupStatus, login, setup } from '@/service/common'
import { cn } from '@/utils/classnames'
import { encryptPassword as encodePassword } from '@/utils/encryption'
import Loading from '../components/base/loading'
const accountFormSchema = z.object({
@ -68,7 +69,7 @@ const InstallForm = () => {
url: '/login',
body: {
email: data.email,
password: data.password,
password: encodePassword(data.password),
},
})

View File

@ -0,0 +1,158 @@
import type { MockedFunction } from 'vitest'
import type { SystemFeatures } from '@/types/feature'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import * as React from 'react'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useLocale } from '@/context/i18n'
import { useSendMail } from '@/service/use-common'
import { defaultSystemFeatures } from '@/types/feature'
import Form from './input-mail'
const mockSubmitMail = vi.fn()
const mockOnSuccess = vi.fn()
type SystemFeaturesOverrides = Partial<Omit<SystemFeatures, 'branding'>> & {
branding?: Partial<SystemFeatures['branding']>
}
const buildSystemFeatures = (overrides: SystemFeaturesOverrides = {}): SystemFeatures => ({
...defaultSystemFeatures,
...overrides,
branding: {
...defaultSystemFeatures.branding,
...overrides.branding,
},
})
vi.mock('next/link', () => ({
default: ({ children, href, className, target, rel }: { children: React.ReactNode, href: string, className?: string, target?: string, rel?: string }) => (
<a href={href} className={className} target={target} rel={rel}>
{children}
</a>
),
}))
vi.mock('@/context/global-public-context', () => ({
useGlobalPublicStore: vi.fn(),
}))
vi.mock('@/context/i18n', () => ({
useLocale: vi.fn(),
}))
vi.mock('@/service/use-common', () => ({
useSendMail: vi.fn(),
}))
type UseSendMailResult = ReturnType<typeof useSendMail>
const mockUseGlobalPublicStore = useGlobalPublicStore as unknown as MockedFunction<typeof useGlobalPublicStore>
const mockUseLocale = useLocale as unknown as MockedFunction<typeof useLocale>
const mockUseSendMail = useSendMail as unknown as MockedFunction<typeof useSendMail>
const renderForm = ({
brandingEnabled = false,
isPending = false,
}: {
brandingEnabled?: boolean
isPending?: boolean
} = {}) => {
mockUseGlobalPublicStore.mockReturnValue({
systemFeatures: buildSystemFeatures({
branding: { enabled: brandingEnabled },
}),
})
mockUseLocale.mockReturnValue('en-US')
mockUseSendMail.mockReturnValue({
mutateAsync: mockSubmitMail,
isPending,
} as unknown as UseSendMailResult)
return render(<Form onSuccess={mockOnSuccess} />)
}
describe('InputMail Form', () => {
beforeEach(() => {
vi.clearAllMocks()
mockSubmitMail.mockResolvedValue({ result: 'success', data: 'token' })
})
// Rendering baseline UI elements.
describe('Rendering', () => {
it('should render email input and submit button', () => {
renderForm()
expect(screen.getByLabelText('login.email')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'login.signup.verifyMail' })).toBeInTheDocument()
expect(screen.getByRole('link', { name: 'login.signup.signIn' })).toBeInTheDocument()
})
})
// Prop-driven branding content visibility.
describe('Props', () => {
it('should show terms links when branding is disabled', () => {
renderForm({ brandingEnabled: false })
expect(screen.getByRole('link', { name: 'login.tos' })).toBeInTheDocument()
expect(screen.getByRole('link', { name: 'login.pp' })).toBeInTheDocument()
})
it('should hide terms links when branding is enabled', () => {
renderForm({ brandingEnabled: true })
expect(screen.queryByRole('link', { name: 'login.tos' })).not.toBeInTheDocument()
expect(screen.queryByRole('link', { name: 'login.pp' })).not.toBeInTheDocument()
})
})
// Submission flow and mutation integration.
describe('User Interactions', () => {
it('should submit email and call onSuccess when mutation succeeds', async () => {
renderForm()
const input = screen.getByLabelText('login.email')
const button = screen.getByRole('button', { name: 'login.signup.verifyMail' })
fireEvent.change(input, { target: { value: 'test@example.com' } })
fireEvent.click(button)
expect(mockSubmitMail).toHaveBeenCalledWith({
email: 'test@example.com',
language: 'en-US',
})
await waitFor(() => {
expect(mockOnSuccess).toHaveBeenCalledWith('test@example.com', 'token')
})
})
})
// Validation and failure paths.
describe('Edge Cases', () => {
it('should block submission when email is invalid', () => {
const { container } = renderForm()
const form = container.querySelector('form')
const input = screen.getByLabelText('login.email')
fireEvent.change(input, { target: { value: 'invalid-email' } })
expect(form).not.toBeNull()
fireEvent.submit(form as HTMLFormElement)
expect(mockSubmitMail).not.toHaveBeenCalled()
expect(mockOnSuccess).not.toHaveBeenCalled()
})
it('should not call onSuccess when mutation does not succeed', async () => {
mockSubmitMail.mockResolvedValue({ result: 'failed', data: 'token' })
renderForm()
const input = screen.getByLabelText('login.email')
const button = screen.getByRole('button', { name: 'login.signup.verifyMail' })
fireEvent.change(input, { target: { value: 'test@example.com' } })
fireEvent.click(button)
await waitFor(() => {
expect(mockSubmitMail).toHaveBeenCalled()
})
expect(mockOnSuccess).not.toHaveBeenCalled()
})
})
})

View File

@ -1,6 +1,5 @@
'use client'
import type { MailSendResponse } from '@/service/use-common'
import { noop } from 'es-toolkit/function'
import Link from 'next/link'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -27,6 +26,9 @@ export default function Form({
const { mutateAsync: submitMail, isPending } = useSendMail()
const handleSubmit = useCallback(async () => {
if (isPending)
return
if (!email) {
Toast.notify({ type: 'error', message: t('error.emailEmpty', { ns: 'login' }) })
return
@ -41,10 +43,14 @@ export default function Form({
const res = await submitMail({ email, language: locale })
if ((res as MailSendResponse).result === 'success')
onSuccess(email, (res as MailSendResponse).data)
}, [email, locale, submitMail, t])
}, [email, locale, submitMail, t, isPending, onSuccess])
return (
<form onSubmit={noop}>
<form onSubmit={(e) => {
e.preventDefault()
handleSubmit()
}}
>
<div className="mb-3">
<label htmlFor="email" className="system-md-semibold my-2 text-text-secondary">
{t('email', { ns: 'login' })}
@ -65,7 +71,7 @@ export default function Form({
<Button
tabIndex={2}
variant="primary"
onClick={handleSubmit}
type="submit"
disabled={isPending || !email}
className="w-full"
>
@ -88,7 +94,7 @@ export default function Form({
<>
<div className="system-xs-regular mt-3 block w-full text-text-tertiary">
{t('tosDesc', { ns: 'login' })}
&nbsp;
&nbsp;
<Link
className="system-xs-medium text-text-secondary hover:underline"
target="_blank"
@ -97,7 +103,7 @@ export default function Form({
>
{t('tos', { ns: 'login' })}
</Link>
&nbsp;&&nbsp;
&nbsp;&&nbsp;
<Link
className="system-xs-medium text-text-secondary hover:underline"
target="_blank"

View File

@ -29,7 +29,6 @@ export type AppContextValue = {
langGeniusVersionInfo: LangGeniusVersionResponse
useSelector: typeof useSelector
isLoadingCurrentWorkspace: boolean
isValidatingCurrentWorkspace: boolean
}
const userProfilePlaceholder = {
@ -59,9 +58,6 @@ const initialWorkspaceInfo: ICurrentWorkspace = {
created_at: 0,
role: 'normal',
providers: [],
trial_credits: 200,
trial_credits_used: 0,
next_credit_reset_date: 0,
}
const AppContext = createContext<AppContextValue>({
@ -76,7 +72,6 @@ const AppContext = createContext<AppContextValue>({
langGeniusVersionInfo: initialLangGeniusVersionInfo,
useSelector,
isLoadingCurrentWorkspace: false,
isValidatingCurrentWorkspace: false,
})
export function useSelector<T>(selector: (value: AppContextValue) => T): T {
@ -91,7 +86,7 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
const queryClient = useQueryClient()
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
const { data: userProfileResp } = useUserProfile()
const { data: currentWorkspaceResp, isPending: isLoadingCurrentWorkspace, isFetching: isValidatingCurrentWorkspace } = useCurrentWorkspace()
const { data: currentWorkspaceResp, isPending: isLoadingCurrentWorkspace } = useCurrentWorkspace()
const langGeniusVersionQuery = useLangGeniusVersion(
userProfileResp?.meta.currentVersion,
!systemFeatures.branding.enabled,
@ -200,7 +195,6 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
isCurrentWorkspaceDatasetOperator,
mutateCurrentWorkspace,
isLoadingCurrentWorkspace,
isValidatingCurrentWorkspace,
}}
>
<div className="flex h-full flex-col overflow-y-auto">

View File

@ -96,7 +96,7 @@
"plansCommon.memberAfter": "Member",
"plansCommon.messageRequest.title": "{{count,number}} message credits",
"plansCommon.messageRequest.titlePerMonth": "{{count,number}} message credits/month",
"plansCommon.messageRequest.tooltip": "Message credits are provided to help you easily try out different models from OpenAI, Anthropic, Gemini, xAI, DeepSeek and Tongyi in Dify. Credits are consumed based on the model type. Once they're used up, you can switch to your own API key.",
"plansCommon.messageRequest.tooltip": "Message credits are provided to help you easily try out different OpenAI models in Dify. Credits are consumed based on the model type. Once theyre used up, you can switch to your own OpenAI API key.",
"plansCommon.modelProviders": "Support OpenAI/Anthropic/Llama2/Azure OpenAI/Hugging Face/Replicate",
"plansCommon.month": "month",
"plansCommon.mostPopular": "Popular",

View File

@ -339,16 +339,13 @@
"modelProvider.callTimes": "Call times",
"modelProvider.card.buyQuota": "Buy Quota",
"modelProvider.card.callTimes": "Call times",
"modelProvider.card.modelAPI": "{{modelName}} models are using the API Key.",
"modelProvider.card.modelNotSupported": "{{modelName}} models are not installed.",
"modelProvider.card.modelSupported": "{{modelName}} models are using this quota.",
"modelProvider.card.onTrial": "On Trial",
"modelProvider.card.paid": "Paid",
"modelProvider.card.priorityUse": "Priority use",
"modelProvider.card.quota": "QUOTA",
"modelProvider.card.quotaExhausted": "Quota exhausted",
"modelProvider.card.removeKey": "Remove API Key",
"modelProvider.card.tip": "Message Credits supports models from OpenAI, Anthropic, Gemini, xAI, DeepSeek and Tongyi. Priority will be given to the paid quota. The free quota will be used after the paid quota is exhausted.",
"modelProvider.card.tip": "Priority will be given to the paid quota. The Trial quota will be used after the paid quota is exhausted.",
"modelProvider.card.tokens": "Tokens",
"modelProvider.collapse": "Collapse",
"modelProvider.config": "Config",
@ -397,7 +394,6 @@
"modelProvider.quotaTip": "Remaining available free tokens",
"modelProvider.rerankModel.key": "Rerank Model",
"modelProvider.rerankModel.tip": "Rerank model will reorder the candidate document list based on the semantic match with user query, improving the results of semantic ranking",
"modelProvider.resetDate": "Reset on {{date}}",
"modelProvider.searchModel": "Search model",
"modelProvider.selectModel": "Select your model",
"modelProvider.selector.emptySetting": "Please go to settings to configure",

View File

@ -96,7 +96,7 @@
"plansCommon.memberAfter": "メンバー",
"plansCommon.messageRequest.title": "{{count,number}}メッセージクレジット",
"plansCommon.messageRequest.titlePerMonth": "{{count,number}}メッセージクレジット/月",
"plansCommon.messageRequest.tooltip": "メッセージクレジットは、DifyでOpenAI、Anthropic、Gemini、xAI、DeepSeek、Tongyiなどのさまざまなモデルを簡単に試すために提供されています。クレジットはモデルの種類に基づいて消費されます。使い切ったら、独自のAPIキーに切り替えることができます。",
"plansCommon.messageRequest.tooltip": "メッセージクレジットは、Dify でさまざまな OpenAI モデルを簡単にお試しいただくためのものです。モデルタイプに応じてクレジットが消費され、使い切った後はご自身の OpenAI API キーに切り替えていただけます。",
"plansCommon.modelProviders": "OpenAI/Anthropic/Llama2/Azure OpenAI/Hugging Face/Replicateをサポート",
"plansCommon.month": "月",
"plansCommon.mostPopular": "人気",

View File

@ -339,16 +339,13 @@
"modelProvider.callTimes": "呼び出し回数",
"modelProvider.card.buyQuota": "クォータを購入",
"modelProvider.card.callTimes": "通話回数",
"modelProvider.card.modelAPI": "{{modelName}} は現在 APIキーを使用しています。",
"modelProvider.card.modelNotSupported": "{{modelName}} 未インストール。",
"modelProvider.card.modelSupported": "このクォータは現在{{modelName}}に使用されています。",
"modelProvider.card.onTrial": "トライアル中",
"modelProvider.card.paid": "有料",
"modelProvider.card.priorityUse": "優先利用",
"modelProvider.card.quota": "クォータ",
"modelProvider.card.quotaExhausted": "クォータが使い果たされました",
"modelProvider.card.removeKey": "API キーを削除",
"modelProvider.card.tip": "メッセージ枠はOpenAI、Anthropic、Gemini、xAI、DeepSeek、Tongyiのモデルを使用することをサポートしています。無料枠は有料枠が使い果たされた後に消費されます。",
"modelProvider.card.tip": "有料クォータは優先して使用されます。有料クォータを使用し終えた後、トライアルクォータが利用されます。",
"modelProvider.card.tokens": "トークン",
"modelProvider.collapse": "折り畳み",
"modelProvider.config": "設定",
@ -397,7 +394,6 @@
"modelProvider.quotaTip": "残りの無料トークン",
"modelProvider.rerankModel.key": "Rerank モデル",
"modelProvider.rerankModel.tip": "Rerank モデルは、ユーザークエリとの意味的一致に基づいて候補文書リストを再配置し、意味的ランキングの結果を向上させます。",
"modelProvider.resetDate": "{{date}} にリセット",
"modelProvider.searchModel": "検索モデル",
"modelProvider.selectModel": "モデルを選択",
"modelProvider.selector.emptySetting": "設定に移動して構成してください",

View File

@ -96,7 +96,7 @@
"plansCommon.memberAfter": "个成员",
"plansCommon.messageRequest.title": "{{count,number}} 条消息额度",
"plansCommon.messageRequest.titlePerMonth": "{{count,number}} 条消息额度/月",
"plansCommon.messageRequest.tooltip": "消息额度旨在帮助您便捷地试用 Dify 中来自 OpenAI、Anthropic、Gemini、xAI、深度求索、通义 的不同模型。不同模型会消耗不同额度。额度用尽后,您可以切换为使用自己的 API 密钥。",
"plansCommon.messageRequest.tooltip": "消息额度旨在帮助您便捷地试用 Dify 中的各类 OpenAI 模型。不同模型会消耗不同额度。额度用尽后,您可以切换为使用自己的 OpenAI API 密钥。",
"plansCommon.modelProviders": "支持 OpenAI/Anthropic/Llama2/Azure OpenAI/Hugging Face/Replicate",
"plansCommon.month": "月",
"plansCommon.mostPopular": "最受欢迎",

View File

@ -339,16 +339,13 @@
"modelProvider.callTimes": "调用次数",
"modelProvider.card.buyQuota": "购买额度",
"modelProvider.card.callTimes": "调用次数",
"modelProvider.card.modelAPI": "{{modelName}} 模型正在使用 API Key。",
"modelProvider.card.modelNotSupported": "{{modelName}} 模型未安装。",
"modelProvider.card.modelSupported": "{{modelName}} 模型正在使用此额度。",
"modelProvider.card.onTrial": "试用中",
"modelProvider.card.paid": "已购买",
"modelProvider.card.priorityUse": "优先使用",
"modelProvider.card.quota": "额度",
"modelProvider.card.quotaExhausted": "配额已用完",
"modelProvider.card.removeKey": "删除 API 密钥",
"modelProvider.card.tip": "消息额度支持使用 OpenAI、Anthropic、Gemini、xAI、深度求索、通义 的模型;免费额度会在付费额度用尽后才会消耗。",
"modelProvider.card.tip": "已付费额度将优先考虑。试用额度将在付费额度用完后使用。",
"modelProvider.card.tokens": "Tokens",
"modelProvider.collapse": "收起",
"modelProvider.config": "配置",
@ -397,7 +394,6 @@
"modelProvider.quotaTip": "剩余免费额度",
"modelProvider.rerankModel.key": "Rerank 模型",
"modelProvider.rerankModel.tip": "重排序模型将根据候选文档列表与用户问题语义匹配度进行重新排序,从而改进语义排序的结果",
"modelProvider.resetDate": "于 {{date}} 重置",
"modelProvider.searchModel": "搜索模型",
"modelProvider.selectModel": "选择您的模型",
"modelProvider.selector.emptySetting": "请前往设置进行配置",

View File

@ -142,9 +142,6 @@ export type IWorkspace = {
export type ICurrentWorkspace = Omit<IWorkspace, 'current'> & {
role: 'owner' | 'admin' | 'editor' | 'dataset_operator' | 'normal'
providers: Provider[]
trial_credits: number
trial_credits_used: number
next_credit_reset_date: number
trial_end_reason?: string
custom_config?: {
remove_webapp_brand?: boolean