mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 20:17:29 +08:00
Merge branch 'p254' into p284
This commit is contained in:
commit
847d257366
@ -33,8 +33,13 @@ else:
|
|||||||
|
|
||||||
from app_factory import create_app
|
from app_factory import create_app
|
||||||
|
|
||||||
app = create_app()
|
socketio_app, app = create_app()
|
||||||
celery = app.extensions["celery"]
|
celery = app.extensions["celery"]
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=5001)
|
from gevent import pywsgi
|
||||||
|
|
||||||
|
host = os.environ.get("HOST", "0.0.0.0")
|
||||||
|
port = int(os.environ.get("PORT", 5001))
|
||||||
|
server = pywsgi.WSGIServer((host, port), socketio_app)
|
||||||
|
server.serve_forever()
|
||||||
|
|||||||
@ -31,10 +31,18 @@ def create_flask_app_with_configs() -> DifyApp:
|
|||||||
return dify_app
|
return dify_app
|
||||||
|
|
||||||
|
|
||||||
def create_app() -> DifyApp:
|
def create_app() -> tuple[any, DifyApp]:
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
app = create_flask_app_with_configs()
|
app = create_flask_app_with_configs()
|
||||||
initialize_extensions(app)
|
initialize_extensions(app)
|
||||||
|
|
||||||
|
import socketio
|
||||||
|
|
||||||
|
from extensions.ext_socketio import sio
|
||||||
|
|
||||||
|
sio.app = app
|
||||||
|
socketio_app = socketio.WSGIApp(sio, app)
|
||||||
|
|
||||||
end_time = time.perf_counter()
|
end_time = time.perf_counter()
|
||||||
if dify_config.DEBUG:
|
if dify_config.DEBUG:
|
||||||
logger.info("Finished create_app (%s ms)", round((end_time - start_time) * 1000, 2))
|
logger.info("Finished create_app (%s ms)", round((end_time - start_time) * 1000, 2))
|
||||||
|
|||||||
@ -79,6 +79,7 @@ from .app import (
|
|||||||
mcp_server,
|
mcp_server,
|
||||||
message,
|
message,
|
||||||
model_config,
|
model_config,
|
||||||
|
online_user,
|
||||||
ops_trace,
|
ops_trace,
|
||||||
site,
|
site,
|
||||||
statistic,
|
statistic,
|
||||||
|
|||||||
213
api/controllers/console/app/online_user.py
Normal file
213
api/controllers/console/app/online_user.py
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
from extensions.ext_redis import redis_client
|
||||||
|
from extensions.ext_socketio import sio
|
||||||
|
from libs.passport import PassportService
|
||||||
|
from services.account_service import AccountService
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on("connect")
|
||||||
|
def socket_connect(sid, environ, auth):
|
||||||
|
"""
|
||||||
|
WebSocket connect event, do authentication here.
|
||||||
|
"""
|
||||||
|
token = None
|
||||||
|
if auth and isinstance(auth, dict):
|
||||||
|
token = auth.get("token")
|
||||||
|
if not token:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
decoded = PassportService().verify(token)
|
||||||
|
user_id = decoded.get("user_id")
|
||||||
|
if not user_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with sio.app.app_context():
|
||||||
|
user = AccountService.load_logged_in_account(account_id=user_id)
|
||||||
|
if not user:
|
||||||
|
return False
|
||||||
|
|
||||||
|
sio.save_session(sid, {"user_id": user.id, "username": user.name, "avatar": user.avatar})
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on("user_connect")
|
||||||
|
def handle_user_connect(sid, data):
|
||||||
|
"""
|
||||||
|
Handle user connect event, check login and get user info.
|
||||||
|
"""
|
||||||
|
|
||||||
|
workflow_id = data.get("workflow_id")
|
||||||
|
if not workflow_id:
|
||||||
|
return {"msg": "workflow_id is required"}, 400
|
||||||
|
|
||||||
|
session = sio.get_session(sid)
|
||||||
|
user_id = session.get("user_id")
|
||||||
|
|
||||||
|
if not user_id:
|
||||||
|
return {"msg": "unauthorized"}, 401
|
||||||
|
|
||||||
|
old_info_json = redis_client.hget(f"workflow_online_users:{workflow_id}", user_id)
|
||||||
|
if old_info_json:
|
||||||
|
old_info = json.loads(old_info_json)
|
||||||
|
old_sid = old_info.get("sid")
|
||||||
|
if old_sid and old_sid != sid:
|
||||||
|
sio.disconnect(sid=old_sid)
|
||||||
|
|
||||||
|
user_info = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"username": session.get("username", "Unknown"),
|
||||||
|
"avatar": session.get("avatar", None),
|
||||||
|
"sid": sid,
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Leader Election Logic ---
|
||||||
|
workflow_users_key = f"workflow_online_users:{workflow_id}"
|
||||||
|
workflow_order_key = f"workflow_user_order:{workflow_id}"
|
||||||
|
|
||||||
|
# Remove user from list in case of reconnection, to add them to the end
|
||||||
|
redis_client.lrem(workflow_order_key, 0, user_id)
|
||||||
|
# Add user to the end of the list
|
||||||
|
redis_client.rpush(workflow_order_key, user_id)
|
||||||
|
|
||||||
|
# The first user in the list is the leader
|
||||||
|
leader_user_id_bytes = redis_client.lindex(workflow_order_key, 0)
|
||||||
|
is_leader = leader_user_id_bytes and leader_user_id_bytes.decode("utf-8") == user_id
|
||||||
|
|
||||||
|
# Notify the connecting client of their leader status
|
||||||
|
sio.emit("status", {"isLeader": is_leader}, room=sid)
|
||||||
|
# --- End of Leader Election Logic ---
|
||||||
|
|
||||||
|
redis_client.hset(workflow_users_key, user_id, json.dumps(user_info))
|
||||||
|
redis_client.set(f"ws_sid_map:{sid}", json.dumps({"workflow_id": workflow_id, "user_id": user_id}))
|
||||||
|
|
||||||
|
sio.enter_room(sid, workflow_id)
|
||||||
|
broadcast_online_users(workflow_id)
|
||||||
|
|
||||||
|
return {"msg": "connected", "user_id": user_id, "sid": sid}
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on("disconnect")
|
||||||
|
def handle_disconnect(sid):
|
||||||
|
"""
|
||||||
|
Handle user disconnect event, remove user from workflow's online user list.
|
||||||
|
"""
|
||||||
|
mapping = redis_client.get(f"ws_sid_map:{sid}")
|
||||||
|
if mapping:
|
||||||
|
data = json.loads(mapping)
|
||||||
|
workflow_id = data["workflow_id"]
|
||||||
|
user_id = data["user_id"]
|
||||||
|
|
||||||
|
workflow_users_key = f"workflow_online_users:{workflow_id}"
|
||||||
|
workflow_order_key = f"workflow_user_order:{workflow_id}"
|
||||||
|
|
||||||
|
# Get leader before any modification
|
||||||
|
leader_user_id_bytes = redis_client.lindex(workflow_order_key, 0)
|
||||||
|
was_leader = leader_user_id_bytes and leader_user_id_bytes.decode("utf-8") == user_id
|
||||||
|
|
||||||
|
# Remove user
|
||||||
|
redis_client.hdel(workflow_users_key, user_id)
|
||||||
|
redis_client.delete(f"ws_sid_map:{sid}")
|
||||||
|
redis_client.lrem(workflow_order_key, 0, user_id)
|
||||||
|
|
||||||
|
# Check if leader disconnected and a new one needs to be elected
|
||||||
|
if was_leader:
|
||||||
|
new_leader_user_id_bytes = redis_client.lindex(workflow_order_key, 0)
|
||||||
|
if new_leader_user_id_bytes:
|
||||||
|
new_leader_user_id = new_leader_user_id_bytes.decode("utf-8")
|
||||||
|
# get new leader's info to find their sid
|
||||||
|
new_leader_info_json = redis_client.hget(workflow_users_key, new_leader_user_id)
|
||||||
|
if new_leader_info_json:
|
||||||
|
new_leader_info = json.loads(new_leader_info_json)
|
||||||
|
new_leader_sid = new_leader_info.get("sid")
|
||||||
|
if new_leader_sid:
|
||||||
|
sio.emit("status", {"isLeader": True}, room=new_leader_sid)
|
||||||
|
|
||||||
|
# If the room is empty, clean up the redis key
|
||||||
|
if redis_client.llen(workflow_order_key) == 0:
|
||||||
|
redis_client.delete(workflow_order_key)
|
||||||
|
|
||||||
|
broadcast_online_users(workflow_id)
|
||||||
|
|
||||||
|
|
||||||
|
def broadcast_online_users(workflow_id):
|
||||||
|
"""
|
||||||
|
broadcast online users to the workflow room
|
||||||
|
"""
|
||||||
|
workflow_users_key = f"workflow_online_users:{workflow_id}"
|
||||||
|
workflow_order_key = f"workflow_user_order:{workflow_id}"
|
||||||
|
|
||||||
|
# The first user in the list is the leader
|
||||||
|
leader_user_id_bytes = redis_client.lindex(workflow_order_key, 0)
|
||||||
|
leader_user_id = leader_user_id_bytes.decode("utf-8") if leader_user_id_bytes else None
|
||||||
|
|
||||||
|
users_json = redis_client.hgetall(workflow_users_key)
|
||||||
|
users = []
|
||||||
|
for _, user_info_json in users_json.items():
|
||||||
|
try:
|
||||||
|
users.append(json.loads(user_info_json))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
sio.emit(
|
||||||
|
"online_users",
|
||||||
|
{"workflow_id": workflow_id, "users": users, "leader": leader_user_id},
|
||||||
|
room=workflow_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on("collaboration_event")
|
||||||
|
def handle_collaboration_event(sid, data):
|
||||||
|
"""
|
||||||
|
Handle general collaboration events, include:
|
||||||
|
1. mouseMove
|
||||||
|
2. varsAndFeaturesUpdate
|
||||||
|
|
||||||
|
"""
|
||||||
|
mapping = redis_client.get(f"ws_sid_map:{sid}")
|
||||||
|
|
||||||
|
if not mapping:
|
||||||
|
return {"msg": "unauthorized"}, 401
|
||||||
|
|
||||||
|
mapping_data = json.loads(mapping)
|
||||||
|
workflow_id = mapping_data["workflow_id"]
|
||||||
|
user_id = mapping_data["user_id"]
|
||||||
|
|
||||||
|
event_type = data.get("type")
|
||||||
|
event_data = data.get("data")
|
||||||
|
timestamp = data.get("timestamp", int(time.time()))
|
||||||
|
|
||||||
|
if not event_type:
|
||||||
|
return {"msg": "invalid event type"}, 400
|
||||||
|
|
||||||
|
sio.emit(
|
||||||
|
"collaboration_update",
|
||||||
|
{"type": event_type, "userId": user_id, "data": event_data, "timestamp": timestamp},
|
||||||
|
room=workflow_id,
|
||||||
|
skip_sid=sid,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"msg": "event_broadcasted"}
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on("graph_event")
|
||||||
|
def handle_graph_event(sid, data):
|
||||||
|
"""
|
||||||
|
Handle graph events - simple broadcast relay.
|
||||||
|
"""
|
||||||
|
mapping = redis_client.get(f"ws_sid_map:{sid}")
|
||||||
|
|
||||||
|
if not mapping:
|
||||||
|
return {"msg": "unauthorized"}, 401
|
||||||
|
|
||||||
|
mapping_data = json.loads(mapping)
|
||||||
|
workflow_id = mapping_data["workflow_id"]
|
||||||
|
|
||||||
|
sio.emit("graph_update", data, room=workflow_id, skip_sid=sid)
|
||||||
|
|
||||||
|
return {"msg": "graph_update_broadcasted"}
|
||||||
@ -23,6 +23,7 @@ from core.helper.trace_id_helper import get_external_trace_id
|
|||||||
from core.workflow.graph_engine.manager import GraphEngineManager
|
from core.workflow.graph_engine.manager import GraphEngineManager
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from factories import file_factory, variable_factory
|
from factories import file_factory, variable_factory
|
||||||
|
from fields.online_user_fields import online_user_list_fields
|
||||||
from fields.workflow_fields import workflow_fields, workflow_pagination_fields
|
from fields.workflow_fields import workflow_fields, workflow_pagination_fields
|
||||||
from fields.workflow_run_fields import workflow_run_node_execution_fields
|
from fields.workflow_run_fields import workflow_run_node_execution_fields
|
||||||
from libs import helper
|
from libs import helper
|
||||||
@ -1004,3 +1005,101 @@ class DraftWorkflowNodeLastRunApi(Resource):
|
|||||||
if node_exec is None:
|
if node_exec is None:
|
||||||
raise NotFound("last run not found")
|
raise NotFound("last run not found")
|
||||||
return node_exec
|
return node_exec
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowOnlineUsersApi(Resource):
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@marshal_with(online_user_list_fields)
|
||||||
|
def get(self):
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument("workflow_ids", type=str, required=True, location="args")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
workflow_ids = [id.strip() for id in args["workflow_ids"].split(",")]
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
for workflow_id in workflow_ids:
|
||||||
|
users_json = redis_client.hgetall(f"workflow_online_users:{workflow_id}")
|
||||||
|
|
||||||
|
users = []
|
||||||
|
for _, user_info_json in users_json.items():
|
||||||
|
try:
|
||||||
|
users.append(json.loads(user_info_json))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
results[workflow_id] = users
|
||||||
|
|
||||||
|
return {"data": results}
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(
|
||||||
|
DraftWorkflowApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/draft",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
WorkflowConfigApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/draft/config",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
AdvancedChatDraftWorkflowRunApi,
|
||||||
|
"/apps/<uuid:app_id>/advanced-chat/workflows/draft/run",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
DraftWorkflowRunApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/draft/run",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
WorkflowTaskStopApi,
|
||||||
|
"/apps/<uuid:app_id>/workflow-runs/tasks/<string:task_id>/stop",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
DraftWorkflowNodeRunApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/run",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
AdvancedChatDraftRunIterationNodeApi,
|
||||||
|
"/apps/<uuid:app_id>/advanced-chat/workflows/draft/iteration/nodes/<string:node_id>/run",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
WorkflowDraftRunIterationNodeApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/draft/iteration/nodes/<string:node_id>/run",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
AdvancedChatDraftRunLoopNodeApi,
|
||||||
|
"/apps/<uuid:app_id>/advanced-chat/workflows/draft/loop/nodes/<string:node_id>/run",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
WorkflowDraftRunLoopNodeApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/draft/loop/nodes/<string:node_id>/run",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
PublishedWorkflowApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/publish",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
PublishedAllWorkflowApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
DefaultBlockConfigsApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/default-workflow-block-configs",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
DefaultBlockConfigApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/default-workflow-block-configs/<string:block_type>",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
ConvertToWorkflowApi,
|
||||||
|
"/apps/<uuid:app_id>/convert-to-workflow",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
WorkflowByIdApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/<string:workflow_id>",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
DraftWorkflowNodeLastRunApi,
|
||||||
|
"/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/last-run",
|
||||||
|
)
|
||||||
|
api.add_resource(WorkflowOnlineUsersApi, "/apps/workflows/online-users")
|
||||||
|
|||||||
@ -38,6 +38,7 @@ elif [[ "${MODE}" == "beat" ]]; then
|
|||||||
exec celery -A app.celery beat --loglevel ${LOG_LEVEL:-INFO}
|
exec celery -A app.celery beat --loglevel ${LOG_LEVEL:-INFO}
|
||||||
else
|
else
|
||||||
if [[ "${DEBUG}" == "true" ]]; then
|
if [[ "${DEBUG}" == "true" ]]; then
|
||||||
|
# TODO: add socketio support
|
||||||
exec flask run --host=${DIFY_BIND_ADDRESS:-0.0.0.0} --port=${DIFY_PORT:-5001} --debug
|
exec flask run --host=${DIFY_BIND_ADDRESS:-0.0.0.0} --port=${DIFY_PORT:-5001} --debug
|
||||||
else
|
else
|
||||||
exec gunicorn \
|
exec gunicorn \
|
||||||
|
|||||||
5
api/extensions/ext_socketio.py
Normal file
5
api/extensions/ext_socketio.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import socketio
|
||||||
|
|
||||||
|
from configs import dify_config
|
||||||
|
|
||||||
|
sio = socketio.Server(async_mode="gevent", cors_allowed_origins=dify_config.CONSOLE_CORS_ALLOW_ORIGINS)
|
||||||
17
api/fields/online_user_fields.py
Normal file
17
api/fields/online_user_fields.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from flask_restful import fields
|
||||||
|
|
||||||
|
online_user_partial_fields = {
|
||||||
|
"id": fields.String,
|
||||||
|
"username": fields.String,
|
||||||
|
"avatar": fields.String,
|
||||||
|
"sid": fields.String,
|
||||||
|
}
|
||||||
|
|
||||||
|
workflow_online_users_fields = {
|
||||||
|
"workflow_id": fields.String,
|
||||||
|
"users": fields.List(fields.Nested(online_user_partial_fields)),
|
||||||
|
}
|
||||||
|
|
||||||
|
online_user_list_fields = {
|
||||||
|
"data": fields.List(fields.Nested(workflow_online_users_fields)),
|
||||||
|
}
|
||||||
@ -71,6 +71,7 @@ dependencies = [
|
|||||||
"pypdfium2==4.30.0",
|
"pypdfium2==4.30.0",
|
||||||
"python-docx~=1.1.0",
|
"python-docx~=1.1.0",
|
||||||
"python-dotenv==1.0.1",
|
"python-dotenv==1.0.1",
|
||||||
|
"python-socketio~=5.13.0",
|
||||||
"pyyaml~=6.0.1",
|
"pyyaml~=6.0.1",
|
||||||
"readabilipy~=0.3.0",
|
"readabilipy~=0.3.0",
|
||||||
"redis[hiredis]~=6.1.0",
|
"redis[hiredis]~=6.1.0",
|
||||||
|
|||||||
8421
api/uv.lock
generated
8421
api/uv.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||||
|
import type { Features as FeaturesData } from '@/app/components/base/features/types'
|
||||||
|
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||||
|
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
||||||
import { WorkflowWithInnerContext } from '@/app/components/workflow'
|
import { WorkflowWithInnerContext } from '@/app/components/workflow'
|
||||||
import type { WorkflowProps } from '@/app/components/workflow'
|
import type { WorkflowProps } from '@/app/components/workflow'
|
||||||
import WorkflowChildren from './workflow-children'
|
import WorkflowChildren from './workflow-children'
|
||||||
|
import UserCursors from '@/app/components/workflow/collaboration/components/user-cursors'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useAvailableNodesMetaData,
|
useAvailableNodesMetaData,
|
||||||
useConfigsMap,
|
useConfigsMap,
|
||||||
@ -18,7 +26,11 @@ import {
|
|||||||
useWorkflowRun,
|
useWorkflowRun,
|
||||||
useWorkflowStartRun,
|
useWorkflowStartRun,
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||||
|
import { useCollaboration } from '@/app/components/workflow/collaboration'
|
||||||
|
import { collaborationManager } from '@/app/components/workflow/collaboration'
|
||||||
|
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||||
|
import { useStoreApi } from 'reactflow'
|
||||||
|
|
||||||
type WorkflowMainProps = Pick<WorkflowProps, 'nodes' | 'edges' | 'viewport'>
|
type WorkflowMainProps = Pick<WorkflowProps, 'nodes' | 'edges' | 'viewport'>
|
||||||
const WorkflowMain = ({
|
const WorkflowMain = ({
|
||||||
@ -28,6 +40,20 @@ const WorkflowMain = ({
|
|||||||
}: WorkflowMainProps) => {
|
}: WorkflowMainProps) => {
|
||||||
const featuresStore = useFeaturesStore()
|
const featuresStore = useFeaturesStore()
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
|
const appId = useStore(s => s.appId)
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const store = useStoreApi()
|
||||||
|
const { startCursorTracking, stopCursorTracking, onlineUsers } = useCollaboration(appId, store)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (containerRef.current)
|
||||||
|
startCursorTracking(containerRef as React.RefObject<HTMLElement>)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
stopCursorTracking()
|
||||||
|
}
|
||||||
|
}, [startCursorTracking, stopCursorTracking])
|
||||||
|
|
||||||
const handleWorkflowDataUpdate = useCallback((payload: any) => {
|
const handleWorkflowDataUpdate = useCallback((payload: any) => {
|
||||||
const {
|
const {
|
||||||
@ -38,7 +64,33 @@ const WorkflowMain = ({
|
|||||||
if (features && featuresStore) {
|
if (features && featuresStore) {
|
||||||
const { setFeatures } = featuresStore.getState()
|
const { setFeatures } = featuresStore.getState()
|
||||||
|
|
||||||
setFeatures(features)
|
const transformedFeatures: FeaturesData = {
|
||||||
|
file: {
|
||||||
|
image: {
|
||||||
|
enabled: !!features.file_upload?.image?.enabled,
|
||||||
|
number_limits: features.file_upload?.image?.number_limits || 3,
|
||||||
|
transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
|
||||||
|
},
|
||||||
|
enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled),
|
||||||
|
allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],
|
||||||
|
allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`),
|
||||||
|
allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
|
||||||
|
number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3,
|
||||||
|
},
|
||||||
|
opening: {
|
||||||
|
enabled: !!features.opening_statement,
|
||||||
|
opening_statement: features.opening_statement,
|
||||||
|
suggested_questions: features.suggested_questions,
|
||||||
|
},
|
||||||
|
suggested: features.suggested_questions_after_answer || { enabled: false },
|
||||||
|
speech2text: features.speech_to_text || { enabled: false },
|
||||||
|
text2speech: features.text_to_speech || { enabled: false },
|
||||||
|
citation: features.retriever_resource || { enabled: false },
|
||||||
|
moderation: features.sensitive_word_avoidance || { enabled: false },
|
||||||
|
annotationReply: features.annotation_reply || { enabled: false },
|
||||||
|
}
|
||||||
|
|
||||||
|
setFeatures(transformedFeatures)
|
||||||
}
|
}
|
||||||
if (conversation_variables) {
|
if (conversation_variables) {
|
||||||
const { setConversationVariables } = workflowStore.getState()
|
const { setConversationVariables } = workflowStore.getState()
|
||||||
@ -50,6 +102,22 @@ const WorkflowMain = ({
|
|||||||
}
|
}
|
||||||
}, [featuresStore, workflowStore])
|
}, [featuresStore, workflowStore])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!appId) return
|
||||||
|
|
||||||
|
const unsubscribe = collaborationManager.onVarsAndFeaturesUpdate(async (update: any) => {
|
||||||
|
try {
|
||||||
|
const response = await fetchWorkflowDraft(`/apps/${appId}/workflows/draft`)
|
||||||
|
handleWorkflowDataUpdate(response)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('workflow vars and features update failed:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return unsubscribe
|
||||||
|
}, [appId, handleWorkflowDataUpdate])
|
||||||
|
|
||||||
const {
|
const {
|
||||||
doSyncWorkflowDraft,
|
doSyncWorkflowDraft,
|
||||||
syncWorkflowDraftWhenPageClose,
|
syncWorkflowDraftWhenPageClose,
|
||||||
@ -75,6 +143,19 @@ const WorkflowMain = ({
|
|||||||
} = useDSL()
|
} = useDSL()
|
||||||
|
|
||||||
const configsMap = useConfigsMap()
|
const configsMap = useConfigsMap()
|
||||||
|
|
||||||
|
const { cursors, isConnected } = useCollaboration(appId)
|
||||||
|
const [myUserId, setMyUserId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isConnected)
|
||||||
|
setMyUserId('current-user')
|
||||||
|
}, [isConnected])
|
||||||
|
|
||||||
|
const filteredCursors = Object.fromEntries(
|
||||||
|
Object.entries(cursors).filter(([userId]) => userId !== myUserId),
|
||||||
|
)
|
||||||
|
|
||||||
const { fetchInspectVars } = useSetWorkflowVarsWithValue({
|
const { fetchInspectVars } = useSetWorkflowVarsWithValue({
|
||||||
...configsMap,
|
...configsMap,
|
||||||
})
|
})
|
||||||
@ -164,15 +245,21 @@ const WorkflowMain = ({
|
|||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowWithInnerContext
|
<div
|
||||||
nodes={nodes}
|
ref={containerRef}
|
||||||
edges={edges}
|
className="relative h-full w-full"
|
||||||
viewport={viewport}
|
|
||||||
onWorkflowDataUpdate={handleWorkflowDataUpdate}
|
|
||||||
hooksStore={hooksStore as any}
|
|
||||||
>
|
>
|
||||||
<WorkflowChildren />
|
<WorkflowWithInnerContext
|
||||||
</WorkflowWithInnerContext>
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
viewport={viewport}
|
||||||
|
onWorkflowDataUpdate={handleWorkflowDataUpdate}
|
||||||
|
hooksStore={hooksStore as any}
|
||||||
|
>
|
||||||
|
<WorkflowChildren />
|
||||||
|
</WorkflowWithInnerContext>
|
||||||
|
<UserCursors cursors={filteredCursors} myUserId={myUserId} onlineUsers={onlineUsers} />
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { syncWorkflowDraft } from '@/service/workflow'
|
|||||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||||
import { API_PREFIX } from '@/config'
|
import { API_PREFIX } from '@/config'
|
||||||
import { useWorkflowRefreshDraft } from '.'
|
import { useWorkflowRefreshDraft } from '.'
|
||||||
|
import { useCollaboration } from '@/app/components/workflow/collaboration/hooks/use-collaboration'
|
||||||
|
|
||||||
export const useNodesSyncDraft = () => {
|
export const useNodesSyncDraft = () => {
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
@ -21,6 +22,7 @@ export const useNodesSyncDraft = () => {
|
|||||||
const { getNodesReadOnly } = useNodesReadOnly()
|
const { getNodesReadOnly } = useNodesReadOnly()
|
||||||
const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
|
const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const { isLeader } = useCollaboration(params.appId as string)
|
||||||
|
|
||||||
const getPostParams = useCallback(() => {
|
const getPostParams = useCallback(() => {
|
||||||
const {
|
const {
|
||||||
@ -85,13 +87,14 @@ export const useNodesSyncDraft = () => {
|
|||||||
environment_variables: environmentVariables,
|
environment_variables: environmentVariables,
|
||||||
conversation_variables: conversationVariables,
|
conversation_variables: conversationVariables,
|
||||||
hash: syncWorkflowDraftHash,
|
hash: syncWorkflowDraftHash,
|
||||||
|
_is_collaborative: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [store, featuresStore, workflowStore])
|
}, [store, featuresStore, workflowStore])
|
||||||
|
|
||||||
const syncWorkflowDraftWhenPageClose = useCallback(() => {
|
const syncWorkflowDraftWhenPageClose = useCallback(() => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly() || !isLeader)
|
||||||
return
|
return
|
||||||
const postParams = getPostParams()
|
const postParams = getPostParams()
|
||||||
|
|
||||||
@ -111,8 +114,10 @@ export const useNodesSyncDraft = () => {
|
|||||||
onSettled?: () => void
|
onSettled?: () => void
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly() || !isLeader)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
console.log('I am the leader, saving draft...')
|
||||||
const postParams = getPostParams()
|
const postParams = getPostParams()
|
||||||
|
|
||||||
if (postParams) {
|
if (postParams) {
|
||||||
@ -130,7 +135,9 @@ export const useNodesSyncDraft = () => {
|
|||||||
if (error && error.json && !error.bodyUsed) {
|
if (error && error.json && !error.bodyUsed) {
|
||||||
error.json().then((err: any) => {
|
error.json().then((err: any) => {
|
||||||
if (err.code === 'draft_workflow_not_sync' && !notRefreshWhenSyncError)
|
if (err.code === 'draft_workflow_not_sync' && !notRefreshWhenSyncError)
|
||||||
handleRefreshWorkflowDraft()
|
// TODO: hjlarry test collaboration
|
||||||
|
// handleRefreshWorkflowDraft()
|
||||||
|
console.error('draft_workflow_not_sync', err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
callback?.onError && callback.onError()
|
callback?.onError && callback.onError()
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||||
import { fetchWorkflowDraft } from '@/service/workflow'
|
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||||
import type { WorkflowDataUpdater } from '@/app/components/workflow/types'
|
|
||||||
import { useWorkflowUpdate } from '@/app/components/workflow/hooks'
|
import { useWorkflowUpdate } from '@/app/components/workflow/hooks'
|
||||||
|
|
||||||
export const useWorkflowRefreshDraft = () => {
|
export const useWorkflowRefreshDraft = () => {
|
||||||
@ -19,7 +18,8 @@ export const useWorkflowRefreshDraft = () => {
|
|||||||
} = workflowStore.getState()
|
} = workflowStore.getState()
|
||||||
setIsSyncingWorkflowDraft(true)
|
setIsSyncingWorkflowDraft(true)
|
||||||
fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => {
|
fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => {
|
||||||
handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater)
|
// TODO: hjlarry test collaboration
|
||||||
|
// handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater)
|
||||||
setSyncWorkflowDraftHash(response.hash)
|
setSyncWorkflowDraftHash(response.hash)
|
||||||
setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
|
setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
|
||||||
acc[env.id] = env.value
|
acc[env.id] = env.value
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import {
|
|||||||
import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store'
|
import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store'
|
||||||
import { createWorkflowSlice } from './store/workflow/workflow-slice'
|
import { createWorkflowSlice } from './store/workflow/workflow-slice'
|
||||||
import WorkflowAppMain from './components/workflow-main'
|
import WorkflowAppMain from './components/workflow-main'
|
||||||
|
import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager'
|
||||||
|
|
||||||
const WorkflowAppWithAdditionalContext = () => {
|
const WorkflowAppWithAdditionalContext = () => {
|
||||||
const {
|
const {
|
||||||
@ -35,15 +36,20 @@ const WorkflowAppWithAdditionalContext = () => {
|
|||||||
const { isLoadingCurrentWorkspace, currentWorkspace } = useAppContext()
|
const { isLoadingCurrentWorkspace, currentWorkspace } = useAppContext()
|
||||||
|
|
||||||
const nodesData = useMemo(() => {
|
const nodesData = useMemo(() => {
|
||||||
if (data)
|
if (data) {
|
||||||
return initialNodes(data.graph.nodes, data.graph.edges)
|
const processedNodes = initialNodes(data.graph.nodes, data.graph.edges)
|
||||||
|
collaborationManager.setNodes([], processedNodes)
|
||||||
|
return processedNodes
|
||||||
|
}
|
||||||
return []
|
return []
|
||||||
}, [data])
|
}, [data])
|
||||||
const edgesData = useMemo(() => {
|
|
||||||
if (data)
|
|
||||||
return initialEdges(data.graph.edges, data.graph.nodes)
|
|
||||||
|
|
||||||
|
const edgesData = useMemo(() => {
|
||||||
|
if (data) {
|
||||||
|
const processedEdges = initialEdges(data.graph.edges, data.graph.nodes)
|
||||||
|
collaborationManager.setEdges([], processedEdges)
|
||||||
|
return processedEdges
|
||||||
|
}
|
||||||
return []
|
return []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,75 @@
|
|||||||
|
import type { FC } from 'react'
|
||||||
|
import type { CursorPosition, OnlineUser } from '@/app/components/workflow/collaboration/types'
|
||||||
|
|
||||||
|
type UserCursorsProps = {
|
||||||
|
cursors: Record<string, CursorPosition>
|
||||||
|
myUserId: string | null
|
||||||
|
onlineUsers: OnlineUser[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserColor = (id: string) => {
|
||||||
|
const colors = ['#3B82F6', '#EF4444', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899', '#06B6D4', '#84CC16']
|
||||||
|
const hash = id.split('').reduce((a, b) => {
|
||||||
|
a = ((a << 5) - a) + b.charCodeAt(0)
|
||||||
|
return a & a
|
||||||
|
}, 0)
|
||||||
|
return colors[Math.abs(hash) % colors.length]
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserCursors: FC<UserCursorsProps> = ({
|
||||||
|
cursors,
|
||||||
|
myUserId,
|
||||||
|
onlineUsers,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Object.entries(cursors || {}).map(([userId, cursor]) => {
|
||||||
|
if (userId === myUserId)
|
||||||
|
return null
|
||||||
|
|
||||||
|
const userInfo = onlineUsers.find(user => user.user_id === userId)
|
||||||
|
const userName = userInfo?.username || `User ${userId.slice(-4)}`
|
||||||
|
const userColor = getUserColor(userId)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={userId}
|
||||||
|
className="pointer-events-none absolute z-[10000] -translate-x-0.5 -translate-y-0.5 transition-all duration-150 ease-out"
|
||||||
|
style={{
|
||||||
|
left: cursor.x,
|
||||||
|
top: cursor.y,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="drop-shadow-md"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 3L16 8L9 10L7 17L3 3Z"
|
||||||
|
fill={userColor}
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="1"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="absolute -top-0.5 left-[18px] max-w-[120px] overflow-hidden text-ellipsis whitespace-nowrap rounded px-1.5 py-0.5 text-[11px] font-medium text-white shadow-sm"
|
||||||
|
style={{
|
||||||
|
backgroundColor: userColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{userName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserCursors
|
||||||
@ -0,0 +1,273 @@
|
|||||||
|
import { LoroDoc } from 'loro-crdt'
|
||||||
|
import { isEqual } from 'lodash-es'
|
||||||
|
import { webSocketClient } from './websocket-manager'
|
||||||
|
import { CRDTProvider } from './crdt-provider'
|
||||||
|
import { EventEmitter } from './event-emitter'
|
||||||
|
import type { Edge, Node } from '../../types'
|
||||||
|
import type { CollaborationState, CursorPosition, OnlineUser } from '../types/collaboration'
|
||||||
|
|
||||||
|
export class CollaborationManager {
|
||||||
|
private doc: LoroDoc | null = null
|
||||||
|
private provider: CRDTProvider | null = null
|
||||||
|
private nodesMap: any = null
|
||||||
|
private edgesMap: any = null
|
||||||
|
private eventEmitter = new EventEmitter()
|
||||||
|
private currentAppId: string | null = null
|
||||||
|
private reactFlowStore: any = null
|
||||||
|
private isLeader = false
|
||||||
|
private leaderId: string | null = null
|
||||||
|
private cursors: Record<string, CursorPosition> = {}
|
||||||
|
|
||||||
|
init = (appId: string, reactFlowStore: any): void => {
|
||||||
|
if (!reactFlowStore) {
|
||||||
|
console.warn('CollaborationManager.init called without reactFlowStore, deferring to connect()')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.connect(appId, reactFlowStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
setNodes = (oldNodes: Node[], newNodes: Node[]): void => {
|
||||||
|
this.syncNodes(oldNodes, newNodes)
|
||||||
|
if (this.doc)
|
||||||
|
this.doc.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
setEdges = (oldEdges: Edge[], newEdges: Edge[]): void => {
|
||||||
|
this.syncEdges(oldEdges, newEdges)
|
||||||
|
if (this.doc)
|
||||||
|
this.doc.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy = (): void => {
|
||||||
|
this.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(appId: string, reactFlowStore: any): Promise<void> {
|
||||||
|
if (this.currentAppId === appId && this.doc) return
|
||||||
|
|
||||||
|
this.disconnect()
|
||||||
|
|
||||||
|
this.currentAppId = appId
|
||||||
|
this.reactFlowStore = reactFlowStore
|
||||||
|
|
||||||
|
const socket = webSocketClient.connect(appId)
|
||||||
|
this.doc = new LoroDoc()
|
||||||
|
this.nodesMap = this.doc.getMap('nodes')
|
||||||
|
this.edgesMap = this.doc.getMap('edges')
|
||||||
|
this.provider = new CRDTProvider(socket, this.doc)
|
||||||
|
|
||||||
|
this.setupSubscriptions()
|
||||||
|
this.setupSocketEventListeners(socket)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect = (): void => {
|
||||||
|
if (this.currentAppId)
|
||||||
|
webSocketClient.disconnect(this.currentAppId)
|
||||||
|
|
||||||
|
this.provider?.destroy()
|
||||||
|
this.doc = null
|
||||||
|
this.provider = null
|
||||||
|
this.nodesMap = null
|
||||||
|
this.edgesMap = null
|
||||||
|
this.currentAppId = null
|
||||||
|
this.reactFlowStore = null
|
||||||
|
this.cursors = {}
|
||||||
|
this.eventEmitter.removeAllListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnected(): boolean {
|
||||||
|
return this.currentAppId ? webSocketClient.isConnected(this.currentAppId) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodes(): Node[] {
|
||||||
|
return this.nodesMap ? Array.from(this.nodesMap.values()) : []
|
||||||
|
}
|
||||||
|
|
||||||
|
getEdges(): Edge[] {
|
||||||
|
return this.edgesMap ? Array.from(this.edgesMap.values()) : []
|
||||||
|
}
|
||||||
|
|
||||||
|
emitCursorMove(position: CursorPosition): void {
|
||||||
|
if (!this.currentAppId || !webSocketClient.isConnected(this.currentAppId)) return
|
||||||
|
|
||||||
|
const socket = webSocketClient.getSocket(this.currentAppId)
|
||||||
|
if (socket) {
|
||||||
|
socket.emit('collaboration_event', {
|
||||||
|
type: 'mouseMove',
|
||||||
|
userId: socket.id,
|
||||||
|
data: { x: position.x, y: position.y },
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onStateChange(callback: (state: Partial<CollaborationState>) => void): () => void {
|
||||||
|
return this.eventEmitter.on('stateChange', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
onCursorUpdate(callback: (cursors: Record<string, CursorPosition>) => void): () => void {
|
||||||
|
return this.eventEmitter.on('cursors', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
onOnlineUsersUpdate(callback: (users: OnlineUser[]) => void): () => void {
|
||||||
|
return this.eventEmitter.on('onlineUsers', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
onVarsAndFeaturesUpdate(callback: (update: any) => void): () => void {
|
||||||
|
return this.eventEmitter.on('varsAndFeaturesUpdate', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
onLeaderChange(callback: (isLeader: boolean) => void): () => void {
|
||||||
|
return this.eventEmitter.on('leaderChange', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private syncNodes(oldNodes: Node[], newNodes: Node[]): void {
|
||||||
|
if (!this.nodesMap) return
|
||||||
|
|
||||||
|
const oldNodesMap = new Map(oldNodes.map(node => [node.id, node]))
|
||||||
|
const newNodesMap = new Map(newNodes.map(node => [node.id, node]))
|
||||||
|
|
||||||
|
oldNodes.forEach((oldNode) => {
|
||||||
|
if (!newNodesMap.has(oldNode.id))
|
||||||
|
this.nodesMap.delete(oldNode.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
newNodes.forEach((newNode) => {
|
||||||
|
const oldNode = oldNodesMap.get(newNode.id)
|
||||||
|
if (!oldNode) {
|
||||||
|
const persistentData = this.getPersistentNodeData(newNode)
|
||||||
|
const clonedData = JSON.parse(JSON.stringify(persistentData))
|
||||||
|
this.nodesMap.set(newNode.id, clonedData)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const oldPersistentData = this.getPersistentNodeData(oldNode)
|
||||||
|
const newPersistentData = this.getPersistentNodeData(newNode)
|
||||||
|
if (!isEqual(oldPersistentData, newPersistentData)) {
|
||||||
|
const clonedData = JSON.parse(JSON.stringify(newPersistentData))
|
||||||
|
this.nodesMap.set(newNode.id, clonedData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private syncEdges(oldEdges: Edge[], newEdges: Edge[]): void {
|
||||||
|
if (!this.edgesMap) return
|
||||||
|
|
||||||
|
const oldEdgesMap = new Map(oldEdges.map(edge => [edge.id, edge]))
|
||||||
|
const newEdgesMap = new Map(newEdges.map(edge => [edge.id, edge]))
|
||||||
|
|
||||||
|
oldEdges.forEach((oldEdge) => {
|
||||||
|
if (!newEdgesMap.has(oldEdge.id))
|
||||||
|
this.edgesMap.delete(oldEdge.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
newEdges.forEach((newEdge) => {
|
||||||
|
const oldEdge = oldEdgesMap.get(newEdge.id)
|
||||||
|
if (!oldEdge) {
|
||||||
|
const clonedEdge = JSON.parse(JSON.stringify(newEdge))
|
||||||
|
this.edgesMap.set(newEdge.id, clonedEdge)
|
||||||
|
}
|
||||||
|
else if (!isEqual(oldEdge, newEdge)) {
|
||||||
|
const clonedEdge = JSON.parse(JSON.stringify(newEdge))
|
||||||
|
this.edgesMap.set(newEdge.id, clonedEdge)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPersistentNodeData(node: Node): any {
|
||||||
|
const { data, ...rest } = node
|
||||||
|
const filteredData = Object.fromEntries(
|
||||||
|
Object.entries(data).filter(([key]) => !key.startsWith('_')),
|
||||||
|
)
|
||||||
|
return { ...rest, data: filteredData }
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupSubscriptions(): void {
|
||||||
|
this.nodesMap?.subscribe((event: any) => {
|
||||||
|
if (event.by === 'import' && this.reactFlowStore) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const { setNodes } = this.reactFlowStore.getState()
|
||||||
|
const updatedNodes = Array.from(this.nodesMap.values())
|
||||||
|
setNodes(updatedNodes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.edgesMap?.subscribe((event: any) => {
|
||||||
|
if (event.by === 'import' && this.reactFlowStore) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const { setEdges } = this.reactFlowStore.getState()
|
||||||
|
const updatedEdges = Array.from(this.edgesMap.values())
|
||||||
|
setEdges(updatedEdges)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupSocketEventListeners(socket: any): void {
|
||||||
|
console.log('Setting up socket event listeners for collaboration')
|
||||||
|
|
||||||
|
socket.on('collaboration_update', (update: any) => {
|
||||||
|
if (update.type === 'mouseMove') {
|
||||||
|
console.log('Processing mouseMove event:', update)
|
||||||
|
|
||||||
|
// Update cursor state for this user
|
||||||
|
this.cursors[update.userId] = {
|
||||||
|
x: update.data.x,
|
||||||
|
y: update.data.y,
|
||||||
|
userId: update.userId,
|
||||||
|
timestamp: update.timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit the complete cursor state
|
||||||
|
console.log('Emitting complete cursor state:', this.cursors)
|
||||||
|
this.eventEmitter.emit('cursors', { ...this.cursors })
|
||||||
|
}
|
||||||
|
else if (update.type === 'varsAndFeaturesUpdate') {
|
||||||
|
console.log('Processing varsAndFeaturesUpdate event:', update)
|
||||||
|
this.eventEmitter.emit('varsAndFeaturesUpdate', update)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('online_users', (data: { users: OnlineUser[]; leader: string }) => {
|
||||||
|
const onlineUserIds = new Set(data.users.map(user => user.user_id))
|
||||||
|
|
||||||
|
// Remove cursors for offline users
|
||||||
|
Object.keys(this.cursors).forEach((userId) => {
|
||||||
|
if (!onlineUserIds.has(userId))
|
||||||
|
delete this.cursors[userId]
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Updated online users and cleaned offline cursors:', data.users)
|
||||||
|
this.leaderId = data.leader
|
||||||
|
this.eventEmitter.emit('onlineUsers', data.users)
|
||||||
|
this.eventEmitter.emit('cursors', { ...this.cursors })
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('status', (data: { isLeader: boolean }) => {
|
||||||
|
if (this.isLeader !== data.isLeader) {
|
||||||
|
this.isLeader = data.isLeader
|
||||||
|
console.log(`Collaboration: I am now the ${this.isLeader ? 'Leader' : 'Follower'}.`)
|
||||||
|
this.eventEmitter.emit('leaderChange', this.isLeader)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('status', (data: { isLeader: boolean }) => {
|
||||||
|
if (this.isLeader !== data.isLeader) {
|
||||||
|
this.isLeader = data.isLeader
|
||||||
|
console.log(`Collaboration: I am now the ${this.isLeader ? 'Leader' : 'Follower'}.`)
|
||||||
|
this.eventEmitter.emit('leaderChange', this.isLeader)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('connect', () => {
|
||||||
|
this.eventEmitter.emit('stateChange', { isConnected: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
this.eventEmitter.emit('stateChange', { isConnected: false })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const collaborationManager = new CollaborationManager()
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import type { LoroDoc } from 'loro-crdt'
|
||||||
|
import type { Socket } from 'socket.io-client'
|
||||||
|
|
||||||
|
export class CRDTProvider {
|
||||||
|
private doc: LoroDoc
|
||||||
|
private socket: Socket
|
||||||
|
|
||||||
|
constructor(socket: Socket, doc: LoroDoc) {
|
||||||
|
this.socket = socket
|
||||||
|
this.doc = doc
|
||||||
|
this.setupEventListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupEventListeners(): void {
|
||||||
|
this.doc.subscribe((event: any) => {
|
||||||
|
if (event.by === 'local') {
|
||||||
|
const update = this.doc.export({ mode: 'update' })
|
||||||
|
this.socket.emit('graph_event', update)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.socket.on('graph_update', (updateData: Uint8Array) => {
|
||||||
|
try {
|
||||||
|
const data = new Uint8Array(updateData)
|
||||||
|
this.doc.import(data)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Error importing graph update:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
this.socket.off('graph_update')
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
export type EventHandler<T = any> = (data: T) => void
|
||||||
|
|
||||||
|
export class EventEmitter {
|
||||||
|
private events: Map<string, Set<EventHandler>> = new Map()
|
||||||
|
|
||||||
|
on<T = any>(event: string, handler: EventHandler<T>): () => void {
|
||||||
|
if (!this.events.has(event))
|
||||||
|
this.events.set(event, new Set())
|
||||||
|
|
||||||
|
this.events.get(event)!.add(handler)
|
||||||
|
|
||||||
|
return () => this.off(event, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
off<T = any>(event: string, handler?: EventHandler<T>): void {
|
||||||
|
if (!this.events.has(event)) return
|
||||||
|
|
||||||
|
const handlers = this.events.get(event)!
|
||||||
|
if (handler)
|
||||||
|
handlers.delete(handler)
|
||||||
|
else
|
||||||
|
handlers.clear()
|
||||||
|
|
||||||
|
if (handlers.size === 0)
|
||||||
|
this.events.delete(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
emit<T = any>(event: string, data: T): void {
|
||||||
|
if (!this.events.has(event)) return
|
||||||
|
|
||||||
|
const handlers = this.events.get(event)!
|
||||||
|
handlers.forEach((handler) => {
|
||||||
|
try {
|
||||||
|
handler(data)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(`Error in event handler for ${event}:`, error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAllListeners(): void {
|
||||||
|
this.events.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
getListenerCount(event: string): number {
|
||||||
|
return this.events.get(event)?.size || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
import type { Socket } from 'socket.io-client'
|
||||||
|
import { io } from 'socket.io-client'
|
||||||
|
import type { DebugInfo, WebSocketConfig } from '../types/websocket'
|
||||||
|
|
||||||
|
export class WebSocketClient {
|
||||||
|
private connections: Map<string, Socket> = new Map()
|
||||||
|
private connecting: Set<string> = new Set()
|
||||||
|
private config: WebSocketConfig
|
||||||
|
|
||||||
|
constructor(config: WebSocketConfig = {}) {
|
||||||
|
this.config = {
|
||||||
|
url: config.url || process.env.NEXT_PUBLIC_SOCKET_URL || 'ws://localhost:5001',
|
||||||
|
transports: config.transports || ['websocket'],
|
||||||
|
withCredentials: config.withCredentials !== false,
|
||||||
|
...config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(appId: string): Socket {
|
||||||
|
const existingSocket = this.connections.get(appId)
|
||||||
|
if (existingSocket?.connected)
|
||||||
|
return existingSocket
|
||||||
|
|
||||||
|
if (this.connecting.has(appId)) {
|
||||||
|
const pendingSocket = this.connections.get(appId)
|
||||||
|
if (pendingSocket)
|
||||||
|
return pendingSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingSocket && !existingSocket.connected) {
|
||||||
|
existingSocket.disconnect()
|
||||||
|
this.connections.delete(appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connecting.add(appId)
|
||||||
|
|
||||||
|
const authToken = localStorage.getItem('console_token')
|
||||||
|
const socket = io(this.config.url!, {
|
||||||
|
path: '/socket.io',
|
||||||
|
transports: this.config.transports,
|
||||||
|
auth: { token: authToken },
|
||||||
|
withCredentials: this.config.withCredentials,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.connections.set(appId, socket)
|
||||||
|
this.setupBaseEventListeners(socket, appId)
|
||||||
|
|
||||||
|
return socket
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(appId?: string): void {
|
||||||
|
if (appId) {
|
||||||
|
const socket = this.connections.get(appId)
|
||||||
|
if (socket) {
|
||||||
|
socket.disconnect()
|
||||||
|
this.connections.delete(appId)
|
||||||
|
this.connecting.delete(appId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.connections.forEach(socket => socket.disconnect())
|
||||||
|
this.connections.clear()
|
||||||
|
this.connecting.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSocket(appId: string): Socket | null {
|
||||||
|
return this.connections.get(appId) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnected(appId: string): boolean {
|
||||||
|
return this.connections.get(appId)?.connected || false
|
||||||
|
}
|
||||||
|
|
||||||
|
getConnectedApps(): string[] {
|
||||||
|
const connectedApps: string[] = []
|
||||||
|
this.connections.forEach((socket, appId) => {
|
||||||
|
if (socket.connected)
|
||||||
|
connectedApps.push(appId)
|
||||||
|
})
|
||||||
|
return connectedApps
|
||||||
|
}
|
||||||
|
|
||||||
|
getDebugInfo(): DebugInfo {
|
||||||
|
const info: DebugInfo = {}
|
||||||
|
this.connections.forEach((socket, appId) => {
|
||||||
|
info[appId] = {
|
||||||
|
connected: socket.connected,
|
||||||
|
connecting: this.connecting.has(appId),
|
||||||
|
socketId: socket.id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupBaseEventListeners(socket: Socket, appId: string): void {
|
||||||
|
socket.on('connect', () => {
|
||||||
|
this.connecting.delete(appId)
|
||||||
|
socket.emit('user_connect', { workflow_id: appId })
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
this.connecting.delete(appId)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('connect_error', () => {
|
||||||
|
this.connecting.delete(appId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const webSocketClient = new WebSocketClient()
|
||||||
|
|
||||||
|
export const fetchAppsOnlineUsers = async (appIds: string[]) => {
|
||||||
|
const response = await fetch(`/api/online-users?${new URLSearchParams({
|
||||||
|
app_ids: appIds.join(','),
|
||||||
|
})}`)
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { collaborationManager } from '../core/collaboration-manager'
|
||||||
|
import { CursorService } from '../services/cursor-service'
|
||||||
|
import type { CollaborationState } from '../types/collaboration'
|
||||||
|
|
||||||
|
export function useCollaboration(appId: string, reactFlowStore?: any) {
|
||||||
|
const [state, setState] = useState<Partial<CollaborationState & { isLeader: boolean }>>({
|
||||||
|
isConnected: false,
|
||||||
|
onlineUsers: [],
|
||||||
|
cursors: {},
|
||||||
|
isLeader: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const cursorServiceRef = useRef<CursorService | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!appId) return
|
||||||
|
|
||||||
|
if (!cursorServiceRef.current) {
|
||||||
|
cursorServiceRef.current = new CursorService({
|
||||||
|
minMoveDistance: 10,
|
||||||
|
throttleMs: 300,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const initCollaboration = async () => {
|
||||||
|
await collaborationManager.connect(appId, reactFlowStore)
|
||||||
|
setState((prev: any) => ({ ...prev, appId, isConnected: collaborationManager.isConnected() }))
|
||||||
|
}
|
||||||
|
|
||||||
|
initCollaboration()
|
||||||
|
|
||||||
|
const unsubscribeStateChange = collaborationManager.onStateChange((newState: any) => {
|
||||||
|
console.log('Collaboration state change:', newState)
|
||||||
|
setState((prev: any) => ({ ...prev, ...newState }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const unsubscribeCursors = collaborationManager.onCursorUpdate((cursors: any) => {
|
||||||
|
console.log('Cursor update received:', cursors)
|
||||||
|
setState((prev: any) => ({ ...prev, cursors }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const unsubscribeUsers = collaborationManager.onOnlineUsersUpdate((users: any) => {
|
||||||
|
console.log('Online users update:', users)
|
||||||
|
setState((prev: any) => ({ ...prev, onlineUsers: users }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const unsubscribeLeaderChange = collaborationManager.onLeaderChange((isLeader: boolean) => {
|
||||||
|
setState((prev: any) => ({ ...prev, isLeader }))
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribeStateChange()
|
||||||
|
unsubscribeCursors()
|
||||||
|
unsubscribeUsers()
|
||||||
|
unsubscribeLeaderChange()
|
||||||
|
cursorServiceRef.current?.stopTracking()
|
||||||
|
collaborationManager.disconnect()
|
||||||
|
}
|
||||||
|
}, [appId, reactFlowStore])
|
||||||
|
|
||||||
|
const startCursorTracking = (containerRef: React.RefObject<HTMLElement>) => {
|
||||||
|
if (cursorServiceRef.current) {
|
||||||
|
cursorServiceRef.current.startTracking(containerRef, (position) => {
|
||||||
|
collaborationManager.emitCursorMove(position)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopCursorTracking = () => {
|
||||||
|
cursorServiceRef.current?.stopTracking()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isConnected: state.isConnected || false,
|
||||||
|
onlineUsers: state.onlineUsers || [],
|
||||||
|
cursors: state.cursors || {},
|
||||||
|
isLeader: state.isLeader || false,
|
||||||
|
startCursorTracking,
|
||||||
|
stopCursorTracking,
|
||||||
|
}
|
||||||
|
}
|
||||||
5
web/app/components/workflow/collaboration/index.ts
Normal file
5
web/app/components/workflow/collaboration/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export { collaborationManager } from './core/collaboration-manager'
|
||||||
|
export { webSocketClient, fetchAppsOnlineUsers } from './core/websocket-manager'
|
||||||
|
export { CursorService } from './services/cursor-service'
|
||||||
|
export { useCollaboration } from './hooks/use-collaboration'
|
||||||
|
export * from './types'
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
import type { RefObject } from 'react'
|
||||||
|
import type { CursorPosition } from '../types/collaboration'
|
||||||
|
|
||||||
|
export type CursorServiceConfig = {
|
||||||
|
minMoveDistance?: number
|
||||||
|
throttleMs?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CursorService {
|
||||||
|
private containerRef: RefObject<HTMLElement> | null = null
|
||||||
|
private isTracking = false
|
||||||
|
private onCursorUpdate: ((cursors: Record<string, CursorPosition>) => void) | null = null
|
||||||
|
private onEmitPosition: ((position: CursorPosition) => void) | null = null
|
||||||
|
private lastEmitTime = 0
|
||||||
|
private lastPosition: { x: number; y: number } | null = null
|
||||||
|
private config: Required<CursorServiceConfig>
|
||||||
|
|
||||||
|
constructor(config: CursorServiceConfig = {}) {
|
||||||
|
this.config = {
|
||||||
|
minMoveDistance: config.minMoveDistance ?? 5,
|
||||||
|
throttleMs: config.throttleMs ?? 300,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startTracking(
|
||||||
|
containerRef: RefObject<HTMLElement>,
|
||||||
|
onEmitPosition: (position: CursorPosition) => void,
|
||||||
|
): void {
|
||||||
|
if (this.isTracking) this.stopTracking()
|
||||||
|
|
||||||
|
this.containerRef = containerRef
|
||||||
|
this.onEmitPosition = onEmitPosition
|
||||||
|
this.isTracking = true
|
||||||
|
|
||||||
|
if (containerRef.current)
|
||||||
|
containerRef.current.addEventListener('mousemove', this.handleMouseMove)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopTracking(): void {
|
||||||
|
if (this.containerRef?.current)
|
||||||
|
this.containerRef.current.removeEventListener('mousemove', this.handleMouseMove)
|
||||||
|
|
||||||
|
this.containerRef = null
|
||||||
|
this.onEmitPosition = null
|
||||||
|
this.isTracking = false
|
||||||
|
this.lastPosition = null
|
||||||
|
}
|
||||||
|
|
||||||
|
setCursorUpdateHandler(handler: (cursors: Record<string, CursorPosition>) => void): void {
|
||||||
|
this.onCursorUpdate = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCursors(cursors: Record<string, CursorPosition>): void {
|
||||||
|
if (this.onCursorUpdate)
|
||||||
|
this.onCursorUpdate(cursors)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMouseMove = (event: MouseEvent): void => {
|
||||||
|
if (!this.containerRef?.current || !this.onEmitPosition) return
|
||||||
|
|
||||||
|
const rect = this.containerRef.current.getBoundingClientRect()
|
||||||
|
const x = event.clientX - rect.left
|
||||||
|
const y = event.clientY - rect.top
|
||||||
|
|
||||||
|
if (x >= 0 && y >= 0 && x <= rect.width && y <= rect.height) {
|
||||||
|
const now = Date.now()
|
||||||
|
const timeThrottled = now - this.lastEmitTime > this.config.throttleMs
|
||||||
|
const distanceThrottled = !this.lastPosition
|
||||||
|
|| (Math.abs(x - this.lastPosition.x) > this.config.minMoveDistance
|
||||||
|
|| Math.abs(y - this.lastPosition.y) > this.config.minMoveDistance)
|
||||||
|
|
||||||
|
if (timeThrottled && distanceThrottled) {
|
||||||
|
this.lastPosition = { x, y }
|
||||||
|
this.lastEmitTime = now
|
||||||
|
this.onEmitPosition({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
userId: '',
|
||||||
|
timestamp: now,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import type { Edge, Node } from '../../types'
|
||||||
|
|
||||||
|
export type OnlineUser = {
|
||||||
|
user_id: string
|
||||||
|
username: string
|
||||||
|
avatar: string
|
||||||
|
sid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkflowOnlineUsers = {
|
||||||
|
workflow_id: string
|
||||||
|
users: OnlineUser[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OnlineUserListResponse = {
|
||||||
|
data: WorkflowOnlineUsers[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CursorPosition = {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
userId: string
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CollaborationState = {
|
||||||
|
appId: string
|
||||||
|
isConnected: boolean
|
||||||
|
onlineUsers: OnlineUser[]
|
||||||
|
cursors: Record<string, CursorPosition>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GraphSyncData = {
|
||||||
|
nodes: Node[]
|
||||||
|
edges: Edge[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CollaborationUpdate = {
|
||||||
|
type: 'mouseMove' | 'graphUpdate' | 'userJoin' | 'userLeave'
|
||||||
|
userId: string
|
||||||
|
data: any
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
38
web/app/components/workflow/collaboration/types/events.ts
Normal file
38
web/app/components/workflow/collaboration/types/events.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
export type CollaborationEvent = {
|
||||||
|
type: string
|
||||||
|
data: any
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GraphUpdateEvent = {
|
||||||
|
type: 'graph_update'
|
||||||
|
data: Uint8Array
|
||||||
|
} & CollaborationEvent
|
||||||
|
|
||||||
|
export type CursorMoveEvent = {
|
||||||
|
type: 'cursor_move'
|
||||||
|
data: {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
userId: string
|
||||||
|
}
|
||||||
|
} & CollaborationEvent
|
||||||
|
|
||||||
|
export type UserConnectEvent = {
|
||||||
|
type: 'user_connect'
|
||||||
|
data: {
|
||||||
|
workflow_id: string
|
||||||
|
}
|
||||||
|
} & CollaborationEvent
|
||||||
|
|
||||||
|
export type OnlineUsersEvent = {
|
||||||
|
type: 'online_users'
|
||||||
|
data: {
|
||||||
|
users: Array<{
|
||||||
|
user_id: string
|
||||||
|
username: string
|
||||||
|
avatar: string
|
||||||
|
sid: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
} & CollaborationEvent
|
||||||
3
web/app/components/workflow/collaboration/types/index.ts
Normal file
3
web/app/components/workflow/collaboration/types/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './websocket'
|
||||||
|
export * from './collaboration'
|
||||||
|
export * from './events'
|
||||||
16
web/app/components/workflow/collaboration/types/websocket.ts
Normal file
16
web/app/components/workflow/collaboration/types/websocket.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export type WebSocketConfig = {
|
||||||
|
url?: string
|
||||||
|
token?: string
|
||||||
|
transports?: string[]
|
||||||
|
withCredentials?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConnectionInfo = {
|
||||||
|
connected: boolean
|
||||||
|
connecting: boolean
|
||||||
|
socketId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DebugInfo = {
|
||||||
|
[appId: string]: ConnectionInfo
|
||||||
|
}
|
||||||
@ -14,14 +14,15 @@ import useConfig from './nodes/start/use-config'
|
|||||||
import type { StartNodeType } from './nodes/start/types'
|
import type { StartNodeType } from './nodes/start/types'
|
||||||
import type { PromptVariable } from '@/models/debug'
|
import type { PromptVariable } from '@/models/debug'
|
||||||
import NewFeaturePanel from '@/app/components/base/features/new-feature-panel'
|
import NewFeaturePanel from '@/app/components/base/features/new-feature-panel'
|
||||||
|
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
|
||||||
|
|
||||||
const Features = () => {
|
const Features = () => {
|
||||||
const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel)
|
const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel)
|
||||||
|
const appId = useStore(s => s.appId)
|
||||||
const isChatMode = useIsChatMode()
|
const isChatMode = useIsChatMode()
|
||||||
const { nodesReadOnly } = useNodesReadOnly()
|
const { nodesReadOnly } = useNodesReadOnly()
|
||||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
const nodes = useNodes<CommonNodeType>()
|
const nodes = useNodes<CommonNodeType>()
|
||||||
|
|
||||||
const startNode = nodes.find(node => node.data.type === 'start')
|
const startNode = nodes.find(node => node.data.type === 'start')
|
||||||
const { id, data } = startNode as Node<StartNodeType>
|
const { id, data } = startNode as Node<StartNodeType>
|
||||||
const { handleAddVariable } = useConfig(id, data)
|
const { handleAddVariable } = useConfig(id, data)
|
||||||
@ -40,9 +41,20 @@ const Features = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleFeaturesChange = useCallback(() => {
|
const handleFeaturesChange = useCallback(() => {
|
||||||
handleSyncWorkflowDraft()
|
doSyncWorkflowDraft(false, {
|
||||||
|
onSuccess() {
|
||||||
|
if (appId) {
|
||||||
|
const socket = webSocketClient.getSocket(appId)
|
||||||
|
if (socket) {
|
||||||
|
socket.emit('collaboration_event', {
|
||||||
|
type: 'varsAndFeaturesUpdate',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
setShowFeaturesPanel(true)
|
setShowFeaturesPanel(true)
|
||||||
}, [handleSyncWorkflowDraft, setShowFeaturesPanel])
|
}, [doSyncWorkflowDraft, setShowFeaturesPanel, appId])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewFeaturePanel
|
<NewFeaturePanel
|
||||||
|
|||||||
@ -0,0 +1,45 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import { useStoreApi } from 'reactflow'
|
||||||
|
import type { Edge, Node } from '../types'
|
||||||
|
import { collaborationManager } from '../collaboration/core/collaboration-manager'
|
||||||
|
|
||||||
|
export const useCollaborativeWorkflow = () => {
|
||||||
|
const store = useStoreApi()
|
||||||
|
const { setNodes: collabSetNodes, setEdges: collabSetEdges } = collaborationManager
|
||||||
|
|
||||||
|
const setNodes = useCallback((newNodes: Node[], shouldBroadcast: boolean = true) => {
|
||||||
|
const { getNodes, setNodes: reactFlowSetNodes } = store.getState()
|
||||||
|
if (shouldBroadcast) {
|
||||||
|
const oldNodes = getNodes()
|
||||||
|
collabSetNodes(oldNodes, newNodes)
|
||||||
|
}
|
||||||
|
reactFlowSetNodes(newNodes)
|
||||||
|
}, [store, collabSetNodes])
|
||||||
|
|
||||||
|
const setEdges = useCallback((newEdges: Edge[], shouldBroadcast: boolean = true) => {
|
||||||
|
const { edges, setEdges: reactFlowSetEdges } = store.getState()
|
||||||
|
if (shouldBroadcast)
|
||||||
|
collabSetEdges(edges, newEdges)
|
||||||
|
|
||||||
|
reactFlowSetEdges(newEdges)
|
||||||
|
}, [store, collabSetEdges])
|
||||||
|
|
||||||
|
const collaborativeStore = useCallback(() => {
|
||||||
|
const state = store.getState()
|
||||||
|
return {
|
||||||
|
|
||||||
|
nodes: state.getNodes(),
|
||||||
|
edges: state.edges,
|
||||||
|
|
||||||
|
setNodes,
|
||||||
|
setEdges,
|
||||||
|
|
||||||
|
}
|
||||||
|
}, [store, setNodes, setEdges])
|
||||||
|
|
||||||
|
return {
|
||||||
|
getState: collaborativeStore,
|
||||||
|
setNodes,
|
||||||
|
setEdges,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,9 +4,7 @@ import type {
|
|||||||
EdgeMouseHandler,
|
EdgeMouseHandler,
|
||||||
OnEdgesChange,
|
OnEdgesChange,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
import {
|
|
||||||
useStoreApi,
|
|
||||||
} from 'reactflow'
|
|
||||||
import type {
|
import type {
|
||||||
Node,
|
Node,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
@ -14,61 +12,55 @@ import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../utils'
|
|||||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
||||||
import { useNodesReadOnly } from './use-workflow'
|
import { useNodesReadOnly } from './use-workflow'
|
||||||
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
||||||
|
import { useCollaborativeWorkflow } from './use-collaborative-workflow'
|
||||||
|
|
||||||
export const useEdgesInteractions = () => {
|
export const useEdgesInteractions = () => {
|
||||||
const store = useStoreApi()
|
|
||||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
const { getNodesReadOnly } = useNodesReadOnly()
|
const { getNodesReadOnly } = useNodesReadOnly()
|
||||||
const { saveStateToHistory } = useWorkflowHistory()
|
const { saveStateToHistory } = useWorkflowHistory()
|
||||||
|
const collaborativeWorkflow = useCollaborativeWorkflow()
|
||||||
|
|
||||||
const handleEdgeEnter = useCallback<EdgeMouseHandler>((_, edge) => {
|
const handleEdgeEnter = useCallback<EdgeMouseHandler>((_, edge) => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
return
|
return
|
||||||
|
|
||||||
const {
|
const { edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
edges,
|
|
||||||
setEdges,
|
|
||||||
} = store.getState()
|
|
||||||
const newEdges = produce(edges, (draft) => {
|
const newEdges = produce(edges, (draft) => {
|
||||||
const currentEdge = draft.find(e => e.id === edge.id)!
|
const currentEdge = draft.find(e => e.id === edge.id)!
|
||||||
|
|
||||||
currentEdge.data._hovering = true
|
currentEdge.data._hovering = true
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges, false)
|
||||||
}, [store, getNodesReadOnly])
|
}, [collaborativeWorkflow, getNodesReadOnly])
|
||||||
|
|
||||||
const handleEdgeLeave = useCallback<EdgeMouseHandler>((_, edge) => {
|
const handleEdgeLeave = useCallback<EdgeMouseHandler>((_, edge) => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
return
|
return
|
||||||
|
|
||||||
const {
|
const { edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
edges,
|
|
||||||
setEdges,
|
|
||||||
} = store.getState()
|
|
||||||
const newEdges = produce(edges, (draft) => {
|
const newEdges = produce(edges, (draft) => {
|
||||||
const currentEdge = draft.find(e => e.id === edge.id)!
|
const currentEdge = draft.find(e => e.id === edge.id)!
|
||||||
|
|
||||||
currentEdge.data._hovering = false
|
currentEdge.data._hovering = false
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges, false)
|
||||||
}, [store, getNodesReadOnly])
|
}, [collaborativeWorkflow, getNodesReadOnly])
|
||||||
|
|
||||||
const handleEdgeDeleteByDeleteBranch = useCallback((nodeId: string, branchId: string) => {
|
const handleEdgeDeleteByDeleteBranch = useCallback((nodeId: string, branchId: string) => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
return
|
return
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getNodes,
|
nodes,
|
||||||
setNodes,
|
setNodes,
|
||||||
edges,
|
edges,
|
||||||
setEdges,
|
setEdges,
|
||||||
} = store.getState()
|
} = collaborativeWorkflow.getState()
|
||||||
const edgeWillBeDeleted = edges.filter(edge => edge.source === nodeId && edge.sourceHandle === branchId)
|
const edgeWillBeDeleted = edges.filter(edge => edge.source === nodeId && edge.sourceHandle === branchId)
|
||||||
|
|
||||||
if (!edgeWillBeDeleted.length)
|
if (!edgeWillBeDeleted.length)
|
||||||
return
|
return
|
||||||
|
|
||||||
const nodes = getNodes()
|
|
||||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||||
edgeWillBeDeleted.map(edge => ({ type: 'remove', edge })),
|
edgeWillBeDeleted.map(edge => ({ type: 'remove', edge })),
|
||||||
nodes,
|
nodes,
|
||||||
@ -90,24 +82,23 @@ export const useEdgesInteractions = () => {
|
|||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
saveStateToHistory(WorkflowHistoryEvent.EdgeDeleteByDeleteBranch)
|
saveStateToHistory(WorkflowHistoryEvent.EdgeDeleteByDeleteBranch)
|
||||||
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])
|
}, [getNodesReadOnly, collaborativeWorkflow, handleSyncWorkflowDraft, saveStateToHistory])
|
||||||
|
|
||||||
const handleEdgeDelete = useCallback(() => {
|
const handleEdgeDelete = useCallback(() => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
return
|
return
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getNodes,
|
nodes,
|
||||||
setNodes,
|
setNodes,
|
||||||
edges,
|
edges,
|
||||||
setEdges,
|
setEdges,
|
||||||
} = store.getState()
|
} = collaborativeWorkflow.getState()
|
||||||
const currentEdgeIndex = edges.findIndex(edge => edge.selected)
|
const currentEdgeIndex = edges.findIndex(edge => edge.selected)
|
||||||
|
|
||||||
if (currentEdgeIndex < 0)
|
if (currentEdgeIndex < 0)
|
||||||
return
|
return
|
||||||
const currentEdge = edges[currentEdgeIndex]
|
const currentEdge = edges[currentEdgeIndex]
|
||||||
const nodes = getNodes()
|
|
||||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||||
[
|
[
|
||||||
{ type: 'remove', edge: currentEdge },
|
{ type: 'remove', edge: currentEdge },
|
||||||
@ -131,7 +122,7 @@ export const useEdgesInteractions = () => {
|
|||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
|
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
|
||||||
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])
|
}, [getNodesReadOnly, collaborativeWorkflow, handleSyncWorkflowDraft, saveStateToHistory])
|
||||||
|
|
||||||
const handleEdgesChange = useCallback<OnEdgesChange>((changes) => {
|
const handleEdgesChange = useCallback<OnEdgesChange>((changes) => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
@ -140,7 +131,7 @@ export const useEdgesInteractions = () => {
|
|||||||
const {
|
const {
|
||||||
edges,
|
edges,
|
||||||
setEdges,
|
setEdges,
|
||||||
} = store.getState()
|
} = collaborativeWorkflow.getState()
|
||||||
|
|
||||||
const newEdges = produce(edges, (draft) => {
|
const newEdges = produce(edges, (draft) => {
|
||||||
changes.forEach((change) => {
|
changes.forEach((change) => {
|
||||||
@ -149,7 +140,7 @@ export const useEdgesInteractions = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
}, [store, getNodesReadOnly])
|
}, [collaborativeWorkflow, getNodesReadOnly])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleEdgeEnter,
|
handleEdgeEnter,
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import {
|
|||||||
getConnectedEdges,
|
getConnectedEdges,
|
||||||
getOutgoers,
|
getOutgoers,
|
||||||
useReactFlow,
|
useReactFlow,
|
||||||
useStoreApi,
|
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
import type { ToolDefaultValue } from '../block-selector/types'
|
import type { ToolDefaultValue } from '../block-selector/types'
|
||||||
import type { Edge, Node, OnNodeAdd } from '../types'
|
import type { Edge, Node, OnNodeAdd } from '../types'
|
||||||
@ -46,7 +45,7 @@ import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
|
|||||||
import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
|
import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
|
||||||
import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions'
|
import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions'
|
||||||
import { useNodeLoopInteractions } from '../nodes/loop/use-interactions'
|
import { useNodeLoopInteractions } from '../nodes/loop/use-interactions'
|
||||||
import { useWorkflowHistoryStore } from '../workflow-history-store'
|
import { collaborationManager } from '../collaboration/core/collaboration-manager'
|
||||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
||||||
import { useHelpline } from './use-helpline'
|
import { useHelpline } from './use-helpline'
|
||||||
import {
|
import {
|
||||||
@ -62,13 +61,13 @@ import { useNodesMetaData } from './use-nodes-meta-data'
|
|||||||
import type { RAGPipelineVariables } from '@/models/pipeline'
|
import type { RAGPipelineVariables } from '@/models/pipeline'
|
||||||
import useInspectVarsCrud from './use-inspect-vars-crud'
|
import useInspectVarsCrud from './use-inspect-vars-crud'
|
||||||
import { getNodeUsedVars } from '../nodes/_base/components/variable/utils'
|
import { getNodeUsedVars } from '../nodes/_base/components/variable/utils'
|
||||||
|
import { useCollaborativeWorkflow } from './use-collaborative-workflow'
|
||||||
|
|
||||||
export const useNodesInteractions = () => {
|
export const useNodesInteractions = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const store = useStoreApi()
|
const collaborativeWorkflow = useCollaborativeWorkflow()
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const reactflow = useReactFlow()
|
const reactflow = useReactFlow()
|
||||||
const { store: workflowHistoryStore } = useWorkflowHistoryStore()
|
|
||||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
const { checkNestedParallelLimit, getAfterNodesInSameBranch } = useWorkflow()
|
const { checkNestedParallelLimit, getAfterNodesInSameBranch } = useWorkflow()
|
||||||
const { getNodesReadOnly } = useNodesReadOnly()
|
const { getNodesReadOnly } = useNodesReadOnly()
|
||||||
@ -120,50 +119,42 @@ export const useNodesInteractions = () => {
|
|||||||
|
|
||||||
if (node.type === CUSTOM_LOOP_START_NODE) return
|
if (node.type === CUSTOM_LOOP_START_NODE) return
|
||||||
|
|
||||||
const { getNodes, setNodes } = store.getState()
|
e.stopPropagation()
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
const nodes = getNodes()
|
const { nodes, setNodes } = collaborativeWorkflow.getState()
|
||||||
|
|
||||||
const { restrictPosition } = handleNodeIterationChildDrag(node)
|
const { restrictPosition } = handleNodeIterationChildDrag(node)
|
||||||
const { restrictPosition: restrictLoopPosition }
|
const { restrictPosition: restrictLoopPosition }
|
||||||
= handleNodeLoopChildDrag(node)
|
= handleNodeLoopChildDrag(node)
|
||||||
|
|
||||||
const { showHorizontalHelpLineNodes, showVerticalHelpLineNodes }
|
const { showHorizontalHelpLineNodes, showVerticalHelpLineNodes }
|
||||||
= handleSetHelpline(node)
|
= handleSetHelpline(node)
|
||||||
const showHorizontalHelpLineNodesLength
|
const showHorizontalHelpLineNodesLength
|
||||||
= showHorizontalHelpLineNodes.length
|
= showHorizontalHelpLineNodes.length
|
||||||
const showVerticalHelpLineNodesLength = showVerticalHelpLineNodes.length
|
const showVerticalHelpLineNodesLength = showVerticalHelpLineNodes.length
|
||||||
|
|
||||||
const newNodes = produce(nodes, (draft) => {
|
const newNodes = produce(nodes, (draft) => {
|
||||||
const currentNode = draft.find(n => n.id === node.id)!
|
const currentNode = draft.find(n => n.id === node.id)!
|
||||||
|
|
||||||
if (showVerticalHelpLineNodesLength > 0)
|
if (showVerticalHelpLineNodesLength > 0)
|
||||||
currentNode.position.x = showVerticalHelpLineNodes[0].position.x
|
currentNode.position.x = showVerticalHelpLineNodes[0].position.x
|
||||||
else if (restrictPosition.x !== undefined)
|
else if (restrictPosition.x !== undefined)
|
||||||
currentNode.position.x = restrictPosition.x
|
currentNode.position.x = restrictPosition.x
|
||||||
else if (restrictLoopPosition.x !== undefined)
|
else if (restrictLoopPosition.x !== undefined)
|
||||||
currentNode.position.x = restrictLoopPosition.x
|
currentNode.position.x = restrictLoopPosition.x
|
||||||
else currentNode.position.x = node.position.x
|
else currentNode.position.x = node.position.x
|
||||||
|
|
||||||
if (showHorizontalHelpLineNodesLength > 0)
|
if (showHorizontalHelpLineNodesLength > 0)
|
||||||
currentNode.position.y = showHorizontalHelpLineNodes[0].position.y
|
currentNode.position.y = showHorizontalHelpLineNodes[0].position.y
|
||||||
else if (restrictPosition.y !== undefined)
|
else if (restrictPosition.y !== undefined)
|
||||||
currentNode.position.y = restrictPosition.y
|
currentNode.position.y = restrictPosition.y
|
||||||
else if (restrictLoopPosition.y !== undefined)
|
else if (restrictLoopPosition.y !== undefined)
|
||||||
currentNode.position.y = restrictLoopPosition.y
|
currentNode.position.y = restrictLoopPosition.y
|
||||||
else currentNode.position.y = node.position.y
|
else
|
||||||
})
|
currentNode.position.y = node.position.y
|
||||||
setNodes(newNodes)
|
})
|
||||||
},
|
setNodes(newNodes)
|
||||||
[
|
}, [getNodesReadOnly, collaborativeWorkflow, handleNodeIterationChildDrag, handleNodeLoopChildDrag, handleSetHelpline])
|
||||||
getNodesReadOnly,
|
|
||||||
store,
|
|
||||||
handleNodeIterationChildDrag,
|
|
||||||
handleNodeLoopChildDrag,
|
|
||||||
handleSetHelpline,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleNodeDragStop = useCallback<NodeDragHandler>(
|
const handleNodeDragStop = useCallback<NodeDragHandler>(
|
||||||
(_, node) => {
|
(_, node) => {
|
||||||
@ -210,62 +201,62 @@ export const useNodesInteractions = () => {
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, edges, setNodes, setEdges } = collaborativeWorkflow.getState()
|
||||||
const nodes = getNodes()
|
const {
|
||||||
const { connectingNodePayload, setEnteringNodePayload }
|
connectingNodePayload,
|
||||||
= workflowStore.getState()
|
setEnteringNodePayload,
|
||||||
|
} = workflowStore.getState()
|
||||||
|
if (connectingNodePayload) {
|
||||||
|
if (connectingNodePayload.nodeId === node.id) return
|
||||||
|
const connectingNode: Node = nodes.find(
|
||||||
|
n => n.id === connectingNodePayload.nodeId,
|
||||||
|
)!
|
||||||
|
const sameLevel = connectingNode.parentId === node.parentId
|
||||||
|
|
||||||
if (connectingNodePayload) {
|
if (sameLevel) {
|
||||||
if (connectingNodePayload.nodeId === node.id) return
|
setEnteringNodePayload({
|
||||||
const connectingNode: Node = nodes.find(
|
nodeId: node.id,
|
||||||
n => n.id === connectingNodePayload.nodeId,
|
nodeData: node.data as VariableAssignerNodeType,
|
||||||
)!
|
|
||||||
const sameLevel = connectingNode.parentId === node.parentId
|
|
||||||
|
|
||||||
if (sameLevel) {
|
|
||||||
setEnteringNodePayload({
|
|
||||||
nodeId: node.id,
|
|
||||||
nodeData: node.data as VariableAssignerNodeType,
|
|
||||||
})
|
|
||||||
const fromType = connectingNodePayload.handleType
|
|
||||||
|
|
||||||
const newNodes = produce(nodes, (draft) => {
|
|
||||||
draft.forEach((n) => {
|
|
||||||
if (
|
|
||||||
n.id === node.id
|
|
||||||
&& fromType === 'source'
|
|
||||||
&& (node.data.type === BlockEnum.VariableAssigner
|
|
||||||
|| node.data.type === BlockEnum.VariableAggregator)
|
|
||||||
) {
|
|
||||||
if (!node.data.advanced_settings?.group_enabled)
|
|
||||||
n.data._isEntering = true
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
n.id === node.id
|
|
||||||
&& fromType === 'target'
|
|
||||||
&& (connectingNode.data.type === BlockEnum.VariableAssigner
|
|
||||||
|| connectingNode.data.type === BlockEnum.VariableAggregator)
|
|
||||||
&& node.data.type !== BlockEnum.IfElse
|
|
||||||
&& node.data.type !== BlockEnum.QuestionClassifier
|
|
||||||
)
|
|
||||||
n.data._isEntering = true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
setNodes(newNodes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const newEdges = produce(edges, (draft) => {
|
|
||||||
const connectedEdges = getConnectedEdges([node], edges)
|
|
||||||
|
|
||||||
connectedEdges.forEach((edge) => {
|
|
||||||
const currentEdge = draft.find(e => e.id === edge.id)
|
|
||||||
if (currentEdge) currentEdge.data._connectedNodeIsHovering = true
|
|
||||||
})
|
})
|
||||||
|
const fromType = connectingNodePayload.handleType
|
||||||
|
|
||||||
|
const newNodes = produce(nodes, (draft) => {
|
||||||
|
draft.forEach((n) => {
|
||||||
|
if (
|
||||||
|
n.id === node.id
|
||||||
|
&& fromType === 'source'
|
||||||
|
&& (node.data.type === BlockEnum.VariableAssigner
|
||||||
|
|| node.data.type === BlockEnum.VariableAggregator)
|
||||||
|
) {
|
||||||
|
if (!node.data.advanced_settings?.group_enabled)
|
||||||
|
n.data._isEntering = true
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
n.id === node.id
|
||||||
|
&& fromType === 'target'
|
||||||
|
&& (connectingNode.data.type === BlockEnum.VariableAssigner
|
||||||
|
|| connectingNode.data.type === BlockEnum.VariableAggregator)
|
||||||
|
&& node.data.type !== BlockEnum.IfElse
|
||||||
|
&& node.data.type !== BlockEnum.QuestionClassifier
|
||||||
|
)
|
||||||
|
n.data._isEntering = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setNodes(newNodes, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newEdges = produce(edges, (draft) => {
|
||||||
|
const connectedEdges = getConnectedEdges([node], edges)
|
||||||
|
|
||||||
|
connectedEdges.forEach((edge) => {
|
||||||
|
const currentEdge = draft.find(e => e.id === edge.id)
|
||||||
|
if (currentEdge) currentEdge.data._connectedNodeIsHovering = true
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
})
|
||||||
},
|
setEdges(newEdges, false)
|
||||||
[store, workflowStore, getNodesReadOnly],
|
},
|
||||||
)
|
[collaborativeWorkflow, workflowStore, getNodesReadOnly],
|
||||||
|
)
|
||||||
|
|
||||||
const handleNodeLeave = useCallback<NodeMouseHandler>(
|
const handleNodeLeave = useCallback<NodeMouseHandler>(
|
||||||
(_, node) => {
|
(_, node) => {
|
||||||
@ -285,21 +276,21 @@ export const useNodesInteractions = () => {
|
|||||||
|
|
||||||
const { setEnteringNodePayload } = workflowStore.getState()
|
const { setEnteringNodePayload } = workflowStore.getState()
|
||||||
setEnteringNodePayload(undefined)
|
setEnteringNodePayload(undefined)
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
const newNodes = produce(getNodes(), (draft) => {
|
const newNodes = produce(nodes, (draft) => {
|
||||||
draft.forEach((node) => {
|
draft.forEach((node) => {
|
||||||
node.data._isEntering = false
|
node.data._isEntering = false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes, false)
|
||||||
const newEdges = produce(edges, (draft) => {
|
const newEdges = produce(edges, (draft) => {
|
||||||
draft.forEach((edge) => {
|
draft.forEach((edge) => {
|
||||||
edge.data._connectedNodeIsHovering = false
|
edge.data._connectedNodeIsHovering = false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges, false)
|
||||||
},
|
},
|
||||||
[store, workflowStore, getNodesReadOnly],
|
[collaborativeWorkflow, workflowStore, getNodesReadOnly],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleNodeSelect = useCallback(
|
const handleNodeSelect = useCallback(
|
||||||
@ -310,9 +301,7 @@ export const useNodesInteractions = () => {
|
|||||||
) => {
|
) => {
|
||||||
if (initShowLastRunTab)
|
if (initShowLastRunTab)
|
||||||
workflowStore.setState({ initShowLastRunTab: true })
|
workflowStore.setState({ initShowLastRunTab: true })
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
|
|
||||||
const nodes = getNodes()
|
|
||||||
const selectedNode = nodes.find(node => node.data.selected)
|
const selectedNode = nodes.find(node => node.data.selected)
|
||||||
|
|
||||||
if (!cancelSelection && selectedNode?.id === nodeId) return
|
if (!cancelSelection && selectedNode?.id === nodeId) return
|
||||||
@ -347,10 +336,8 @@ export const useNodesInteractions = () => {
|
|||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
|
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
},
|
}, [collaborativeWorkflow, handleSyncWorkflowDraft])
|
||||||
[store, handleSyncWorkflowDraft],
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleNodeClick = useCallback<NodeMouseHandler>(
|
const handleNodeClick = useCallback<NodeMouseHandler>(
|
||||||
(_, node) => {
|
(_, node) => {
|
||||||
@ -367,10 +354,9 @@ export const useNodesInteractions = () => {
|
|||||||
if (source === target) return
|
if (source === target) return
|
||||||
if (getNodesReadOnly()) return
|
if (getNodesReadOnly()) return
|
||||||
|
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, edges, setNodes, setEdges } = collaborativeWorkflow.getState()
|
||||||
const nodes = getNodes()
|
const targetNode = nodes.find(node => node.id === target!)
|
||||||
const targetNode = nodes.find(node => node.id === target!)
|
const sourceNode = nodes.find(node => node.id === source!)
|
||||||
const sourceNode = nodes.find(node => node.id === source!)
|
|
||||||
|
|
||||||
if (targetNode?.parentId !== sourceNode?.parentId) return
|
if (targetNode?.parentId !== sourceNode?.parentId) return
|
||||||
|
|
||||||
@ -454,7 +440,7 @@ export const useNodesInteractions = () => {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
getNodesReadOnly,
|
getNodesReadOnly,
|
||||||
store,
|
collaborativeWorkflow,
|
||||||
workflowStore,
|
workflowStore,
|
||||||
handleSyncWorkflowDraft,
|
handleSyncWorkflowDraft,
|
||||||
saveStateToHistory,
|
saveStateToHistory,
|
||||||
@ -468,8 +454,8 @@ export const useNodesInteractions = () => {
|
|||||||
|
|
||||||
if (nodeId && handleType) {
|
if (nodeId && handleType) {
|
||||||
const { setConnectingNodePayload } = workflowStore.getState()
|
const { setConnectingNodePayload } = workflowStore.getState()
|
||||||
const { getNodes } = store.getState()
|
const { nodes } = collaborativeWorkflow.getState()
|
||||||
const node = getNodes().find(n => n.id === nodeId)!
|
const node = nodes.find(n => n.id === nodeId)!
|
||||||
|
|
||||||
if (node.type === CUSTOM_NOTE_NODE) return
|
if (node.type === CUSTOM_NOTE_NODE) return
|
||||||
|
|
||||||
@ -479,16 +465,14 @@ export const useNodesInteractions = () => {
|
|||||||
)
|
)
|
||||||
if (handleType === 'target') return
|
if (handleType === 'target') return
|
||||||
|
|
||||||
setConnectingNodePayload({
|
setConnectingNodePayload({
|
||||||
nodeId,
|
nodeId,
|
||||||
nodeType: node.data.type,
|
nodeType: node.data.type,
|
||||||
handleType,
|
handleType,
|
||||||
handleId,
|
handleId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
}, [collaborativeWorkflow, workflowStore, getNodesReadOnly])
|
||||||
[store, workflowStore, getNodesReadOnly],
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleNodeConnectEnd = useCallback<OnConnectEnd>(
|
const handleNodeConnectEnd = useCallback<OnConnectEnd>(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
@ -504,8 +488,7 @@ export const useNodesInteractions = () => {
|
|||||||
const { setShowAssignVariablePopup, hoveringAssignVariableGroupId }
|
const { setShowAssignVariablePopup, hoveringAssignVariableGroupId }
|
||||||
= workflowStore.getState()
|
= workflowStore.getState()
|
||||||
const { screenToFlowPosition } = reactflow
|
const { screenToFlowPosition } = reactflow
|
||||||
const { getNodes, setNodes } = store.getState()
|
const { nodes, setNodes } = collaborativeWorkflow.getState()
|
||||||
const nodes = getNodes()
|
|
||||||
const fromHandleType = connectingNodePayload.handleType
|
const fromHandleType = connectingNodePayload.handleType
|
||||||
const fromHandleId = connectingNodePayload.handleId
|
const fromHandleId = connectingNodePayload.handleId
|
||||||
const fromNode = nodes.find(
|
const fromNode = nodes.find(
|
||||||
@ -562,7 +545,7 @@ export const useNodesInteractions = () => {
|
|||||||
setConnectingNodePayload(undefined)
|
setConnectingNodePayload(undefined)
|
||||||
setEnteringNodePayload(undefined)
|
setEnteringNodePayload(undefined)
|
||||||
},
|
},
|
||||||
[store, handleNodeConnect, getNodesReadOnly, workflowStore, reactflow],
|
[collaborativeWorkflow, handleNodeConnect, getNodesReadOnly, workflowStore, reactflow],
|
||||||
)
|
)
|
||||||
|
|
||||||
const { deleteNodeInspectorVars } = useInspectVarsCrud()
|
const { deleteNodeInspectorVars } = useInspectVarsCrud()
|
||||||
@ -571,9 +554,7 @@ export const useNodesInteractions = () => {
|
|||||||
(nodeId: string) => {
|
(nodeId: string) => {
|
||||||
if (getNodesReadOnly()) return
|
if (getNodesReadOnly()) return
|
||||||
|
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
|
|
||||||
const nodes = getNodes()
|
|
||||||
const currentNodeIndex = nodes.findIndex(node => node.id === nodeId)
|
const currentNodeIndex = nodes.findIndex(node => node.id === nodeId)
|
||||||
const currentNode = nodes[currentNodeIndex]
|
const currentNode = nodes[currentNodeIndex]
|
||||||
|
|
||||||
@ -728,7 +709,7 @@ export const useNodesInteractions = () => {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
getNodesReadOnly,
|
getNodesReadOnly,
|
||||||
store,
|
collaborativeWorkflow,
|
||||||
handleSyncWorkflowDraft,
|
handleSyncWorkflowDraft,
|
||||||
saveStateToHistory,
|
saveStateToHistory,
|
||||||
workflowStore,
|
workflowStore,
|
||||||
@ -750,8 +731,7 @@ export const useNodesInteractions = () => {
|
|||||||
) => {
|
) => {
|
||||||
if (getNodesReadOnly()) return
|
if (getNodesReadOnly()) return
|
||||||
|
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
const nodes = getNodes()
|
|
||||||
const nodesWithSameType = nodes.filter(
|
const nodesWithSameType = nodes.filter(
|
||||||
node => node.data.type === nodeType,
|
node => node.data.type === nodeType,
|
||||||
)
|
)
|
||||||
@ -1292,7 +1272,7 @@ export const useNodesInteractions = () => {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
getNodesReadOnly,
|
getNodesReadOnly,
|
||||||
store,
|
collaborativeWorkflow,
|
||||||
handleSyncWorkflowDraft,
|
handleSyncWorkflowDraft,
|
||||||
saveStateToHistory,
|
saveStateToHistory,
|
||||||
workflowStore,
|
workflowStore,
|
||||||
@ -1311,8 +1291,7 @@ export const useNodesInteractions = () => {
|
|||||||
) => {
|
) => {
|
||||||
if (getNodesReadOnly()) return
|
if (getNodesReadOnly()) return
|
||||||
|
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
const nodes = getNodes()
|
|
||||||
const currentNode = nodes.find(node => node.id === currentNodeId)!
|
const currentNode = nodes.find(node => node.id === currentNodeId)!
|
||||||
const connectedEdges = getConnectedEdges([currentNode], edges)
|
const connectedEdges = getConnectedEdges([currentNode], edges)
|
||||||
const nodesWithSameType = nodes.filter(
|
const nodesWithSameType = nodes.filter(
|
||||||
@ -1390,7 +1369,7 @@ export const useNodesInteractions = () => {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
getNodesReadOnly,
|
getNodesReadOnly,
|
||||||
store,
|
collaborativeWorkflow,
|
||||||
handleSyncWorkflowDraft,
|
handleSyncWorkflowDraft,
|
||||||
saveStateToHistory,
|
saveStateToHistory,
|
||||||
nodesMetaDataMap,
|
nodesMetaDataMap,
|
||||||
@ -1398,16 +1377,14 @@ export const useNodesInteractions = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const handleNodesCancelSelected = useCallback(() => {
|
const handleNodesCancelSelected = useCallback(() => {
|
||||||
const { getNodes, setNodes } = store.getState()
|
const { nodes, setNodes } = collaborativeWorkflow.getState()
|
||||||
|
|
||||||
const nodes = getNodes()
|
|
||||||
const newNodes = produce(nodes, (draft) => {
|
const newNodes = produce(nodes, (draft) => {
|
||||||
draft.forEach((node) => {
|
draft.forEach((node) => {
|
||||||
node.data.selected = false
|
node.data.selected = false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
}, [store])
|
}, [collaborativeWorkflow])
|
||||||
|
|
||||||
const handleNodeContextMenu = useCallback(
|
const handleNodeContextMenu = useCallback(
|
||||||
(e: MouseEvent, node: Node) => {
|
(e: MouseEvent, node: Node) => {
|
||||||
@ -1444,9 +1421,7 @@ export const useNodesInteractions = () => {
|
|||||||
|
|
||||||
const { setClipboardElements } = workflowStore.getState()
|
const { setClipboardElements } = workflowStore.getState()
|
||||||
|
|
||||||
const { getNodes } = store.getState()
|
const { nodes } = collaborativeWorkflow.getState()
|
||||||
|
|
||||||
const nodes = getNodes()
|
|
||||||
|
|
||||||
if (nodeId) {
|
if (nodeId) {
|
||||||
// If nodeId is provided, copy that specific node
|
// If nodeId is provided, copy that specific node
|
||||||
@ -1485,7 +1460,7 @@ export const useNodesInteractions = () => {
|
|||||||
if (selectedNode) setClipboardElements([selectedNode])
|
if (selectedNode) setClipboardElements([selectedNode])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[getNodesReadOnly, store, workflowStore],
|
[getNodesReadOnly, collaborativeWorkflow, workflowStore],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleNodesPaste = useCallback(() => {
|
const handleNodesPaste = useCallback(() => {
|
||||||
@ -1493,11 +1468,10 @@ export const useNodesInteractions = () => {
|
|||||||
|
|
||||||
const { clipboardElements, mousePosition } = workflowStore.getState()
|
const { clipboardElements, mousePosition } = workflowStore.getState()
|
||||||
|
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
|
|
||||||
const nodesToPaste: Node[] = []
|
const nodesToPaste: Node[] = []
|
||||||
const edgesToPaste: Edge[] = []
|
const edgesToPaste: Edge[] = []
|
||||||
const nodes = getNodes()
|
|
||||||
|
|
||||||
if (clipboardElements.length) {
|
if (clipboardElements.length) {
|
||||||
const { x, y } = getTopLeftNodePosition(clipboardElements)
|
const { x, y } = getTopLeftNodePosition(clipboardElements)
|
||||||
@ -1649,7 +1623,7 @@ export const useNodesInteractions = () => {
|
|||||||
}, [
|
}, [
|
||||||
getNodesReadOnly,
|
getNodesReadOnly,
|
||||||
workflowStore,
|
workflowStore,
|
||||||
store,
|
collaborativeWorkflow,
|
||||||
reactflow,
|
reactflow,
|
||||||
saveStateToHistory,
|
saveStateToHistory,
|
||||||
handleSyncWorkflowDraft,
|
handleSyncWorkflowDraft,
|
||||||
@ -1671,9 +1645,8 @@ export const useNodesInteractions = () => {
|
|||||||
const handleNodesDelete = useCallback(() => {
|
const handleNodesDelete = useCallback(() => {
|
||||||
if (getNodesReadOnly()) return
|
if (getNodesReadOnly()) return
|
||||||
|
|
||||||
const { getNodes, edges } = store.getState()
|
const { nodes, edges } = collaborativeWorkflow.getState()
|
||||||
|
|
||||||
const nodes = getNodes()
|
|
||||||
const bundledNodes = nodes.filter(
|
const bundledNodes = nodes.filter(
|
||||||
node => node.data._isBundled && node.data.type !== BlockEnum.Start,
|
node => node.data._isBundled && node.data.type !== BlockEnum.Start,
|
||||||
)
|
)
|
||||||
@ -1692,16 +1665,15 @@ export const useNodesInteractions = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (selectedNode) handleNodeDelete(selectedNode.id)
|
if (selectedNode) handleNodeDelete(selectedNode.id)
|
||||||
}, [store, getNodesReadOnly, handleNodeDelete])
|
}, [collaborativeWorkflow, getNodesReadOnly, handleNodeDelete])
|
||||||
|
|
||||||
const handleNodeResize = useCallback(
|
const handleNodeResize = useCallback(
|
||||||
(nodeId: string, params: ResizeParamsWithDirection) => {
|
(nodeId: string, params: ResizeParamsWithDirection) => {
|
||||||
if (getNodesReadOnly()) return
|
if (getNodesReadOnly()) return
|
||||||
|
|
||||||
const { getNodes, setNodes } = store.getState()
|
const { nodes, setNodes } = collaborativeWorkflow.getState()
|
||||||
const { x, y, width, height } = params
|
const { x, y, width, height } = params
|
||||||
|
|
||||||
const nodes = getNodes()
|
|
||||||
const currentNode = nodes.find(n => n.id === nodeId)!
|
const currentNode = nodes.find(n => n.id === nodeId)!
|
||||||
const childrenNodes = nodes.filter(n =>
|
const childrenNodes = nodes.filter(n =>
|
||||||
currentNode.data._children?.find((c: any) => c.nodeId === n.id),
|
currentNode.data._children?.find((c: any) => c.nodeId === n.id),
|
||||||
@ -1760,15 +1732,14 @@ export const useNodesInteractions = () => {
|
|||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeResize, { nodeId })
|
saveStateToHistory(WorkflowHistoryEvent.NodeResize, { nodeId })
|
||||||
},
|
},
|
||||||
[getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory],
|
[getNodesReadOnly, collaborativeWorkflow, handleSyncWorkflowDraft, saveStateToHistory],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleNodeDisconnect = useCallback(
|
const handleNodeDisconnect = useCallback(
|
||||||
(nodeId: string) => {
|
(nodeId: string) => {
|
||||||
if (getNodesReadOnly()) return
|
if (getNodesReadOnly()) return
|
||||||
|
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
const nodes = getNodes()
|
|
||||||
const currentNode = nodes.find(node => node.id === nodeId)!
|
const currentNode = nodes.find(node => node.id === nodeId)!
|
||||||
const connectedEdges = getConnectedEdges([currentNode], edges)
|
const connectedEdges = getConnectedEdges([currentNode], edges)
|
||||||
const nodesConnectedSourceOrTargetHandleIdsMap
|
const nodesConnectedSourceOrTargetHandleIdsMap
|
||||||
@ -1799,24 +1770,24 @@ export const useNodesInteractions = () => {
|
|||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
|
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
|
||||||
},
|
},
|
||||||
[store, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory],
|
[collaborativeWorkflow, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleHistoryBack = useCallback(() => {
|
const handleHistoryBack = useCallback(() => {
|
||||||
if (getNodesReadOnly() || getWorkflowReadOnly()) return
|
if (getNodesReadOnly() || getWorkflowReadOnly()) return
|
||||||
|
|
||||||
const { setEdges, setNodes } = store.getState()
|
// Use collaborative undo from Loro
|
||||||
undo()
|
const undoResult = collaborationManager.undo()
|
||||||
|
|
||||||
const { edges, nodes } = workflowHistoryStore.getState()
|
if (undoResult) {
|
||||||
if (edges.length === 0 && nodes.length === 0) return
|
// The undo operation will automatically trigger subscriptions
|
||||||
|
// which will update the nodes and edges through setupSubscriptions
|
||||||
setEdges(edges)
|
console.log('Collaborative undo performed')
|
||||||
setNodes(nodes)
|
}
|
||||||
|
else {
|
||||||
|
console.log('Nothing to undo')
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
store,
|
|
||||||
undo,
|
|
||||||
workflowHistoryStore,
|
|
||||||
getNodesReadOnly,
|
getNodesReadOnly,
|
||||||
getWorkflowReadOnly,
|
getWorkflowReadOnly,
|
||||||
])
|
])
|
||||||
@ -1824,18 +1795,17 @@ export const useNodesInteractions = () => {
|
|||||||
const handleHistoryForward = useCallback(() => {
|
const handleHistoryForward = useCallback(() => {
|
||||||
if (getNodesReadOnly() || getWorkflowReadOnly()) return
|
if (getNodesReadOnly() || getWorkflowReadOnly()) return
|
||||||
|
|
||||||
const { setEdges, setNodes } = store.getState()
|
// Use collaborative redo from Loro
|
||||||
redo()
|
const redoResult = collaborationManager.redo()
|
||||||
|
|
||||||
const { edges, nodes } = workflowHistoryStore.getState()
|
if (redoResult) {
|
||||||
if (edges.length === 0 && nodes.length === 0) return
|
// The redo operation will automatically trigger subscriptions
|
||||||
|
// which will update the nodes and edges through setupSubscriptions
|
||||||
setEdges(edges)
|
console.log('Collaborative redo performed')
|
||||||
setNodes(nodes)
|
} else {
|
||||||
|
console.log('Nothing to redo')
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
redo,
|
|
||||||
store,
|
|
||||||
workflowHistoryStore,
|
|
||||||
getNodesReadOnly,
|
getNodesReadOnly,
|
||||||
getWorkflowReadOnly,
|
getWorkflowReadOnly,
|
||||||
])
|
])
|
||||||
@ -1844,8 +1814,7 @@ export const useNodesInteractions = () => {
|
|||||||
/** Add opacity-30 to all nodes except the nodeId */
|
/** Add opacity-30 to all nodes except the nodeId */
|
||||||
const dimOtherNodes = useCallback(() => {
|
const dimOtherNodes = useCallback(() => {
|
||||||
if (isDimming) return
|
if (isDimming) return
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
const nodes = getNodes()
|
|
||||||
|
|
||||||
const selectedNode = nodes.find(n => n.data.selected)
|
const selectedNode = nodes.find(n => n.data.selected)
|
||||||
if (!selectedNode) return
|
if (!selectedNode) return
|
||||||
@ -1938,12 +1907,11 @@ export const useNodesInteractions = () => {
|
|||||||
draft.push(...tempEdges)
|
draft.push(...tempEdges)
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
}, [isDimming, store])
|
}, [isDimming, collaborativeWorkflow])
|
||||||
|
|
||||||
/** Restore all nodes to full opacity */
|
/** Restore all nodes to full opacity */
|
||||||
const undimAllNodes = useCallback(() => {
|
const undimAllNodes = useCallback(() => {
|
||||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
|
||||||
const nodes = getNodes()
|
|
||||||
setIsDimming(false)
|
setIsDimming(false)
|
||||||
|
|
||||||
const newNodes = produce(nodes, (draft) => {
|
const newNodes = produce(nodes, (draft) => {
|
||||||
@ -1963,7 +1931,7 @@ export const useNodesInteractions = () => {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
}, [store])
|
}, [collaborativeWorkflow])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleNodeDragStart,
|
handleNodeDragStart,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import type {
|
|||||||
import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
|
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
|
||||||
import { BlockEnum } from '@/app/components/workflow/types'
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
|
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
|
||||||
import { useDocLink } from '@/context/i18n'
|
import { useDocLink } from '@/context/i18n'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
|
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
|
||||||
@ -32,6 +33,7 @@ const ChatVariablePanel = () => {
|
|||||||
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
|
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
|
||||||
const varList = useStore(s => s.conversationVariables) as ConversationVariable[]
|
const varList = useStore(s => s.conversationVariables) as ConversationVariable[]
|
||||||
const updateChatVarList = useStore(s => s.setConversationVariables)
|
const updateChatVarList = useStore(s => s.setConversationVariables)
|
||||||
|
const appId = useStore(s => s.appId)
|
||||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
const {
|
const {
|
||||||
invalidateConversationVarValues,
|
invalidateConversationVarValues,
|
||||||
@ -40,9 +42,17 @@ const ChatVariablePanel = () => {
|
|||||||
doSyncWorkflowDraft(false, {
|
doSyncWorkflowDraft(false, {
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
invalidateConversationVarValues()
|
invalidateConversationVarValues()
|
||||||
|
if (appId) {
|
||||||
|
const socket = webSocketClient.getSocket(appId)
|
||||||
|
if (socket) {
|
||||||
|
socket.emit('collaboration_event', {
|
||||||
|
type: 'varsAndFeaturesUpdate',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [doSyncWorkflowDraft, invalidateConversationVarValues])
|
}, [doSyncWorkflowDraft, invalidateConversationVarValues, appId])
|
||||||
|
|
||||||
const [showTip, setShowTip] = useState(true)
|
const [showTip, setShowTip] = useState(true)
|
||||||
const [showVariableModal, setShowVariableModal] = useState(false)
|
const [showVariableModal, setShowVariableModal] = useState(false)
|
||||||
|
|||||||
@ -18,6 +18,8 @@ import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/node
|
|||||||
import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm'
|
import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
|
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
|
||||||
|
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
|
||||||
|
import { useStore as useWorkflowStore } from '@/app/components/workflow/store'
|
||||||
|
|
||||||
const EnvPanel = () => {
|
const EnvPanel = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -28,6 +30,7 @@ const EnvPanel = () => {
|
|||||||
const updateEnvList = useStore(s => s.setEnvironmentVariables)
|
const updateEnvList = useStore(s => s.setEnvironmentVariables)
|
||||||
const setEnvSecrets = useStore(s => s.setEnvSecrets)
|
const setEnvSecrets = useStore(s => s.setEnvSecrets)
|
||||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
const appId = useWorkflowStore(s => s.appId)
|
||||||
|
|
||||||
const [showVariableModal, setShowVariableModal] = useState(false)
|
const [showVariableModal, setShowVariableModal] = useState(false)
|
||||||
const [currentVar, setCurrentVar] = useState<EnvironmentVariable>()
|
const [currentVar, setCurrentVar] = useState<EnvironmentVariable>()
|
||||||
@ -65,18 +68,28 @@ const EnvPanel = () => {
|
|||||||
setShowVariableModal(true)
|
setShowVariableModal(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDelete = useCallback((env: EnvironmentVariable) => {
|
const handleDelete = useCallback(async (env: EnvironmentVariable) => {
|
||||||
removeUsedVarInNodes(env)
|
removeUsedVarInNodes(env)
|
||||||
updateEnvList(envList.filter(e => e.id !== env.id))
|
updateEnvList(envList.filter(e => e.id !== env.id))
|
||||||
setCacheForDelete(undefined)
|
setCacheForDelete(undefined)
|
||||||
setShowRemoveConfirm(false)
|
setShowRemoveConfirm(false)
|
||||||
doSyncWorkflowDraft()
|
await doSyncWorkflowDraft()
|
||||||
|
|
||||||
|
// Emit update event to other connected clients
|
||||||
|
const socket = webSocketClient.getSocket(appId)
|
||||||
|
if (socket?.connected) {
|
||||||
|
socket.emit('collaboration_event', {
|
||||||
|
type: 'varsAndFeaturesUpdate',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (env.value_type === 'secret') {
|
if (env.value_type === 'secret') {
|
||||||
const newMap = { ...envSecrets }
|
const newMap = { ...envSecrets }
|
||||||
delete newMap[env.id]
|
delete newMap[env.id]
|
||||||
setEnvSecrets(newMap)
|
setEnvSecrets(newMap)
|
||||||
}
|
}
|
||||||
}, [doSyncWorkflowDraft, envList, envSecrets, removeUsedVarInNodes, setEnvSecrets, updateEnvList])
|
}, [doSyncWorkflowDraft, envList, envSecrets, removeUsedVarInNodes, setEnvSecrets, updateEnvList, appId])
|
||||||
|
|
||||||
const deleteCheck = useCallback((env: EnvironmentVariable) => {
|
const deleteCheck = useCallback((env: EnvironmentVariable) => {
|
||||||
const effectedNodes = getEffectedNodes(env)
|
const effectedNodes = getEffectedNodes(env)
|
||||||
@ -102,6 +115,12 @@ const EnvPanel = () => {
|
|||||||
const newList = [env, ...envList]
|
const newList = [env, ...envList]
|
||||||
updateEnvList(newList)
|
updateEnvList(newList)
|
||||||
await doSyncWorkflowDraft()
|
await doSyncWorkflowDraft()
|
||||||
|
const socket = webSocketClient.getSocket(appId)
|
||||||
|
if (socket) {
|
||||||
|
socket.emit('collaboration_event', {
|
||||||
|
type: 'varsAndFeaturesUpdate',
|
||||||
|
})
|
||||||
|
}
|
||||||
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
|
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -143,8 +162,14 @@ const EnvPanel = () => {
|
|||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
}
|
}
|
||||||
await doSyncWorkflowDraft()
|
await doSyncWorkflowDraft()
|
||||||
|
const socket = webSocketClient.getSocket(appId)
|
||||||
|
if (socket) {
|
||||||
|
socket.emit('collaboration_event', {
|
||||||
|
type: 'varsAndFeaturesUpdate',
|
||||||
|
})
|
||||||
|
}
|
||||||
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
|
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
|
||||||
}, [currentVar, doSyncWorkflowDraft, envList, envSecrets, getEffectedNodes, setEnvSecrets, store, updateEnvList])
|
}, [currentVar, doSyncWorkflowDraft, envList, envSecrets, getEffectedNodes, setEnvSecrets, store, updateEnvList, appId])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -26,9 +26,10 @@ export type WorkflowDraftSliceShape = {
|
|||||||
export const createWorkflowDraftSlice: StateCreator<WorkflowDraftSliceShape> = set => ({
|
export const createWorkflowDraftSlice: StateCreator<WorkflowDraftSliceShape> = set => ({
|
||||||
backupDraft: undefined,
|
backupDraft: undefined,
|
||||||
setBackupDraft: backupDraft => set(() => ({ backupDraft })),
|
setBackupDraft: backupDraft => set(() => ({ backupDraft })),
|
||||||
|
// TODO: hjlarry test collaboration
|
||||||
debouncedSyncWorkflowDraft: debounce((syncWorkflowDraft) => {
|
debouncedSyncWorkflowDraft: debounce((syncWorkflowDraft) => {
|
||||||
syncWorkflowDraft()
|
syncWorkflowDraft()
|
||||||
}, 5000),
|
}, 500000),
|
||||||
syncWorkflowDraftHash: '',
|
syncWorkflowDraftHash: '',
|
||||||
setSyncWorkflowDraftHash: syncWorkflowDraftHash => set(() => ({ syncWorkflowDraftHash })),
|
setSyncWorkflowDraftHash: syncWorkflowDraftHash => set(() => ({ syncWorkflowDraftHash })),
|
||||||
isSyncingWorkflowDraft: false,
|
isSyncingWorkflowDraft: false,
|
||||||
|
|||||||
@ -92,8 +92,14 @@ const remoteImageURLs = [hasSetWebPrefix ? new URL(`${process.env.NEXT_PUBLIC_WE
|
|||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
|
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
|
||||||
webpack: (config, { dev, isServer }) => {
|
webpack: (config, { dev, isServer }) => {
|
||||||
if (dev) {
|
config.plugins.push(codeInspectorPlugin({ bundler: 'webpack' }))
|
||||||
config.plugins.push(codeInspectorPlugin({ bundler: 'webpack' }))
|
|
||||||
|
config.experiments = {
|
||||||
|
asyncWebAssembly: true,
|
||||||
|
layers: true,
|
||||||
|
}
|
||||||
|
config.output.environment = {
|
||||||
|
asyncFunction: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|||||||
@ -101,6 +101,7 @@
|
|||||||
"lexical": "^0.30.0",
|
"lexical": "^0.30.0",
|
||||||
"line-clamp": "^1.0.0",
|
"line-clamp": "^1.0.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"loro-crdt": "^1.5.9",
|
||||||
"mermaid": "11.10.0",
|
"mermaid": "11.10.0",
|
||||||
"mime": "^4.0.4",
|
"mime": "^4.0.4",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
@ -144,6 +145,7 @@
|
|||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"sharp": "^0.33.2",
|
"sharp": "^0.33.2",
|
||||||
"shave": "^5.0.4",
|
"shave": "^5.0.4",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"sortablejs": "^1.15.0",
|
"sortablejs": "^1.15.0",
|
||||||
"swr": "^2.3.0",
|
"swr": "^2.3.0",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
|
|||||||
247
web/pnpm-lock.yaml
generated
247
web/pnpm-lock.yaml
generated
@ -217,6 +217,9 @@ importers:
|
|||||||
lodash-es:
|
lodash-es:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
|
loro-crdt:
|
||||||
|
specifier: ^1.5.9
|
||||||
|
version: 1.7.3
|
||||||
mermaid:
|
mermaid:
|
||||||
specifier: 11.10.0
|
specifier: 11.10.0
|
||||||
version: 11.10.0
|
version: 11.10.0
|
||||||
@ -346,6 +349,9 @@ importers:
|
|||||||
shave:
|
shave:
|
||||||
specifier: ^5.0.4
|
specifier: ^5.0.4
|
||||||
version: 5.0.4
|
version: 5.0.4
|
||||||
|
socket.io-client:
|
||||||
|
specifier: ^4.8.1
|
||||||
|
version: 4.8.1
|
||||||
sortablejs:
|
sortablejs:
|
||||||
specifier: ^1.15.0
|
specifier: ^1.15.0
|
||||||
version: 1.15.6
|
version: 1.15.6
|
||||||
@ -1778,144 +1784,170 @@ packages:
|
|||||||
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
|
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-arm64@1.2.0':
|
'@img/sharp-libvips-linux-arm64@1.2.0':
|
||||||
resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==}
|
resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-arm@1.0.5':
|
'@img/sharp-libvips-linux-arm@1.0.5':
|
||||||
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
|
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-arm@1.2.0':
|
'@img/sharp-libvips-linux-arm@1.2.0':
|
||||||
resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==}
|
resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-ppc64@1.2.0':
|
'@img/sharp-libvips-linux-ppc64@1.2.0':
|
||||||
resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==}
|
resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-s390x@1.0.4':
|
'@img/sharp-libvips-linux-s390x@1.0.4':
|
||||||
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
|
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-s390x@1.2.0':
|
'@img/sharp-libvips-linux-s390x@1.2.0':
|
||||||
resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==}
|
resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-x64@1.0.4':
|
'@img/sharp-libvips-linux-x64@1.0.4':
|
||||||
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
|
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-x64@1.2.0':
|
'@img/sharp-libvips-linux-x64@1.2.0':
|
||||||
resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==}
|
resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
|
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
|
||||||
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
|
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.0':
|
'@img/sharp-libvips-linuxmusl-arm64@1.2.0':
|
||||||
resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==}
|
resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
|
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
|
||||||
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
|
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@img/sharp-libvips-linuxmusl-x64@1.2.0':
|
'@img/sharp-libvips-linuxmusl-x64@1.2.0':
|
||||||
resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==}
|
resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@img/sharp-linux-arm64@0.33.5':
|
'@img/sharp-linux-arm64@0.33.5':
|
||||||
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
|
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-linux-arm64@0.34.3':
|
'@img/sharp-linux-arm64@0.34.3':
|
||||||
resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==}
|
resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-linux-arm@0.33.5':
|
'@img/sharp-linux-arm@0.33.5':
|
||||||
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
|
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-linux-arm@0.34.3':
|
'@img/sharp-linux-arm@0.34.3':
|
||||||
resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==}
|
resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-linux-ppc64@0.34.3':
|
'@img/sharp-linux-ppc64@0.34.3':
|
||||||
resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==}
|
resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-linux-s390x@0.33.5':
|
'@img/sharp-linux-s390x@0.33.5':
|
||||||
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
|
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-linux-s390x@0.34.3':
|
'@img/sharp-linux-s390x@0.34.3':
|
||||||
resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==}
|
resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-linux-x64@0.33.5':
|
'@img/sharp-linux-x64@0.33.5':
|
||||||
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
|
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-linux-x64@0.34.3':
|
'@img/sharp-linux-x64@0.34.3':
|
||||||
resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==}
|
resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/sharp-linuxmusl-arm64@0.33.5':
|
'@img/sharp-linuxmusl-arm64@0.33.5':
|
||||||
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
|
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@img/sharp-linuxmusl-arm64@0.34.3':
|
'@img/sharp-linuxmusl-arm64@0.34.3':
|
||||||
resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==}
|
resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@img/sharp-linuxmusl-x64@0.33.5':
|
'@img/sharp-linuxmusl-x64@0.33.5':
|
||||||
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
|
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@img/sharp-linuxmusl-x64@0.34.3':
|
'@img/sharp-linuxmusl-x64@0.34.3':
|
||||||
resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==}
|
resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@img/sharp-wasm32@0.33.5':
|
'@img/sharp-wasm32@0.33.5':
|
||||||
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
|
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
|
||||||
@ -2205,24 +2237,28 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@15.5.0':
|
'@next/swc-linux-arm64-musl@15.5.0':
|
||||||
resolution: {integrity: sha512-biWqIOE17OW/6S34t1X8K/3vb1+svp5ji5QQT/IKR+VfM3B7GvlCwmz5XtlEan2ukOUf9tj2vJJBffaGH4fGRw==}
|
resolution: {integrity: sha512-biWqIOE17OW/6S34t1X8K/3vb1+svp5ji5QQT/IKR+VfM3B7GvlCwmz5XtlEan2ukOUf9tj2vJJBffaGH4fGRw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@15.5.0':
|
'@next/swc-linux-x64-gnu@15.5.0':
|
||||||
resolution: {integrity: sha512-zPisT+obYypM/l6EZ0yRkK3LEuoZqHaSoYKj+5jiD9ESHwdr6QhnabnNxYkdy34uCigNlWIaCbjFmQ8FY5AlxA==}
|
resolution: {integrity: sha512-zPisT+obYypM/l6EZ0yRkK3LEuoZqHaSoYKj+5jiD9ESHwdr6QhnabnNxYkdy34uCigNlWIaCbjFmQ8FY5AlxA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@15.5.0':
|
'@next/swc-linux-x64-musl@15.5.0':
|
||||||
resolution: {integrity: sha512-+t3+7GoU9IYmk+N+FHKBNFdahaReoAktdOpXHFIPOU1ixxtdge26NgQEEkJkCw2dHT9UwwK5zw4mAsURw4E8jA==}
|
resolution: {integrity: sha512-+t3+7GoU9IYmk+N+FHKBNFdahaReoAktdOpXHFIPOU1ixxtdge26NgQEEkJkCw2dHT9UwwK5zw4mAsURw4E8jA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@15.5.0':
|
'@next/swc-win32-arm64-msvc@15.5.0':
|
||||||
resolution: {integrity: sha512-d8MrXKh0A+c9DLiy1BUFwtg3Hu90Lucj3k6iKTUdPOv42Ve2UiIG8HYi3UAb8kFVluXxEfdpCoPPCSODk5fDcw==}
|
resolution: {integrity: sha512-d8MrXKh0A+c9DLiy1BUFwtg3Hu90Lucj3k6iKTUdPOv42Ve2UiIG8HYi3UAb8kFVluXxEfdpCoPPCSODk5fDcw==}
|
||||||
@ -2444,36 +2480,42 @@ packages:
|
|||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-win32-arm64@2.5.1':
|
'@parcel/watcher-win32-arm64@2.5.1':
|
||||||
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||||
@ -2868,6 +2910,9 @@ packages:
|
|||||||
'@sinonjs/fake-timers@10.3.0':
|
'@sinonjs/fake-timers@10.3.0':
|
||||||
resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
|
resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
|
||||||
|
|
||||||
|
'@socket.io/component-emitter@3.1.2':
|
||||||
|
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||||
|
|
||||||
'@storybook/addon-actions@8.5.0':
|
'@storybook/addon-actions@8.5.0':
|
||||||
resolution: {integrity: sha512-6CW9+17rk5eNx6I8EKqCxRKtsJFTR/lHL+xiJ6/iBWApIm8sg63vhXvUTJ58UixmIkT5oLh0+ESNPh+x10D8fw==}
|
resolution: {integrity: sha512-6CW9+17rk5eNx6I8EKqCxRKtsJFTR/lHL+xiJ6/iBWApIm8sg63vhXvUTJ58UixmIkT5oLh0+ESNPh+x10D8fw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3632,41 +3677,49 @@ packages:
|
|||||||
resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
|
resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
|
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
|
||||||
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
|
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
|
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
|
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
|
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
|
||||||
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
|
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
|
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
|
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
|
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
|
||||||
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
|
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
|
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
|
||||||
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
|
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
|
||||||
@ -4733,8 +4786,26 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
debug@4.4.1:
|
debug@4.3.7:
|
||||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
debug@4.4.0:
|
||||||
|
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
debug@4.4.3:
|
||||||
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
supports-color: '*'
|
supports-color: '*'
|
||||||
@ -4946,8 +5017,15 @@ packages:
|
|||||||
endent@2.1.0:
|
endent@2.1.0:
|
||||||
resolution: {integrity: sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==}
|
resolution: {integrity: sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==}
|
||||||
|
|
||||||
enhanced-resolve@5.18.2:
|
engine.io-client@6.6.3:
|
||||||
resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
|
resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==}
|
||||||
|
|
||||||
|
engine.io-parser@5.2.3:
|
||||||
|
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
|
enhanced-resolve@5.18.1:
|
||||||
|
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
entities@2.2.0:
|
entities@2.2.0:
|
||||||
@ -6453,8 +6531,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
loupe@3.1.4:
|
loro-crdt@1.7.3:
|
||||||
resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==}
|
resolution: {integrity: sha512-bP/Su2mVDf0ieIdOH8RyQ4Dg8hIJUQCzwTfzpuPtQl7XoAk2tzYe5Dt77kIAmioN6DyCI0Frbt2Nr9lHBPf9Mw==}
|
||||||
|
|
||||||
|
loupe@3.1.3:
|
||||||
|
resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
|
||||||
|
|
||||||
lower-case@2.0.2:
|
lower-case@2.0.2:
|
||||||
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
|
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
|
||||||
@ -7986,6 +8067,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
|
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
socket.io-client@4.8.1:
|
||||||
|
resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
|
socket.io-parser@4.2.4:
|
||||||
|
resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
sortablejs@1.15.6:
|
sortablejs@1.15.6:
|
||||||
resolution: {integrity: sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==}
|
resolution: {integrity: sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==}
|
||||||
|
|
||||||
@ -8824,8 +8913,20 @@ packages:
|
|||||||
utf-8-validate:
|
utf-8-validate:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
ws@8.18.3:
|
ws@8.17.1:
|
||||||
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
|
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
bufferutil: ^4.0.1
|
||||||
|
utf-8-validate: '>=5.0.2'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bufferutil:
|
||||||
|
optional: true
|
||||||
|
utf-8-validate:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
ws@8.18.1:
|
||||||
|
resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
bufferutil: ^4.0.1
|
bufferutil: ^4.0.1
|
||||||
@ -8840,6 +8941,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
|
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
xmlhttprequest-ssl@2.1.2:
|
||||||
|
resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
xtend@4.0.2:
|
xtend@4.0.2:
|
||||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
@ -9020,7 +9125,7 @@ snapshots:
|
|||||||
'@babel/traverse': 7.28.3
|
'@babel/traverse': 7.28.3
|
||||||
'@babel/types': 7.28.2
|
'@babel/types': 7.28.2
|
||||||
convert-source-map: 2.0.0
|
convert-source-map: 2.0.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
gensync: 1.0.0-beta.2
|
gensync: 1.0.0-beta.2
|
||||||
json5: 2.2.3
|
json5: 2.2.3
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
@ -9093,7 +9198,7 @@ snapshots:
|
|||||||
'@babel/core': 7.28.3
|
'@babel/core': 7.28.3
|
||||||
'@babel/helper-compilation-targets': 7.27.2
|
'@babel/helper-compilation-targets': 7.27.2
|
||||||
'@babel/helper-plugin-utils': 7.27.1
|
'@babel/helper-plugin-utils': 7.27.1
|
||||||
debug: 4.4.1
|
debug: 4.4.3
|
||||||
lodash.debounce: 4.0.8
|
lodash.debounce: 4.0.8
|
||||||
resolve: 1.22.10
|
resolve: 1.22.10
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@ -9842,7 +9947,7 @@ snapshots:
|
|||||||
'@babel/parser': 7.28.0
|
'@babel/parser': 7.28.0
|
||||||
'@babel/template': 7.27.2
|
'@babel/template': 7.27.2
|
||||||
'@babel/types': 7.28.1
|
'@babel/types': 7.28.1
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -9854,7 +9959,7 @@ snapshots:
|
|||||||
'@babel/parser': 7.28.3
|
'@babel/parser': 7.28.3
|
||||||
'@babel/template': 7.27.2
|
'@babel/template': 7.27.2
|
||||||
'@babel/types': 7.28.2
|
'@babel/types': 7.28.2
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -10151,7 +10256,7 @@ snapshots:
|
|||||||
'@eslint/config-array@0.21.0':
|
'@eslint/config-array@0.21.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint/object-schema': 2.1.6
|
'@eslint/object-schema': 2.1.6
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -10165,7 +10270,7 @@ snapshots:
|
|||||||
'@eslint/eslintrc@3.3.1':
|
'@eslint/eslintrc@3.3.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
espree: 10.4.0
|
espree: 10.4.0
|
||||||
globals: 14.0.0
|
globals: 14.0.0
|
||||||
ignore: 5.3.2
|
ignore: 5.3.2
|
||||||
@ -10281,7 +10386,7 @@ snapshots:
|
|||||||
'@antfu/install-pkg': 1.1.0
|
'@antfu/install-pkg': 1.1.0
|
||||||
'@antfu/utils': 8.1.1
|
'@antfu/utils': 8.1.1
|
||||||
'@iconify/types': 2.0.0
|
'@iconify/types': 2.0.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
globals: 15.15.0
|
globals: 15.15.0
|
||||||
kolorist: 1.8.0
|
kolorist: 1.8.0
|
||||||
local-pkg: 1.1.2
|
local-pkg: 1.1.2
|
||||||
@ -11559,6 +11664,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@sinonjs/commons': 3.0.1
|
'@sinonjs/commons': 3.0.1
|
||||||
|
|
||||||
|
'@socket.io/component-emitter@3.1.2': {}
|
||||||
|
|
||||||
'@storybook/addon-actions@8.5.0(storybook@8.5.0)':
|
'@storybook/addon-actions@8.5.0(storybook@8.5.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@storybook/global': 5.0.0
|
'@storybook/global': 5.0.0
|
||||||
@ -11741,7 +11848,7 @@ snapshots:
|
|||||||
recast: 0.23.11
|
recast: 0.23.11
|
||||||
semver: 7.7.2
|
semver: 7.7.2
|
||||||
util: 0.12.5
|
util: 0.12.5
|
||||||
ws: 8.18.3
|
ws: 8.18.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -11875,7 +11982,7 @@ snapshots:
|
|||||||
|
|
||||||
'@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))':
|
'@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
endent: 2.1.0
|
endent: 2.1.0
|
||||||
find-cache-dir: 3.3.2
|
find-cache-dir: 3.3.2
|
||||||
flat-cache: 3.2.0
|
flat-cache: 3.2.0
|
||||||
@ -12410,7 +12517,7 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.38.0
|
'@typescript-eslint/types': 8.38.0
|
||||||
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3)
|
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.38.0
|
'@typescript-eslint/visitor-keys': 8.38.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
eslint: 9.32.0(jiti@1.21.7)
|
eslint: 9.32.0(jiti@1.21.7)
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@ -12420,7 +12527,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.37.0(typescript@5.8.3)
|
'@typescript-eslint/tsconfig-utils': 8.37.0(typescript@5.8.3)
|
||||||
'@typescript-eslint/types': 8.37.0
|
'@typescript-eslint/types': 8.37.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -12429,7 +12536,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.8.3)
|
'@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.8.3)
|
||||||
'@typescript-eslint/types': 8.38.0
|
'@typescript-eslint/types': 8.38.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -12457,7 +12564,7 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.37.0
|
'@typescript-eslint/types': 8.37.0
|
||||||
'@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3)
|
'@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3)
|
||||||
'@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)
|
'@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
eslint: 9.32.0(jiti@1.21.7)
|
eslint: 9.32.0(jiti@1.21.7)
|
||||||
ts-api-utils: 2.1.0(typescript@5.8.3)
|
ts-api-utils: 2.1.0(typescript@5.8.3)
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
@ -12469,7 +12576,7 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.38.0
|
'@typescript-eslint/types': 8.38.0
|
||||||
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3)
|
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3)
|
||||||
'@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)
|
'@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
eslint: 9.32.0(jiti@1.21.7)
|
eslint: 9.32.0(jiti@1.21.7)
|
||||||
ts-api-utils: 2.1.0(typescript@5.8.3)
|
ts-api-utils: 2.1.0(typescript@5.8.3)
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
@ -12486,7 +12593,7 @@ snapshots:
|
|||||||
'@typescript-eslint/tsconfig-utils': 8.37.0(typescript@5.8.3)
|
'@typescript-eslint/tsconfig-utils': 8.37.0(typescript@5.8.3)
|
||||||
'@typescript-eslint/types': 8.37.0
|
'@typescript-eslint/types': 8.37.0
|
||||||
'@typescript-eslint/visitor-keys': 8.37.0
|
'@typescript-eslint/visitor-keys': 8.37.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
fast-glob: 3.3.3
|
fast-glob: 3.3.3
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
@ -12502,7 +12609,7 @@ snapshots:
|
|||||||
'@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.8.3)
|
'@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.8.3)
|
||||||
'@typescript-eslint/types': 8.38.0
|
'@typescript-eslint/types': 8.38.0
|
||||||
'@typescript-eslint/visitor-keys': 8.38.0
|
'@typescript-eslint/visitor-keys': 8.38.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
fast-glob: 3.3.3
|
fast-glob: 3.3.3
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
@ -12637,13 +12744,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/pretty-format': 2.0.5
|
'@vitest/pretty-format': 2.0.5
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
loupe: 3.1.4
|
loupe: 3.1.3
|
||||||
tinyrainbow: 1.2.0
|
tinyrainbow: 1.2.0
|
||||||
|
|
||||||
'@vitest/utils@2.1.9':
|
'@vitest/utils@2.1.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/pretty-format': 2.1.9
|
'@vitest/pretty-format': 2.1.9
|
||||||
loupe: 3.1.4
|
loupe: 3.1.3
|
||||||
tinyrainbow: 1.2.0
|
tinyrainbow: 1.2.0
|
||||||
|
|
||||||
'@vue/compiler-core@3.5.17':
|
'@vue/compiler-core@3.5.17':
|
||||||
@ -12788,7 +12895,7 @@ snapshots:
|
|||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
optional: true
|
optional: true
|
||||||
@ -13202,7 +13309,7 @@ snapshots:
|
|||||||
assertion-error: 2.0.1
|
assertion-error: 2.0.1
|
||||||
check-error: 2.1.1
|
check-error: 2.1.1
|
||||||
deep-eql: 5.0.2
|
deep-eql: 5.0.2
|
||||||
loupe: 3.1.4
|
loupe: 3.1.3
|
||||||
pathval: 2.0.1
|
pathval: 2.0.1
|
||||||
|
|
||||||
chalk@3.0.0:
|
chalk@3.0.0:
|
||||||
@ -13772,7 +13879,15 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
debug@4.4.1:
|
debug@4.3.7:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
|
||||||
|
debug@4.4.0:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
|
||||||
|
debug@4.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
@ -13966,7 +14081,21 @@ snapshots:
|
|||||||
fast-json-parse: 1.0.3
|
fast-json-parse: 1.0.3
|
||||||
objectorarray: 1.0.5
|
objectorarray: 1.0.5
|
||||||
|
|
||||||
enhanced-resolve@5.18.2:
|
engine.io-client@6.6.3:
|
||||||
|
dependencies:
|
||||||
|
'@socket.io/component-emitter': 3.1.2
|
||||||
|
debug: 4.3.7
|
||||||
|
engine.io-parser: 5.2.3
|
||||||
|
ws: 8.17.1
|
||||||
|
xmlhttprequest-ssl: 2.1.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
|
engine.io-parser@5.2.3: {}
|
||||||
|
|
||||||
|
enhanced-resolve@5.18.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
tapable: 2.2.2
|
tapable: 2.2.2
|
||||||
@ -14013,7 +14142,7 @@ snapshots:
|
|||||||
|
|
||||||
esbuild-register@3.6.0(esbuild@0.25.0):
|
esbuild-register@3.6.0(esbuild@0.25.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
esbuild: 0.25.0
|
esbuild: 0.25.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -14106,7 +14235,7 @@ snapshots:
|
|||||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@1.21.7)):
|
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@1.21.7)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nolyfill/is-core-module': 1.0.39
|
'@nolyfill/is-core-module': 1.0.39
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
eslint: 9.32.0(jiti@1.21.7)
|
eslint: 9.32.0(jiti@1.21.7)
|
||||||
get-tsconfig: 4.10.1
|
get-tsconfig: 4.10.1
|
||||||
is-bun-module: 2.0.0
|
is-bun-module: 2.0.0
|
||||||
@ -14197,7 +14326,7 @@ snapshots:
|
|||||||
'@es-joy/jsdoccomment': 0.52.0
|
'@es-joy/jsdoccomment': 0.52.0
|
||||||
are-docs-informative: 0.0.2
|
are-docs-informative: 0.0.2
|
||||||
comment-parser: 1.4.1
|
comment-parser: 1.4.1
|
||||||
debug: 4.4.1
|
debug: 4.4.3
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
eslint: 9.32.0(jiti@1.21.7)
|
eslint: 9.32.0(jiti@1.21.7)
|
||||||
espree: 10.4.0
|
espree: 10.4.0
|
||||||
@ -14244,7 +14373,7 @@ snapshots:
|
|||||||
eslint-plugin-n@17.21.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3):
|
eslint-plugin-n@17.21.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7))
|
'@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7))
|
||||||
enhanced-resolve: 5.18.2
|
enhanced-resolve: 5.18.1
|
||||||
eslint: 9.32.0(jiti@1.21.7)
|
eslint: 9.32.0(jiti@1.21.7)
|
||||||
eslint-plugin-es-x: 7.8.0(eslint@9.32.0(jiti@1.21.7))
|
eslint-plugin-es-x: 7.8.0(eslint@9.32.0(jiti@1.21.7))
|
||||||
get-tsconfig: 4.10.1
|
get-tsconfig: 4.10.1
|
||||||
@ -14477,7 +14606,7 @@ snapshots:
|
|||||||
|
|
||||||
eslint-plugin-toml@0.12.0(eslint@9.32.0(jiti@1.21.7)):
|
eslint-plugin-toml@0.12.0(eslint@9.32.0(jiti@1.21.7)):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
eslint: 9.32.0(jiti@1.21.7)
|
eslint: 9.32.0(jiti@1.21.7)
|
||||||
eslint-compat-utils: 0.6.5(eslint@9.32.0(jiti@1.21.7))
|
eslint-compat-utils: 0.6.5(eslint@9.32.0(jiti@1.21.7))
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
@ -14528,7 +14657,7 @@ snapshots:
|
|||||||
|
|
||||||
eslint-plugin-yml@1.18.0(eslint@9.32.0(jiti@1.21.7)):
|
eslint-plugin-yml@1.18.0(eslint@9.32.0(jiti@1.21.7)):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
eslint: 9.32.0(jiti@1.21.7)
|
eslint: 9.32.0(jiti@1.21.7)
|
||||||
eslint-compat-utils: 0.6.5(eslint@9.32.0(jiti@1.21.7))
|
eslint-compat-utils: 0.6.5(eslint@9.32.0(jiti@1.21.7))
|
||||||
@ -14574,7 +14703,7 @@ snapshots:
|
|||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
eslint-scope: 8.4.0
|
eslint-scope: 8.4.0
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
@ -15214,7 +15343,7 @@ snapshots:
|
|||||||
https-proxy-agent@5.0.1:
|
https-proxy-agent@5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 6.0.2
|
agent-base: 6.0.2
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
optional: true
|
optional: true
|
||||||
@ -15418,7 +15547,7 @@ snapshots:
|
|||||||
|
|
||||||
istanbul-lib-source-maps@4.0.1:
|
istanbul-lib-source-maps@4.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
istanbul-lib-coverage: 3.2.2
|
istanbul-lib-coverage: 3.2.2
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@ -15899,7 +16028,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
chalk: 5.4.1
|
chalk: 5.4.1
|
||||||
commander: 13.1.0
|
commander: 13.1.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
execa: 8.0.1
|
execa: 8.0.1
|
||||||
lilconfig: 3.1.3
|
lilconfig: 3.1.3
|
||||||
listr2: 8.3.3
|
listr2: 8.3.3
|
||||||
@ -15981,7 +16110,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
loupe@3.1.4: {}
|
loro-crdt@1.7.3: {}
|
||||||
|
|
||||||
|
loupe@3.1.3: {}
|
||||||
|
|
||||||
lower-case@2.0.2:
|
lower-case@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -16540,7 +16671,7 @@ snapshots:
|
|||||||
micromark@4.0.2:
|
micromark@4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/debug': 4.1.12
|
'@types/debug': 4.1.12
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
decode-named-character-reference: 1.2.0
|
decode-named-character-reference: 1.2.0
|
||||||
devlop: 1.1.0
|
devlop: 1.1.0
|
||||||
micromark-core-commonmark: 2.0.3
|
micromark-core-commonmark: 2.0.3
|
||||||
@ -17063,7 +17194,7 @@ snapshots:
|
|||||||
portfinder@1.0.37:
|
portfinder@1.0.37:
|
||||||
dependencies:
|
dependencies:
|
||||||
async: 3.2.6
|
async: 3.2.6
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -17990,6 +18121,24 @@ snapshots:
|
|||||||
ansi-styles: 6.2.1
|
ansi-styles: 6.2.1
|
||||||
is-fullwidth-code-point: 5.0.0
|
is-fullwidth-code-point: 5.0.0
|
||||||
|
|
||||||
|
socket.io-client@4.8.1:
|
||||||
|
dependencies:
|
||||||
|
'@socket.io/component-emitter': 3.1.2
|
||||||
|
debug: 4.3.7
|
||||||
|
engine.io-client: 6.6.3
|
||||||
|
socket.io-parser: 4.2.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
|
socket.io-parser@4.2.4:
|
||||||
|
dependencies:
|
||||||
|
'@socket.io/component-emitter': 3.1.2
|
||||||
|
debug: 4.3.7
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
sortablejs@1.15.6: {}
|
sortablejs@1.15.6: {}
|
||||||
|
|
||||||
source-list-map@2.0.1: {}
|
source-list-map@2.0.1: {}
|
||||||
@ -18366,7 +18515,7 @@ snapshots:
|
|||||||
tsconfig-paths-webpack-plugin@4.2.0:
|
tsconfig-paths-webpack-plugin@4.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
enhanced-resolve: 5.18.2
|
enhanced-resolve: 5.18.1
|
||||||
tapable: 2.2.2
|
tapable: 2.2.2
|
||||||
tsconfig-paths: 4.2.0
|
tsconfig-paths: 4.2.0
|
||||||
|
|
||||||
@ -18649,7 +18798,7 @@ snapshots:
|
|||||||
|
|
||||||
vue-eslint-parser@10.2.0(eslint@9.32.0(jiti@1.21.7)):
|
vue-eslint-parser@10.2.0(eslint@9.32.0(jiti@1.21.7)):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.1
|
debug: 4.4.0
|
||||||
eslint: 9.32.0(jiti@1.21.7)
|
eslint: 9.32.0(jiti@1.21.7)
|
||||||
eslint-scope: 8.4.0
|
eslint-scope: 8.4.0
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
@ -18739,7 +18888,7 @@ snapshots:
|
|||||||
acorn-import-phases: 1.0.4(acorn@8.15.0)
|
acorn-import-phases: 1.0.4(acorn@8.15.0)
|
||||||
browserslist: 4.25.1
|
browserslist: 4.25.1
|
||||||
chrome-trace-event: 1.0.4
|
chrome-trace-event: 1.0.4
|
||||||
enhanced-resolve: 5.18.2
|
enhanced-resolve: 5.18.1
|
||||||
es-module-lexer: 1.7.0
|
es-module-lexer: 1.7.0
|
||||||
eslint-scope: 5.1.1
|
eslint-scope: 5.1.1
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
@ -18936,10 +19085,14 @@ snapshots:
|
|||||||
|
|
||||||
ws@7.5.10: {}
|
ws@7.5.10: {}
|
||||||
|
|
||||||
ws@8.18.3: {}
|
ws@8.17.1: {}
|
||||||
|
|
||||||
|
ws@8.18.1: {}
|
||||||
|
|
||||||
xml-name-validator@4.0.0: {}
|
xml-name-validator@4.0.0: {}
|
||||||
|
|
||||||
|
xmlhttprequest-ssl@2.1.2: {}
|
||||||
|
|
||||||
xtend@4.0.2: {}
|
xtend@4.0.2: {}
|
||||||
|
|
||||||
y18n@5.0.8: {}
|
y18n@5.0.8: {}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user