mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:28:10 +08:00
Merge remote-tracking branch 'origin/main' into feat/quota-icon
This commit is contained in:
@ -0,0 +1,27 @@
|
|||||||
|
# Notes: `large_language_model.py`
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Provides the base `LargeLanguageModel` implementation used by the model runtime to invoke plugin-backed LLMs and to
|
||||||
|
bridge plugin daemon streaming semantics back into API-layer entities (`LLMResult`, `LLMResultChunk`).
|
||||||
|
|
||||||
|
## Key behaviors / invariants
|
||||||
|
|
||||||
|
- `invoke(..., stream=False)` still calls the plugin in streaming mode and then synthesizes a single `LLMResult` from
|
||||||
|
the first yielded `LLMResultChunk`.
|
||||||
|
- Plugin invocation is wrapped by `_invoke_llm_via_plugin(...)`, and `stream=False` normalization is handled by
|
||||||
|
`_normalize_non_stream_plugin_result(...)` / `_build_llm_result_from_first_chunk(...)`.
|
||||||
|
- Tool call deltas are merged incrementally via `_increase_tool_call(...)` to support multiple provider chunking
|
||||||
|
patterns (IDs anchored to first chunk, every chunk, or missing entirely).
|
||||||
|
- A tool-call delta with an empty `id` requires at least one existing tool call; otherwise we raise `ValueError` to
|
||||||
|
surface invalid delta sequences explicitly.
|
||||||
|
- Callback invocation is centralized in `_run_callbacks(...)` to ensure consistent error handling/logging.
|
||||||
|
- For compatibility with dify issue `#17799`, `prompt_messages` may be removed by the plugin daemon in chunks and must
|
||||||
|
be re-attached in this layer before callbacks/consumers use them.
|
||||||
|
- Callback hooks (`on_before_invoke`, `on_new_chunk`, `on_after_invoke`, `on_invoke_error`) must not break invocation
|
||||||
|
unless `callback.raise_error` is true.
|
||||||
|
|
||||||
|
## Test focus
|
||||||
|
|
||||||
|
- `api/tests/unit_tests/core/model_runtime/__base/test_increase_tool_call.py` validates tool-call delta merging and
|
||||||
|
patches `_gen_tool_call_id` for deterministic IDs.
|
||||||
@ -1,20 +1,19 @@
|
|||||||
|
from typing import Literal
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Resource, fields
|
|
||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
from controllers.fastopenapi import console_router
|
||||||
from libs.helper import EmailStr, extract_remote_ip
|
from libs.helper import EmailStr, extract_remote_ip
|
||||||
from libs.password import valid_password
|
from libs.password import valid_password
|
||||||
from models.model import DifySetup, db
|
from models.model import DifySetup, db
|
||||||
from services.account_service import RegisterService, TenantService
|
from services.account_service import RegisterService, TenantService
|
||||||
|
|
||||||
from . import console_ns
|
|
||||||
from .error import AlreadySetupError, NotInitValidateError
|
from .error import AlreadySetupError, NotInitValidateError
|
||||||
from .init_validate import get_init_validate_status
|
from .init_validate import get_init_validate_status
|
||||||
from .wraps import only_edition_self_hosted
|
from .wraps import only_edition_self_hosted
|
||||||
|
|
||||||
DEFAULT_REF_TEMPLATE_SWAGGER_2_0 = "#/definitions/{model}"
|
|
||||||
|
|
||||||
|
|
||||||
class SetupRequestPayload(BaseModel):
|
class SetupRequestPayload(BaseModel):
|
||||||
email: EmailStr = Field(..., description="Admin email address")
|
email: EmailStr = Field(..., description="Admin email address")
|
||||||
@ -28,78 +27,66 @@ class SetupRequestPayload(BaseModel):
|
|||||||
return valid_password(value)
|
return valid_password(value)
|
||||||
|
|
||||||
|
|
||||||
console_ns.schema_model(
|
class SetupStatusResponse(BaseModel):
|
||||||
SetupRequestPayload.__name__,
|
step: Literal["not_started", "finished"] = Field(description="Setup step status")
|
||||||
SetupRequestPayload.model_json_schema(ref_template=DEFAULT_REF_TEMPLATE_SWAGGER_2_0),
|
setup_at: str | None = Field(default=None, description="Setup completion time (ISO format)")
|
||||||
|
|
||||||
|
|
||||||
|
class SetupResponse(BaseModel):
|
||||||
|
result: str = Field(description="Setup result", examples=["success"])
|
||||||
|
|
||||||
|
|
||||||
|
@console_router.get(
|
||||||
|
"/setup",
|
||||||
|
response_model=SetupStatusResponse,
|
||||||
|
tags=["console"],
|
||||||
)
|
)
|
||||||
|
def get_setup_status_api() -> SetupStatusResponse:
|
||||||
|
"""Get system setup status."""
|
||||||
|
if dify_config.EDITION == "SELF_HOSTED":
|
||||||
|
setup_status = get_setup_status()
|
||||||
|
if setup_status and not isinstance(setup_status, bool):
|
||||||
|
return SetupStatusResponse(step="finished", setup_at=setup_status.setup_at.isoformat())
|
||||||
|
if setup_status:
|
||||||
|
return SetupStatusResponse(step="finished")
|
||||||
|
return SetupStatusResponse(step="not_started")
|
||||||
|
return SetupStatusResponse(step="finished")
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/setup")
|
@console_router.post(
|
||||||
class SetupApi(Resource):
|
"/setup",
|
||||||
@console_ns.doc("get_setup_status")
|
response_model=SetupResponse,
|
||||||
@console_ns.doc(description="Get system setup status")
|
tags=["console"],
|
||||||
@console_ns.response(
|
status_code=201,
|
||||||
200,
|
)
|
||||||
"Success",
|
@only_edition_self_hosted
|
||||||
console_ns.model(
|
def setup_system(payload: SetupRequestPayload) -> SetupResponse:
|
||||||
"SetupStatusResponse",
|
"""Initialize system setup with admin account."""
|
||||||
{
|
if get_setup_status():
|
||||||
"step": fields.String(description="Setup step status", enum=["not_started", "finished"]),
|
raise AlreadySetupError()
|
||||||
"setup_at": fields.String(description="Setup completion time (ISO format)", required=False),
|
|
||||||
},
|
tenant_count = TenantService.get_tenant_count()
|
||||||
),
|
if tenant_count > 0:
|
||||||
|
raise AlreadySetupError()
|
||||||
|
|
||||||
|
if not get_init_validate_status():
|
||||||
|
raise NotInitValidateError()
|
||||||
|
|
||||||
|
normalized_email = payload.email.lower()
|
||||||
|
|
||||||
|
RegisterService.setup(
|
||||||
|
email=normalized_email,
|
||||||
|
name=payload.name,
|
||||||
|
password=payload.password,
|
||||||
|
ip_address=extract_remote_ip(request),
|
||||||
|
language=payload.language,
|
||||||
)
|
)
|
||||||
def get(self):
|
|
||||||
"""Get system setup status"""
|
|
||||||
if dify_config.EDITION == "SELF_HOSTED":
|
|
||||||
setup_status = get_setup_status()
|
|
||||||
# Check if setup_status is a DifySetup object rather than a bool
|
|
||||||
if setup_status and not isinstance(setup_status, bool):
|
|
||||||
return {"step": "finished", "setup_at": setup_status.setup_at.isoformat()}
|
|
||||||
elif setup_status:
|
|
||||||
return {"step": "finished"}
|
|
||||||
return {"step": "not_started"}
|
|
||||||
return {"step": "finished"}
|
|
||||||
|
|
||||||
@console_ns.doc("setup_system")
|
return SetupResponse(result="success")
|
||||||
@console_ns.doc(description="Initialize system setup with admin account")
|
|
||||||
@console_ns.expect(console_ns.models[SetupRequestPayload.__name__])
|
|
||||||
@console_ns.response(
|
|
||||||
201, "Success", console_ns.model("SetupResponse", {"result": fields.String(description="Setup result")})
|
|
||||||
)
|
|
||||||
@console_ns.response(400, "Already setup or validation failed")
|
|
||||||
@only_edition_self_hosted
|
|
||||||
def post(self):
|
|
||||||
"""Initialize system setup with admin account"""
|
|
||||||
# is set up
|
|
||||||
if get_setup_status():
|
|
||||||
raise AlreadySetupError()
|
|
||||||
|
|
||||||
# is tenant created
|
|
||||||
tenant_count = TenantService.get_tenant_count()
|
|
||||||
if tenant_count > 0:
|
|
||||||
raise AlreadySetupError()
|
|
||||||
|
|
||||||
if not get_init_validate_status():
|
|
||||||
raise NotInitValidateError()
|
|
||||||
|
|
||||||
args = SetupRequestPayload.model_validate(console_ns.payload)
|
|
||||||
normalized_email = args.email.lower()
|
|
||||||
|
|
||||||
# setup
|
|
||||||
RegisterService.setup(
|
|
||||||
email=normalized_email,
|
|
||||||
name=args.name,
|
|
||||||
password=args.password,
|
|
||||||
ip_address=extract_remote_ip(request),
|
|
||||||
language=args.language,
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"result": "success"}, 201
|
|
||||||
|
|
||||||
|
|
||||||
def get_setup_status():
|
def get_setup_status() -> DifySetup | bool | None:
|
||||||
if dify_config.EDITION == "SELF_HOSTED":
|
if dify_config.EDITION == "SELF_HOSTED":
|
||||||
return db.session.query(DifySetup).first()
|
return db.session.query(DifySetup).first()
|
||||||
else:
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from flask import request
|
|
||||||
from flask_restx import Resource, fields
|
|
||||||
from packaging import version
|
from packaging import version
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
from controllers.fastopenapi import console_router
|
||||||
from . import console_ns
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -18,69 +14,61 @@ class VersionQuery(BaseModel):
|
|||||||
current_version: str = Field(..., description="Current application version")
|
current_version: str = Field(..., description="Current application version")
|
||||||
|
|
||||||
|
|
||||||
console_ns.schema_model(
|
class VersionFeatures(BaseModel):
|
||||||
VersionQuery.__name__,
|
can_replace_logo: bool = Field(description="Whether logo replacement is supported")
|
||||||
VersionQuery.model_json_schema(ref_template="#/definitions/{model}"),
|
model_load_balancing_enabled: bool = Field(description="Whether model load balancing is enabled")
|
||||||
|
|
||||||
|
|
||||||
|
class VersionResponse(BaseModel):
|
||||||
|
version: str = Field(description="Latest version number")
|
||||||
|
release_date: str = Field(description="Release date of latest version")
|
||||||
|
release_notes: str = Field(description="Release notes for latest version")
|
||||||
|
can_auto_update: bool = Field(description="Whether auto-update is supported")
|
||||||
|
features: VersionFeatures = Field(description="Feature flags and capabilities")
|
||||||
|
|
||||||
|
|
||||||
|
@console_router.get(
|
||||||
|
"/version",
|
||||||
|
response_model=VersionResponse,
|
||||||
|
tags=["console"],
|
||||||
)
|
)
|
||||||
|
def check_version_update(query: VersionQuery) -> VersionResponse:
|
||||||
|
"""Check for application version updates."""
|
||||||
|
check_update_url = dify_config.CHECK_UPDATE_URL
|
||||||
|
|
||||||
|
result = VersionResponse(
|
||||||
@console_ns.route("/version")
|
version=dify_config.project.version,
|
||||||
class VersionApi(Resource):
|
release_date="",
|
||||||
@console_ns.doc("check_version_update")
|
release_notes="",
|
||||||
@console_ns.doc(description="Check for application version updates")
|
can_auto_update=False,
|
||||||
@console_ns.expect(console_ns.models[VersionQuery.__name__])
|
features=VersionFeatures(
|
||||||
@console_ns.response(
|
can_replace_logo=dify_config.CAN_REPLACE_LOGO,
|
||||||
200,
|
model_load_balancing_enabled=dify_config.MODEL_LB_ENABLED,
|
||||||
"Success",
|
|
||||||
console_ns.model(
|
|
||||||
"VersionResponse",
|
|
||||||
{
|
|
||||||
"version": fields.String(description="Latest version number"),
|
|
||||||
"release_date": fields.String(description="Release date of latest version"),
|
|
||||||
"release_notes": fields.String(description="Release notes for latest version"),
|
|
||||||
"can_auto_update": fields.Boolean(description="Whether auto-update is supported"),
|
|
||||||
"features": fields.Raw(description="Feature flags and capabilities"),
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def get(self):
|
|
||||||
"""Check for application version updates"""
|
|
||||||
args = VersionQuery.model_validate(request.args.to_dict(flat=True)) # type: ignore
|
|
||||||
check_update_url = dify_config.CHECK_UPDATE_URL
|
|
||||||
|
|
||||||
result = {
|
if not check_update_url:
|
||||||
"version": dify_config.project.version,
|
|
||||||
"release_date": "",
|
|
||||||
"release_notes": "",
|
|
||||||
"can_auto_update": False,
|
|
||||||
"features": {
|
|
||||||
"can_replace_logo": dify_config.CAN_REPLACE_LOGO,
|
|
||||||
"model_load_balancing_enabled": dify_config.MODEL_LB_ENABLED,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if not check_update_url:
|
|
||||||
return result
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = httpx.get(
|
|
||||||
check_update_url,
|
|
||||||
params={"current_version": args.current_version},
|
|
||||||
timeout=httpx.Timeout(timeout=10.0, connect=3.0),
|
|
||||||
)
|
|
||||||
except Exception as error:
|
|
||||||
logger.warning("Check update version error: %s.", str(error))
|
|
||||||
result["version"] = args.current_version
|
|
||||||
return result
|
|
||||||
|
|
||||||
content = json.loads(response.content)
|
|
||||||
if _has_new_version(latest_version=content["version"], current_version=f"{args.current_version}"):
|
|
||||||
result["version"] = content["version"]
|
|
||||||
result["release_date"] = content["releaseDate"]
|
|
||||||
result["release_notes"] = content["releaseNotes"]
|
|
||||||
result["can_auto_update"] = content["canAutoUpdate"]
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = httpx.get(
|
||||||
|
check_update_url,
|
||||||
|
params={"current_version": query.current_version},
|
||||||
|
timeout=httpx.Timeout(timeout=10.0, connect=3.0),
|
||||||
|
)
|
||||||
|
content = response.json()
|
||||||
|
except Exception as error:
|
||||||
|
logger.warning("Check update version error: %s.", str(error))
|
||||||
|
result.version = query.current_version
|
||||||
|
return result
|
||||||
|
latest_version = content.get("version", result.version)
|
||||||
|
if _has_new_version(latest_version=latest_version, current_version=f"{query.current_version}"):
|
||||||
|
result.version = latest_version
|
||||||
|
result.release_date = content.get("releaseDate", "")
|
||||||
|
result.release_notes = content.get("releaseNotes", "")
|
||||||
|
result.can_auto_update = content.get("canAutoUpdate", False)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _has_new_version(*, latest_version: str, current_version: str) -> bool:
|
def _has_new_version(*, latest_version: str, current_version: str) -> bool:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Generator, Sequence
|
from collections.abc import Callable, Generator, Iterator, Sequence
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from pydantic import ConfigDict
|
from pydantic import ConfigDict
|
||||||
@ -30,6 +30,142 @@ def _gen_tool_call_id() -> str:
|
|||||||
return f"chatcmpl-tool-{str(uuid.uuid4().hex)}"
|
return f"chatcmpl-tool-{str(uuid.uuid4().hex)}"
|
||||||
|
|
||||||
|
|
||||||
|
def _run_callbacks(callbacks: Sequence[Callback] | None, *, event: str, invoke: Callable[[Callback], None]) -> None:
|
||||||
|
if not callbacks:
|
||||||
|
return
|
||||||
|
|
||||||
|
for callback in callbacks:
|
||||||
|
try:
|
||||||
|
invoke(callback)
|
||||||
|
except Exception as e:
|
||||||
|
if callback.raise_error:
|
||||||
|
raise
|
||||||
|
logger.warning("Callback %s %s failed with error %s", callback.__class__.__name__, event, e)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_or_create_tool_call(
|
||||||
|
existing_tools_calls: list[AssistantPromptMessage.ToolCall],
|
||||||
|
tool_call_id: str,
|
||||||
|
) -> AssistantPromptMessage.ToolCall:
|
||||||
|
"""
|
||||||
|
Get or create a tool call by ID.
|
||||||
|
|
||||||
|
If `tool_call_id` is empty, returns the most recently created tool call.
|
||||||
|
"""
|
||||||
|
if not tool_call_id:
|
||||||
|
if not existing_tools_calls:
|
||||||
|
raise ValueError("tool_call_id is empty but no existing tool call is available to apply the delta")
|
||||||
|
return existing_tools_calls[-1]
|
||||||
|
|
||||||
|
tool_call = next((tool_call for tool_call in existing_tools_calls if tool_call.id == tool_call_id), None)
|
||||||
|
if tool_call is None:
|
||||||
|
tool_call = AssistantPromptMessage.ToolCall(
|
||||||
|
id=tool_call_id,
|
||||||
|
type="function",
|
||||||
|
function=AssistantPromptMessage.ToolCall.ToolCallFunction(name="", arguments=""),
|
||||||
|
)
|
||||||
|
existing_tools_calls.append(tool_call)
|
||||||
|
|
||||||
|
return tool_call
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_tool_call_delta(
|
||||||
|
tool_call: AssistantPromptMessage.ToolCall,
|
||||||
|
delta: AssistantPromptMessage.ToolCall,
|
||||||
|
) -> None:
|
||||||
|
if delta.id:
|
||||||
|
tool_call.id = delta.id
|
||||||
|
if delta.type:
|
||||||
|
tool_call.type = delta.type
|
||||||
|
if delta.function.name:
|
||||||
|
tool_call.function.name = delta.function.name
|
||||||
|
if delta.function.arguments:
|
||||||
|
tool_call.function.arguments += delta.function.arguments
|
||||||
|
|
||||||
|
|
||||||
|
def _build_llm_result_from_first_chunk(
|
||||||
|
model: str,
|
||||||
|
prompt_messages: Sequence[PromptMessage],
|
||||||
|
chunks: Iterator[LLMResultChunk],
|
||||||
|
) -> LLMResult:
|
||||||
|
"""
|
||||||
|
Build a single `LLMResult` from the first returned chunk.
|
||||||
|
|
||||||
|
This is used for `stream=False` because the plugin side may still implement the response via a chunked stream.
|
||||||
|
"""
|
||||||
|
content = ""
|
||||||
|
content_list: list[PromptMessageContentUnionTypes] = []
|
||||||
|
usage = LLMUsage.empty_usage()
|
||||||
|
system_fingerprint: str | None = None
|
||||||
|
tools_calls: list[AssistantPromptMessage.ToolCall] = []
|
||||||
|
|
||||||
|
first_chunk = next(chunks, None)
|
||||||
|
if first_chunk is not None:
|
||||||
|
if isinstance(first_chunk.delta.message.content, str):
|
||||||
|
content += first_chunk.delta.message.content
|
||||||
|
elif isinstance(first_chunk.delta.message.content, list):
|
||||||
|
content_list.extend(first_chunk.delta.message.content)
|
||||||
|
|
||||||
|
if first_chunk.delta.message.tool_calls:
|
||||||
|
_increase_tool_call(first_chunk.delta.message.tool_calls, tools_calls)
|
||||||
|
|
||||||
|
usage = first_chunk.delta.usage or LLMUsage.empty_usage()
|
||||||
|
system_fingerprint = first_chunk.system_fingerprint
|
||||||
|
|
||||||
|
return LLMResult(
|
||||||
|
model=model,
|
||||||
|
prompt_messages=prompt_messages,
|
||||||
|
message=AssistantPromptMessage(
|
||||||
|
content=content or content_list,
|
||||||
|
tool_calls=tools_calls,
|
||||||
|
),
|
||||||
|
usage=usage,
|
||||||
|
system_fingerprint=system_fingerprint,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _invoke_llm_via_plugin(
|
||||||
|
*,
|
||||||
|
tenant_id: str,
|
||||||
|
user_id: str,
|
||||||
|
plugin_id: str,
|
||||||
|
provider: str,
|
||||||
|
model: str,
|
||||||
|
credentials: dict,
|
||||||
|
model_parameters: dict,
|
||||||
|
prompt_messages: Sequence[PromptMessage],
|
||||||
|
tools: list[PromptMessageTool] | None,
|
||||||
|
stop: Sequence[str] | None,
|
||||||
|
stream: bool,
|
||||||
|
) -> Union[LLMResult, Generator[LLMResultChunk, None, None]]:
|
||||||
|
from core.plugin.impl.model import PluginModelClient
|
||||||
|
|
||||||
|
plugin_model_manager = PluginModelClient()
|
||||||
|
return plugin_model_manager.invoke_llm(
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
user_id=user_id,
|
||||||
|
plugin_id=plugin_id,
|
||||||
|
provider=provider,
|
||||||
|
model=model,
|
||||||
|
credentials=credentials,
|
||||||
|
model_parameters=model_parameters,
|
||||||
|
prompt_messages=list(prompt_messages),
|
||||||
|
tools=tools,
|
||||||
|
stop=list(stop) if stop else None,
|
||||||
|
stream=stream,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_non_stream_plugin_result(
|
||||||
|
model: str,
|
||||||
|
prompt_messages: Sequence[PromptMessage],
|
||||||
|
result: Union[LLMResult, Iterator[LLMResultChunk]],
|
||||||
|
) -> LLMResult:
|
||||||
|
if isinstance(result, LLMResult):
|
||||||
|
return result
|
||||||
|
return _build_llm_result_from_first_chunk(model=model, prompt_messages=prompt_messages, chunks=result)
|
||||||
|
|
||||||
|
|
||||||
def _increase_tool_call(
|
def _increase_tool_call(
|
||||||
new_tool_calls: list[AssistantPromptMessage.ToolCall], existing_tools_calls: list[AssistantPromptMessage.ToolCall]
|
new_tool_calls: list[AssistantPromptMessage.ToolCall], existing_tools_calls: list[AssistantPromptMessage.ToolCall]
|
||||||
):
|
):
|
||||||
@ -40,42 +176,13 @@ def _increase_tool_call(
|
|||||||
:param existing_tools_calls: List of existing tool calls to be modified IN-PLACE.
|
:param existing_tools_calls: List of existing tool calls to be modified IN-PLACE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_tool_call(tool_call_id: str):
|
|
||||||
"""
|
|
||||||
Get or create a tool call by ID
|
|
||||||
|
|
||||||
:param tool_call_id: tool call ID
|
|
||||||
:return: existing or new tool call
|
|
||||||
"""
|
|
||||||
if not tool_call_id:
|
|
||||||
return existing_tools_calls[-1]
|
|
||||||
|
|
||||||
_tool_call = next((_tool_call for _tool_call in existing_tools_calls if _tool_call.id == tool_call_id), None)
|
|
||||||
if _tool_call is None:
|
|
||||||
_tool_call = AssistantPromptMessage.ToolCall(
|
|
||||||
id=tool_call_id,
|
|
||||||
type="function",
|
|
||||||
function=AssistantPromptMessage.ToolCall.ToolCallFunction(name="", arguments=""),
|
|
||||||
)
|
|
||||||
existing_tools_calls.append(_tool_call)
|
|
||||||
|
|
||||||
return _tool_call
|
|
||||||
|
|
||||||
for new_tool_call in new_tool_calls:
|
for new_tool_call in new_tool_calls:
|
||||||
# generate ID for tool calls with function name but no ID to track them
|
# generate ID for tool calls with function name but no ID to track them
|
||||||
if new_tool_call.function.name and not new_tool_call.id:
|
if new_tool_call.function.name and not new_tool_call.id:
|
||||||
new_tool_call.id = _gen_tool_call_id()
|
new_tool_call.id = _gen_tool_call_id()
|
||||||
# get tool call
|
|
||||||
tool_call = get_tool_call(new_tool_call.id)
|
tool_call = _get_or_create_tool_call(existing_tools_calls, new_tool_call.id)
|
||||||
# update tool call
|
_merge_tool_call_delta(tool_call, new_tool_call)
|
||||||
if new_tool_call.id:
|
|
||||||
tool_call.id = new_tool_call.id
|
|
||||||
if new_tool_call.type:
|
|
||||||
tool_call.type = new_tool_call.type
|
|
||||||
if new_tool_call.function.name:
|
|
||||||
tool_call.function.name = new_tool_call.function.name
|
|
||||||
if new_tool_call.function.arguments:
|
|
||||||
tool_call.function.arguments += new_tool_call.function.arguments
|
|
||||||
|
|
||||||
|
|
||||||
class LargeLanguageModel(AIModel):
|
class LargeLanguageModel(AIModel):
|
||||||
@ -141,10 +248,7 @@ class LargeLanguageModel(AIModel):
|
|||||||
result: Union[LLMResult, Generator[LLMResultChunk, None, None]]
|
result: Union[LLMResult, Generator[LLMResultChunk, None, None]]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from core.plugin.impl.model import PluginModelClient
|
result = _invoke_llm_via_plugin(
|
||||||
|
|
||||||
plugin_model_manager = PluginModelClient()
|
|
||||||
result = plugin_model_manager.invoke_llm(
|
|
||||||
tenant_id=self.tenant_id,
|
tenant_id=self.tenant_id,
|
||||||
user_id=user or "unknown",
|
user_id=user or "unknown",
|
||||||
plugin_id=self.plugin_id,
|
plugin_id=self.plugin_id,
|
||||||
@ -154,38 +258,13 @@ class LargeLanguageModel(AIModel):
|
|||||||
model_parameters=model_parameters,
|
model_parameters=model_parameters,
|
||||||
prompt_messages=prompt_messages,
|
prompt_messages=prompt_messages,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
stop=list(stop) if stop else None,
|
stop=stop,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not stream:
|
if not stream:
|
||||||
content = ""
|
result = _normalize_non_stream_plugin_result(
|
||||||
content_list = []
|
model=model, prompt_messages=prompt_messages, result=result
|
||||||
usage = LLMUsage.empty_usage()
|
|
||||||
system_fingerprint = None
|
|
||||||
tools_calls: list[AssistantPromptMessage.ToolCall] = []
|
|
||||||
|
|
||||||
for chunk in result:
|
|
||||||
if isinstance(chunk.delta.message.content, str):
|
|
||||||
content += chunk.delta.message.content
|
|
||||||
elif isinstance(chunk.delta.message.content, list):
|
|
||||||
content_list.extend(chunk.delta.message.content)
|
|
||||||
if chunk.delta.message.tool_calls:
|
|
||||||
_increase_tool_call(chunk.delta.message.tool_calls, tools_calls)
|
|
||||||
|
|
||||||
usage = chunk.delta.usage or LLMUsage.empty_usage()
|
|
||||||
system_fingerprint = chunk.system_fingerprint
|
|
||||||
break
|
|
||||||
|
|
||||||
result = LLMResult(
|
|
||||||
model=model,
|
|
||||||
prompt_messages=prompt_messages,
|
|
||||||
message=AssistantPromptMessage(
|
|
||||||
content=content or content_list,
|
|
||||||
tool_calls=tools_calls,
|
|
||||||
),
|
|
||||||
usage=usage,
|
|
||||||
system_fingerprint=system_fingerprint,
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._trigger_invoke_error_callbacks(
|
self._trigger_invoke_error_callbacks(
|
||||||
@ -425,27 +504,21 @@ class LargeLanguageModel(AIModel):
|
|||||||
:param user: unique user id
|
:param user: unique user id
|
||||||
:param callbacks: callbacks
|
:param callbacks: callbacks
|
||||||
"""
|
"""
|
||||||
if callbacks:
|
_run_callbacks(
|
||||||
for callback in callbacks:
|
callbacks,
|
||||||
try:
|
event="on_before_invoke",
|
||||||
callback.on_before_invoke(
|
invoke=lambda callback: callback.on_before_invoke(
|
||||||
llm_instance=self,
|
llm_instance=self,
|
||||||
model=model,
|
model=model,
|
||||||
credentials=credentials,
|
credentials=credentials,
|
||||||
prompt_messages=prompt_messages,
|
prompt_messages=prompt_messages,
|
||||||
model_parameters=model_parameters,
|
model_parameters=model_parameters,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
stop=stop,
|
stop=stop,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
user=user,
|
user=user,
|
||||||
)
|
),
|
||||||
except Exception as e:
|
)
|
||||||
if callback.raise_error:
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
"Callback %s on_before_invoke failed with error %s", callback.__class__.__name__, e
|
|
||||||
)
|
|
||||||
|
|
||||||
def _trigger_new_chunk_callbacks(
|
def _trigger_new_chunk_callbacks(
|
||||||
self,
|
self,
|
||||||
@ -473,26 +546,22 @@ class LargeLanguageModel(AIModel):
|
|||||||
:param stream: is stream response
|
:param stream: is stream response
|
||||||
:param user: unique user id
|
:param user: unique user id
|
||||||
"""
|
"""
|
||||||
if callbacks:
|
_run_callbacks(
|
||||||
for callback in callbacks:
|
callbacks,
|
||||||
try:
|
event="on_new_chunk",
|
||||||
callback.on_new_chunk(
|
invoke=lambda callback: callback.on_new_chunk(
|
||||||
llm_instance=self,
|
llm_instance=self,
|
||||||
chunk=chunk,
|
chunk=chunk,
|
||||||
model=model,
|
model=model,
|
||||||
credentials=credentials,
|
credentials=credentials,
|
||||||
prompt_messages=prompt_messages,
|
prompt_messages=prompt_messages,
|
||||||
model_parameters=model_parameters,
|
model_parameters=model_parameters,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
stop=stop,
|
stop=stop,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
user=user,
|
user=user,
|
||||||
)
|
),
|
||||||
except Exception as e:
|
)
|
||||||
if callback.raise_error:
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
logger.warning("Callback %s on_new_chunk failed with error %s", callback.__class__.__name__, e)
|
|
||||||
|
|
||||||
def _trigger_after_invoke_callbacks(
|
def _trigger_after_invoke_callbacks(
|
||||||
self,
|
self,
|
||||||
@ -521,28 +590,22 @@ class LargeLanguageModel(AIModel):
|
|||||||
:param user: unique user id
|
:param user: unique user id
|
||||||
:param callbacks: callbacks
|
:param callbacks: callbacks
|
||||||
"""
|
"""
|
||||||
if callbacks:
|
_run_callbacks(
|
||||||
for callback in callbacks:
|
callbacks,
|
||||||
try:
|
event="on_after_invoke",
|
||||||
callback.on_after_invoke(
|
invoke=lambda callback: callback.on_after_invoke(
|
||||||
llm_instance=self,
|
llm_instance=self,
|
||||||
result=result,
|
result=result,
|
||||||
model=model,
|
model=model,
|
||||||
credentials=credentials,
|
credentials=credentials,
|
||||||
prompt_messages=prompt_messages,
|
prompt_messages=prompt_messages,
|
||||||
model_parameters=model_parameters,
|
model_parameters=model_parameters,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
stop=stop,
|
stop=stop,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
user=user,
|
user=user,
|
||||||
)
|
),
|
||||||
except Exception as e:
|
)
|
||||||
if callback.raise_error:
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
"Callback %s on_after_invoke failed with error %s", callback.__class__.__name__, e
|
|
||||||
)
|
|
||||||
|
|
||||||
def _trigger_invoke_error_callbacks(
|
def _trigger_invoke_error_callbacks(
|
||||||
self,
|
self,
|
||||||
@ -571,25 +634,19 @@ class LargeLanguageModel(AIModel):
|
|||||||
:param user: unique user id
|
:param user: unique user id
|
||||||
:param callbacks: callbacks
|
:param callbacks: callbacks
|
||||||
"""
|
"""
|
||||||
if callbacks:
|
_run_callbacks(
|
||||||
for callback in callbacks:
|
callbacks,
|
||||||
try:
|
event="on_invoke_error",
|
||||||
callback.on_invoke_error(
|
invoke=lambda callback: callback.on_invoke_error(
|
||||||
llm_instance=self,
|
llm_instance=self,
|
||||||
ex=ex,
|
ex=ex,
|
||||||
model=model,
|
model=model,
|
||||||
credentials=credentials,
|
credentials=credentials,
|
||||||
prompt_messages=prompt_messages,
|
prompt_messages=prompt_messages,
|
||||||
model_parameters=model_parameters,
|
model_parameters=model_parameters,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
stop=stop,
|
stop=stop,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
user=user,
|
user=user,
|
||||||
)
|
),
|
||||||
except Exception as e:
|
)
|
||||||
if callback.raise_error:
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
"Callback %s on_invoke_error failed with error %s", callback.__class__.__name__, e
|
|
||||||
)
|
|
||||||
|
|||||||
@ -28,8 +28,10 @@ def init_app(app: DifyApp) -> None:
|
|||||||
|
|
||||||
# Ensure route decorators are evaluated.
|
# Ensure route decorators are evaluated.
|
||||||
import controllers.console.ping as ping_module
|
import controllers.console.ping as ping_module
|
||||||
|
from controllers.console import setup
|
||||||
|
|
||||||
_ = ping_module
|
_ = ping_module
|
||||||
|
_ = setup
|
||||||
|
|
||||||
router.include_router(console_router, prefix="/console/api")
|
router.include_router(console_router, prefix="/console/api")
|
||||||
CORS(
|
CORS(
|
||||||
|
|||||||
@ -781,15 +781,16 @@ class AppDslService:
|
|||||||
return dependencies
|
return dependencies
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_leaked_dependencies(cls, tenant_id: str, dsl_dependencies: list[dict]) -> list[PluginDependency]:
|
def get_leaked_dependencies(
|
||||||
|
cls, tenant_id: str, dsl_dependencies: list[PluginDependency]
|
||||||
|
) -> list[PluginDependency]:
|
||||||
"""
|
"""
|
||||||
Returns the leaked dependencies in current workspace
|
Returns the leaked dependencies in current workspace
|
||||||
"""
|
"""
|
||||||
dependencies = [PluginDependency.model_validate(dep) for dep in dsl_dependencies]
|
if not dsl_dependencies:
|
||||||
if not dependencies:
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return DependenciesAnalysisService.get_leaked_dependencies(tenant_id=tenant_id, dependencies=dependencies)
|
return DependenciesAnalysisService.get_leaked_dependencies(tenant_id=tenant_id, dependencies=dsl_dependencies)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_aes_key(tenant_id: str) -> bytes:
|
def _generate_aes_key(tenant_id: str) -> bytes:
|
||||||
|
|||||||
@ -870,15 +870,16 @@ class RagPipelineDslService:
|
|||||||
return dependencies
|
return dependencies
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_leaked_dependencies(cls, tenant_id: str, dsl_dependencies: list[dict]) -> list[PluginDependency]:
|
def get_leaked_dependencies(
|
||||||
|
cls, tenant_id: str, dsl_dependencies: list[PluginDependency]
|
||||||
|
) -> list[PluginDependency]:
|
||||||
"""
|
"""
|
||||||
Returns the leaked dependencies in current workspace
|
Returns the leaked dependencies in current workspace
|
||||||
"""
|
"""
|
||||||
dependencies = [PluginDependency.model_validate(dep) for dep in dsl_dependencies]
|
if not dsl_dependencies:
|
||||||
if not dependencies:
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return DependenciesAnalysisService.get_leaked_dependencies(tenant_id=tenant_id, dependencies=dependencies)
|
return DependenciesAnalysisService.get_leaked_dependencies(tenant_id=tenant_id, dependencies=dsl_dependencies)
|
||||||
|
|
||||||
def _generate_aes_key(self, tenant_id: str) -> bytes:
|
def _generate_aes_key(self, tenant_id: str) -> bytes:
|
||||||
"""Generate AES key based on tenant_id"""
|
"""Generate AES key based on tenant_id"""
|
||||||
|
|||||||
@ -44,7 +44,7 @@ class RagPipelineTransformService:
|
|||||||
doc_form = dataset.doc_form
|
doc_form = dataset.doc_form
|
||||||
if not doc_form:
|
if not doc_form:
|
||||||
return self._transform_to_empty_pipeline(dataset)
|
return self._transform_to_empty_pipeline(dataset)
|
||||||
retrieval_model = dataset.retrieval_model
|
retrieval_model = RetrievalSetting.model_validate(dataset.retrieval_model) if dataset.retrieval_model else None
|
||||||
pipeline_yaml = self._get_transform_yaml(doc_form, datasource_type, indexing_technique)
|
pipeline_yaml = self._get_transform_yaml(doc_form, datasource_type, indexing_technique)
|
||||||
# deal dependencies
|
# deal dependencies
|
||||||
self._deal_dependencies(pipeline_yaml, dataset.tenant_id)
|
self._deal_dependencies(pipeline_yaml, dataset.tenant_id)
|
||||||
@ -154,7 +154,12 @@ class RagPipelineTransformService:
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
def _deal_knowledge_index(
|
def _deal_knowledge_index(
|
||||||
self, dataset: Dataset, doc_form: str, indexing_technique: str | None, retrieval_model: dict, node: dict
|
self,
|
||||||
|
dataset: Dataset,
|
||||||
|
doc_form: str,
|
||||||
|
indexing_technique: str | None,
|
||||||
|
retrieval_model: RetrievalSetting | None,
|
||||||
|
node: dict,
|
||||||
):
|
):
|
||||||
knowledge_configuration_dict = node.get("data", {})
|
knowledge_configuration_dict = node.get("data", {})
|
||||||
knowledge_configuration = KnowledgeConfiguration.model_validate(knowledge_configuration_dict)
|
knowledge_configuration = KnowledgeConfiguration.model_validate(knowledge_configuration_dict)
|
||||||
@ -163,10 +168,9 @@ class RagPipelineTransformService:
|
|||||||
knowledge_configuration.embedding_model = dataset.embedding_model
|
knowledge_configuration.embedding_model = dataset.embedding_model
|
||||||
knowledge_configuration.embedding_model_provider = dataset.embedding_model_provider
|
knowledge_configuration.embedding_model_provider = dataset.embedding_model_provider
|
||||||
if retrieval_model:
|
if retrieval_model:
|
||||||
retrieval_setting = RetrievalSetting.model_validate(retrieval_model)
|
|
||||||
if indexing_technique == "economy":
|
if indexing_technique == "economy":
|
||||||
retrieval_setting.search_method = RetrievalMethod.KEYWORD_SEARCH
|
retrieval_model.search_method = RetrievalMethod.KEYWORD_SEARCH
|
||||||
knowledge_configuration.retrieval_model = retrieval_setting
|
knowledge_configuration.retrieval_model = retrieval_model
|
||||||
else:
|
else:
|
||||||
dataset.retrieval_model = knowledge_configuration.retrieval_model.model_dump()
|
dataset.retrieval_model = knowledge_configuration.retrieval_model.model_dump()
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,56 @@
|
|||||||
|
import builtins
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from flask import Flask
|
||||||
|
from flask.views import MethodView
|
||||||
|
|
||||||
|
from extensions import ext_fastopenapi
|
||||||
|
|
||||||
|
if not hasattr(builtins, "MethodView"):
|
||||||
|
builtins.MethodView = MethodView # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def app() -> Flask:
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config["TESTING"] = True
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def test_console_setup_fastopenapi_get_not_started(app: Flask):
|
||||||
|
ext_fastopenapi.init_app(app)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("controllers.console.setup.dify_config.EDITION", "SELF_HOSTED"),
|
||||||
|
patch("controllers.console.setup.get_setup_status", return_value=None),
|
||||||
|
):
|
||||||
|
client = app.test_client()
|
||||||
|
response = client.get("/console/api/setup")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.get_json() == {"step": "not_started", "setup_at": None}
|
||||||
|
|
||||||
|
|
||||||
|
def test_console_setup_fastopenapi_post_success(app: Flask):
|
||||||
|
ext_fastopenapi.init_app(app)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"name": "Admin",
|
||||||
|
"password": "Passw0rd1",
|
||||||
|
"language": "en-US",
|
||||||
|
}
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("controllers.console.wraps.dify_config.EDITION", "SELF_HOSTED"),
|
||||||
|
patch("controllers.console.setup.get_setup_status", return_value=None),
|
||||||
|
patch("controllers.console.setup.TenantService.get_tenant_count", return_value=0),
|
||||||
|
patch("controllers.console.setup.get_init_validate_status", return_value=True),
|
||||||
|
patch("controllers.console.setup.RegisterService.setup"),
|
||||||
|
):
|
||||||
|
client = app.test_client()
|
||||||
|
response = client.post("/console/api/setup", json=payload)
|
||||||
|
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert response.get_json() == {"result": "success"}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import builtins
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from flask import Flask
|
||||||
|
from flask.views import MethodView
|
||||||
|
|
||||||
|
from configs import dify_config
|
||||||
|
from extensions import ext_fastopenapi
|
||||||
|
|
||||||
|
if not hasattr(builtins, "MethodView"):
|
||||||
|
builtins.MethodView = MethodView # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def app() -> Flask:
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config["TESTING"] = True
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def test_console_version_fastopenapi_returns_current_version(app: Flask):
|
||||||
|
ext_fastopenapi.init_app(app)
|
||||||
|
|
||||||
|
with patch("controllers.console.version.dify_config.CHECK_UPDATE_URL", None):
|
||||||
|
client = app.test_client()
|
||||||
|
response = client.get("/console/api/version", query_string={"current_version": "0.0.0"})
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.get_json()
|
||||||
|
assert data["version"] == dify_config.project.version
|
||||||
|
assert data["release_date"] == ""
|
||||||
|
assert data["release_notes"] == ""
|
||||||
|
assert data["can_auto_update"] is False
|
||||||
|
assert "features" in data
|
||||||
@ -1,39 +0,0 @@
|
|||||||
from types import SimpleNamespace
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from controllers.console.setup import SetupApi
|
|
||||||
|
|
||||||
|
|
||||||
class TestSetupApi:
|
|
||||||
def test_post_lowercases_email_before_register(self):
|
|
||||||
"""Ensure setup registration normalizes email casing."""
|
|
||||||
payload = {
|
|
||||||
"email": "Admin@Example.com",
|
|
||||||
"name": "Admin User",
|
|
||||||
"password": "ValidPass123!",
|
|
||||||
"language": "en-US",
|
|
||||||
}
|
|
||||||
setup_api = SetupApi(api=None)
|
|
||||||
|
|
||||||
mock_console_ns = SimpleNamespace(payload=payload)
|
|
||||||
|
|
||||||
with (
|
|
||||||
patch("controllers.console.setup.console_ns", mock_console_ns),
|
|
||||||
patch("controllers.console.setup.get_setup_status", return_value=False),
|
|
||||||
patch("controllers.console.setup.TenantService.get_tenant_count", return_value=0),
|
|
||||||
patch("controllers.console.setup.get_init_validate_status", return_value=True),
|
|
||||||
patch("controllers.console.setup.extract_remote_ip", return_value="127.0.0.1"),
|
|
||||||
patch("controllers.console.setup.request", object()),
|
|
||||||
patch("controllers.console.setup.RegisterService.setup") as mock_register,
|
|
||||||
):
|
|
||||||
response, status = setup_api.post()
|
|
||||||
|
|
||||||
assert response == {"result": "success"}
|
|
||||||
assert status == 201
|
|
||||||
mock_register.assert_called_once_with(
|
|
||||||
email="admin@example.com",
|
|
||||||
name=payload["name"],
|
|
||||||
password=payload["password"],
|
|
||||||
ip_address="127.0.0.1",
|
|
||||||
language=payload["language"],
|
|
||||||
)
|
|
||||||
@ -1,5 +1,7 @@
|
|||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from core.model_runtime.entities.message_entities import AssistantPromptMessage
|
from core.model_runtime.entities.message_entities import AssistantPromptMessage
|
||||||
from core.model_runtime.model_providers.__base.large_language_model import _increase_tool_call
|
from core.model_runtime.model_providers.__base.large_language_model import _increase_tool_call
|
||||||
|
|
||||||
@ -97,3 +99,14 @@ def test__increase_tool_call():
|
|||||||
mock_id_generator.side_effect = [_exp_case.id for _exp_case in EXPECTED_CASE_4]
|
mock_id_generator.side_effect = [_exp_case.id for _exp_case in EXPECTED_CASE_4]
|
||||||
with patch("core.model_runtime.model_providers.__base.large_language_model._gen_tool_call_id", mock_id_generator):
|
with patch("core.model_runtime.model_providers.__base.large_language_model._gen_tool_call_id", mock_id_generator):
|
||||||
_run_case(INPUTS_CASE_4, EXPECTED_CASE_4)
|
_run_case(INPUTS_CASE_4, EXPECTED_CASE_4)
|
||||||
|
|
||||||
|
|
||||||
|
def test__increase_tool_call__no_id_no_name_first_delta_should_raise():
|
||||||
|
inputs = [
|
||||||
|
ToolCall(id="", type="function", function=ToolCall.ToolCallFunction(name="", arguments='{"arg1": ')),
|
||||||
|
ToolCall(id="", type="function", function=ToolCall.ToolCallFunction(name="func_foo", arguments='"value"}')),
|
||||||
|
]
|
||||||
|
actual: list[ToolCall] = []
|
||||||
|
with patch("core.model_runtime.model_providers.__base.large_language_model._gen_tool_call_id", MagicMock()):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
_increase_tool_call(inputs, actual)
|
||||||
|
|||||||
@ -0,0 +1,103 @@
|
|||||||
|
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
|
||||||
|
from core.model_runtime.entities.message_entities import (
|
||||||
|
AssistantPromptMessage,
|
||||||
|
TextPromptMessageContent,
|
||||||
|
UserPromptMessage,
|
||||||
|
)
|
||||||
|
from core.model_runtime.model_providers.__base.large_language_model import _normalize_non_stream_plugin_result
|
||||||
|
|
||||||
|
|
||||||
|
def _make_chunk(
|
||||||
|
*,
|
||||||
|
model: str = "test-model",
|
||||||
|
content: str | list[TextPromptMessageContent] | None,
|
||||||
|
tool_calls: list[AssistantPromptMessage.ToolCall] | None = None,
|
||||||
|
usage: LLMUsage | None = None,
|
||||||
|
system_fingerprint: str | None = None,
|
||||||
|
) -> LLMResultChunk:
|
||||||
|
message = AssistantPromptMessage(content=content, tool_calls=tool_calls or [])
|
||||||
|
delta = LLMResultChunkDelta(index=0, message=message, usage=usage)
|
||||||
|
return LLMResultChunk(model=model, delta=delta, system_fingerprint=system_fingerprint)
|
||||||
|
|
||||||
|
|
||||||
|
def test__normalize_non_stream_plugin_result__from_first_chunk_str_content_and_tool_calls():
|
||||||
|
prompt_messages = [UserPromptMessage(content="hi")]
|
||||||
|
|
||||||
|
tool_calls = [
|
||||||
|
AssistantPromptMessage.ToolCall(
|
||||||
|
id="1",
|
||||||
|
type="function",
|
||||||
|
function=AssistantPromptMessage.ToolCall.ToolCallFunction(name="func_foo", arguments=""),
|
||||||
|
),
|
||||||
|
AssistantPromptMessage.ToolCall(
|
||||||
|
id="",
|
||||||
|
type="function",
|
||||||
|
function=AssistantPromptMessage.ToolCall.ToolCallFunction(name="", arguments='{"arg1": '),
|
||||||
|
),
|
||||||
|
AssistantPromptMessage.ToolCall(
|
||||||
|
id="",
|
||||||
|
type="function",
|
||||||
|
function=AssistantPromptMessage.ToolCall.ToolCallFunction(name="", arguments='"value"}'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
usage = LLMUsage.empty_usage().model_copy(update={"prompt_tokens": 1, "total_tokens": 1})
|
||||||
|
chunk = _make_chunk(content="hello", tool_calls=tool_calls, usage=usage, system_fingerprint="fp-1")
|
||||||
|
|
||||||
|
result = _normalize_non_stream_plugin_result(
|
||||||
|
model="test-model", prompt_messages=prompt_messages, result=iter([chunk])
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.model == "test-model"
|
||||||
|
assert result.prompt_messages == prompt_messages
|
||||||
|
assert result.message.content == "hello"
|
||||||
|
assert result.usage.prompt_tokens == 1
|
||||||
|
assert result.system_fingerprint == "fp-1"
|
||||||
|
assert result.message.tool_calls == [
|
||||||
|
AssistantPromptMessage.ToolCall(
|
||||||
|
id="1",
|
||||||
|
type="function",
|
||||||
|
function=AssistantPromptMessage.ToolCall.ToolCallFunction(name="func_foo", arguments='{"arg1": "value"}'),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test__normalize_non_stream_plugin_result__from_first_chunk_list_content():
|
||||||
|
prompt_messages = [UserPromptMessage(content="hi")]
|
||||||
|
|
||||||
|
content_list = [TextPromptMessageContent(data="a"), TextPromptMessageContent(data="b")]
|
||||||
|
chunk = _make_chunk(content=content_list, usage=LLMUsage.empty_usage())
|
||||||
|
|
||||||
|
result = _normalize_non_stream_plugin_result(
|
||||||
|
model="test-model", prompt_messages=prompt_messages, result=iter([chunk])
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.message.content == content_list
|
||||||
|
|
||||||
|
|
||||||
|
def test__normalize_non_stream_plugin_result__passthrough_llm_result():
|
||||||
|
prompt_messages = [UserPromptMessage(content="hi")]
|
||||||
|
llm_result = LLMResult(
|
||||||
|
model="test-model",
|
||||||
|
prompt_messages=prompt_messages,
|
||||||
|
message=AssistantPromptMessage(content="ok"),
|
||||||
|
usage=LLMUsage.empty_usage(),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
_normalize_non_stream_plugin_result(model="test-model", prompt_messages=prompt_messages, result=llm_result)
|
||||||
|
== llm_result
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test__normalize_non_stream_plugin_result__empty_iterator_defaults():
|
||||||
|
prompt_messages = [UserPromptMessage(content="hi")]
|
||||||
|
|
||||||
|
result = _normalize_non_stream_plugin_result(model="test-model", prompt_messages=prompt_messages, result=iter([]))
|
||||||
|
|
||||||
|
assert result.model == "test-model"
|
||||||
|
assert result.prompt_messages == prompt_messages
|
||||||
|
assert result.message.content == []
|
||||||
|
assert result.message.tool_calls == []
|
||||||
|
assert result.usage == LLMUsage.empty_usage()
|
||||||
|
assert result.system_fingerprint is None
|
||||||
@ -1,27 +1,15 @@
|
|||||||
import type { StorybookConfig } from '@storybook/nextjs'
|
import type { StorybookConfig } from '@storybook/nextjs-vite'
|
||||||
import path from 'node:path'
|
|
||||||
import { fileURLToPath } from 'node:url'
|
|
||||||
|
|
||||||
const storybookDir = path.dirname(fileURLToPath(import.meta.url))
|
|
||||||
|
|
||||||
const config: StorybookConfig = {
|
const config: StorybookConfig = {
|
||||||
stories: ['../app/components/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
stories: ['../app/components/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||||
addons: [
|
addons: [
|
||||||
'@storybook/addon-onboarding',
|
// Not working with Storybook Vite framework
|
||||||
|
// '@storybook/addon-onboarding',
|
||||||
'@storybook/addon-links',
|
'@storybook/addon-links',
|
||||||
'@storybook/addon-docs',
|
'@storybook/addon-docs',
|
||||||
'@chromatic-com/storybook',
|
'@chromatic-com/storybook',
|
||||||
],
|
],
|
||||||
framework: {
|
framework: '@storybook/nextjs-vite',
|
||||||
name: '@storybook/nextjs',
|
|
||||||
options: {
|
|
||||||
builder: {
|
|
||||||
useSWC: true,
|
|
||||||
lazyCompilation: false,
|
|
||||||
},
|
|
||||||
nextConfigPath: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
staticDirs: ['../public'],
|
staticDirs: ['../public'],
|
||||||
core: {
|
core: {
|
||||||
disableWhatsNewNotifications: true,
|
disableWhatsNewNotifications: true,
|
||||||
@ -29,17 +17,5 @@ const config: StorybookConfig = {
|
|||||||
docs: {
|
docs: {
|
||||||
defaultName: 'Documentation',
|
defaultName: 'Documentation',
|
||||||
},
|
},
|
||||||
webpackFinal: async (config) => {
|
|
||||||
// Add alias to mock problematic modules with circular dependencies
|
|
||||||
config.resolve = config.resolve || {}
|
|
||||||
config.resolve.alias = {
|
|
||||||
...config.resolve.alias,
|
|
||||||
// Mock the plugin index files to avoid circular dependencies
|
|
||||||
[path.resolve(storybookDir, '../app/components/base/prompt-editor/plugins/context-block/index.tsx')]: path.resolve(storybookDir, '__mocks__/context-block.tsx'),
|
|
||||||
[path.resolve(storybookDir, '../app/components/base/prompt-editor/plugins/history-block/index.tsx')]: path.resolve(storybookDir, '__mocks__/history-block.tsx'),
|
|
||||||
[path.resolve(storybookDir, '../app/components/base/prompt-editor/plugins/query-block/index.tsx')]: path.resolve(storybookDir, '__mocks__/query-block.tsx'),
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
export default config
|
export default config
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @vitest-environment jsdom
|
||||||
|
*/
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import type { ModalContextState } from '@/context/modal-context'
|
import type { ModalContextState } from '@/context/modal-context'
|
||||||
import type { ProviderContextState } from '@/context/provider-context'
|
import type { ProviderContextState } from '@/context/provider-context'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { RiAddLine, RiDeleteBinLine, RiEditLine, RiMore2Fill, RiSaveLine, RiShareLine } from '@remixicon/react'
|
import { RiAddLine, RiDeleteBinLine, RiEditLine, RiMore2Fill, RiSaveLine, RiShareLine } from '@remixicon/react'
|
||||||
import ActionButton, { ActionButtonState } from '.'
|
import ActionButton, { ActionButtonState } from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||||
import type { AgentLogDetailResponse } from '@/models/log'
|
import type { AgentLogDetailResponse } from '@/models/log'
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import AnswerIcon from '.'
|
import AnswerIcon from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { AppIconSelection } from '.'
|
import type { AppIconSelection } from '.'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import AppIconPicker from '.'
|
import AppIconPicker from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { ComponentProps } from 'react'
|
import type { ComponentProps } from 'react'
|
||||||
import AppIcon from '.'
|
import AppIcon from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { ComponentProps } from 'react'
|
import type { ComponentProps } from 'react'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import AudioBtn from '.'
|
import AudioBtn from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import AudioGallery from '.'
|
import AudioGallery from '.'
|
||||||
|
|
||||||
const AUDIO_SOURCES = [
|
const AUDIO_SOURCES = [
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import AutoHeightTextarea from '.'
|
import AutoHeightTextarea from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import Avatar from '.'
|
import Avatar from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import Badge from '../badge'
|
import Badge from '../badge'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import BlockInput from '.'
|
import BlockInput from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import AddButton from './add-button'
|
import AddButton from './add-button'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
|
|
||||||
import { RocketLaunchIcon } from '@heroicons/react/20/solid'
|
import { RocketLaunchIcon } from '@heroicons/react/20/solid'
|
||||||
import { Button } from '.'
|
import { Button } from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import SyncButton from './sync-button'
|
import SyncButton from './sync-button'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { ChatItem } from '../../types'
|
import type { ChatItem } from '../../types'
|
||||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||||
import Answer from '.'
|
import Answer from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
|
|
||||||
import type { ChatItem } from '../types'
|
import type { ChatItem } from '../types'
|
||||||
import { User } from '@/app/components/base/icons/src/public/avatar'
|
import { User } from '@/app/components/base/icons/src/public/avatar'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Checkbox from '.'
|
import Checkbox from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { Item } from '.'
|
import type { Item } from '.'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Chip from '.'
|
import Chip from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Confirm from '.'
|
import Confirm from '.'
|
||||||
import Button from '../button'
|
import Button from '../button'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import ContentDialog from '.'
|
import ContentDialog from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import CopyFeedback, { CopyFeedbackNew } from '.'
|
import CopyFeedback, { CopyFeedbackNew } from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import CopyIcon from '.'
|
import CopyIcon from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import CornerLabel from '.'
|
import CornerLabel from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { DatePickerProps } from './types'
|
import type { DatePickerProps } from './types'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { fn } from 'storybook/test'
|
import { fn } from 'storybook/test'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Dialog from '.'
|
import Dialog from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import Divider from '.'
|
import Divider from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { fn } from 'storybook/test'
|
import { fn } from 'storybook/test'
|
||||||
import DrawerPlus from '.'
|
import DrawerPlus from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { fn } from 'storybook/test'
|
import { fn } from 'storybook/test'
|
||||||
import Drawer from '.'
|
import Drawer from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { Item } from '.'
|
import type { Item } from '.'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { fn } from 'storybook/test'
|
import { fn } from 'storybook/test'
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable tailwindcss/classnames-order */
|
/* eslint-disable tailwindcss/classnames-order */
|
||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import Effect from '.'
|
import Effect from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import EmojiPickerInner from './Inner'
|
import EmojiPickerInner from './Inner'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import EmojiPicker from '.'
|
import EmojiPicker from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { Features } from './types'
|
import type { Features } from './types'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { FeaturesProvider } from '.'
|
import { FeaturesProvider } from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import FileIcon from '.'
|
import FileIcon from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import FileImageRender from './file-image-render'
|
import FileImageRender from './file-image-render'
|
||||||
|
|
||||||
const SAMPLE_IMAGE = 'data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'320\' height=\'180\'><defs><linearGradient id=\'grad\' x1=\'0%\' y1=\'0%\' x2=\'100%\' y2=\'100%\'><stop offset=\'0%\' stop-color=\'#FEE2FF\'/><stop offset=\'100%\' stop-color=\'#E0EAFF\'/></linearGradient></defs><rect width=\'320\' height=\'180\' rx=\'18\' fill=\'url(#grad)\'/><text x=\'50%\' y=\'50%\' dominant-baseline=\'middle\' text-anchor=\'middle\' font-family=\'sans-serif\' font-size=\'24\' fill=\'#1F2937\'>Preview</text></svg>'
|
const SAMPLE_IMAGE = 'data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'320\' height=\'180\'><defs><linearGradient id=\'grad\' x1=\'0%\' y1=\'0%\' x2=\'100%\' y2=\'100%\'><stop offset=\'0%\' stop-color=\'#FEE2FF\'/><stop offset=\'100%\' stop-color=\'#E0EAFF\'/></linearGradient></defs><rect width=\'320\' height=\'180\' rx=\'18\' fill=\'url(#grad)\'/><text x=\'50%\' y=\'50%\' dominant-baseline=\'middle\' text-anchor=\'middle\' font-family=\'sans-serif\' font-size=\'24\' fill=\'#1F2937\'>Preview</text></svg>'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { FileEntity } from './types'
|
import type { FileEntity } from './types'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import FileTypeIcon from './file-type-icon'
|
import FileTypeIcon from './file-type-icon'
|
||||||
import { FileAppearanceTypeEnum } from './types'
|
import { FileAppearanceTypeEnum } from './types'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { FileEntity } from '../types'
|
import type { FileEntity } from '../types'
|
||||||
import type { FileUpload } from '@/app/components/base/features/types'
|
import type { FileUpload } from '@/app/components/base/features/types'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { FileEntity } from '../types'
|
import type { FileEntity } from '../types'
|
||||||
import type { FileUpload } from '@/app/components/base/features/types'
|
import type { FileUpload } from '@/app/components/base/features/types'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { fn } from 'storybook/test'
|
import { fn } from 'storybook/test'
|
||||||
import FloatRightContainer from '.'
|
import FloatRightContainer from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { FormStoryRender } from '../../../../.storybook/utils/form-story-wrapper'
|
import type { FormStoryRender } from '../../../../.storybook/utils/form-story-wrapper'
|
||||||
import type { FormSchema } from './types'
|
import type { FormSchema } from './types'
|
||||||
import { useStore } from '@tanstack/react-form'
|
import { useStore } from '@tanstack/react-form'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import FullScreenModal from '.'
|
import FullScreenModal from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import GridMask from '.'
|
import GridMask from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
declare const require: any
|
declare const require: any
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import ImageGallery from '.'
|
import ImageGallery from '.'
|
||||||
|
|
||||||
const IMAGE_SOURCES = [
|
const IMAGE_SOURCES = [
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { ImageFile } from '@/types/app'
|
import type { ImageFile } from '@/types/app'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { TransferMethod } from '@/types/app'
|
import { TransferMethod } from '@/types/app'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { fn } from 'storybook/test'
|
import { fn } from 'storybook/test'
|
||||||
import InlineDeleteConfirm from '.'
|
import InlineDeleteConfirm from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { InputNumber } from '.'
|
import { InputNumber } from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Input from '.'
|
import Input from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { RelatedApp } from '@/models/datasets'
|
import type { RelatedApp } from '@/models/datasets'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
import LinkedAppsPanel from '.'
|
import LinkedAppsPanel from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import ListEmpty from '.'
|
import ListEmpty from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import Loading from '.'
|
import Loading from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { ThemeProvider } from 'next-themes'
|
import { ThemeProvider } from 'next-themes'
|
||||||
import DifyLogo from './dify-logo'
|
import DifyLogo from './dify-logo'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import CodeBlock from './code-block'
|
import CodeBlock from './code-block'
|
||||||
|
|
||||||
const SAMPLE_CODE = `const greet = (name: string) => {
|
const SAMPLE_CODE = `const greet = (name: string) => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { ChatContextProvider } from '@/app/components/base/chat/chat/context'
|
import { ChatContextProvider } from '@/app/components/base/chat/chat/context'
|
||||||
import ThinkBlock from './think-block'
|
import ThinkBlock from './think-block'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Markdown } from '.'
|
import { Markdown } from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Flowchart from '.'
|
import Flowchart from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||||
import type { WorkflowRunDetailResponse } from '@/models/log'
|
import type { WorkflowRunDetailResponse } from '@/models/log'
|
||||||
import type { NodeTracing, NodeTracingListResponse } from '@/types/workflow'
|
import type { NodeTracing, NodeTracingListResponse } from '@/types/workflow'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import ModalLikeWrap from '.'
|
import ModalLikeWrap from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Modal from '.'
|
import Modal from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Modal from './modal'
|
import Modal from './modal'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { ComponentProps } from 'react'
|
import type { ComponentProps } from 'react'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import AudioBtn from '.'
|
import AudioBtn from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import NotionConnector from '.'
|
import NotionConnector from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import NotionIcon from '.'
|
import NotionIcon from '.'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { DataSourceCredential } from '@/app/components/header/account-setting/data-source-page-new/types'
|
import type { DataSourceCredential } from '@/app/components/header/account-setting/data-source-page-new/types'
|
||||||
import type { NotionPage } from '@/models/common'
|
import type { NotionPage } from '@/models/common'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import Pagination from '.'
|
import Pagination from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import ParamItem from '.'
|
import ParamItem from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import CustomPopover from '.'
|
import CustomPopover from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
|
|||||||
@ -61,9 +61,12 @@ export function usePortalToFollowElem({
|
|||||||
}),
|
}),
|
||||||
shift({ padding: 5 }),
|
shift({ padding: 5 }),
|
||||||
size({
|
size({
|
||||||
apply({ rects, elements }) {
|
apply({ rects, elements, availableHeight }) {
|
||||||
if (triggerPopupSameWidth)
|
Object.assign(elements.floating.style, {
|
||||||
elements.floating.style.width = `${rects.reference.width}px`
|
maxHeight: `${Math.max(0, availableHeight)}px`,
|
||||||
|
overflowY: 'auto',
|
||||||
|
...(triggerPopupSameWidth && { width: `${rects.reference.width}px` }),
|
||||||
|
})
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import PremiumBadge from '.'
|
import PremiumBadge from '.'
|
||||||
|
|
||||||
const colors: Array<NonNullable<React.ComponentProps<typeof PremiumBadge>['color']>> = ['blue', 'indigo', 'gray', 'orange']
|
const colors: Array<NonNullable<React.ComponentProps<typeof PremiumBadge>['color']>> = ['blue', 'indigo', 'gray', 'orange']
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import ProgressCircle from './progress-circle'
|
import ProgressCircle from './progress-circle'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
// Mock component to avoid complex initialization issues
|
// Mock component to avoid complex initialization issues
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useStore } from '@/app/components/app/store'
|
import { useStore } from '@/app/components/app/store'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import ShareQRCode from '.'
|
import ShareQRCode from '.'
|
||||||
|
|
||||||
const QRDemo = ({
|
const QRDemo = ({
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { RiCloudLine, RiCpuLine, RiDatabase2Line, RiLightbulbLine, RiRocketLine, RiShieldLine } from '@remixicon/react'
|
import { RiCloudLine, RiCpuLine, RiDatabase2Line, RiLightbulbLine, RiRocketLine, RiShieldLine } from '@remixicon/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import RadioCard from '.'
|
import RadioCard from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Radio from '.'
|
import Radio from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import SearchInput from '.'
|
import SearchInput from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { RiLineChartLine, RiListCheck2, RiRobot2Line } from '@remixicon/react'
|
import { RiLineChartLine, RiListCheck2, RiRobot2Line } from '@remixicon/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { SegmentedControl } from '.'
|
import { SegmentedControl } from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import type { Item } from '.'
|
import type { Item } from '.'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Select, { PortalSelect, SimpleSelect } from '.'
|
import Select, { PortalSelect, SimpleSelect } from '.'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import SimplePieChart from '.'
|
import SimplePieChart from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import {
|
import {
|
||||||
SkeletonContainer,
|
SkeletonContainer,
|
||||||
SkeletonPoint,
|
SkeletonPoint,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Slider from '.'
|
import Slider from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import Sort from '.'
|
import Sort from '.'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Spinner from '.'
|
import Spinner from '.'
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user