Commit 910b67cb authored by Hoanganhvu123's avatar Hoanganhvu123

Integrate Clerk auth, memo retrieval tool, and chatbot UI

parent fbd1e348
...@@ -3,23 +3,24 @@ Tools Factory ...@@ -3,23 +3,24 @@ Tools Factory
Chỉ return 1 tool duy nhất: data_retrieval_tool Chỉ return 1 tool duy nhất: data_retrieval_tool
""" """
from langchain_core.tools import Tool from langchain_core.tools import BaseTool
from .brand_knowledge_tool import canifa_knowledge_search from .brand_knowledge_tool import canifa_knowledge_search
from .customer_info_tool import collect_customer_info from .customer_info_tool import collect_customer_info
from .data_retrieval_tool import data_retrieval_tool from .data_retrieval_tool import data_retrieval_tool
from .memo_retrieval_tool import memo_retrieval_tool
def get_retrieval_tools() -> list[Tool]: def get_retrieval_tools() -> list[BaseTool]:
"""Các tool chỉ dùng để đọc/truy vấn dữ liệu (Có thể cache)""" """Các tool chỉ dùng để đọc/truy vấn dữ liệu (Có thể cache)"""
return [data_retrieval_tool, canifa_knowledge_search] return [data_retrieval_tool, canifa_knowledge_search, memo_retrieval_tool]
def get_collection_tools() -> list[Tool]: def get_collection_tools() -> list[BaseTool]:
"""Các tool dùng để ghi/thu thập dữ liệu (KHÔNG cache)""" """Các tool dùng để ghi/thu thập dữ liệu (KHÔNG cache)"""
return [collect_customer_info] return [collect_customer_info]
def get_all_tools() -> list[Tool]: def get_all_tools() -> list[BaseTool]:
"""Return toàn bộ list tools cho Agent""" """Return toàn bộ list tools cho Agent"""
return get_retrieval_tools() + get_collection_tools() return get_retrieval_tools() + get_collection_tools()
"""
Memo Retrieval Tool - đọc ghi chú từ SQLite memos.db cho Agent.
Dùng để trả lời các câu hỏi kiểu:
- "Hôm nay tôi đã note gì?"
- "Tuần trước tôi note những gì về dự án X?"
"""
from __future__ import annotations
import json
import logging
from datetime import date
from typing import Optional
from langchain_core.tools import tool
from memos_core import db as memo_db
logger = logging.getLogger(__name__)
@tool
async def memo_retrieval_tool(
date: str,
tag: Optional[str] = None,
) -> str:
"""
Truy vấn các memo đã note trong memos.db (SQLite).
- Ưu tiên dùng để trả lời câu hỏi về lịch sử note, ví dụ:
- "Hôm nay tôi đã note gì thế?"
- "Ngày 2026-01-15 tôi note những gì?"
- "Hôm nay tôi note gì về tag #work?"
- Không làm semantic search phức tạp, chỉ lọc theo ngày và tag.
"""
try:
target_date = date or date_today_iso()
logger.info("memo_retrieval_tool started, date=%s, tag=%s", target_date, tag)
sql = """
SELECT id, content, visibility, tags_json, created_at, updated_at
FROM memos
WHERE DATE(created_at) = ?
"""
params: list[object] = [target_date]
if tag:
# Lọc đơn giản: tags_json là chuỗi JSON array, tìm substring theo tag.
sql += " AND tags_json LIKE ?"
params.append(f"%{tag}%")
rows = await memo_db.fetch_all(sql, tuple(params))
memos = []
for row in rows:
# Chuẩn hóa tags
raw_tags = row.get("tags_json") or "[]"
try:
tags = json.loads(raw_tags)
if not isinstance(tags, list):
tags = []
except Exception:
tags = []
memos.append(
{
"id": row.get("id"),
"content": row.get("content"),
"visibility": row.get("visibility"),
"tags": tags,
"created_at": row.get("created_at"),
"updated_at": row.get("updated_at"),
}
)
return json.dumps(
{
"status": "success",
"date": target_date,
"tag": tag,
"count": len(memos),
"memos": memos,
},
ensure_ascii=False,
)
except Exception as exc: # pragma: no cover
logger.exception("memo_retrieval_tool error: %s", exc)
return json.dumps(
{
"status": "error",
"message": str(exc),
},
ensure_ascii=False,
)
def date_today_iso() -> str:
"""Return today's date in YYYY-MM-DD format."""
return date.today().isoformat()
from __future__ import annotations
import logging
from functools import lru_cache
from typing import Any
import jwt
from jwt import PyJWKClient
from config import CLERK_ISSUER, CLERK_JWKS_URL
logger = logging.getLogger(__name__)
@lru_cache(maxsize=1)
def _jwks_client() -> PyJWKClient:
if not CLERK_JWKS_URL:
raise ValueError("CLERK_JWKS_URL is not configured")
return PyJWKClient(CLERK_JWKS_URL)
def verify_clerk_jwt(token: str) -> dict[str, Any]:
"""
Verify Clerk JWT using JWKS.
Requires:
- CLERK_JWKS_URL: e.g. https://<your-clerk-domain>/.well-known/jwks.json
- CLERK_ISSUER: e.g. https://<your-clerk-domain>
"""
if not token:
raise ValueError("Missing token")
if not CLERK_JWKS_URL or not CLERK_ISSUER:
raise ValueError("Missing CLERK_JWKS_URL / CLERK_ISSUER")
signing_key = _jwks_client().get_signing_key_from_jwt(token).key
# Clerk tokens are typically RS256.
payload = jwt.decode(
token,
signing_key,
algorithms=["RS256"],
issuer=CLERK_ISSUER,
options={
"verify_aud": False, # allow multiple audiences in dev
},
)
return payload
...@@ -8,10 +8,11 @@ import logging ...@@ -8,10 +8,11 @@ import logging
from collections.abc import Callable from collections.abc import Callable
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from fastapi import HTTPException, Request, status from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
from common.clerk_auth import verify_clerk_jwt
from config import DISABLE_AUTH from config import DISABLE_AUTH
from starlette.middleware.base import BaseHTTPMiddleware
if TYPE_CHECKING: if TYPE_CHECKING:
from fastapi import FastAPI from fastapi import FastAPI
...@@ -50,7 +51,7 @@ RATE_LIMITED_PATHS = [ ...@@ -50,7 +51,7 @@ RATE_LIMITED_PATHS = [
class CanifaAuthMiddleware(BaseHTTPMiddleware): class CanifaAuthMiddleware(BaseHTTPMiddleware):
""" """
Canifa Authentication + Rate Limit Middleware Authentication + Rate Limit Middleware
Flow: Flow:
1. Frontend gửi request với Authorization: Bearer <canifa_token> 1. Frontend gửi request với Authorization: Bearer <canifa_token>
...@@ -81,7 +82,7 @@ class CanifaAuthMiddleware(BaseHTTPMiddleware): ...@@ -81,7 +82,7 @@ class CanifaAuthMiddleware(BaseHTTPMiddleware):
return await call_next(request) return await call_next(request)
# ===================================================================== # =====================================================================
# STEP 1: AUTHENTICATION (Canifa API) # STEP 1: AUTHENTICATION (Clerk JWT)
# ===================================================================== # =====================================================================
try: try:
auth_header = request.headers.get("Authorization") auth_header = request.headers.get("Authorization")
...@@ -104,31 +105,26 @@ class CanifaAuthMiddleware(BaseHTTPMiddleware): ...@@ -104,31 +105,26 @@ class CanifaAuthMiddleware(BaseHTTPMiddleware):
request.state.is_authenticated = False request.state.is_authenticated = False
request.state.device_id = device_id request.state.device_id = device_id
else: else:
# --- TRƯỜNG HỢP 2: CÓ TOKEN -> GỌI CANIFA VERIFY --- # --- TRƯỜNG HỢP 2: CÓ TOKEN -> VERIFY CLERK JWT ---
token = auth_header.replace("Bearer ", "") token = auth_header.replace("Bearer ", "")
from common.canifa_api import verify_canifa_token, extract_user_id_from_canifa_response
try: try:
user_data = await verify_canifa_token(token) payload = verify_clerk_jwt(token)
user_id = await extract_user_id_from_canifa_response(user_data) user_id = payload.get("sub")
if user_id: if user_id:
request.state.user = user_data request.state.user = payload
request.state.user_id = user_id request.state.user_id = str(user_id)
request.state.token = token request.state.token = token
request.state.is_authenticated = True request.state.is_authenticated = True
request.state.device_id = device_id request.state.device_id = device_id or str(user_id)
logger.debug(f"✅ Canifa Auth Success: User {user_id}") logger.debug("✅ Clerk Auth Success: user_id=%s", user_id)
else: else:
logger.warning(f"⚠️ Invalid Canifa Token -> Guest Mode") logger.warning("⚠️ Clerk token missing sub -> Guest Mode")
request.state.user = None request.state.user = None
request.state.user_id = None request.state.user_id = None
request.state.is_authenticated = False request.state.is_authenticated = False
request.state.device_id = device_id request.state.device_id = device_id
except Exception as e: except Exception as e:
logger.error(f"❌ Canifa Auth Error: {e} -> Guest Mode") logger.error("❌ Clerk Auth Error: %s -> Guest Mode", e)
request.state.user = None request.state.user = None
request.state.user_id = None request.state.user_id = None
request.state.is_authenticated = False request.state.is_authenticated = False
...@@ -169,8 +165,10 @@ class CanifaAuthMiddleware(BaseHTTPMiddleware): ...@@ -169,8 +165,10 @@ class CanifaAuthMiddleware(BaseHTTPMiddleware):
if not can_send: if not can_send:
logger.warning( logger.warning(
f"⚠️ Rate Limit Exceeded: {rate_limit_key} | " "⚠️ Rate Limit Exceeded: %s | used=%s/%s",
f"used={limit_info['used']}/{limit_info['limit']}" rate_limit_key,
limit_info["used"],
limit_info["limit"],
) )
return JSONResponse( return JSONResponse(
status_code=429, status_code=429,
......
...@@ -18,6 +18,8 @@ __all__ = [ ...@@ -18,6 +18,8 @@ __all__ = [
"CHECKPOINT_POSTGRES_SCHEMA", "CHECKPOINT_POSTGRES_SCHEMA",
"CHECKPOINT_POSTGRES_URL", "CHECKPOINT_POSTGRES_URL",
"CLERK_SECRET_KEY", "CLERK_SECRET_KEY",
"CLERK_JWKS_URL",
"CLERK_ISSUER",
"CONV_DATABASE_URL", "CONV_DATABASE_URL",
"CONV_SUPABASE_KEY", "CONV_SUPABASE_KEY",
"CONV_SUPABASE_URL", "CONV_SUPABASE_URL",
...@@ -78,8 +80,8 @@ OPENAI_API_KEY: str | None = os.getenv("OPENAI_API_KEY") ...@@ -78,8 +80,8 @@ OPENAI_API_KEY: str | None = os.getenv("OPENAI_API_KEY")
GOOGLE_API_KEY: str | None = os.getenv("GOOGLE_API_KEY") GOOGLE_API_KEY: str | None = os.getenv("GOOGLE_API_KEY")
GROQ_API_KEY: str | None = os.getenv("GROQ_API_KEY") GROQ_API_KEY: str | None = os.getenv("GROQ_API_KEY")
DEFAULT_MODEL: str = os.getenv("DEFAULT_MODEL", "gpt-5-nano") DEFAULT_MODEL: str = os.getenv("DEFAULT_MODEL", "gpt-4o-mini")
# DEFAULT_MODEL: str = os.getenv("DEFAULT_MODEL") AI_MODEL_NAME = DEFAULT_MODEL
# ====================== JWT CONFIGURATION ====================== # ====================== JWT CONFIGURATION ======================
JWT_SECRET: str | None = os.getenv("JWT_SECRET") JWT_SECRET: str | None = os.getenv("JWT_SECRET")
...@@ -106,6 +108,8 @@ LANGSMITH_PROJECT = None ...@@ -106,6 +108,8 @@ LANGSMITH_PROJECT = None
# ====================== CLERK AUTHENTICATION ====================== # ====================== CLERK AUTHENTICATION ======================
CLERK_SECRET_KEY: str | None = os.getenv("CLERK_SECRET_KEY") CLERK_SECRET_KEY: str | None = os.getenv("CLERK_SECRET_KEY")
CLERK_JWKS_URL: str | None = os.getenv("CLERK_JWKS_URL")
CLERK_ISSUER: str | None = os.getenv("CLERK_ISSUER")
# ====================== DATABASE CONNECTION ====================== # ====================== DATABASE CONNECTION ======================
# Redis Cache Configuration # Redis Cache Configuration
...@@ -133,7 +137,6 @@ STARROCKS_PASSWORD: str | None = os.getenv("STARROCKS_PASSWORD") ...@@ -133,7 +137,6 @@ STARROCKS_PASSWORD: str | None = os.getenv("STARROCKS_PASSWORD")
STARROCKS_DB: str | None = os.getenv("STARROCKS_DB") STARROCKS_DB: str | None = os.getenv("STARROCKS_DB")
# Placeholder for backward compatibility if needed # Placeholder for backward compatibility if needed
AI_MODEL_NAME = DEFAULT_MODEL
# ====================== OPENTELEMETRY CONFIGURATION ====================== # ====================== OPENTELEMETRY CONFIGURATION ======================
OTEL_EXPORTER_JAEGER_AGENT_HOST = os.getenv("OTEL_EXPORTER_JAEGER_AGENT_HOST") OTEL_EXPORTER_JAEGER_AGENT_HOST = os.getenv("OTEL_EXPORTER_JAEGER_AGENT_HOST")
OTEL_EXPORTER_JAEGER_AGENT_PORT = os.getenv("OTEL_EXPORTER_JAEGER_AGENT_PORT") OTEL_EXPORTER_JAEGER_AGENT_PORT = os.getenv("OTEL_EXPORTER_JAEGER_AGENT_PORT")
......
No preview for this file type
...@@ -94,6 +94,7 @@ Pygments==2.19.2 ...@@ -94,6 +94,7 @@ Pygments==2.19.2
PyMySQL==1.1.2 PyMySQL==1.1.2
pyscn==1.5.5 pyscn==1.5.5
pytest==9.0.2 pytest==9.0.2
PyJWT==2.10.1
python-dotenv==1.2.1 python-dotenv==1.2.1
python-multipart==0.0.20 python-multipart==0.0.20
python-engineio==4.12.3 python-engineio==4.12.3
......
VITE_CLERK_PUBLISHABLE_KEY=pk_test_Y29tbXVuYWwtc3VuYmVhbS0wLmNsZXJrLmFjY291bnRzLmRldiQ
CLERK_SECRET_KEY=sk_test_ek7ozVR80Qi9UdvhGaTmlXovS16GDuBDlDrpH1rkyQ
\ No newline at end of file
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
"": { "": {
"name": "memos", "name": "memos",
"dependencies": { "dependencies": {
"@clerk/nextjs": "^6.36.8",
"@connectrpc/connect": "^2.1.1", "@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1", "@connectrpc/connect-web": "^2.1.1",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
...@@ -131,7 +132,6 @@ ...@@ -131,7 +132,6 @@
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.28.6", "@babel/code-frame": "^7.28.6",
"@babel/generator": "^7.28.6", "@babel/generator": "^7.28.6",
...@@ -563,8 +563,7 @@ ...@@ -563,8 +563,7 @@
"version": "2.10.2", "version": "2.10.2",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.2.tgz", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.2.tgz",
"integrity": "sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A==", "integrity": "sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A==",
"license": "(Apache-2.0 AND BSD-3-Clause)", "license": "(Apache-2.0 AND BSD-3-Clause)"
"peer": true
}, },
"node_modules/@chevrotain/cst-dts-gen": { "node_modules/@chevrotain/cst-dts-gen": {
"version": "11.0.3", "version": "11.0.3",
...@@ -617,12 +616,122 @@ ...@@ -617,12 +616,122 @@
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@clerk/backend": {
"version": "2.29.3",
"resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-2.29.3.tgz",
"integrity": "sha512-BLepnFJRsnkqqXu2a79pgbzZz+veecB2bqMrqcmzLl+nBdUPPdeCTRazcmIifKB/424nyT8eX9ADqOz5iySoug==",
"license": "MIT",
"dependencies": {
"@clerk/shared": "^3.43.0",
"@clerk/types": "^4.101.11",
"standardwebhooks": "^1.0.0",
"tslib": "2.8.1"
},
"engines": {
"node": ">=18.17.0"
}
},
"node_modules/@clerk/clerk-react": {
"version": "5.59.4",
"resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-5.59.4.tgz",
"integrity": "sha512-CNr9n7uJT4cRx+cc3fzWr4l4x47+3S5j32HPOP5oUGeIF8O0QHHaoIQ8BHc3lnr4zJJpZxAyrLfwYPv3krtYIw==",
"license": "MIT",
"dependencies": {
"@clerk/shared": "^3.43.0",
"tslib": "2.8.1"
},
"engines": {
"node": ">=18.17.0"
},
"peerDependencies": {
"react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0",
"react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0"
}
},
"node_modules/@clerk/nextjs": {
"version": "6.36.8",
"resolved": "https://registry.npmjs.org/@clerk/nextjs/-/nextjs-6.36.8.tgz",
"integrity": "sha512-Hipw/B/AqdkkcrLPVfVOW47YT+Nt8PwYzpxQv0iMWezdP9u4RWkQ0OfrhluvC7eSOLk/YCCljjaP+S4+VPfHig==",
"license": "MIT",
"dependencies": {
"@clerk/backend": "^2.29.3",
"@clerk/clerk-react": "^5.59.4",
"@clerk/shared": "^3.43.0",
"@clerk/types": "^4.101.11",
"server-only": "0.0.1",
"tslib": "2.8.1"
},
"engines": {
"node": ">=18.17.0"
},
"peerDependencies": {
"next": "^13.5.7 || ^14.2.25 || ^15.2.3 || ^16",
"react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0",
"react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0"
}
},
"node_modules/@clerk/shared": {
"version": "3.43.0",
"resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-3.43.0.tgz",
"integrity": "sha512-pj8jgV5TX7l0ClHMvDLG7Ensp1BwA63LNvOE2uLwRV4bx3j9s4oGHy5bZlLBoOxdvRPCMpQksHi/O0x1Y+obdw==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"csstype": "3.1.3",
"dequal": "2.0.3",
"glob-to-regexp": "0.4.1",
"js-cookie": "3.0.5",
"std-env": "^3.9.0",
"swr": "2.3.4"
},
"engines": {
"node": ">=18.17.0"
},
"peerDependencies": {
"react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0",
"react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/@clerk/shared/node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/@clerk/shared/node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@clerk/types": {
"version": "4.101.11",
"resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.101.11.tgz",
"integrity": "sha512-6m1FQSLFqb4L+ovMDxNIRSrw6I0ByVX5hs6slcevOaaD5UXNzSANWqVtKaU80AZwcm391lZqVS5fRisHt9tmXA==",
"license": "MIT",
"dependencies": {
"@clerk/shared": "^3.43.0"
},
"engines": {
"node": ">=18.17.0"
}
},
"node_modules/@connectrpc/connect": { "node_modules/@connectrpc/connect": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.1.tgz", "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.1.tgz",
"integrity": "sha512-JzhkaTvM73m2K1URT6tv53k2RwngSmCXLZJgK580qNQOXRzZRR/BCMfZw3h+90JpnG6XksP5bYT+cz0rpUzUWQ==", "integrity": "sha512-JzhkaTvM73m2K1URT6tv53k2RwngSmCXLZJgK580qNQOXRzZRR/BCMfZw3h+90JpnG6XksP5bYT+cz0rpUzUWQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@bufbuild/protobuf": "^2.7.0" "@bufbuild/protobuf": "^2.7.0"
} }
...@@ -637,6 +746,16 @@ ...@@ -637,6 +746,16 @@
"@connectrpc/connect": "2.1.1" "@connectrpc/connect": "2.1.1"
} }
}, },
"node_modules/@emnapi/runtime": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
"integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emotion/babel-plugin": { "node_modules/@emotion/babel-plugin": {
"version": "11.13.5", "version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
...@@ -695,7 +814,6 @@ ...@@ -695,7 +814,6 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5", "@emotion/babel-plugin": "^11.13.5",
...@@ -1261,6 +1379,497 @@ ...@@ -1261,6 +1379,497 @@
"mlly": "^1.8.0" "mlly": "^1.8.0"
} }
}, },
"node_modules/@img/colour": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
"cpu": [
"ppc64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
"cpu": [
"riscv64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
"cpu": [
"s390x"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.4"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
"cpu": [
"ppc64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
"cpu": [
"riscv64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"peer": true,
"dependencies": {
"@emnapi/runtime": "^1.7.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
...@@ -1326,6 +1935,149 @@ ...@@ -1326,6 +1935,149 @@
"langium": "3.3.1" "langium": "3.3.1"
} }
}, },
"node_modules/@next/env": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.4.tgz",
"integrity": "sha512-gkrXnZyxPUy0Gg6SrPQPccbNVLSP3vmW8LU5dwEttEEC1RwDivk8w4O+sZIjFvPrSICXyhQDCG+y3VmjlJf+9A==",
"license": "MIT",
"peer": true
},
"node_modules/@next/swc-darwin-arm64": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.4.tgz",
"integrity": "sha512-T8atLKuvk13XQUdVLCv1ZzMPgLPW0+DWWbHSQXs0/3TjPrKNxTmUIhOEaoEyl3Z82k8h/gEtqyuoZGv6+Ugawg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.4.tgz",
"integrity": "sha512-AKC/qVjUGUQDSPI6gESTx0xOnOPQ5gttogNS3o6bA83yiaSZJek0Am5yXy82F1KcZCx3DdOwdGPZpQCluonuxg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.4.tgz",
"integrity": "sha512-POQ65+pnYOkZNdngWfMEt7r53bzWiKkVNbjpmCt1Zb3V6lxJNXSsjwRuTQ8P/kguxDC8LRkqaL3vvsFrce4dMQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.4.tgz",
"integrity": "sha512-3Wm0zGYVCs6qDFAiSSDL+Z+r46EdtCv/2l+UlIdMbAq9hPJBvGu/rZOeuvCaIUjbArkmXac8HnTyQPJFzFWA0Q==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.4.tgz",
"integrity": "sha512-lWAYAezFinaJiD5Gv8HDidtsZdT3CDaCeqoPoJjeB57OqzvMajpIhlZFce5sCAH6VuX4mdkxCRqecCJFwfm2nQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.4.tgz",
"integrity": "sha512-fHaIpT7x4gA6VQbdEpYUXRGyge/YbRrkG6DXM60XiBqDM2g2NcrsQaIuj375egnGFkJow4RHacgBOEsHfGbiUw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.4.tgz",
"integrity": "sha512-MCrXxrTSE7jPN1NyXJr39E+aNFBrQZtO154LoCz7n99FuKqJDekgxipoodLNWdQP7/DZ5tKMc/efybx1l159hw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.4.tgz",
"integrity": "sha512-JSVlm9MDhmTXw/sO2PE/MRj+G6XOSMZB+BcZ0a7d6KwVFZVpkHcb2okyoYFBaco6LeiL53BBklRlOrDDbOeE5w==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/@radix-ui/number": { "node_modules/@radix-ui/number": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
...@@ -2705,6 +3457,22 @@ ...@@ -2705,6 +3457,22 @@
"win32" "win32"
] ]
}, },
"node_modules/@stablelib/base64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
"integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==",
"license": "MIT"
},
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@tailwindcss/node": { "node_modules/@tailwindcss/node": {
"version": "4.1.18", "version": "4.1.18",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
...@@ -2987,7 +3755,6 @@ ...@@ -2987,7 +3755,6 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.19.tgz", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.19.tgz",
"integrity": "sha512-qTZRZ4QyTzQc+M0IzrbKHxSeISUmRB3RPGmao5bT+sI6ayxSRhn0FXEnT5Hg3as8SBFcRosrXXRFB+yAcxVxJQ==", "integrity": "sha512-qTZRZ4QyTzQc+M0IzrbKHxSeISUmRB3RPGmao5bT+sI6ayxSRhn0FXEnT5Hg3as8SBFcRosrXXRFB+yAcxVxJQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@tanstack/query-core": "5.90.19" "@tanstack/query-core": "5.90.19"
}, },
...@@ -3419,7 +4186,6 @@ ...@@ -3419,7 +4186,6 @@
"integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~7.16.0" "undici-types": "~7.16.0"
} }
...@@ -3448,7 +4214,6 @@ ...@@ -3448,7 +4214,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.2.2" "csstype": "^3.2.2"
...@@ -3460,7 +4225,6 @@ ...@@ -3460,7 +4225,6 @@
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^18.0.0" "@types/react": "^18.0.0"
} }
...@@ -3587,7 +4351,6 @@ ...@@ -3587,7 +4351,6 @@
"version": "2.9.15", "version": "2.9.15",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz",
"integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==", "integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"baseline-browser-mapping": "dist/cli.js" "baseline-browser-mapping": "dist/cli.js"
...@@ -3623,7 +4386,6 @@ ...@@ -3623,7 +4386,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
...@@ -3658,7 +4420,6 @@ ...@@ -3658,7 +4420,6 @@
"version": "1.0.30001765", "version": "1.0.30001765",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz",
"integrity": "sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==", "integrity": "sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
...@@ -3781,6 +4542,13 @@ ...@@ -3781,6 +4542,13 @@
"url": "https://polar.sh/cva" "url": "https://polar.sh/cva"
} }
}, },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT",
"peer": true
},
"node_modules/clsx": { "node_modules/clsx": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
...@@ -3912,15 +4680,13 @@ ...@@ -3912,15 +4680,13 @@
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/cytoscape": { "node_modules/cytoscape": {
"version": "3.33.1", "version": "3.33.1",
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"
} }
...@@ -4358,7 +5124,6 @@ ...@@ -4358,7 +5124,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC", "license": "ISC",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
...@@ -4679,6 +5444,12 @@ ...@@ -4679,6 +5444,12 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-sha256": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
"integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==",
"license": "Unlicense"
},
"node_modules/fast-shallow-equal": { "node_modules/fast-shallow-equal": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz",
...@@ -4804,6 +5575,12 @@ ...@@ -4804,6 +5575,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"license": "BSD-2-Clause"
},
"node_modules/goober": { "node_modules/goober": {
"version": "2.1.18", "version": "2.1.18",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz",
...@@ -5136,7 +5913,6 @@ ...@@ -5136,7 +5913,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4" "@babel/runtime": "^7.28.4"
}, },
...@@ -5407,8 +6183,7 @@ ...@@ -5407,8 +6183,7 @@
"version": "1.9.4", "version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause"
"peer": true
}, },
"node_modules/leaflet.markercluster": { "node_modules/leaflet.markercluster": {
"version": "1.5.3", "version": "1.5.3",
...@@ -6773,6 +7548,89 @@ ...@@ -6773,6 +7548,89 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/next": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/next/-/next-16.1.4.tgz",
"integrity": "sha512-gKSecROqisnV7Buen5BfjmXAm7Xlpx9o2ueVQRo5DxQcjC8d330dOM1xiGWc2k3Dcnz0In3VybyRPOsudwgiqQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@next/env": "16.1.4",
"@swc/helpers": "0.5.15",
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
"styled-jsx": "5.1.6"
},
"bin": {
"next": "dist/bin/next"
},
"engines": {
"node": ">=20.9.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "16.1.4",
"@next/swc-darwin-x64": "16.1.4",
"@next/swc-linux-arm64-gnu": "16.1.4",
"@next/swc-linux-arm64-musl": "16.1.4",
"@next/swc-linux-x64-gnu": "16.1.4",
"@next/swc-linux-x64-musl": "16.1.4",
"@next/swc-win32-arm64-msvc": "16.1.4",
"@next/swc-win32-x64-msvc": "16.1.4",
"sharp": "^0.34.4"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
"@playwright/test": "^1.51.1",
"babel-plugin-react-compiler": "*",
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
"sass": "^1.3.0"
},
"peerDependenciesMeta": {
"@opentelemetry/api": {
"optional": true
},
"@playwright/test": {
"optional": true
},
"babel-plugin-react-compiler": {
"optional": true
},
"sass": {
"optional": true
}
}
},
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.27", "version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
...@@ -6900,7 +7758,6 @@ ...@@ -6900,7 +7758,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
...@@ -6999,7 +7856,6 @@ ...@@ -6999,7 +7856,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
...@@ -7012,7 +7868,6 @@ ...@@ -7012,7 +7868,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
...@@ -7107,7 +7962,6 @@ ...@@ -7107,7 +7962,6 @@
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
"integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==",
"license": "Hippocratic-2.1", "license": "Hippocratic-2.1",
"peer": true,
"dependencies": { "dependencies": {
"@react-leaflet/core": "^2.1.0" "@react-leaflet/core": "^2.1.0"
}, },
...@@ -7605,6 +8459,12 @@ ...@@ -7605,6 +8459,12 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/server-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
"license": "MIT"
},
"node_modules/set-cookie-parser": { "node_modules/set-cookie-parser": {
"version": "2.7.2", "version": "2.7.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
...@@ -7620,6 +8480,66 @@ ...@@ -7620,6 +8480,66 @@
"node": ">=6.9" "node": ">=6.9"
} }
}, },
"node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"optional": true,
"peer": true,
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.2",
"semver": "^7.7.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.5",
"@img/sharp-darwin-x64": "0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4",
"@img/sharp-libvips-darwin-x64": "1.2.4",
"@img/sharp-libvips-linux-arm": "1.2.4",
"@img/sharp-libvips-linux-arm64": "1.2.4",
"@img/sharp-libvips-linux-ppc64": "1.2.4",
"@img/sharp-libvips-linux-riscv64": "1.2.4",
"@img/sharp-libvips-linux-s390x": "1.2.4",
"@img/sharp-libvips-linux-x64": "1.2.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
"@img/sharp-linux-arm": "0.34.5",
"@img/sharp-linux-arm64": "0.34.5",
"@img/sharp-linux-ppc64": "0.34.5",
"@img/sharp-linux-riscv64": "0.34.5",
"@img/sharp-linux-s390x": "0.34.5",
"@img/sharp-linux-x64": "0.34.5",
"@img/sharp-linuxmusl-arm64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.34.5",
"@img/sharp-wasm32": "0.34.5",
"@img/sharp-win32-arm64": "0.34.5",
"@img/sharp-win32-ia32": "0.34.5",
"@img/sharp-win32-x64": "0.34.5"
}
},
"node_modules/sharp/node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
"optional": true,
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.5.7", "version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
...@@ -7714,6 +8634,22 @@ ...@@ -7714,6 +8634,22 @@
"stacktrace-gps": "^3.0.4" "stacktrace-gps": "^3.0.4"
} }
}, },
"node_modules/standardwebhooks": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz",
"integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==",
"license": "MIT",
"dependencies": {
"@stablelib/base64": "^1.0.0",
"fast-sha256": "^1.3.0"
}
},
"node_modules/std-env": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"license": "MIT"
},
"node_modules/stringify-entities": { "node_modules/stringify-entities": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
...@@ -7746,6 +8682,30 @@ ...@@ -7746,6 +8682,30 @@
"inline-style-parser": "0.2.7" "inline-style-parser": "0.2.7"
} }
}, },
"node_modules/styled-jsx": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
"license": "MIT",
"peer": true,
"dependencies": {
"client-only": "0.0.1"
},
"engines": {
"node": ">= 12.0.0"
},
"peerDependencies": {
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"babel-plugin-macros": {
"optional": true
}
}
},
"node_modules/stylis": { "node_modules/stylis": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
...@@ -7764,6 +8724,19 @@ ...@@ -7764,6 +8724,19 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/swr": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz",
"integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/tailwind-merge": { "node_modules/tailwind-merge": {
"version": "3.4.0", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
...@@ -7910,8 +8883,7 @@ ...@@ -7910,8 +8883,7 @@
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD", "license": "0BSD"
"peer": true
}, },
"node_modules/tw-animate-css": { "node_modules/tw-animate-css": {
"version": "1.4.0", "version": "1.4.0",
...@@ -7929,7 +8901,6 @@ ...@@ -7929,7 +8901,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
...@@ -8140,6 +9111,15 @@ ...@@ -8140,6 +9111,15 @@
} }
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/uuid": { "node_modules/uuid": {
"version": "11.1.0", "version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
...@@ -8200,7 +9180,6 @@ ...@@ -8200,7 +9180,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.27.0", "esbuild": "^0.27.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"format": "biome format --write src" "format": "biome format --write src"
}, },
"dependencies": { "dependencies": {
"@clerk/clerk-react": "^6.36.8",
"@connectrpc/connect": "^2.1.1", "@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1", "@connectrpc/connect-web": "^2.1.1",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
......
...@@ -6,6 +6,7 @@ import { Badge } from "@/components/ui/badge"; ...@@ -6,6 +6,7 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { getClerkSessionToken } from "@/utils/clerk";
import { generateUUID } from "@/utils/uuid"; import { generateUUID } from "@/utils/uuid";
type ProductSummary = { type ProductSummary = {
...@@ -26,6 +27,10 @@ type ChatMessage = { ...@@ -26,6 +27,10 @@ type ChatMessage = {
const DEVICE_STORAGE_KEY = "canifa_device_id"; const DEVICE_STORAGE_KEY = "canifa_device_id";
// Call backend directly (bypass Vite proxy).
// Override via VITE_API_BASE_URL, e.g. "http://localhost:5000"
const API_ORIGIN = (import.meta.env.VITE_API_BASE_URL as string | undefined) || "http://localhost:5000";
const getDeviceId = (): string => { const getDeviceId = (): string => {
if (typeof window === "undefined") return "unknown"; if (typeof window === "undefined") return "unknown";
try { try {
...@@ -90,12 +95,12 @@ const ChatbotPanel = ({ className, variant = "inline", onClose }: ChatbotPanelPr ...@@ -90,12 +95,12 @@ const ChatbotPanel = ({ className, variant = "inline", onClose }: ChatbotPanelPr
"Content-Type": "application/json", "Content-Type": "application/json",
device_id: deviceId, device_id: deviceId,
}; };
const token = getAccessToken(); const token = (await getClerkSessionToken()) || getAccessToken();
if (token) { if (token) {
headers["Authorization"] = `Bearer ${token}`; headers["Authorization"] = `Bearer ${token}`;
} }
const response = await fetch("/api/agent/chat", { const response = await fetch(`${API_ORIGIN}/api/agent/chat`, {
method: "POST", method: "POST",
headers, headers,
credentials: "include", credentials: "include",
......
...@@ -27,8 +27,12 @@ import { ...@@ -27,8 +27,12 @@ import {
type UserWebhook, type UserWebhook,
} from "./types/proto/api/v1/user_service_pb"; } from "./types/proto/api/v1/user_service_pb";
import { redirectOnAuthFailure } from "./utils/auth-redirect"; import { redirectOnAuthFailure } from "./utils/auth-redirect";
import { getClerkSessionToken } from "./utils/clerk";
const API_BASE = "/api/v1"; // Call backend directly (bypass Vite proxy).
// Override via VITE_API_BASE_URL, e.g. "http://localhost:5000"
const API_ORIGIN = (import.meta.env.VITE_API_BASE_URL as string | undefined) || "http://localhost:5000";
const API_BASE = `${API_ORIGIN}/api/v1`;
type ApiMemo = { type ApiMemo = {
id: number; id: number;
...@@ -102,7 +106,9 @@ const parseBody = async (response: Response): Promise<unknown> => { ...@@ -102,7 +106,9 @@ const parseBody = async (response: Response): Promise<unknown> => {
}; };
const fetchJson = async <T>(path: string, options: RequestOptions = {}): Promise<T> => { const fetchJson = async <T>(path: string, options: RequestOptions = {}): Promise<T> => {
const token = getAccessToken(); // Prefer Clerk token if available; fallback to legacy token store.
const clerkToken = await getClerkSessionToken();
const token = clerkToken || getAccessToken();
const headers = new Headers(options.headers); const headers = new Headers(options.headers);
if (token) { if (token) {
headers.set("Authorization", `Bearer ${token}`); headers.set("Authorization", `Bearer ${token}`);
...@@ -368,7 +374,7 @@ export const instanceServiceClient = { ...@@ -368,7 +374,7 @@ export const instanceServiceClient = {
async getInstanceProfile(_request?: unknown): Promise<InstanceProfile> { async getInstanceProfile(_request?: unknown): Promise<InstanceProfile> {
void _request; void _request;
const data = await fetchJson<ApiInstanceInfo>("/instance", { method: "GET" }); const data = await fetchJson<ApiInstanceInfo>("/instance", { method: "GET" });
const instanceUrl = typeof window === "undefined" ? "" : window.location.origin; const instanceUrl = API_ORIGIN;
return { return {
owner: "users/1", owner: "users/1",
version: typeof data?.version === "string" ? data.version : "", version: typeof data?.version === "string" ? data.version : "",
......
import { Suspense, useEffect, useMemo } from "react"; import { Suspense, useEffect, useMemo } from "react";
import { Outlet, useLocation, useSearchParams } from "react-router-dom"; import { Outlet, useLocation, useSearchParams, Link } from "react-router-dom";
import usePrevious from "react-use/lib/usePrevious"; import usePrevious from "react-use/lib/usePrevious";
import { SignedIn, SignedOut, UserButton } from "@clerk/clerk-react";
import Navigation from "@/components/Navigation"; import Navigation from "@/components/Navigation";
import ChatbotWidget from "@/components/ChatbotWidget"; import ChatbotWidget from "@/components/ChatbotWidget";
import Spinner from "@/components/Spinner"; import Spinner from "@/components/Spinner";
...@@ -58,6 +59,19 @@ const RootLayout = () => { ...@@ -58,6 +59,19 @@ const RootLayout = () => {
<Outlet /> <Outlet />
</Suspense> </Suspense>
</main> </main>
<div className="fixed top-4 right-4 z-50">
<SignedOut>
<Link
to="/auth"
className="rounded-full bg-primary px-3 py-2 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90"
>
Sign in
</Link>
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
</div>
<ChatbotWidget /> <ChatbotWidget />
</div> </div>
); );
......
import "@github/relative-time-element"; import "@github/relative-time-element";
import { QueryClientProvider } from "@tanstack/react-query"; import { QueryClientProvider } from "@tanstack/react-query";
import { ClerkProvider } from "@clerk/clerk-react";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
...@@ -51,20 +52,44 @@ function AppInitializer({ children }: { children: React.ReactNode }) { ...@@ -51,20 +52,44 @@ function AppInitializer({ children }: { children: React.ReactNode }) {
} }
function Main() { function Main() {
const publishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string | undefined;
if (!publishableKey) {
return (
<div className="w-full min-h-svh flex items-center justify-center p-6">
<div className="max-w-xl w-full rounded-xl border border-border bg-background p-6 space-y-3">
<h1 className="text-xl font-semibold">Missing Clerk publishable key</h1>
<p className="text-sm text-muted-foreground">
App requires <code className="px-1 py-0.5 rounded bg-muted">VITE_CLERK_PUBLISHABLE_KEY</code>.
</p>
<div className="text-sm space-y-2">
<p>Create file <code className="px-1 py-0.5 rounded bg-muted">frontend/.env</code> with:</p>
<pre className="whitespace-pre-wrap rounded bg-muted p-3 text-xs">
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
VITE_API_BASE_URL=http://localhost:5000
</pre>
<p>Then restart Vite dev server.</p>
</div>
</div>
</div>
);
}
return ( return (
<ErrorBoundary> <ErrorBoundary>
<QueryClientProvider client={queryClient}> <ClerkProvider publishableKey={publishableKey}>
<InstanceProvider> <QueryClientProvider client={queryClient}>
<AuthProvider> <InstanceProvider>
<ViewProvider> <AuthProvider>
<AppInitializer> <ViewProvider>
<RouterProvider router={router} /> <AppInitializer>
<Toaster position="top-right" /> <RouterProvider router={router} />
</AppInitializer> <Toaster position="top-right" />
</ViewProvider> </AppInitializer>
</AuthProvider> </ViewProvider>
</InstanceProvider> </AuthProvider>
</QueryClientProvider> </InstanceProvider>
</QueryClientProvider>
</ClerkProvider>
</ErrorBoundary> </ErrorBoundary>
); );
} }
......
import { SignedIn, SignedOut, SignIn } from "@clerk/clerk-react";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
const AuthPage = () => {
const navigate = useNavigate();
// Nếu đã đăng nhập rồi thì đưa về trang chủ
useEffect(() => {
// nhỏ gọn: SignedIn phía dưới cũng handle, đây chỉ là fallback
}, [navigate]);
return (
<div className="w-full min-h-svh flex items-center justify-center bg-background px-4">
<div className="max-w-md w-full flex flex-col items-center gap-6">
<SignedOut>
<SignIn
routing="path"
path="/auth"
signUpUrl="/auth"
redirectUrl="/"
/>
</SignedOut>
<SignedIn>
<button
type="button"
className="rounded-full bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90"
onClick={() => navigate("/")}
>
You are already signed in – Go to app
</button>
</SignedIn>
</div>
</div>
);
};
export default AuthPage;
...@@ -17,6 +17,7 @@ const Attachments = lazy(() => import("@/pages/Attachments")); ...@@ -17,6 +17,7 @@ const Attachments = lazy(() => import("@/pages/Attachments"));
const Setting = lazy(() => import("@/pages/Setting")); const Setting = lazy(() => import("@/pages/Setting"));
const UserProfile = lazy(() => import("@/pages/UserProfile")); const UserProfile = lazy(() => import("@/pages/UserProfile"));
const MemoDetailRedirect = lazy(() => import("./MemoDetailRedirect")); const MemoDetailRedirect = lazy(() => import("./MemoDetailRedirect"));
const AuthPage = lazy(() => import("@/pages/Auth"));
import { ROUTES } from "./routes"; import { ROUTES } from "./routes";
...@@ -55,6 +56,7 @@ const router = createBrowserRouter([ ...@@ -55,6 +56,7 @@ const router = createBrowserRouter([
{ path: "u/:username", element: <LazyRoute component={UserProfile} /> }, { path: "u/:username", element: <LazyRoute component={UserProfile} /> },
], ],
}, },
{ path: Routes.AUTH, element: <LazyRoute component={AuthPage} /> },
{ path: Routes.ATTACHMENTS, element: <LazyRoute component={Attachments} /> }, { path: Routes.ATTACHMENTS, element: <LazyRoute component={Attachments} /> },
{ path: Routes.INBOX, element: <LazyRoute component={Inboxes} /> }, { path: Routes.INBOX, element: <LazyRoute component={Inboxes} /> },
{ path: Routes.SETTING, element: <LazyRoute component={Setting} /> }, { path: Routes.SETTING, element: <LazyRoute component={Setting} /> },
......
declare global {
interface Window {
Clerk?: {
session?: {
getToken: (options?: Record<string, unknown>) => Promise<string>;
};
};
}
}
/**
* Get a Clerk session JWT without React hooks (works from non-React modules).
* Returns null if Clerk isn't initialized or user isn't signed in.
*/
export async function getClerkSessionToken(): Promise<string | null> {
try {
const clerk = typeof window === "undefined" ? undefined : window.Clerk;
if (!clerk?.session?.getToken) return null;
const token = await clerk.session.getToken();
return token || null;
} catch {
return null;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment