From 747954533932949a54d6a78b68b18258c7912c0d Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 25 Dec 2025 20:54:00 +0800 Subject: [PATCH 01/30] feat: implement models dialog for managing LLM and embedding models with dynamic URL handling --- .../components/home-sidebar/HomeSidebar.tsx | 48 +++- .../home-sidebar/sidbarConfigList.tsx | 21 -- .../models-dialog}/LLMConfig.module.css | 0 .../models-dialog/ModelsDialog.tsx} | 219 ++++++++++-------- .../component/ChooseRequesterEntity.ts | 0 .../component/ICreateEmbeddingField.ts | 0 .../component/ICreateLLMField.ts | 0 .../embedding-card/EmbeddingCard.module.css | 0 .../embedding-card/EmbeddingCard.tsx | 0 .../embedding-card/EmbeddingCardVO.ts | 0 .../embedding-form/EmbeddingForm.tsx | 0 .../component/llm-card/LLMCard.module.css | 0 .../component/llm-card/LLMCard.tsx | 0 .../component/llm-card/LLMCardVO.ts | 0 .../component/llm-form/LLMForm.tsx | 0 web/src/i18n/locales/zh-Hans.ts | 2 +- 16 files changed, 175 insertions(+), 115 deletions(-) rename web/src/app/home/{models => components/models-dialog}/LLMConfig.module.css (100%) rename web/src/app/home/{models/page.tsx => components/models-dialog/ModelsDialog.tsx} (57%) rename web/src/app/home/{models => components/models-dialog}/component/ChooseRequesterEntity.ts (100%) rename web/src/app/home/{models => components/models-dialog}/component/ICreateEmbeddingField.ts (100%) rename web/src/app/home/{models => components/models-dialog}/component/ICreateLLMField.ts (100%) rename web/src/app/home/{models => components/models-dialog}/component/embedding-card/EmbeddingCard.module.css (100%) rename web/src/app/home/{models => components/models-dialog}/component/embedding-card/EmbeddingCard.tsx (100%) rename web/src/app/home/{models => components/models-dialog}/component/embedding-card/EmbeddingCardVO.ts (100%) rename web/src/app/home/{models => components/models-dialog}/component/embedding-form/EmbeddingForm.tsx (100%) rename web/src/app/home/{models => components/models-dialog}/component/llm-card/LLMCard.module.css (100%) rename web/src/app/home/{models => components/models-dialog}/component/llm-card/LLMCard.tsx (100%) rename web/src/app/home/{models => components/models-dialog}/component/llm-card/LLMCardVO.ts (100%) rename web/src/app/home/{models => components/models-dialog}/component/llm-form/LLMForm.tsx (100%) diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index db2418df..764acfe2 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -6,7 +6,7 @@ import { SidebarChild, SidebarChildVO, } from '@/app/home/components/home-sidebar/HomeSidebarChild'; -import { useRouter, usePathname } from 'next/navigation'; +import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConfigList'; import langbotIcon from '@/app/assets/langbot-logo.webp'; import { systemInfo } from '@/app/infra/http/HttpClient'; @@ -36,6 +36,7 @@ import { Badge } from '@/components/ui/badge'; import PasswordChangeDialog from '@/app/home/components/password-change-dialog/PasswordChangeDialog'; import ApiIntegrationDialog from '@/app/home/components/api-integration-dialog/ApiIntegrationDialog'; import NewVersionDialog from '@/app/home/components/new-version-dialog/NewVersionDialog'; +import ModelsDialog from '@/app/home/components/models-dialog/ModelsDialog'; import { GitHubRelease } from '@/app/infra/http/CloudServiceClient'; // Compare two version strings, returns true if v1 > v2 @@ -67,11 +68,19 @@ export default function HomeSidebar({ // 路由相关 const router = useRouter(); const pathname = usePathname(); + const searchParams = useSearchParams(); // 路由被动变化时处理 useEffect(() => { handleRouteChange(pathname); }, [pathname]); + // 检查 URL 参数,自动打开模型对话框 + useEffect(() => { + if (searchParams.get('action') === 'showModelSettings') { + setModelsDialogOpen(true); + } + }, [searchParams]); + const [selectedChild, setSelectedChild] = useState(); const { theme, setTheme } = useTheme(); const { t } = useTranslation(); @@ -85,6 +94,24 @@ export default function HomeSidebar({ ); const [hasNewVersion, setHasNewVersion] = useState(false); const [versionDialogOpen, setVersionDialogOpen] = useState(false); + const [modelsDialogOpen, setModelsDialogOpen] = useState(false); + + // 处理模型对话框的打开和关闭,同时更新 URL + function handleModelsDialogChange(open: boolean) { + setModelsDialogOpen(open); + if (open) { + const params = new URLSearchParams(searchParams.toString()); + params.set('action', 'showModelSettings'); + router.replace(`${pathname}?${params.toString()}`, { scroll: false }); + } else { + const params = new URLSearchParams(searchParams.toString()); + params.delete('action'); + const newUrl = params.toString() + ? `${pathname}?${params.toString()}` + : pathname; + router.replace(newUrl, { scroll: false }); + } + } useEffect(() => { initSelect(); @@ -252,6 +279,21 @@ export default function HomeSidebar({ )} + handleModelsDialogChange(true)} + isSelected={false} + icon={ + + + + } + name={t('models.title')} + /> + { @@ -414,6 +456,10 @@ export default function HomeSidebar({ onOpenChange={setVersionDialogOpen} release={latestRelease} /> + ); } diff --git a/web/src/app/home/components/home-sidebar/sidbarConfigList.tsx b/web/src/app/home/components/home-sidebar/sidbarConfigList.tsx index b3edb98a..868ac5b4 100644 --- a/web/src/app/home/components/home-sidebar/sidbarConfigList.tsx +++ b/web/src/app/home/components/home-sidebar/sidbarConfigList.tsx @@ -27,27 +27,6 @@ export const sidebarConfigList = [ zh_Hans: 'https://docs.langbot.app/zh/deploy/platforms/readme.html', }, }), - new SidebarChildVO({ - id: 'models', - name: t('models.title'), - icon: ( - - - - ), - route: '/home/models', - description: t('models.description'), - helpLink: { - en_US: 'https://docs.langbot.app/en/deploy/models/readme.html', - zh_Hans: 'https://docs.langbot.app/zh/deploy/models/readme.html', - }, - }), - new SidebarChildVO({ id: 'pipelines', name: t('pipelines.title'), diff --git a/web/src/app/home/models/LLMConfig.module.css b/web/src/app/home/components/models-dialog/LLMConfig.module.css similarity index 100% rename from web/src/app/home/models/LLMConfig.module.css rename to web/src/app/home/components/models-dialog/LLMConfig.module.css diff --git a/web/src/app/home/models/page.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx similarity index 57% rename from web/src/app/home/models/page.tsx rename to web/src/app/home/components/models-dialog/ModelsDialog.tsx index 7c33918e..6a624d17 100644 --- a/web/src/app/home/models/page.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -1,11 +1,10 @@ 'use client'; import { useState, useEffect } from 'react'; -import { LLMCardVO } from '@/app/home/models/component/llm-card/LLMCardVO'; -import styles from './LLMConfig.module.css'; -import LLMCard from '@/app/home/models/component/llm-card/LLMCard'; -import LLMForm from '@/app/home/models/component/llm-form/LLMForm'; -import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent'; +import { Plus, MessageSquareText, Cpu, Info } from 'lucide-react'; +import { LLMCardVO } from './component/llm-card/LLMCardVO'; +import LLMCard from './component/llm-card/LLMCard'; +import LLMForm from './component/llm-form/LLMForm'; import { httpClient } from '@/app/infra/http/HttpClient'; import { LLMModel } from '@/app/infra/entities/api'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; @@ -15,15 +14,25 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; import { extractI18nObject } from '@/i18n/I18nProvider'; -import { EmbeddingCardVO } from '@/app/home/models/component/embedding-card/EmbeddingCardVO'; -import EmbeddingCard from '@/app/home/models/component/embedding-card/EmbeddingCard'; -import EmbeddingForm from '@/app/home/models/component/embedding-form/EmbeddingForm'; +import { EmbeddingCardVO } from './component/embedding-card/EmbeddingCardVO'; +import EmbeddingCard from './component/embedding-card/EmbeddingCard'; +import EmbeddingForm from './component/embedding-form/EmbeddingForm'; -export default function LLMConfigPage() { +interface ModelsDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export default function ModelsDialog({ + open, + onOpenChange, +}: ModelsDialogProps) { const { t } = useTranslation(); + const [activeTab, setActiveTab] = useState('llm'); const [cardList, setCardList] = useState([]); const [modalOpen, setModalOpen] = useState(false); const [isEditForm, setIsEditForm] = useState(false); @@ -37,9 +46,11 @@ export default function LLMConfigPage() { useState(null); useEffect(() => { - getLLMModelList(); - getEmbeddingModelList(); - }, []); + if (open) { + getLLMModelList(); + getEmbeddingModelList(); + } + }, [open]); async function getLLMModelList() { const requesterNameListResp = await httpClient.getProviderRequesters('llm'); @@ -134,7 +145,108 @@ export default function LLMConfigPage() { } return ( -
+ <> + { + if (!newOpen && (modalOpen || embeddingModalOpen)) { + return; + } + onOpenChange(newOpen); + }} + > + + + {t('models.title')} + + + +
+ + + + {t('llm.llmModels')} + + + + {t('embedding.embeddingModels')} + + + +
+ +
+ + {activeTab === 'llm' ? ( +

+ {t('llm.description')} +

+ ) : ( +

+ {t('embedding.description')} +

+ )} +
+ + +
+ {cardList.map((cardVO) => { + return ( +
{ + selectLLM(cardVO); + }} + > + +
+ ); + })} +
+
+ + +
+ {embeddingCardList.map((cardVO) => { + return ( +
{ + selectEmbedding(cardVO); + }} + > + +
+ ); + })} +
+
+
+
+
+ @@ -159,6 +271,7 @@ export default function LLMConfigPage() { /> + @@ -185,84 +298,6 @@ export default function LLMConfigPage() { /> - - -
-
- - - {t('llm.llmModels')} - - - {t('embedding.embeddingModels')} - - -
- -
-

- {t('llm.description')} -

-
-
- -
-

- {t('embedding.description')} -

-
-
-
- - -
- - {cardList.map((cardVO) => { - return ( -
{ - selectLLM(cardVO); - }} - > - -
- ); - })} -
-
- - -
- - {embeddingCardList.map((cardVO) => { - return ( -
{ - selectEmbedding(cardVO); - }} - > - -
- ); - })} -
-
-
-
+ ); } diff --git a/web/src/app/home/models/component/ChooseRequesterEntity.ts b/web/src/app/home/components/models-dialog/component/ChooseRequesterEntity.ts similarity index 100% rename from web/src/app/home/models/component/ChooseRequesterEntity.ts rename to web/src/app/home/components/models-dialog/component/ChooseRequesterEntity.ts diff --git a/web/src/app/home/models/component/ICreateEmbeddingField.ts b/web/src/app/home/components/models-dialog/component/ICreateEmbeddingField.ts similarity index 100% rename from web/src/app/home/models/component/ICreateEmbeddingField.ts rename to web/src/app/home/components/models-dialog/component/ICreateEmbeddingField.ts diff --git a/web/src/app/home/models/component/ICreateLLMField.ts b/web/src/app/home/components/models-dialog/component/ICreateLLMField.ts similarity index 100% rename from web/src/app/home/models/component/ICreateLLMField.ts rename to web/src/app/home/components/models-dialog/component/ICreateLLMField.ts diff --git a/web/src/app/home/models/component/embedding-card/EmbeddingCard.module.css b/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.module.css similarity index 100% rename from web/src/app/home/models/component/embedding-card/EmbeddingCard.module.css rename to web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.module.css diff --git a/web/src/app/home/models/component/embedding-card/EmbeddingCard.tsx b/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.tsx similarity index 100% rename from web/src/app/home/models/component/embedding-card/EmbeddingCard.tsx rename to web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.tsx diff --git a/web/src/app/home/models/component/embedding-card/EmbeddingCardVO.ts b/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCardVO.ts similarity index 100% rename from web/src/app/home/models/component/embedding-card/EmbeddingCardVO.ts rename to web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCardVO.ts diff --git a/web/src/app/home/models/component/embedding-form/EmbeddingForm.tsx b/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx similarity index 100% rename from web/src/app/home/models/component/embedding-form/EmbeddingForm.tsx rename to web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx diff --git a/web/src/app/home/models/component/llm-card/LLMCard.module.css b/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.module.css similarity index 100% rename from web/src/app/home/models/component/llm-card/LLMCard.module.css rename to web/src/app/home/components/models-dialog/component/llm-card/LLMCard.module.css diff --git a/web/src/app/home/models/component/llm-card/LLMCard.tsx b/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx similarity index 100% rename from web/src/app/home/models/component/llm-card/LLMCard.tsx rename to web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx diff --git a/web/src/app/home/models/component/llm-card/LLMCardVO.ts b/web/src/app/home/components/models-dialog/component/llm-card/LLMCardVO.ts similarity index 100% rename from web/src/app/home/models/component/llm-card/LLMCardVO.ts rename to web/src/app/home/components/models-dialog/component/llm-card/LLMCardVO.ts diff --git a/web/src/app/home/models/component/llm-form/LLMForm.tsx b/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx similarity index 100% rename from web/src/app/home/models/component/llm-form/LLMForm.tsx rename to web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index d747680c..9756dd93 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -104,7 +104,7 @@ const zhHans = { models: { title: '模型配置', description: '配置和管理可在流水线中使用的模型', - createModel: '创建模型', + createModel: '创建对话模型', editModel: '编辑模型', getModelListError: '获取模型列表失败:', modelName: '模型名称', From 8caab43b00ac309538eb8eb8eccb54ae7d6267ed Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Fri, 26 Dec 2025 00:35:47 +0800 Subject: [PATCH 02/30] feat: add Space integration for user authentication and model management with OAuth support --- .../pkg/api/http/controller/groups/space.py | 52 + .../pkg/api/http/controller/groups/user.py | 70 +- .../pkg/api/http/service/space_models.py | 247 + src/langbot/pkg/api/http/service/user.py | 166 +- src/langbot/pkg/core/app.py | 3 + src/langbot/pkg/core/stages/build_app.py | 4 + src/langbot/pkg/entity/persistence/model.py | 8 + src/langbot/pkg/entity/persistence/user.py | 10 + .../dbm014_space_account_support.py | 82 + .../dbm015_model_source_tracking.py | 78 + src/langbot/pkg/utils/constants.py | 2 +- src/langbot/templates/config.yaml | 7 + web/package.json | 2 + web/pnpm-lock.yaml | 9530 ++++++----------- web/src/app/auth/space/callback/page.tsx | 122 + .../components/models-dialog/ModelsDialog.tsx | 555 +- .../embedding-card/EmbeddingCard.tsx | 2 +- .../embedding-form/EmbeddingForm.tsx | 4 +- .../component/llm-card/LLMCard.tsx | 2 +- .../component/llm-form/LLMForm.tsx | 4 +- web/src/app/infra/http/BackendClient.ts | 85 + web/src/app/login/page.tsx | 95 +- web/src/app/register/page.tsx | 94 +- web/src/i18n/locales/en-US.ts | 37 + web/src/i18n/locales/ja-JP.ts | 41 + web/src/i18n/locales/zh-Hans.ts | 34 + web/src/i18n/locales/zh-Hant.ts | 34 + 27 files changed, 5214 insertions(+), 6156 deletions(-) create mode 100644 src/langbot/pkg/api/http/controller/groups/space.py create mode 100644 src/langbot/pkg/api/http/service/space_models.py create mode 100644 src/langbot/pkg/persistence/migrations/dbm014_space_account_support.py create mode 100644 src/langbot/pkg/persistence/migrations/dbm015_model_source_tracking.py create mode 100644 web/src/app/auth/space/callback/page.tsx diff --git a/src/langbot/pkg/api/http/controller/groups/space.py b/src/langbot/pkg/api/http/controller/groups/space.py new file mode 100644 index 00000000..cefdce19 --- /dev/null +++ b/src/langbot/pkg/api/http/controller/groups/space.py @@ -0,0 +1,52 @@ +import quart + +from .. import group + + +DEFAULT_SPACE_URL = 'https://space.langbot.app' + + +@group.group_class('space', '/api/v1/space') +class SpaceRouterGroup(group.RouterGroup): + async def initialize(self) -> None: + @self.route('/models/sync', methods=['POST'], auth_type=group.AuthType.USER_TOKEN) + async def _(user_email: str) -> str: + """Sync models from Space MaaS to local database""" + json_data = await quart.request.json or {} + space_url = json_data.get('space_url', DEFAULT_SPACE_URL) + + try: + stats = await self.ap.space_models_service.sync_models_from_space(user_email, space_url) + return self.success(data=stats) + except ValueError as e: + return self.fail(1, str(e)) + except Exception as e: + return self.fail(2, f'Failed to sync models: {str(e)}') + + @self.route('/models', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) + async def _(user_email: str) -> str: + """Get all synced Space models""" + if quart.request.method == 'GET': + try: + models = await self.ap.space_models_service.get_space_models() + return self.success(data=models) + except Exception as e: + return self.fail(1, f'Failed to get Space models: {str(e)}') + elif quart.request.method == 'DELETE': + try: + stats = await self.ap.space_models_service.delete_space_models() + return self.success(data=stats) + except Exception as e: + return self.fail(1, f'Failed to delete Space models: {str(e)}') + + @self.route('/models/available', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) + async def _(user_email: str) -> str: + """Get available models from Space (preview before sync)""" + try: + space_url = quart.request.args.get('space_url', DEFAULT_SPACE_URL) + models_data = await self.ap.space_models_service.fetch_space_models(space_url) + return self.success(data=models_data) + except ValueError as e: + return self.fail(1, str(e)) + except Exception as e: + return self.fail(2, f'Failed to fetch available models: {str(e)}') diff --git a/src/langbot/pkg/api/http/controller/groups/user.py b/src/langbot/pkg/api/http/controller/groups/user.py index 0a5aad77..16aa9a51 100644 --- a/src/langbot/pkg/api/http/controller/groups/user.py +++ b/src/langbot/pkg/api/http/controller/groups/user.py @@ -33,6 +33,8 @@ class UserRouterGroup(group.RouterGroup): token = await self.ap.user_service.authenticate(json_data['user'], json_data['password']) except argon2.exceptions.VerifyMismatchError: return self.fail(1, 'Invalid username or password') + except ValueError as e: + return self.fail(1, str(e)) return self.success(data={'token': token}) @@ -71,9 +73,7 @@ class UserRouterGroup(group.RouterGroup): @self.route('/change-password', methods=['POST'], auth_type=group.AuthType.USER_TOKEN) async def _(user_email: str) -> str: # Check if password change is allowed - allow_change_password = self.ap.instance_config.data.get('system', {}).get( - 'allow_change_password', True - ) + allow_change_password = self.ap.instance_config.data.get('system', {}).get('allow_change_password', True) if not allow_change_password: return self.http_status(403, -1, 'Password change is disabled') @@ -90,3 +90,67 @@ class UserRouterGroup(group.RouterGroup): return self.http_status(400, -1, str(e)) return self.success(data={'user': user_email}) + + # Space OAuth endpoints (redirect flow) + + @self.route('/space/authorize-url', methods=['GET'], auth_type=group.AuthType.NONE) + async def _() -> str: + """Get Space OAuth authorization URL for redirect""" + redirect_uri = quart.request.args.get('redirect_uri', '') + state = quart.request.args.get('state', '') + + if not redirect_uri: + return self.fail(1, 'Missing redirect_uri parameter') + + try: + authorize_url = self.ap.user_service.get_space_oauth_authorize_url(redirect_uri, state) + return self.success(data={'authorize_url': authorize_url}) + except Exception as e: + return self.fail(1, str(e)) + + @self.route('/space/callback', methods=['POST'], auth_type=group.AuthType.NONE) + async def _() -> str: + """Handle OAuth callback - exchange code for tokens and authenticate""" + json_data = await quart.request.json + code = json_data.get('code') + + if not code: + return self.fail(1, 'Missing authorization code') + + try: + # Exchange code for tokens + token_data = await self.ap.user_service.exchange_space_oauth_code(code) + access_token = token_data.get('access_token') + refresh_token = token_data.get('refresh_token') + + if not access_token: + return self.fail(1, 'Failed to get access token from Space') + + # Authenticate and create/update local user + jwt_token, user_obj = await self.ap.user_service.authenticate_space_user(access_token, refresh_token) + + return self.success( + data={ + 'token': jwt_token, + 'user': user_obj.user, + } + ) + except ValueError as e: + return self.fail(1, str(e)) + except Exception as e: + return self.fail(2, f'OAuth callback failed: {str(e)}') + + @self.route('/info', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) + async def _(user_email: str) -> str: + """Get current user information including account type""" + user_obj = await self.ap.user_service.get_user_by_email(user_email) + + if user_obj is None: + return self.http_status(404, -1, 'User not found') + + return self.success( + data={ + 'user': user_obj.user, + 'account_type': user_obj.account_type, + } + ) diff --git a/src/langbot/pkg/api/http/service/space_models.py b/src/langbot/pkg/api/http/service/space_models.py new file mode 100644 index 00000000..634c7e39 --- /dev/null +++ b/src/langbot/pkg/api/http/service/space_models.py @@ -0,0 +1,247 @@ +from __future__ import annotations + +import typing +import uuid as uuid_lib +import aiohttp +import sqlalchemy + +from ....core import app +from ....entity.persistence import model as persistence_model +from ....entity.persistence import user as persistence_user + + +DEFAULT_SPACE_URL = 'http://localhost:8383' + +# Space's base URL for model API requests (used for requester_config) +SPACE_API_BASE_URL = 'http://localhost:8383' + + +class SpaceModelsService: + """Service for syncing models from Space MaaS""" + + ap: app.Application + + def __init__(self, ap: app.Application) -> None: + self.ap = ap + + async def get_space_user_info(self, user_email: str) -> persistence_user.User | None: + """Get Space user info for sync operations""" + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_user.User).where(persistence_user.User.user == user_email) + ) + result_list = result.all() + return result_list[0] if result_list else None + + async def fetch_space_models(self, space_url: str = DEFAULT_SPACE_URL) -> typing.Dict: + """Fetch available models from Space API""" + async with aiohttp.ClientSession() as session: + async with session.get(f'{space_url}/api/v1/models', params={'page_size': 100}) as response: + if response.status != 200: + raise ValueError(f'Failed to fetch models from Space: {await response.text()}') + data = await response.json() + if data.get('code') != 0: + raise ValueError(f'Failed to fetch models from Space: {data.get("msg")}') + return data.get('data', {}) + + async def sync_models_from_space( + self, user_email: str, space_url: str = DEFAULT_SPACE_URL + ) -> typing.Dict[str, typing.Any]: + """ + Sync models from Space to local database. + Returns statistics about the sync operation. + """ + # Get user info for API key + user_obj = await self.get_space_user_info(user_email) + if user_obj is None: + raise ValueError('User not found') + + if user_obj.account_type != 'space': + raise ValueError('User is not a Space account') + + if not user_obj.space_api_key: + raise ValueError('User does not have a Space API key configured') + + # Fetch models from Space + models_data = await self.fetch_space_models(space_url) + space_models = models_data.get('models', []) + + # Get existing Space models in local database + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.LLMModel).where(persistence_model.LLMModel.source == 'space') + ) + existing_space_models = {m.space_model_id: m for m in result.all()} + + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.EmbeddingModel).where( + persistence_model.EmbeddingModel.source == 'space' + ) + ) + existing_space_embedding_models = {m.space_model_id: m for m in result.all()} + + stats = {'created_llm': 0, 'updated_llm': 0, 'created_embedding': 0, 'updated_embedding': 0, 'skipped': 0} + + for model in space_models: + model_id = model.get('model_id') + category = model.get('category', '') + + if not model_id: + stats['skipped'] += 1 + continue + + if category == 'embedding': + # Handle embedding model + await self._sync_embedding_model(model, user_obj.space_api_key, existing_space_embedding_models, stats) + else: + # Handle LLM model (chat, completion, etc.) + await self._sync_llm_model(model, user_obj.space_api_key, existing_space_models, stats) + + return stats + + async def _sync_llm_model( + self, + model: typing.Dict, + api_key: str, + existing_models: typing.Dict[str, persistence_model.LLMModel], + stats: typing.Dict, + ) -> None: + """Sync a single LLM model from Space""" + model_id = model.get('model_id') + display_name = model.get('display_name', {}) + name = display_name.get('zh_Hans', display_name.get('en_US', model_id)) + description_obj = model.get('description', {}) + description = description_obj.get('zh_Hans', description_obj.get('en_US', '')) if description_obj else '' + + # Infer abilities from model capabilities + abilities = [] + supported_endpoints = model.get('supported_endpoints', []) + if 'vision' in str(supported_endpoints).lower() or 'vision' in model_id.lower(): + abilities.append('vision') + if 'function' in str(supported_endpoints).lower() or 'tool' in str(supported_endpoints).lower(): + abilities.append('function_call') + + model_data = { + 'name': name, + 'description': description[:255] if description else 'Model from Space MaaS', + 'requester': 'openai-chat-completions', # Space uses OpenAI-compatible API + 'requester_config': { + 'base-url': SPACE_API_BASE_URL, + 'args': {}, + 'timeout': 120, + }, + 'api_keys': [api_key], + 'abilities': abilities, + 'extra_args': {'model': model_id}, + 'source': 'space', + 'space_model_id': model_id, + } + + if model_id in existing_models: + # Update existing model + await self.ap.persistence_mgr.execute_async( + sqlalchemy.update(persistence_model.LLMModel) + .where(persistence_model.LLMModel.space_model_id == model_id) + .values(**model_data) + ) + stats['updated_llm'] += 1 + else: + # Create new model + model_data['uuid'] = str(uuid_lib.uuid4()) + await self.ap.persistence_mgr.execute_async( + sqlalchemy.insert(persistence_model.LLMModel).values(**model_data) + ) + stats['created_llm'] += 1 + + async def _sync_embedding_model( + self, + model: typing.Dict, + api_key: str, + existing_models: typing.Dict[str, persistence_model.EmbeddingModel], + stats: typing.Dict, + ) -> None: + """Sync a single embedding model from Space""" + model_id = model.get('model_id') + display_name = model.get('display_name', {}) + name = display_name.get('zh_Hans', display_name.get('en_US', model_id)) + description_obj = model.get('description', {}) + description = description_obj.get('zh_Hans', description_obj.get('en_US', '')) if description_obj else '' + + model_data = { + 'name': name, + 'description': description[:255] if description else 'Embedding model from Space MaaS', + 'requester': 'openai-embedding', # Space uses OpenAI-compatible API + 'requester_config': { + 'base-url': SPACE_API_BASE_URL, + 'args': {}, + 'timeout': 120, + }, + 'api_keys': [api_key], + 'extra_args': {'model': model_id}, + 'source': 'space', + 'space_model_id': model_id, + } + + if model_id in existing_models: + # Update existing model + await self.ap.persistence_mgr.execute_async( + sqlalchemy.update(persistence_model.EmbeddingModel) + .where(persistence_model.EmbeddingModel.space_model_id == model_id) + .values(**model_data) + ) + stats['updated_embedding'] += 1 + else: + # Create new model + model_data['uuid'] = str(uuid_lib.uuid4()) + await self.ap.persistence_mgr.execute_async( + sqlalchemy.insert(persistence_model.EmbeddingModel).values(**model_data) + ) + stats['created_embedding'] += 1 + + async def get_space_models(self) -> typing.Dict[str, typing.List]: + """Get all synced Space models""" + llm_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.LLMModel).where(persistence_model.LLMModel.source == 'space') + ) + embedding_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.EmbeddingModel).where( + persistence_model.EmbeddingModel.source == 'space' + ) + ) + + return { + 'llm_models': [ + self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, m) for m in llm_result.all() + ], + 'embedding_models': [ + self.ap.persistence_mgr.serialize_model(persistence_model.EmbeddingModel, m) + for m in embedding_result.all() + ], + } + + async def delete_space_models(self) -> typing.Dict[str, int]: + """Delete all synced Space models""" + # Remove from model manager first + llm_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.LLMModel).where(persistence_model.LLMModel.source == 'space') + ) + for model in llm_result.all(): + await self.ap.model_mgr.remove_llm_model(model.uuid) + + embedding_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.EmbeddingModel).where( + persistence_model.EmbeddingModel.source == 'space' + ) + ) + for model in embedding_result.all(): + await self.ap.model_mgr.remove_embedding_model(model.uuid) + + # Delete from database + llm_delete = await self.ap.persistence_mgr.execute_async( + sqlalchemy.delete(persistence_model.LLMModel).where(persistence_model.LLMModel.source == 'space') + ) + embedding_delete = await self.ap.persistence_mgr.execute_async( + sqlalchemy.delete(persistence_model.EmbeddingModel).where( + persistence_model.EmbeddingModel.source == 'space' + ) + ) + + return {'deleted_llm': llm_delete.rowcount, 'deleted_embedding': embedding_delete.rowcount} diff --git a/src/langbot/pkg/api/http/service/user.py b/src/langbot/pkg/api/http/service/user.py index b2403d15..5f98dc6f 100644 --- a/src/langbot/pkg/api/http/service/user.py +++ b/src/langbot/pkg/api/http/service/user.py @@ -4,6 +4,8 @@ import sqlalchemy import argon2 import jwt import datetime +import aiohttp +import typing from ....core import app from ....entity.persistence import user @@ -16,6 +18,15 @@ class UserService: def __init__(self, ap: app.Application) -> None: self.ap = ap + def _get_space_config(self) -> typing.Dict[str, str]: + """Get Space configuration from config file""" + space_config = self.ap.instance_config.data.get('space', {}) + return { + 'url': space_config.get('url', 'https://space.langbot.app'), + 'api_url': space_config.get('api_url', 'https://api.langbot.app'), + 'oauth_authorize_url': space_config.get('oauth_authorize_url', 'https://space.langbot.app/auth/authorize'), + } + async def is_initialized(self) -> bool: result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(user.User).limit(1)) @@ -28,7 +39,7 @@ class UserService: hashed_password = ph.hash(password) await self.ap.persistence_mgr.execute_async( - sqlalchemy.insert(user.User).values(user=user_email, password=hashed_password) + sqlalchemy.insert(user.User).values(user=user_email, password=hashed_password, account_type='local') ) async def get_user_by_email(self, user_email: str) -> user.User | None: @@ -39,6 +50,15 @@ class UserService: result_list = result.all() return result_list[0] if result_list is not None and len(result_list) > 0 else None + async def get_user_by_space_account_uuid(self, space_account_uuid: str) -> user.User | None: + """Get user by Space account UUID""" + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(user.User).where(user.User.space_account_uuid == space_account_uuid) + ) + + result_list = result.all() + return result_list[0] if result_list is not None and len(result_list) > 0 else None + async def authenticate(self, user_email: str, password: str) -> str | None: result = await self.ap.persistence_mgr.execute_async( sqlalchemy.select(user.User).where(user.User.user == user_email) @@ -51,6 +71,10 @@ class UserService: user_obj = result_list[0] + # Check if this is a Space account + if user_obj.account_type == 'space': + raise ValueError('请使用 Space 账户登录') + ph = argon2.PasswordHasher() ph.verify(user_obj.password, password) @@ -90,6 +114,10 @@ class UserService: if user_obj is None: raise ValueError('User not found') + # Space accounts cannot change password locally + if user_obj.account_type == 'space': + raise ValueError('Space account cannot change password locally') + ph.verify(user_obj.password, current_password) hashed_password = ph.hash(new_password) @@ -97,3 +125,139 @@ class UserService: await self.ap.persistence_mgr.execute_async( sqlalchemy.update(user.User).where(user.User.user == user_email).values(password=hashed_password) ) + + # Space OAuth methods (redirect flow) + + def get_space_oauth_authorize_url(self, redirect_uri: str, state: str = '') -> str: + """Get the Space OAuth authorization URL for redirect""" + space_config = self._get_space_config() + authorize_url = space_config['oauth_authorize_url'] + + # Build the authorization URL with redirect_uri + params = f'redirect_uri={redirect_uri}' + if state: + params += f'&state={state}' + + return f'{authorize_url}?{params}' + + async def exchange_space_oauth_code(self, code: str) -> typing.Dict: + """Exchange OAuth authorization code for tokens""" + space_config = self._get_space_config() + space_url = space_config['url'] + + async with aiohttp.ClientSession() as session: + async with session.post( + f'{space_url}/api/v1/accounts/oauth/token', + json={'code': code}, + ) as response: + if response.status != 200: + raise ValueError(f'Failed to exchange OAuth code: {await response.text()}') + data = await response.json() + if data.get('code') != 0: + raise ValueError(f'Failed to exchange OAuth code: {data.get("msg")}') + return data.get('data', {}) + + async def get_space_user_info(self, access_token: str) -> typing.Dict: + """Get user info from Space using access token""" + space_config = self._get_space_config() + space_url = space_config['url'] + + async with aiohttp.ClientSession() as session: + async with session.get( + f'{space_url}/api/v1/accounts/me', headers={'Authorization': f'Bearer {access_token}'} + ) as response: + if response.status != 200: + raise ValueError(f'Failed to get user info: {await response.text()}') + data = await response.json() + if data.get('code') != 0: + raise ValueError(f'Failed to get user info: {data.get("msg")}') + return data.get('data', {}) + + async def refresh_space_token(self, refresh_token: str) -> typing.Dict: + """Refresh Space access token""" + space_config = self._get_space_config() + space_url = space_config['url'] + + async with aiohttp.ClientSession() as session: + async with session.post( + f'{space_url}/api/v1/accounts/token/refresh', json={'refresh_token': refresh_token} + ) as response: + if response.status != 200: + raise ValueError(f'Failed to refresh token: {await response.text()}') + data = await response.json() + if data.get('code') != 0: + raise ValueError(f'Failed to refresh token: {data.get("msg")}') + return data.get('data', {}) + + async def create_or_update_space_user( + self, + space_account_uuid: str, + email: str, + access_token: str, + refresh_token: str, + api_key: str, + ) -> user.User: + """Create or update a Space user account""" + # Check if user with this Space UUID already exists + existing_user = await self.get_user_by_space_account_uuid(space_account_uuid) + + if existing_user: + # Update existing user's tokens + await self.ap.persistence_mgr.execute_async( + sqlalchemy.update(user.User) + .where(user.User.space_account_uuid == space_account_uuid) + .values( + space_access_token=access_token, + space_refresh_token=refresh_token, + space_api_key=api_key, + ) + ) + return await self.get_user_by_space_account_uuid(space_account_uuid) + + # Check if user with same email exists as local account + existing_email_user = await self.get_user_by_email(email) + if existing_email_user and existing_email_user.account_type == 'local': + raise ValueError('A local account with this email already exists. Please use a different email.') + + # Create new Space user + await self.ap.persistence_mgr.execute_async( + sqlalchemy.insert(user.User).values( + user=email, + password='', # Space users don't have local password + account_type='space', + space_account_uuid=space_account_uuid, + space_access_token=access_token, + space_refresh_token=refresh_token, + space_api_key=api_key, + ) + ) + + return await self.get_user_by_space_account_uuid(space_account_uuid) + + async def authenticate_space_user(self, access_token: str, refresh_token: str) -> typing.Tuple[str, user.User]: + """Authenticate with Space and return JWT token""" + # Get user info from Space + user_info = await self.get_space_user_info(access_token) + + account = user_info.get('account', {}) + api_key = user_info.get('api_key', '') + + space_account_uuid = account.get('uuid') + email = account.get('email') + + if not space_account_uuid or not email: + raise ValueError('Invalid Space user info') + + # Create or update Space user in local database + user_obj = await self.create_or_update_space_user( + space_account_uuid=space_account_uuid, + email=email, + access_token=access_token, + refresh_token=refresh_token, + api_key=api_key, + ) + + # Generate JWT token + jwt_token = await self.generate_jwt_token(email) + + return jwt_token, user_obj diff --git a/src/langbot/pkg/core/app.py b/src/langbot/pkg/core/app.py index fdbce8e2..4b8d22a3 100644 --- a/src/langbot/pkg/core/app.py +++ b/src/langbot/pkg/core/app.py @@ -27,6 +27,7 @@ from ..api.http.service import mcp as mcp_service from ..api.http.service import apikey as apikey_service from ..api.http.service import webhook as webhook_service from ..api.http.service import external_kb as external_kb_service +from ..api.http.service import space_models as space_models_service from ..discover import engine as discover_engine from ..storage import mgr as storagemgr from ..utils import logcache @@ -132,6 +133,8 @@ class Application: webhook_service: webhook_service.WebhookService = None + space_models_service: space_models_service.SpaceModelsService = None + def __init__(self): pass diff --git a/src/langbot/pkg/core/stages/build_app.py b/src/langbot/pkg/core/stages/build_app.py index 31d1bb92..94a4c293 100644 --- a/src/langbot/pkg/core/stages/build_app.py +++ b/src/langbot/pkg/core/stages/build_app.py @@ -24,6 +24,7 @@ from ...api.http.service import mcp as mcp_service from ...api.http.service import apikey as apikey_service from ...api.http.service import webhook as webhook_service from ...api.http.service import external_kb as external_kb_service +from ...api.http.service import space_models as space_models_service from ...discover import engine as discover_engine from ...storage import mgr as storagemgr from ...utils import logcache @@ -135,6 +136,9 @@ class BuildAppStage(stage.BootingStage): webhook_service_inst = webhook_service.WebhookService(ap) ap.webhook_service = webhook_service_inst + space_models_service_inst = space_models_service.SpaceModelsService(ap) + ap.space_models_service = space_models_service_inst + async def runtime_disconnect_callback(connector: plugin_connector.PluginRuntimeConnector) -> None: await asyncio.sleep(3) await plugin_connector_inst.initialize() diff --git a/src/langbot/pkg/entity/persistence/model.py b/src/langbot/pkg/entity/persistence/model.py index e9a104c4..0dea51ff 100644 --- a/src/langbot/pkg/entity/persistence/model.py +++ b/src/langbot/pkg/entity/persistence/model.py @@ -16,6 +16,10 @@ class LLMModel(Base): api_keys = sqlalchemy.Column(sqlalchemy.JSON, nullable=False) abilities = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=[]) extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={}) + # Source tracking for Space integration: 'local' or 'space' + source = sqlalchemy.Column(sqlalchemy.String(32), nullable=False, server_default='local') + # Space model ID for synced models (used to track and update synced models) + space_model_id = sqlalchemy.Column(sqlalchemy.String(255), nullable=True) created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) updated_at = sqlalchemy.Column( sqlalchemy.DateTime, @@ -37,6 +41,10 @@ class EmbeddingModel(Base): requester_config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={}) api_keys = sqlalchemy.Column(sqlalchemy.JSON, nullable=False) extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={}) + # Source tracking for Space integration: 'local' or 'space' + source = sqlalchemy.Column(sqlalchemy.String(32), nullable=False, server_default='local') + # Space model ID for synced models (used to track and update synced models) + space_model_id = sqlalchemy.Column(sqlalchemy.String(255), nullable=True) created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) updated_at = sqlalchemy.Column( sqlalchemy.DateTime, diff --git a/src/langbot/pkg/entity/persistence/user.py b/src/langbot/pkg/entity/persistence/user.py index 04a5b374..6e834f0b 100644 --- a/src/langbot/pkg/entity/persistence/user.py +++ b/src/langbot/pkg/entity/persistence/user.py @@ -9,6 +9,16 @@ class User(Base): id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) user = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) password = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) + + # Account type: 'local' (default) or 'space' + account_type = sqlalchemy.Column(sqlalchemy.String(32), nullable=False, server_default='local') + + # Space account fields (nullable, only used when account_type='space') + space_account_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=True) + space_access_token = sqlalchemy.Column(sqlalchemy.Text, nullable=True) + space_refresh_token = sqlalchemy.Column(sqlalchemy.Text, nullable=True) + space_api_key = sqlalchemy.Column(sqlalchemy.String(255), nullable=True) + created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) updated_at = sqlalchemy.Column( sqlalchemy.DateTime, diff --git a/src/langbot/pkg/persistence/migrations/dbm014_space_account_support.py b/src/langbot/pkg/persistence/migrations/dbm014_space_account_support.py new file mode 100644 index 00000000..5e594a75 --- /dev/null +++ b/src/langbot/pkg/persistence/migrations/dbm014_space_account_support.py @@ -0,0 +1,82 @@ +import sqlalchemy +from .. import migration + + +@migration.migration_class(14) +class DBMigrateSpaceAccountSupport(migration.DBMigration): + """Add Space account support fields to users table""" + + async def upgrade(self): + """Upgrade""" + # Get all column names from the users table + columns = [] + + if self.ap.persistence_mgr.db.name == 'postgresql': + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.text("SELECT column_name FROM information_schema.columns WHERE table_name = 'users';") + ) + all_result = result.fetchall() + columns = [row[0] for row in all_result] + else: + result = await self.ap.persistence_mgr.execute_async(sqlalchemy.text('PRAGMA table_info(users);')) + all_result = result.fetchall() + columns = [row[1] for row in all_result] + + # Add account_type column + if 'account_type' not in columns: + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text("ALTER TABLE users ADD COLUMN account_type VARCHAR(32) DEFAULT 'local' NOT NULL") + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text("ALTER TABLE users ADD COLUMN account_type VARCHAR(32) DEFAULT 'local' NOT NULL") + ) + + # Add space_account_uuid column + if 'space_account_uuid' not in columns: + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE users ADD COLUMN space_account_uuid VARCHAR(255)') + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE users ADD COLUMN space_account_uuid VARCHAR(255)') + ) + + # Add space_access_token column + if 'space_access_token' not in columns: + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE users ADD COLUMN space_access_token TEXT') + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE users ADD COLUMN space_access_token TEXT') + ) + + # Add space_refresh_token column + if 'space_refresh_token' not in columns: + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE users ADD COLUMN space_refresh_token TEXT') + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE users ADD COLUMN space_refresh_token TEXT') + ) + + # Add space_api_key column + if 'space_api_key' not in columns: + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE users ADD COLUMN space_api_key VARCHAR(255)') + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE users ADD COLUMN space_api_key VARCHAR(255)') + ) + + async def downgrade(self): + """Downgrade""" + pass diff --git a/src/langbot/pkg/persistence/migrations/dbm015_model_source_tracking.py b/src/langbot/pkg/persistence/migrations/dbm015_model_source_tracking.py new file mode 100644 index 00000000..05182b36 --- /dev/null +++ b/src/langbot/pkg/persistence/migrations/dbm015_model_source_tracking.py @@ -0,0 +1,78 @@ +import sqlalchemy +from .. import migration + + +@migration.migration_class(15) +class DBMigrateModelSourceTracking(migration.DBMigration): + """Add source tracking fields to models tables for Space integration""" + + async def upgrade(self): + """Upgrade""" + # Add source column to llm_models table + llm_columns = await self._get_columns('llm_models') + + if 'source' not in llm_columns: + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text("ALTER TABLE llm_models ADD COLUMN source VARCHAR(32) DEFAULT 'local' NOT NULL") + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text("ALTER TABLE llm_models ADD COLUMN source VARCHAR(32) DEFAULT 'local' NOT NULL") + ) + + if 'space_model_id' not in llm_columns: + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE llm_models ADD COLUMN space_model_id VARCHAR(255)') + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE llm_models ADD COLUMN space_model_id VARCHAR(255)') + ) + + # Add source column to embedding_models table + embedding_columns = await self._get_columns('embedding_models') + + if 'source' not in embedding_columns: + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text( + "ALTER TABLE embedding_models ADD COLUMN source VARCHAR(32) DEFAULT 'local' NOT NULL" + ) + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text( + "ALTER TABLE embedding_models ADD COLUMN source VARCHAR(32) DEFAULT 'local' NOT NULL" + ) + ) + + if 'space_model_id' not in embedding_columns: + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE embedding_models ADD COLUMN space_model_id VARCHAR(255)') + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE embedding_models ADD COLUMN space_model_id VARCHAR(255)') + ) + + async def _get_columns(self, table_name: str) -> list: + """Get column names for a table""" + if self.ap.persistence_mgr.db.name == 'postgresql': + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.text( + f"SELECT column_name FROM information_schema.columns WHERE table_name = '{table_name}';" + ) + ) + all_result = result.fetchall() + return [row[0] for row in all_result] + else: + result = await self.ap.persistence_mgr.execute_async(sqlalchemy.text(f'PRAGMA table_info({table_name});')) + all_result = result.fetchall() + return [row[1] for row in all_result] + + async def downgrade(self): + """Downgrade""" + pass diff --git a/src/langbot/pkg/utils/constants.py b/src/langbot/pkg/utils/constants.py index c92288f6..66541ba8 100644 --- a/src/langbot/pkg/utils/constants.py +++ b/src/langbot/pkg/utils/constants.py @@ -2,7 +2,7 @@ import langbot semantic_version = f'v{langbot.__version__}' -required_database_version = 13 +required_database_version = 15 """Tag the version of the database schema, used to check if the database needs to be migrated""" debug_mode = False diff --git a/src/langbot/templates/config.yaml b/src/langbot/templates/config.yaml index 32937959..bd0a2ff7 100644 --- a/src/langbot/templates/config.yaml +++ b/src/langbot/templates/config.yaml @@ -71,3 +71,10 @@ plugin: enable_marketplace: true cloud_service_url: 'https://space.langbot.app' display_plugin_debug_url: 'ws://localhost:5401/plugin/debug/ws' +space: + # Space service URL for OAuth and API + url: 'https://space.langbot.app' + # Space API URL for model requests (MaaS) + api_url: 'https://api.langbot.app' + # OAuth authorization page URL (user will be redirected here) + oauth_authorize_url: 'https://space.langbot.app/auth/authorize' diff --git a/web/package.json b/web/package.json index c39fc9f1..90f25b55 100644 --- a/web/package.json +++ b/web/package.json @@ -54,6 +54,7 @@ "next": "~15.5.9", "next-themes": "^0.4.6", "postcss": "^8.5.3", + "qrcode": "^1.5.4", "react": "19.2.1", "react-dom": "19.2.1", "react-hook-form": "^7.56.3", @@ -82,6 +83,7 @@ "@types/mdast": "^4.0.4", "@types/ms": "^2.1.0", "@types/node": "^20", + "@types/qrcode": "^1.5.6", "@types/react": "~19.2.7", "@types/react-dom": "~19.2.3", "@types/react-syntax-highlighter": "^15.5.13", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index e2234bee..45ec69d1 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -1,5394 +1,363 @@ -lockfileVersion: '9.0' +lockfileVersion: '6.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -importers: - .: - dependencies: - '@dnd-kit/core': - specifier: ^6.3.1 - version: 6.3.1(react-dom@19.2.1)(react@19.2.1) - '@dnd-kit/sortable': - specifier: ^10.0.0 - version: 10.0.0(@dnd-kit/core@6.3.1)(react@19.2.1) - '@dnd-kit/utilities': - specifier: ^3.2.2 - version: 3.2.2(react@19.2.1) - '@hookform/resolvers': - specifier: ^5.0.1 - version: 5.2.2(react-hook-form@7.66.1) - '@radix-ui/react-alert-dialog': - specifier: ^1.1.15 - version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-checkbox': - specifier: ^1.3.1 - version: 1.3.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-context-menu': - specifier: ^2.2.15 - version: 2.2.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-dialog': - specifier: ^1.1.14 - version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-dropdown-menu': - specifier: ^2.1.16 - version: 2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-hover-card': - specifier: ^1.1.13 - version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-label': - specifier: ^2.1.6 - version: 2.1.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-popover': - specifier: ^1.1.14 - version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-scroll-area': - specifier: ^1.2.9 - version: 1.2.10(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-select': - specifier: ^2.2.4 - version: 2.2.6(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-separator': - specifier: ^1.1.7 - version: 1.1.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': - specifier: ^1.2.3 - version: 1.2.4(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-switch': - specifier: ^1.2.4 - version: 1.2.6(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-tabs': - specifier: ^1.1.11 - version: 1.1.13(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-toggle': - specifier: ^1.1.8 - version: 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-toggle-group': - specifier: ^1.1.9 - version: 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-tooltip': - specifier: ^1.2.7 - version: 1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) - '@tailwindcss/postcss': - specifier: ^4.1.5 - version: 4.1.17 - '@tanstack/react-table': - specifier: ^8.21.3 - version: 8.21.3(react-dom@19.2.1)(react@19.2.1) - axios: - specifier: ^1.12.0 - version: 1.13.2 - class-variance-authority: - specifier: ^0.7.1 - version: 0.7.1 - clsx: - specifier: ^2.1.1 - version: 2.1.1 - highlight.js: - specifier: ^11.11.1 - version: 11.11.1 - i18next: - specifier: ^25.1.2 - version: 25.6.3(typescript@5.9.3) - i18next-browser-languagedetector: - specifier: ^8.1.0 - version: 8.2.0 - input-otp: - specifier: ^1.4.2 - version: 1.4.2(react-dom@19.2.1)(react@19.2.1) - lodash: - specifier: ^4.17.21 - version: 4.17.21 - lucide-react: - specifier: ^0.507.0 - version: 0.507.0(react@19.2.1) - next: - specifier: ~15.5.9 - version: 15.5.9(react-dom@19.2.1)(react@19.2.1) - next-themes: - specifier: ^0.4.6 - version: 0.4.6(react-dom@19.2.1)(react@19.2.1) - postcss: - specifier: ^8.5.3 - version: 8.5.6 - react: - specifier: 19.2.1 - version: 19.2.1 - react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) - react-hook-form: - specifier: ^7.56.3 - version: 7.66.1(react@19.2.1) - react-i18next: - specifier: ^15.5.1 - version: 15.7.4(i18next@25.6.3)(react-dom@19.2.1)(react@19.2.1)(typescript@5.9.3) - react-markdown: - specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.7)(react@19.2.1) - react-photo-view: - specifier: ^1.2.7 - version: 1.2.7(react-dom@19.2.1)(react@19.2.1) - react-syntax-highlighter: - specifier: ^16.1.0 - version: 16.1.0(react@19.2.1) - rehype-autolink-headings: - specifier: ^7.1.0 - version: 7.1.0 - rehype-highlight: - specifier: ^7.0.2 - version: 7.0.2 - rehype-raw: - specifier: ^7.0.0 - version: 7.0.0 - rehype-slug: - specifier: ^6.0.0 - version: 6.0.0 - remark-gfm: - specifier: ^4.0.1 - version: 4.0.1 - sonner: - specifier: ^2.0.3 - version: 2.0.7(react-dom@19.2.1)(react@19.2.1) - tailwind-merge: - specifier: ^3.2.0 - version: 3.4.0 - tailwindcss: - specifier: ^4.1.5 - version: 4.1.17 - uuidjs: - specifier: ^5.1.0 - version: 5.1.0 - zod: - specifier: ^3.24.4 - version: 3.25.76 - devDependencies: - '@eslint/eslintrc': - specifier: ^3 - version: 3.3.1 - '@types/debug': - specifier: ^4.1.12 - version: 4.1.12 - '@types/estree': - specifier: ^1.0.8 - version: 1.0.8 - '@types/estree-jsx': - specifier: ^1.0.5 - version: 1.0.5 - '@types/hast': - specifier: ^3.0.4 - version: 3.0.4 - '@types/lodash': - specifier: ^4.17.16 - version: 4.17.21 - '@types/mdast': - specifier: ^4.0.4 - version: 4.0.4 - '@types/ms': - specifier: ^2.1.0 - version: 2.1.0 - '@types/node': - specifier: ^20 - version: 20.19.25 - '@types/react': - specifier: ~19.2.7 - version: 19.2.7 - '@types/react-dom': - specifier: ~19.2.3 - version: 19.2.3(@types/react@19.2.7) - '@types/react-syntax-highlighter': - specifier: ^15.5.13 - version: 15.5.13 - '@types/unist': - specifier: ^3.0.3 - version: 3.0.3 - eslint: - specifier: ^9 - version: 9.39.1 - eslint-config-next: - specifier: 15.2.4 - version: 15.2.4(eslint@9.39.1)(typescript@5.9.3) - eslint-config-prettier: - specifier: ^10.1.2 - version: 10.1.8(eslint@9.39.1) - eslint-plugin-prettier: - specifier: ^5.2.6 - version: 5.5.4(eslint-config-prettier@10.1.8)(eslint@9.39.1)(prettier@3.6.2) - lint-staged: - specifier: ^15.5.1 - version: 15.5.2 - prettier: - specifier: ^3.5.3 - version: 3.6.2 - tw-animate-css: - specifier: ^1.2.9 - version: 1.4.0 - typescript: - specifier: ^5.8.3 - version: 5.9.3 - typescript-eslint: - specifier: ^8.31.1 - version: 8.48.0(eslint@9.39.1)(typescript@5.9.3) +dependencies: + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@19.2.1)(react@19.2.1) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1)(react@19.2.1) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@19.2.1) + '@hookform/resolvers': + specifier: ^5.0.1 + version: 5.2.2(react-hook-form@7.66.1) + '@radix-ui/react-alert-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-checkbox': + specifier: ^1.3.1 + version: 1.3.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-context-menu': + specifier: ^2.2.15 + version: 2.2.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-dialog': + specifier: ^1.1.14 + version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-hover-card': + specifier: ^1.1.13 + version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-label': + specifier: ^2.1.6 + version: 2.1.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-popover': + specifier: ^1.1.14 + version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-scroll-area': + specifier: ^1.2.9 + version: 1.2.10(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-select': + specifier: ^2.2.4 + version: 2.2.6(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-separator': + specifier: ^1.1.7 + version: 1.1.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slot': + specifier: ^1.2.3 + version: 1.2.4(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-switch': + specifier: ^1.2.4 + version: 1.2.6(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-tabs': + specifier: ^1.1.11 + version: 1.1.13(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-toggle': + specifier: ^1.1.8 + version: 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-toggle-group': + specifier: ^1.1.9 + version: 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-tooltip': + specifier: ^1.2.7 + version: 1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@tailwindcss/postcss': + specifier: ^4.1.5 + version: 4.1.17 + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.2.1)(react@19.2.1) + axios: + specifier: ^1.12.0 + version: 1.13.2 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + highlight.js: + specifier: ^11.11.1 + version: 11.11.1 + i18next: + specifier: ^25.1.2 + version: 25.6.3(typescript@5.9.3) + i18next-browser-languagedetector: + specifier: ^8.1.0 + version: 8.2.0 + input-otp: + specifier: ^1.4.2 + version: 1.4.2(react-dom@19.2.1)(react@19.2.1) + lodash: + specifier: ^4.17.21 + version: 4.17.21 + lucide-react: + specifier: ^0.507.0 + version: 0.507.0(react@19.2.1) + next: + specifier: ~15.5.9 + version: 15.5.9(react-dom@19.2.1)(react@19.2.1) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.1)(react@19.2.1) + postcss: + specifier: ^8.5.3 + version: 8.5.6 + qrcode: + specifier: ^1.5.4 + version: 1.5.4 + react: + specifier: 19.2.1 + version: 19.2.1 + react-dom: + specifier: 19.2.1 + version: 19.2.1(react@19.2.1) + react-hook-form: + specifier: ^7.56.3 + version: 7.66.1(react@19.2.1) + react-i18next: + specifier: ^15.5.1 + version: 15.7.4(i18next@25.6.3)(react-dom@19.2.1)(react@19.2.1)(typescript@5.9.3) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.2.7)(react@19.2.1) + react-photo-view: + specifier: ^1.2.7 + version: 1.2.7(react-dom@19.2.1)(react@19.2.1) + react-syntax-highlighter: + specifier: ^16.1.0 + version: 16.1.0(react@19.2.1) + rehype-autolink-headings: + specifier: ^7.1.0 + version: 7.1.0 + rehype-highlight: + specifier: ^7.0.2 + version: 7.0.2 + rehype-raw: + specifier: ^7.0.0 + version: 7.0.0 + rehype-slug: + specifier: ^6.0.0 + version: 6.0.0 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + sonner: + specifier: ^2.0.3 + version: 2.0.7(react-dom@19.2.1)(react@19.2.1) + tailwind-merge: + specifier: ^3.2.0 + version: 3.4.0 + tailwindcss: + specifier: ^4.1.5 + version: 4.1.17 + uuidjs: + specifier: ^5.1.0 + version: 5.1.0 + zod: + specifier: ^3.24.4 + version: 3.25.76 + +devDependencies: + '@eslint/eslintrc': + specifier: ^3 + version: 3.3.1 + '@types/debug': + specifier: ^4.1.12 + version: 4.1.12 + '@types/estree': + specifier: ^1.0.8 + version: 1.0.8 + '@types/estree-jsx': + specifier: ^1.0.5 + version: 1.0.5 + '@types/hast': + specifier: ^3.0.4 + version: 3.0.4 + '@types/lodash': + specifier: ^4.17.16 + version: 4.17.21 + '@types/mdast': + specifier: ^4.0.4 + version: 4.0.4 + '@types/ms': + specifier: ^2.1.0 + version: 2.1.0 + '@types/node': + specifier: ^20 + version: 20.19.25 + '@types/qrcode': + specifier: ^1.5.6 + version: 1.5.6 + '@types/react': + specifier: ~19.2.7 + version: 19.2.7 + '@types/react-dom': + specifier: ~19.2.3 + version: 19.2.3(@types/react@19.2.7) + '@types/react-syntax-highlighter': + specifier: ^15.5.13 + version: 15.5.13 + '@types/unist': + specifier: ^3.0.3 + version: 3.0.3 + eslint: + specifier: ^9 + version: 9.39.1 + eslint-config-next: + specifier: 15.2.4 + version: 15.2.4(eslint@9.39.1)(typescript@5.9.3) + eslint-config-prettier: + specifier: ^10.1.2 + version: 10.1.8(eslint@9.39.1) + eslint-plugin-prettier: + specifier: ^5.2.6 + version: 5.5.4(eslint-config-prettier@10.1.8)(eslint@9.39.1)(prettier@3.6.2) + lint-staged: + specifier: ^15.5.1 + version: 15.5.2 + prettier: + specifier: ^3.5.3 + version: 3.6.2 + tw-animate-css: + specifier: ^1.2.9 + version: 1.4.0 + typescript: + specifier: ^5.8.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.31.1 + version: 8.48.0(eslint@9.39.1)(typescript@5.9.3) packages: - '@alloc/quick-lru@5.2.0': - resolution: - { - integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==, - } - engines: { node: '>=10' } - '@babel/runtime@7.28.4': - resolution: - { - integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==, - } - engines: { node: '>=6.9.0' } + /@alloc/quick-lru@5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + dev: false - '@dnd-kit/accessibility@3.1.1': - resolution: - { - integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==, - } + /@babel/runtime@7.28.4: + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + dev: false + + /@dnd-kit/accessibility@3.1.1(react@19.2.1): + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} peerDependencies: react: '>=16.8.0' - - '@dnd-kit/core@6.3.1': - resolution: - { - integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==, - } - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@dnd-kit/sortable@10.0.0': - resolution: - { - integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==, - } - peerDependencies: - '@dnd-kit/core': ^6.3.0 - react: '>=16.8.0' - - '@dnd-kit/utilities@3.2.2': - resolution: - { - integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==, - } - peerDependencies: - react: '>=16.8.0' - - '@emnapi/core@1.7.1': - resolution: - { - integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==, - } - - '@emnapi/runtime@1.7.1': - resolution: - { - integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==, - } - - '@emnapi/wasi-threads@1.1.0': - resolution: - { - integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==, - } - - '@eslint-community/eslint-utils@4.9.0': - resolution: - { - integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.2': - resolution: - { - integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==, - } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - - '@eslint/config-array@0.21.1': - resolution: - { - integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - '@eslint/config-helpers@0.4.2': - resolution: - { - integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - '@eslint/core@0.17.0': - resolution: - { - integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - '@eslint/eslintrc@3.3.1': - resolution: - { - integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - '@eslint/js@9.39.1': - resolution: - { - integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - '@eslint/object-schema@2.1.7': - resolution: - { - integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - '@eslint/plugin-kit@0.4.1': - resolution: - { - integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - '@floating-ui/core@1.7.3': - resolution: - { - integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==, - } - - '@floating-ui/dom@1.7.4': - resolution: - { - integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==, - } - - '@floating-ui/react-dom@2.1.6': - resolution: - { - integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==, - } - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/utils@0.2.10': - resolution: - { - integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==, - } - - '@hookform/resolvers@5.2.2': - resolution: - { - integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==, - } - peerDependencies: - react-hook-form: ^7.55.0 - - '@humanfs/core@0.19.1': - resolution: - { - integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, - } - engines: { node: '>=18.18.0' } - - '@humanfs/node@0.16.7': - resolution: - { - integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==, - } - engines: { node: '>=18.18.0' } - - '@humanwhocodes/module-importer@1.0.1': - resolution: - { - integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, - } - engines: { node: '>=12.22' } - - '@humanwhocodes/retry@0.4.3': - resolution: - { - integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, - } - engines: { node: '>=18.18' } - - '@img/colour@1.0.0': - resolution: - { - integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==, - } - engines: { node: '>=18' } - - '@img/sharp-darwin-arm64@0.34.5': - resolution: - { - integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.34.5': - resolution: - { - integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: - { - integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==, - } - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: - { - integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==, - } - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: - { - integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==, - } - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: - { - integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==, - } - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: - { - integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==, - } - cpu: [ppc64] - os: [linux] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: - { - integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==, - } - cpu: [riscv64] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: - { - integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==, - } - cpu: [s390x] - os: [linux] - - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: - { - integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==, - } - cpu: [x64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: - { - integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==, - } - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: - { - integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==, - } - cpu: [x64] - os: [linux] - - '@img/sharp-linux-arm64@0.34.5': - resolution: - { - integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm@0.34.5': - resolution: - { - integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [arm] - os: [linux] - - '@img/sharp-linux-ppc64@0.34.5': - resolution: - { - integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [ppc64] - os: [linux] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: - { - integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [riscv64] - os: [linux] - - '@img/sharp-linux-s390x@0.34.5': - resolution: - { - integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.34.5': - resolution: - { - integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: - { - integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: - { - integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [x64] - os: [linux] - - '@img/sharp-wasm32@0.34.5': - resolution: - { - integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.34.5': - resolution: - { - integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.5': - resolution: - { - integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.34.5': - resolution: - { - integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [x64] - os: [win32] - - '@jridgewell/gen-mapping@0.3.13': - resolution: - { - integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, - } - - '@jridgewell/remapping@2.3.5': - resolution: - { - integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==, - } - - '@jridgewell/resolve-uri@3.1.2': - resolution: - { - integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, - } - engines: { node: '>=6.0.0' } - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: - { - integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, - } - - '@jridgewell/trace-mapping@0.3.31': - resolution: - { - integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, - } - - '@napi-rs/wasm-runtime@0.2.12': - resolution: - { - integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==, - } - - '@next/env@15.5.9': - resolution: - { - integrity: sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==, - } - - '@next/eslint-plugin-next@15.2.4': - resolution: - { - integrity: sha512-O8ScvKtnxkp8kL9TpJTTKnMqlkZnS+QxwoQnJwPGBxjBbzd6OVVPEJ5/pMNrktSyXQD/chEfzfFzYLM6JANOOQ==, - } - - '@next/swc-darwin-arm64@15.5.7': - resolution: - { - integrity: sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-x64@15.5.7': - resolution: - { - integrity: sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [darwin] - - '@next/swc-linux-arm64-gnu@15.5.7': - resolution: - { - integrity: sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [linux] - - '@next/swc-linux-arm64-musl@15.5.7': - resolution: - { - integrity: sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [linux] - - '@next/swc-linux-x64-gnu@15.5.7': - resolution: - { - integrity: sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [linux] - - '@next/swc-linux-x64-musl@15.5.7': - resolution: - { - integrity: sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [linux] - - '@next/swc-win32-arm64-msvc@15.5.7': - resolution: - { - integrity: sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [win32] - - '@next/swc-win32-x64-msvc@15.5.7': - resolution: - { - integrity: sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [win32] - - '@nodelib/fs.scandir@2.1.5': - resolution: - { - integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, - } - engines: { node: '>= 8' } - - '@nodelib/fs.stat@2.0.5': - resolution: - { - integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, - } - engines: { node: '>= 8' } - - '@nodelib/fs.walk@1.2.8': - resolution: - { - integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, - } - engines: { node: '>= 8' } - - '@nolyfill/is-core-module@1.0.39': - resolution: - { - integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==, - } - engines: { node: '>=12.4.0' } - - '@pkgr/core@0.2.9': - resolution: - { - integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==, - } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } - - '@radix-ui/number@1.1.1': - resolution: - { - integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==, - } - - '@radix-ui/primitive@1.1.3': - resolution: - { - integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==, - } - - '@radix-ui/react-alert-dialog@1.1.15': - resolution: - { - integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-arrow@1.1.7': - resolution: - { - integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-checkbox@1.3.3': - resolution: - { - integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-collection@1.1.7': - resolution: - { - integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-compose-refs@1.1.2': - resolution: - { - integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-context-menu@2.2.16': - resolution: - { - integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-context@1.1.2': - resolution: - { - integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dialog@1.1.15': - resolution: - { - integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-direction@1.1.1': - resolution: - { - integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dismissable-layer@1.1.11': - resolution: - { - integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-dropdown-menu@2.1.16': - resolution: - { - integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-focus-guards@1.1.3': - resolution: - { - integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-focus-scope@1.1.7': - resolution: - { - integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-hover-card@1.1.15': - resolution: - { - integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-id@1.1.1': - resolution: - { - integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-label@2.1.8': - resolution: - { - integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-menu@2.1.16': - resolution: - { - integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-popover@1.1.15': - resolution: - { - integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-popper@1.2.8': - resolution: - { - integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-portal@1.1.9': - resolution: - { - integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-presence@1.1.5': - resolution: - { - integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.3': - resolution: - { - integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.4': - resolution: - { - integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-roving-focus@1.1.11': - resolution: - { - integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-scroll-area@1.2.10': - resolution: - { - integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-select@2.2.6': - resolution: - { - integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-separator@1.1.8': - resolution: - { - integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slot@1.2.3': - resolution: - { - integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-slot@1.2.4': - resolution: - { - integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-switch@1.2.6': - resolution: - { - integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-tabs@1.1.13': - resolution: - { - integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-toggle-group@1.1.11': - resolution: - { - integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-toggle@1.1.10': - resolution: - { - integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-tooltip@1.2.8': - resolution: - { - integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-use-callback-ref@1.1.1': - resolution: - { - integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.2.2': - resolution: - { - integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-effect-event@0.0.2': - resolution: - { - integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-escape-keydown@1.1.1': - resolution: - { - integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.1.1': - resolution: - { - integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-previous@1.1.1': - resolution: - { - integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-rect@1.1.1': - resolution: - { - integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-size@1.1.1': - resolution: - { - integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==, - } - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-visually-hidden@1.2.3': - resolution: - { - integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==, - } - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/rect@1.1.1': - resolution: - { - integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==, - } - - '@rtsao/scc@1.1.0': - resolution: - { - integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==, - } - - '@rushstack/eslint-patch@1.15.0': - resolution: - { - integrity: sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==, - } - - '@standard-schema/utils@0.3.0': - resolution: - { - integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==, - } - - '@swc/helpers@0.5.15': - resolution: - { - integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==, - } - - '@tailwindcss/node@4.1.17': - resolution: - { - integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==, - } - - '@tailwindcss/oxide-android-arm64@4.1.17': - resolution: - { - integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.1.17': - resolution: - { - integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.1.17': - resolution: - { - integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.1.17': - resolution: - { - integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': - resolution: - { - integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==, - } - engines: { node: '>= 10' } - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': - resolution: - { - integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-musl@4.1.17': - resolution: - { - integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-gnu@4.1.17': - resolution: - { - integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-musl@4.1.17': - resolution: - { - integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-wasm32-wasi@4.1.17': - resolution: - { - integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==, - } - engines: { node: '>=14.0.0' } - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': - resolution: - { - integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.1.17': - resolution: - { - integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.1.17': - resolution: - { - integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==, - } - engines: { node: '>= 10' } - - '@tailwindcss/postcss@4.1.17': - resolution: - { - integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==, - } - - '@tanstack/react-table@8.21.3': - resolution: - { - integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==, - } - engines: { node: '>=12' } - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - '@tanstack/table-core@8.21.3': - resolution: - { - integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==, - } - engines: { node: '>=12' } - - '@tybys/wasm-util@0.10.1': - resolution: - { - integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==, - } - - '@types/debug@4.1.12': - resolution: - { - integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==, - } - - '@types/estree-jsx@1.0.5': - resolution: - { - integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==, - } - - '@types/estree@1.0.8': - resolution: - { - integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, - } - - '@types/hast@3.0.4': - resolution: - { - integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==, - } - - '@types/json-schema@7.0.15': - resolution: - { - integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, - } - - '@types/json5@0.0.29': - resolution: - { - integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==, - } - - '@types/lodash@4.17.21': - resolution: - { - integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==, - } - - '@types/mdast@4.0.4': - resolution: - { - integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==, - } - - '@types/ms@2.1.0': - resolution: - { - integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==, - } - - '@types/node@20.19.25': - resolution: - { - integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==, - } - - '@types/prismjs@1.26.5': - resolution: - { - integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==, - } - - '@types/react-dom@19.2.3': - resolution: - { - integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==, - } - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react-syntax-highlighter@15.5.13': - resolution: - { - integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==, - } - - '@types/react@19.2.7': - resolution: - { - integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==, - } - - '@types/unist@2.0.11': - resolution: - { - integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==, - } - - '@types/unist@3.0.3': - resolution: - { - integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, - } - - '@typescript-eslint/eslint-plugin@8.48.0': - resolution: - { - integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - '@typescript-eslint/parser': ^8.48.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/parser@8.48.0': - resolution: - { - integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/project-service@8.48.0': - resolution: - { - integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/scope-manager@8.48.0': - resolution: - { - integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - '@typescript-eslint/tsconfig-utils@8.48.0': - resolution: - { - integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/type-utils@8.48.0': - resolution: - { - integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/types@8.48.0': - resolution: - { - integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - '@typescript-eslint/typescript-estree@8.48.0': - resolution: - { - integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/utils@8.48.0': - resolution: - { - integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/visitor-keys@8.48.0': - resolution: - { - integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - '@ungap/structured-clone@1.3.0': - resolution: - { - integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==, - } - - '@unrs/resolver-binding-android-arm-eabi@1.11.1': - resolution: - { - integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==, - } - cpu: [arm] - os: [android] - - '@unrs/resolver-binding-android-arm64@1.11.1': - resolution: - { - integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==, - } - cpu: [arm64] - os: [android] - - '@unrs/resolver-binding-darwin-arm64@1.11.1': - resolution: - { - integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==, - } - cpu: [arm64] - os: [darwin] - - '@unrs/resolver-binding-darwin-x64@1.11.1': - resolution: - { - integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==, - } - cpu: [x64] - os: [darwin] - - '@unrs/resolver-binding-freebsd-x64@1.11.1': - resolution: - { - integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==, - } - cpu: [x64] - os: [freebsd] - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': - resolution: - { - integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==, - } - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': - resolution: - { - integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==, - } - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': - resolution: - { - integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==, - } - cpu: [arm64] - os: [linux] - - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': - resolution: - { - integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==, - } - cpu: [arm64] - os: [linux] - - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': - resolution: - { - integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==, - } - cpu: [ppc64] - os: [linux] - - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': - resolution: - { - integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==, - } - cpu: [riscv64] - os: [linux] - - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': - resolution: - { - integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==, - } - cpu: [riscv64] - os: [linux] - - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': - resolution: - { - integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==, - } - cpu: [s390x] - os: [linux] - - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': - resolution: - { - integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==, - } - cpu: [x64] - os: [linux] - - '@unrs/resolver-binding-linux-x64-musl@1.11.1': - resolution: - { - integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==, - } - cpu: [x64] - os: [linux] - - '@unrs/resolver-binding-wasm32-wasi@1.11.1': - resolution: - { - integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==, - } - engines: { node: '>=14.0.0' } - cpu: [wasm32] - - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': - resolution: - { - integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==, - } - cpu: [arm64] - os: [win32] - - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': - resolution: - { - integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==, - } - cpu: [ia32] - os: [win32] - - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': - resolution: - { - integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==, - } - cpu: [x64] - os: [win32] - - acorn-jsx@5.3.2: - resolution: - { - integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, - } - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.15.0: - resolution: - { - integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, - } - engines: { node: '>=0.4.0' } - hasBin: true - - ajv@6.12.6: - resolution: - { - integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, - } - - ansi-escapes@7.2.0: - resolution: - { - integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==, - } - engines: { node: '>=18' } - - ansi-regex@6.2.2: - resolution: - { - integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, - } - engines: { node: '>=12' } - - ansi-styles@4.3.0: - resolution: - { - integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, - } - engines: { node: '>=8' } - - ansi-styles@6.2.3: - resolution: - { - integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, - } - engines: { node: '>=12' } - - argparse@2.0.1: - resolution: - { - integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, - } - - aria-hidden@1.2.6: - resolution: - { - integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==, - } - engines: { node: '>=10' } - - aria-query@5.3.2: - resolution: - { - integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==, - } - engines: { node: '>= 0.4' } - - array-buffer-byte-length@1.0.2: - resolution: - { - integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==, - } - engines: { node: '>= 0.4' } - - array-includes@3.1.9: - resolution: - { - integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==, - } - engines: { node: '>= 0.4' } - - array.prototype.findlast@1.2.5: - resolution: - { - integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==, - } - engines: { node: '>= 0.4' } - - array.prototype.findlastindex@1.2.6: - resolution: - { - integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==, - } - engines: { node: '>= 0.4' } - - array.prototype.flat@1.3.3: - resolution: - { - integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==, - } - engines: { node: '>= 0.4' } - - array.prototype.flatmap@1.3.3: - resolution: - { - integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==, - } - engines: { node: '>= 0.4' } - - array.prototype.tosorted@1.1.4: - resolution: - { - integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==, - } - engines: { node: '>= 0.4' } - - arraybuffer.prototype.slice@1.0.4: - resolution: - { - integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==, - } - engines: { node: '>= 0.4' } - - ast-types-flow@0.0.8: - resolution: - { - integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==, - } - - async-function@1.0.0: - resolution: - { - integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==, - } - engines: { node: '>= 0.4' } - - asynckit@0.4.0: - resolution: - { - integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, - } - - available-typed-arrays@1.0.7: - resolution: - { - integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==, - } - engines: { node: '>= 0.4' } - - axe-core@4.11.0: - resolution: - { - integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==, - } - engines: { node: '>=4' } - - axios@1.13.2: - resolution: - { - integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==, - } - - axobject-query@4.1.0: - resolution: - { - integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==, - } - engines: { node: '>= 0.4' } - - bail@2.0.2: - resolution: - { - integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==, - } - - balanced-match@1.0.2: - resolution: - { - integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, - } - - brace-expansion@1.1.12: - resolution: - { - integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==, - } - - brace-expansion@2.0.2: - resolution: - { - integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, - } - - braces@3.0.3: - resolution: - { - integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, - } - engines: { node: '>=8' } - - call-bind-apply-helpers@1.0.2: - resolution: - { - integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, - } - engines: { node: '>= 0.4' } - - call-bind@1.0.8: - resolution: - { - integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==, - } - engines: { node: '>= 0.4' } - - call-bound@1.0.4: - resolution: - { - integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==, - } - engines: { node: '>= 0.4' } - - callsites@3.1.0: - resolution: - { - integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, - } - engines: { node: '>=6' } - - caniuse-lite@1.0.30001760: - resolution: - { - integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==, - } - - ccount@2.0.1: - resolution: - { - integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, - } - - chalk@4.1.2: - resolution: - { - integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, - } - engines: { node: '>=10' } - - chalk@5.6.2: - resolution: - { - integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==, - } - engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } - - character-entities-html4@2.1.0: - resolution: - { - integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==, - } - - character-entities-legacy@3.0.0: - resolution: - { - integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==, - } - - character-entities@2.0.2: - resolution: - { - integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==, - } - - character-reference-invalid@2.0.1: - resolution: - { - integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==, - } - - class-variance-authority@0.7.1: - resolution: - { - integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==, - } - - cli-cursor@5.0.0: - resolution: - { - integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==, - } - engines: { node: '>=18' } - - cli-truncate@4.0.0: - resolution: - { - integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==, - } - engines: { node: '>=18' } - - client-only@0.0.1: - resolution: - { - integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==, - } - - clsx@2.1.1: - resolution: - { - integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==, - } - engines: { node: '>=6' } - - color-convert@2.0.1: - resolution: - { - integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, - } - engines: { node: '>=7.0.0' } - - color-name@1.1.4: - resolution: - { - integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, - } - - colorette@2.0.20: - resolution: - { - integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, - } - - combined-stream@1.0.8: - resolution: - { - integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, - } - engines: { node: '>= 0.8' } - - comma-separated-tokens@2.0.3: - resolution: - { - integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==, - } - - commander@13.1.0: - resolution: - { - integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==, - } - engines: { node: '>=18' } - - concat-map@0.0.1: - resolution: - { - integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, - } - - cross-spawn@7.0.6: - resolution: - { - integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, - } - engines: { node: '>= 8' } - - csstype@3.2.3: - resolution: - { - integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==, - } - - damerau-levenshtein@1.0.8: - resolution: - { - integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==, - } - - data-view-buffer@1.0.2: - resolution: - { - integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==, - } - engines: { node: '>= 0.4' } - - data-view-byte-length@1.0.2: - resolution: - { - integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==, - } - engines: { node: '>= 0.4' } - - data-view-byte-offset@1.0.1: - resolution: - { - integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==, - } - engines: { node: '>= 0.4' } - - debug@3.2.7: - resolution: - { - integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==, - } - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.4.3: - resolution: - { - integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, - } - engines: { node: '>=6.0' } - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decode-named-character-reference@1.2.0: - resolution: - { - integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==, - } - - deep-is@0.1.4: - resolution: - { - integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, - } - - define-data-property@1.1.4: - resolution: - { - integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, - } - engines: { node: '>= 0.4' } - - define-properties@1.2.1: - resolution: - { - integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==, - } - engines: { node: '>= 0.4' } - - delayed-stream@1.0.0: - resolution: - { - integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, - } - engines: { node: '>=0.4.0' } - - dequal@2.0.3: - resolution: - { - integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==, - } - engines: { node: '>=6' } - - detect-libc@2.1.2: - resolution: - { - integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, - } - engines: { node: '>=8' } - - detect-node-es@1.1.0: - resolution: - { - integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==, - } - - devlop@1.1.0: - resolution: - { - integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==, - } - - doctrine@2.1.0: - resolution: - { - integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==, - } - engines: { node: '>=0.10.0' } - - dunder-proto@1.0.1: - resolution: - { - integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, - } - engines: { node: '>= 0.4' } - - emoji-regex@10.6.0: - resolution: - { - integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, - } - - emoji-regex@9.2.2: - resolution: - { - integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, - } - - enhanced-resolve@5.18.3: - resolution: - { - integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==, - } - engines: { node: '>=10.13.0' } - - entities@6.0.1: - resolution: - { - integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==, - } - engines: { node: '>=0.12' } - - environment@1.1.0: - resolution: - { - integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==, - } - engines: { node: '>=18' } - - es-abstract@1.24.0: - resolution: - { - integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==, - } - engines: { node: '>= 0.4' } - - es-define-property@1.0.1: - resolution: - { - integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, - } - engines: { node: '>= 0.4' } - - es-errors@1.3.0: - resolution: - { - integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, - } - engines: { node: '>= 0.4' } - - es-iterator-helpers@1.2.1: - resolution: - { - integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==, - } - engines: { node: '>= 0.4' } - - es-object-atoms@1.1.1: - resolution: - { - integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, - } - engines: { node: '>= 0.4' } - - es-set-tostringtag@2.1.0: - resolution: - { - integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, - } - engines: { node: '>= 0.4' } - - es-shim-unscopables@1.1.0: - resolution: - { - integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==, - } - engines: { node: '>= 0.4' } - - es-to-primitive@1.3.0: - resolution: - { - integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==, - } - engines: { node: '>= 0.4' } - - escape-string-regexp@4.0.0: - resolution: - { - integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, - } - engines: { node: '>=10' } - - escape-string-regexp@5.0.0: - resolution: - { - integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==, - } - engines: { node: '>=12' } - - eslint-config-next@15.2.4: - resolution: - { - integrity: sha512-v4gYjd4eYIme8qzaJItpR5MMBXJ0/YV07u7eb50kEnlEmX7yhOjdUdzz70v4fiINYRjLf8X8TbogF0k7wlz6sA==, - } - peerDependencies: - eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 - typescript: '>=3.3.1' - peerDependenciesMeta: - typescript: - optional: true - - eslint-config-prettier@10.1.8: - resolution: - { - integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==, - } - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-import-resolver-node@0.3.9: - resolution: - { - integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==, - } - - eslint-import-resolver-typescript@3.10.1: - resolution: - { - integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==, - } - engines: { node: ^14.18.0 || >=16.0.0 } - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - eslint-plugin-import-x: '*' - peerDependenciesMeta: - eslint-plugin-import: - optional: true - eslint-plugin-import-x: - optional: true - - eslint-module-utils@2.12.1: - resolution: - { - integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==, - } - engines: { node: '>=4' } - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - - eslint-plugin-import@2.32.0: - resolution: - { - integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==, - } - engines: { node: '>=4' } - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - - eslint-plugin-jsx-a11y@6.10.2: - resolution: - { - integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==, - } - engines: { node: '>=4.0' } - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - - eslint-plugin-prettier@5.5.4: - resolution: - { - integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==, - } - engines: { node: ^14.18.0 || >=16.0.0 } - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true - - eslint-plugin-react-hooks@5.2.0: - resolution: - { - integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==, - } - engines: { node: '>=10' } - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - - eslint-plugin-react@7.37.5: - resolution: - { - integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==, - } - engines: { node: '>=4' } - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - - eslint-scope@8.4.0: - resolution: - { - integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - eslint-visitor-keys@3.4.3: - resolution: - { - integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - - eslint-visitor-keys@4.2.1: - resolution: - { - integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - eslint@9.39.1: - resolution: - { - integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@10.4.0: - resolution: - { - integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - esquery@1.6.0: - resolution: - { - integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==, - } - engines: { node: '>=0.10' } - - esrecurse@4.3.0: - resolution: - { - integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, - } - engines: { node: '>=4.0' } - - estraverse@5.3.0: - resolution: - { - integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, - } - engines: { node: '>=4.0' } - - estree-util-is-identifier-name@3.0.0: - resolution: - { - integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==, - } - - esutils@2.0.3: - resolution: - { - integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, - } - engines: { node: '>=0.10.0' } - - eventemitter3@5.0.1: - resolution: - { - integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==, - } - - execa@8.0.1: - resolution: - { - integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==, - } - engines: { node: '>=16.17' } - - extend@3.0.2: - resolution: - { - integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, - } - - fast-deep-equal@3.1.3: - resolution: - { - integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, - } - - fast-diff@1.3.0: - resolution: - { - integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==, - } - - fast-glob@3.3.1: - resolution: - { - integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==, - } - engines: { node: '>=8.6.0' } - - fast-json-stable-stringify@2.1.0: - resolution: - { - integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, - } - - fast-levenshtein@2.0.6: - resolution: - { - integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, - } - - fastq@1.19.1: - resolution: - { - integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==, - } - - fault@1.0.4: - resolution: - { - integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==, - } - - fdir@6.5.0: - resolution: - { - integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, - } - engines: { node: '>=12.0.0' } - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - file-entry-cache@8.0.0: - resolution: - { - integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, - } - engines: { node: '>=16.0.0' } - - fill-range@7.1.1: - resolution: - { - integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, - } - engines: { node: '>=8' } - - find-up@5.0.0: - resolution: - { - integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, - } - engines: { node: '>=10' } - - flat-cache@4.0.1: - resolution: - { - integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, - } - engines: { node: '>=16' } - - flatted@3.3.3: - resolution: - { - integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, - } - - follow-redirects@1.15.11: - resolution: - { - integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==, - } - engines: { node: '>=4.0' } - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - for-each@0.3.5: - resolution: - { - integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, - } - engines: { node: '>= 0.4' } - - form-data@4.0.5: - resolution: - { - integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==, - } - engines: { node: '>= 6' } - - format@0.2.2: - resolution: - { - integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==, - } - engines: { node: '>=0.4.x' } - - function-bind@1.1.2: - resolution: - { - integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, - } - - function.prototype.name@1.1.8: - resolution: - { - integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==, - } - engines: { node: '>= 0.4' } - - functions-have-names@1.2.3: - resolution: - { - integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, - } - - generator-function@2.0.1: - resolution: - { - integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==, - } - engines: { node: '>= 0.4' } - - get-east-asian-width@1.4.0: - resolution: - { - integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==, - } - engines: { node: '>=18' } - - get-intrinsic@1.3.0: - resolution: - { - integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, - } - engines: { node: '>= 0.4' } - - get-nonce@1.0.1: - resolution: - { - integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==, - } - engines: { node: '>=6' } - - get-proto@1.0.1: - resolution: - { - integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, - } - engines: { node: '>= 0.4' } - - get-stream@8.0.1: - resolution: - { - integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==, - } - engines: { node: '>=16' } - - get-symbol-description@1.1.0: - resolution: - { - integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==, - } - engines: { node: '>= 0.4' } - - get-tsconfig@4.13.0: - resolution: - { - integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==, - } - - github-slugger@2.0.0: - resolution: - { - integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==, - } - - glob-parent@5.1.2: - resolution: - { - integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, - } - engines: { node: '>= 6' } - - glob-parent@6.0.2: - resolution: - { - integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, - } - engines: { node: '>=10.13.0' } - - globals@14.0.0: - resolution: - { - integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, - } - engines: { node: '>=18' } - - globalthis@1.0.4: - resolution: - { - integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==, - } - engines: { node: '>= 0.4' } - - gopd@1.2.0: - resolution: - { - integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, - } - engines: { node: '>= 0.4' } - - graceful-fs@4.2.11: - resolution: - { - integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, - } - - graphemer@1.4.0: - resolution: - { - integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==, - } - - has-bigints@1.1.0: - resolution: - { - integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==, - } - engines: { node: '>= 0.4' } - - has-flag@4.0.0: - resolution: - { - integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, - } - engines: { node: '>=8' } - - has-property-descriptors@1.0.2: - resolution: - { - integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, - } - - has-proto@1.2.0: - resolution: - { - integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==, - } - engines: { node: '>= 0.4' } - - has-symbols@1.1.0: - resolution: - { - integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, - } - engines: { node: '>= 0.4' } - - has-tostringtag@1.0.2: - resolution: - { - integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, - } - engines: { node: '>= 0.4' } - - hasown@2.0.2: - resolution: - { - integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, - } - engines: { node: '>= 0.4' } - - hast-util-from-parse5@8.0.3: - resolution: - { - integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==, - } - - hast-util-heading-rank@3.0.0: - resolution: - { - integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==, - } - - hast-util-is-element@3.0.0: - resolution: - { - integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==, - } - - hast-util-parse-selector@4.0.0: - resolution: - { - integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==, - } - - hast-util-raw@9.1.0: - resolution: - { - integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==, - } - - hast-util-to-jsx-runtime@2.3.6: - resolution: - { - integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==, - } - - hast-util-to-parse5@8.0.0: - resolution: - { - integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==, - } - - hast-util-to-string@3.0.1: - resolution: - { - integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==, - } - - hast-util-to-text@4.0.2: - resolution: - { - integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==, - } - - hast-util-whitespace@3.0.0: - resolution: - { - integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==, - } - - hastscript@9.0.1: - resolution: - { - integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==, - } - - highlight.js@10.7.3: - resolution: - { - integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==, - } - - highlight.js@11.11.1: - resolution: - { - integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==, - } - engines: { node: '>=12.0.0' } - - highlightjs-vue@1.0.0: - resolution: - { - integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==, - } - - html-parse-stringify@3.0.1: - resolution: - { - integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==, - } - - html-url-attributes@3.0.1: - resolution: - { - integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==, - } - - html-void-elements@3.0.0: - resolution: - { - integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==, - } - - human-signals@5.0.0: - resolution: - { - integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==, - } - engines: { node: '>=16.17.0' } - - i18next-browser-languagedetector@8.2.0: - resolution: - { - integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==, - } - - i18next@25.6.3: - resolution: - { - integrity: sha512-AEQvoPDljhp67a1+NsnG/Wb1Nh6YoSvtrmeEd24sfGn3uujCtXCF3cXpr7ulhMywKNFF7p3TX1u2j7y+caLOJg==, - } - peerDependencies: - typescript: ^5 - peerDependenciesMeta: - typescript: - optional: true - - ignore@5.3.2: - resolution: - { - integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, - } - engines: { node: '>= 4' } - - ignore@7.0.5: - resolution: - { - integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, - } - engines: { node: '>= 4' } - - import-fresh@3.3.1: - resolution: - { - integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, - } - engines: { node: '>=6' } - - imurmurhash@0.1.4: - resolution: - { - integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, - } - engines: { node: '>=0.8.19' } - - inline-style-parser@0.2.7: - resolution: - { - integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==, - } - - input-otp@1.4.2: - resolution: - { - integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==, - } - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc - - internal-slot@1.1.0: - resolution: - { - integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==, - } - engines: { node: '>= 0.4' } - - is-alphabetical@2.0.1: - resolution: - { - integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==, - } - - is-alphanumerical@2.0.1: - resolution: - { - integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==, - } - - is-array-buffer@3.0.5: - resolution: - { - integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==, - } - engines: { node: '>= 0.4' } - - is-async-function@2.1.1: - resolution: - { - integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==, - } - engines: { node: '>= 0.4' } - - is-bigint@1.1.0: - resolution: - { - integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==, - } - engines: { node: '>= 0.4' } - - is-boolean-object@1.2.2: - resolution: - { - integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==, - } - engines: { node: '>= 0.4' } - - is-bun-module@2.0.0: - resolution: - { - integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==, - } - - is-callable@1.2.7: - resolution: - { - integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==, - } - engines: { node: '>= 0.4' } - - is-core-module@2.16.1: - resolution: - { - integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==, - } - engines: { node: '>= 0.4' } - - is-data-view@1.0.2: - resolution: - { - integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==, - } - engines: { node: '>= 0.4' } - - is-date-object@1.1.0: - resolution: - { - integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==, - } - engines: { node: '>= 0.4' } - - is-decimal@2.0.1: - resolution: - { - integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==, - } - - is-extglob@2.1.1: - resolution: - { - integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, - } - engines: { node: '>=0.10.0' } - - is-finalizationregistry@1.1.1: - resolution: - { - integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==, - } - engines: { node: '>= 0.4' } - - is-fullwidth-code-point@4.0.0: - resolution: - { - integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==, - } - engines: { node: '>=12' } - - is-fullwidth-code-point@5.1.0: - resolution: - { - integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==, - } - engines: { node: '>=18' } - - is-generator-function@1.1.2: - resolution: - { - integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==, - } - engines: { node: '>= 0.4' } - - is-glob@4.0.3: - resolution: - { - integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, - } - engines: { node: '>=0.10.0' } - - is-hexadecimal@2.0.1: - resolution: - { - integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==, - } - - is-map@2.0.3: - resolution: - { - integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==, - } - engines: { node: '>= 0.4' } - - is-negative-zero@2.0.3: - resolution: - { - integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==, - } - engines: { node: '>= 0.4' } - - is-number-object@1.1.1: - resolution: - { - integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==, - } - engines: { node: '>= 0.4' } - - is-number@7.0.0: - resolution: - { - integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, - } - engines: { node: '>=0.12.0' } - - is-plain-obj@4.1.0: - resolution: - { - integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==, - } - engines: { node: '>=12' } - - is-regex@1.2.1: - resolution: - { - integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==, - } - engines: { node: '>= 0.4' } - - is-set@2.0.3: - resolution: - { - integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==, - } - engines: { node: '>= 0.4' } - - is-shared-array-buffer@1.0.4: - resolution: - { - integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==, - } - engines: { node: '>= 0.4' } - - is-stream@3.0.0: - resolution: - { - integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } - - is-string@1.1.1: - resolution: - { - integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==, - } - engines: { node: '>= 0.4' } - - is-symbol@1.1.1: - resolution: - { - integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==, - } - engines: { node: '>= 0.4' } - - is-typed-array@1.1.15: - resolution: - { - integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==, - } - engines: { node: '>= 0.4' } - - is-weakmap@2.0.2: - resolution: - { - integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==, - } - engines: { node: '>= 0.4' } - - is-weakref@1.1.1: - resolution: - { - integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==, - } - engines: { node: '>= 0.4' } - - is-weakset@2.0.4: - resolution: - { - integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==, - } - engines: { node: '>= 0.4' } - - isarray@2.0.5: - resolution: - { - integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==, - } - - isexe@2.0.0: - resolution: - { - integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, - } - - iterator.prototype@1.1.5: - resolution: - { - integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==, - } - engines: { node: '>= 0.4' } - - jiti@2.6.1: - resolution: - { - integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==, - } - hasBin: true - - js-tokens@4.0.0: - resolution: - { - integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, - } - - js-yaml@4.1.1: - resolution: - { - integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, - } - hasBin: true - - json-buffer@3.0.1: - resolution: - { - integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, - } - - json-schema-traverse@0.4.1: - resolution: - { - integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, - } - - json-stable-stringify-without-jsonify@1.0.1: - resolution: - { - integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, - } - - json5@1.0.2: - resolution: - { - integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==, - } - hasBin: true - - jsx-ast-utils@3.3.5: - resolution: - { - integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==, - } - engines: { node: '>=4.0' } - - keyv@4.5.4: - resolution: - { - integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, - } - - language-subtag-registry@0.3.23: - resolution: - { - integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==, - } - - language-tags@1.0.9: - resolution: - { - integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==, - } - engines: { node: '>=0.10' } - - levn@0.4.1: - resolution: - { - integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, - } - engines: { node: '>= 0.8.0' } - - lightningcss-android-arm64@1.30.2: - resolution: - { - integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==, - } - engines: { node: '>= 12.0.0' } - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.30.2: - resolution: - { - integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==, - } - engines: { node: '>= 12.0.0' } - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.30.2: - resolution: - { - integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==, - } - engines: { node: '>= 12.0.0' } - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.30.2: - resolution: - { - integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==, - } - engines: { node: '>= 12.0.0' } - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.30.2: - resolution: - { - integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==, - } - engines: { node: '>= 12.0.0' } - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.30.2: - resolution: - { - integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==, - } - engines: { node: '>= 12.0.0' } - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.30.2: - resolution: - { - integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==, - } - engines: { node: '>= 12.0.0' } - cpu: [arm64] - os: [linux] - - lightningcss-linux-x64-gnu@1.30.2: - resolution: - { - integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==, - } - engines: { node: '>= 12.0.0' } - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.30.2: - resolution: - { - integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==, - } - engines: { node: '>= 12.0.0' } - cpu: [x64] - os: [linux] - - lightningcss-win32-arm64-msvc@1.30.2: - resolution: - { - integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==, - } - engines: { node: '>= 12.0.0' } - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.30.2: - resolution: - { - integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==, - } - engines: { node: '>= 12.0.0' } - cpu: [x64] - os: [win32] - - lightningcss@1.30.2: - resolution: - { - integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==, - } - engines: { node: '>= 12.0.0' } - - lilconfig@3.1.3: - resolution: - { - integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==, - } - engines: { node: '>=14' } - - lint-staged@15.5.2: - resolution: - { - integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==, - } - engines: { node: '>=18.12.0' } - hasBin: true - - listr2@8.3.3: - resolution: - { - integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==, - } - engines: { node: '>=18.0.0' } - - locate-path@6.0.0: - resolution: - { - integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, - } - engines: { node: '>=10' } - - lodash.merge@4.6.2: - resolution: - { - integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, - } - - lodash@4.17.21: - resolution: - { - integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, - } - - log-update@6.1.0: - resolution: - { - integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==, - } - engines: { node: '>=18' } - - longest-streak@3.1.0: - resolution: - { - integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==, - } - - loose-envify@1.4.0: - resolution: - { - integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, - } - hasBin: true - - lowlight@1.20.0: - resolution: - { - integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==, - } - - lowlight@3.3.0: - resolution: - { - integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==, - } - - lucide-react@0.507.0: - resolution: - { - integrity: sha512-XfgE6gvAHwAtnbUvWiTTHx4S3VGR+cUJHEc0vrh9Ogu672I1Tue2+Cp/8JJqpytgcBHAB1FVI297W4XGNwc2dQ==, - } - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - magic-string@0.30.21: - resolution: - { - integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, - } - - markdown-table@3.0.4: - resolution: - { - integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==, - } - - math-intrinsics@1.1.0: - resolution: - { - integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, - } - engines: { node: '>= 0.4' } - - mdast-util-find-and-replace@3.0.2: - resolution: - { - integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==, - } - - mdast-util-from-markdown@2.0.2: - resolution: - { - integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==, - } - - mdast-util-gfm-autolink-literal@2.0.1: - resolution: - { - integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==, - } - - mdast-util-gfm-footnote@2.1.0: - resolution: - { - integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==, - } - - mdast-util-gfm-strikethrough@2.0.0: - resolution: - { - integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==, - } - - mdast-util-gfm-table@2.0.0: - resolution: - { - integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==, - } - - mdast-util-gfm-task-list-item@2.0.0: - resolution: - { - integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==, - } - - mdast-util-gfm@3.1.0: - resolution: - { - integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==, - } - - mdast-util-mdx-expression@2.0.1: - resolution: - { - integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==, - } - - mdast-util-mdx-jsx@3.2.0: - resolution: - { - integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==, - } - - mdast-util-mdxjs-esm@2.0.1: - resolution: - { - integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==, - } - - mdast-util-phrasing@4.1.0: - resolution: - { - integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==, - } - - mdast-util-to-hast@13.2.1: - resolution: - { - integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==, - } - - mdast-util-to-markdown@2.1.2: - resolution: - { - integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==, - } - - mdast-util-to-string@4.0.0: - resolution: - { - integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==, - } - - merge-stream@2.0.0: - resolution: - { - integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, - } - - merge2@1.4.1: - resolution: - { - integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, - } - engines: { node: '>= 8' } - - micromark-core-commonmark@2.0.3: - resolution: - { - integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==, - } - - micromark-extension-gfm-autolink-literal@2.1.0: - resolution: - { - integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==, - } - - micromark-extension-gfm-footnote@2.1.0: - resolution: - { - integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==, - } - - micromark-extension-gfm-strikethrough@2.1.0: - resolution: - { - integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==, - } - - micromark-extension-gfm-table@2.1.1: - resolution: - { - integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==, - } - - micromark-extension-gfm-tagfilter@2.0.0: - resolution: - { - integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==, - } - - micromark-extension-gfm-task-list-item@2.1.0: - resolution: - { - integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==, - } - - micromark-extension-gfm@3.0.0: - resolution: - { - integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==, - } - - micromark-factory-destination@2.0.1: - resolution: - { - integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==, - } - - micromark-factory-label@2.0.1: - resolution: - { - integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==, - } - - micromark-factory-space@2.0.1: - resolution: - { - integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==, - } - - micromark-factory-title@2.0.1: - resolution: - { - integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==, - } - - micromark-factory-whitespace@2.0.1: - resolution: - { - integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==, - } - - micromark-util-character@2.1.1: - resolution: - { - integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==, - } - - micromark-util-chunked@2.0.1: - resolution: - { - integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==, - } - - micromark-util-classify-character@2.0.1: - resolution: - { - integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==, - } - - micromark-util-combine-extensions@2.0.1: - resolution: - { - integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==, - } - - micromark-util-decode-numeric-character-reference@2.0.2: - resolution: - { - integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==, - } - - micromark-util-decode-string@2.0.1: - resolution: - { - integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==, - } - - micromark-util-encode@2.0.1: - resolution: - { - integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==, - } - - micromark-util-html-tag-name@2.0.1: - resolution: - { - integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==, - } - - micromark-util-normalize-identifier@2.0.1: - resolution: - { - integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==, - } - - micromark-util-resolve-all@2.0.1: - resolution: - { - integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==, - } - - micromark-util-sanitize-uri@2.0.1: - resolution: - { - integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==, - } - - micromark-util-subtokenize@2.1.0: - resolution: - { - integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==, - } - - micromark-util-symbol@2.0.1: - resolution: - { - integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==, - } - - micromark-util-types@2.0.2: - resolution: - { - integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==, - } - - micromark@4.0.2: - resolution: - { - integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==, - } - - micromatch@4.0.8: - resolution: - { - integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, - } - engines: { node: '>=8.6' } - - mime-db@1.52.0: - resolution: - { - integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, - } - engines: { node: '>= 0.6' } - - mime-types@2.1.35: - resolution: - { - integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, - } - engines: { node: '>= 0.6' } - - mimic-fn@4.0.0: - resolution: - { - integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==, - } - engines: { node: '>=12' } - - mimic-function@5.0.1: - resolution: - { - integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==, - } - engines: { node: '>=18' } - - minimatch@3.1.2: - resolution: - { - integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, - } - - minimatch@9.0.5: - resolution: - { - integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, - } - engines: { node: '>=16 || 14 >=14.17' } - - minimist@1.2.8: - resolution: - { - integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, - } - - ms@2.1.3: - resolution: - { - integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, - } - - nanoid@3.3.11: - resolution: - { - integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, - } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } - hasBin: true - - napi-postinstall@0.3.4: - resolution: - { - integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==, - } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } - hasBin: true - - natural-compare@1.4.0: - resolution: - { - integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, - } - - next-themes@0.4.6: - resolution: - { - integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==, - } - peerDependencies: - react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - - next@15.5.9: - resolution: - { - integrity: sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==, - } - engines: { node: ^18.18.0 || ^19.8.0 || >= 20.0.0 } - hasBin: true - 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 - - npm-run-path@5.3.0: - resolution: - { - integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } - - object-assign@4.1.1: - resolution: - { - integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, - } - engines: { node: '>=0.10.0' } - - object-inspect@1.13.4: - resolution: - { - integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, - } - engines: { node: '>= 0.4' } - - object-keys@1.1.1: - resolution: - { - integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, - } - engines: { node: '>= 0.4' } - - object.assign@4.1.7: - resolution: - { - integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==, - } - engines: { node: '>= 0.4' } - - object.entries@1.1.9: - resolution: - { - integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==, - } - engines: { node: '>= 0.4' } - - object.fromentries@2.0.8: - resolution: - { - integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==, - } - engines: { node: '>= 0.4' } - - object.groupby@1.0.3: - resolution: - { - integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==, - } - engines: { node: '>= 0.4' } - - object.values@1.2.1: - resolution: - { - integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==, - } - engines: { node: '>= 0.4' } - - onetime@6.0.0: - resolution: - { - integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==, - } - engines: { node: '>=12' } - - onetime@7.0.0: - resolution: - { - integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==, - } - engines: { node: '>=18' } - - optionator@0.9.4: - resolution: - { - integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, - } - engines: { node: '>= 0.8.0' } - - own-keys@1.0.1: - resolution: - { - integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==, - } - engines: { node: '>= 0.4' } - - p-limit@3.1.0: - resolution: - { - integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, - } - engines: { node: '>=10' } - - p-locate@5.0.0: - resolution: - { - integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, - } - engines: { node: '>=10' } - - parent-module@1.0.1: - resolution: - { - integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, - } - engines: { node: '>=6' } - - parse-entities@4.0.2: - resolution: - { - integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==, - } - - parse5@7.3.0: - resolution: - { - integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==, - } - - path-exists@4.0.0: - resolution: - { - integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, - } - engines: { node: '>=8' } - - path-key@3.1.1: - resolution: - { - integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, - } - engines: { node: '>=8' } - - path-key@4.0.0: - resolution: - { - integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==, - } - engines: { node: '>=12' } - - path-parse@1.0.7: - resolution: - { - integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, - } - - picocolors@1.1.1: - resolution: - { - integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, - } - - picomatch@2.3.1: - resolution: - { - integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, - } - engines: { node: '>=8.6' } - - picomatch@4.0.3: - resolution: - { - integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, - } - engines: { node: '>=12' } - - pidtree@0.6.0: - resolution: - { - integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==, - } - engines: { node: '>=0.10' } - hasBin: true - - possible-typed-array-names@1.1.0: - resolution: - { - integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==, - } - engines: { node: '>= 0.4' } - - postcss@8.4.31: - resolution: - { - integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==, - } - engines: { node: ^10 || ^12 || >=14 } - - postcss@8.5.6: - resolution: - { - integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, - } - engines: { node: ^10 || ^12 || >=14 } - - prelude-ls@1.2.1: - resolution: - { - integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, - } - engines: { node: '>= 0.8.0' } - - prettier-linter-helpers@1.0.0: - resolution: - { - integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==, - } - engines: { node: '>=6.0.0' } - - prettier@3.6.2: - resolution: - { - integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==, - } - engines: { node: '>=14' } - hasBin: true - - prismjs@1.30.0: - resolution: - { - integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==, - } - engines: { node: '>=6' } - - prop-types@15.8.1: - resolution: - { - integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, - } - - property-information@6.5.0: - resolution: - { - integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==, - } - - property-information@7.1.0: - resolution: - { - integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==, - } - - proxy-from-env@1.1.0: - resolution: - { - integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==, - } - - punycode@2.3.1: - resolution: - { - integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, - } - engines: { node: '>=6' } - - queue-microtask@1.2.3: - resolution: - { - integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, - } - - react-dom@19.2.1: - resolution: - { - integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==, - } - peerDependencies: - react: ^19.2.1 - - react-hook-form@7.66.1: - resolution: - { - integrity: sha512-2KnjpgG2Rhbi+CIiIBQQ9Df6sMGH5ExNyFl4Hw9qO7pIqMBR8Bvu9RQyjl3JM4vehzCh9soiNUM/xYMswb2EiA==, - } - engines: { node: '>=18.0.0' } - peerDependencies: - react: ^16.8.0 || ^17 || ^18 || ^19 - - react-i18next@15.7.4: - resolution: - { - integrity: sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==, - } - peerDependencies: - i18next: '>= 23.4.0' - react: '>= 16.8.0' - react-dom: '*' - react-native: '*' - typescript: ^5 - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - typescript: - optional: true - - react-is@16.13.1: - resolution: - { - integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, - } - - react-markdown@10.1.0: - resolution: - { - integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==, - } - peerDependencies: - '@types/react': '>=18' - react: '>=18' - - react-photo-view@1.2.7: - resolution: - { - integrity: sha512-MfOWVPxuibncRLaycZUNxqYU8D9IA+rbGDDaq6GM8RIoGJal592hEJoRAyRSI7ZxyyJNJTLMUWWL3UIXHJJOpw==, - } - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - react-remove-scroll-bar@2.3.8: - resolution: - { - integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==, - } - engines: { node: '>=10' } - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.7.1: - resolution: - { - integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==, - } - engines: { node: '>=10' } - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-style-singleton@2.2.3: - resolution: - { - integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==, - } - engines: { node: '>=10' } - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-syntax-highlighter@16.1.0: - resolution: - { - integrity: sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==, - } - engines: { node: '>= 16.20.2' } - peerDependencies: - react: '>= 0.14.0' - - react@19.2.1: - resolution: - { - integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==, - } - engines: { node: '>=0.10.0' } - - reflect.getprototypeof@1.0.10: - resolution: - { - integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==, - } - engines: { node: '>= 0.4' } - - refractor@5.0.0: - resolution: - { - integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==, - } - - regexp.prototype.flags@1.5.4: - resolution: - { - integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==, - } - engines: { node: '>= 0.4' } - - rehype-autolink-headings@7.1.0: - resolution: - { - integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==, - } - - rehype-highlight@7.0.2: - resolution: - { - integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==, - } - - rehype-raw@7.0.0: - resolution: - { - integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==, - } - - rehype-slug@6.0.0: - resolution: - { - integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==, - } - - remark-gfm@4.0.1: - resolution: - { - integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==, - } - - remark-parse@11.0.0: - resolution: - { - integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==, - } - - remark-rehype@11.1.2: - resolution: - { - integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==, - } - - remark-stringify@11.0.0: - resolution: - { - integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==, - } - - resolve-from@4.0.0: - resolution: - { - integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, - } - engines: { node: '>=4' } - - resolve-pkg-maps@1.0.0: - resolution: - { - integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, - } - - resolve@1.22.11: - resolution: - { - integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==, - } - engines: { node: '>= 0.4' } - hasBin: true - - resolve@2.0.0-next.5: - resolution: - { - integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==, - } - hasBin: true - - restore-cursor@5.1.0: - resolution: - { - integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==, - } - engines: { node: '>=18' } - - reusify@1.1.0: - resolution: - { - integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, - } - engines: { iojs: '>=1.0.0', node: '>=0.10.0' } - - rfdc@1.4.1: - resolution: - { - integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==, - } - - run-parallel@1.2.0: - resolution: - { - integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, - } - - safe-array-concat@1.1.3: - resolution: - { - integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==, - } - engines: { node: '>=0.4' } - - safe-push-apply@1.0.0: - resolution: - { - integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==, - } - engines: { node: '>= 0.4' } - - safe-regex-test@1.1.0: - resolution: - { - integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==, - } - engines: { node: '>= 0.4' } - - scheduler@0.27.0: - resolution: - { - integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==, - } - - semver@6.3.1: - resolution: - { - integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, - } - hasBin: true - - semver@7.7.3: - resolution: - { - integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==, - } - engines: { node: '>=10' } - hasBin: true - - set-function-length@1.2.2: - resolution: - { - integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, - } - engines: { node: '>= 0.4' } - - set-function-name@2.0.2: - resolution: - { - integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==, - } - engines: { node: '>= 0.4' } - - set-proto@1.0.0: - resolution: - { - integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==, - } - engines: { node: '>= 0.4' } - - sharp@0.34.5: - resolution: - { - integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - - shebang-command@2.0.0: - resolution: - { - integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, - } - engines: { node: '>=8' } - - shebang-regex@3.0.0: - resolution: - { - integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, - } - engines: { node: '>=8' } - - side-channel-list@1.0.0: - resolution: - { - integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==, - } - engines: { node: '>= 0.4' } - - side-channel-map@1.0.1: - resolution: - { - integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, - } - engines: { node: '>= 0.4' } - - side-channel-weakmap@1.0.2: - resolution: - { - integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, - } - engines: { node: '>= 0.4' } - - side-channel@1.1.0: - resolution: - { - integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, - } - engines: { node: '>= 0.4' } - - signal-exit@4.1.0: - resolution: - { - integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, - } - engines: { node: '>=14' } - - slice-ansi@5.0.0: - resolution: - { - integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==, - } - engines: { node: '>=12' } - - slice-ansi@7.1.2: - resolution: - { - integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==, - } - engines: { node: '>=18' } - - sonner@2.0.7: - resolution: - { - integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==, - } - peerDependencies: - react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - - source-map-js@1.2.1: - resolution: - { - integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, - } - engines: { node: '>=0.10.0' } - - space-separated-tokens@2.0.2: - resolution: - { - integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==, - } - - stable-hash@0.0.5: - resolution: - { - integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==, - } - - stop-iteration-iterator@1.1.0: - resolution: - { - integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==, - } - engines: { node: '>= 0.4' } - - string-argv@0.3.2: - resolution: - { - integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==, - } - engines: { node: '>=0.6.19' } - - string-width@7.2.0: - resolution: - { - integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==, - } - engines: { node: '>=18' } - - string.prototype.includes@2.0.1: - resolution: - { - integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==, - } - engines: { node: '>= 0.4' } - - string.prototype.matchall@4.0.12: - resolution: - { - integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==, - } - engines: { node: '>= 0.4' } - - string.prototype.repeat@1.0.0: - resolution: - { - integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==, - } - - string.prototype.trim@1.2.10: - resolution: - { - integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==, - } - engines: { node: '>= 0.4' } - - string.prototype.trimend@1.0.9: - resolution: - { - integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==, - } - engines: { node: '>= 0.4' } - - string.prototype.trimstart@1.0.8: - resolution: - { - integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==, - } - engines: { node: '>= 0.4' } - - stringify-entities@4.0.4: - resolution: - { - integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==, - } - - strip-ansi@7.1.2: - resolution: - { - integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==, - } - engines: { node: '>=12' } - - strip-bom@3.0.0: - resolution: - { - integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==, - } - engines: { node: '>=4' } - - strip-final-newline@3.0.0: - resolution: - { - integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==, - } - engines: { node: '>=12' } - - strip-json-comments@3.1.1: - resolution: - { - integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, - } - engines: { node: '>=8' } - - style-to-js@1.1.21: - resolution: - { - integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==, - } - - style-to-object@1.0.14: - resolution: - { - integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==, - } - - styled-jsx@5.1.6: - resolution: - { - integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==, - } - engines: { node: '>= 12.0.0' } - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - 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 - - supports-color@7.2.0: - resolution: - { - integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, - } - engines: { node: '>=8' } - - supports-preserve-symlinks-flag@1.0.0: - resolution: - { - integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, - } - engines: { node: '>= 0.4' } - - synckit@0.11.11: - resolution: - { - integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==, - } - engines: { node: ^14.18.0 || >=16.0.0 } - - tailwind-merge@3.4.0: - resolution: - { - integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==, - } - - tailwindcss@4.1.17: - resolution: - { - integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==, - } - - tapable@2.3.0: - resolution: - { - integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==, - } - engines: { node: '>=6' } - - tinyglobby@0.2.15: - resolution: - { - integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, - } - engines: { node: '>=12.0.0' } - - to-regex-range@5.0.1: - resolution: - { - integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, - } - engines: { node: '>=8.0' } - - trim-lines@3.0.1: - resolution: - { - integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==, - } - - trough@2.2.0: - resolution: - { - integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==, - } - - ts-api-utils@2.1.0: - resolution: - { - integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==, - } - engines: { node: '>=18.12' } - peerDependencies: - typescript: '>=4.8.4' - - tsconfig-paths@3.15.0: - resolution: - { - integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==, - } - - tslib@2.8.1: - resolution: - { - integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, - } - - tw-animate-css@1.4.0: - resolution: - { - integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==, - } - - type-check@0.4.0: - resolution: - { - integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, - } - engines: { node: '>= 0.8.0' } - - typed-array-buffer@1.0.3: - resolution: - { - integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==, - } - engines: { node: '>= 0.4' } - - typed-array-byte-length@1.0.3: - resolution: - { - integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==, - } - engines: { node: '>= 0.4' } - - typed-array-byte-offset@1.0.4: - resolution: - { - integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==, - } - engines: { node: '>= 0.4' } - - typed-array-length@1.0.7: - resolution: - { - integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==, - } - engines: { node: '>= 0.4' } - - typescript-eslint@8.48.0: - resolution: - { - integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - typescript@5.9.3: - resolution: - { - integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==, - } - engines: { node: '>=14.17' } - hasBin: true - - unbox-primitive@1.1.0: - resolution: - { - integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==, - } - engines: { node: '>= 0.4' } - - undici-types@6.21.0: - resolution: - { - integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, - } - - unified@11.0.5: - resolution: - { - integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==, - } - - unist-util-find-after@5.0.0: - resolution: - { - integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==, - } - - unist-util-is@6.0.1: - resolution: - { - integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==, - } - - unist-util-position@5.0.0: - resolution: - { - integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==, - } - - unist-util-stringify-position@4.0.0: - resolution: - { - integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==, - } - - unist-util-visit-parents@6.0.2: - resolution: - { - integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==, - } - - unist-util-visit@5.0.0: - resolution: - { - integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==, - } - - unrs-resolver@1.11.1: - resolution: - { - integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==, - } - - uri-js@4.4.1: - resolution: - { - integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, - } - - use-callback-ref@1.3.3: - resolution: - { - integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==, - } - engines: { node: '>=10' } - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sidecar@1.1.3: - resolution: - { - integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==, - } - engines: { node: '>=10' } - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - uuidjs@5.1.0: - resolution: - { - integrity: sha512-HAQPtUkr7t5Ud3uCwRcqtBRNagu/2aerrrBQE6PzgSluGijvFF75UaOq22Xw545GGviRjSLhc4c8CaSMI5h4Ng==, - } - hasBin: true - - vfile-location@5.0.3: - resolution: - { - integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==, - } - - vfile-message@4.0.3: - resolution: - { - integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==, - } - - vfile@6.0.3: - resolution: - { - integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==, - } - - void-elements@3.1.0: - resolution: - { - integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==, - } - engines: { node: '>=0.10.0' } - - web-namespaces@2.0.1: - resolution: - { - integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==, - } - - which-boxed-primitive@1.1.1: - resolution: - { - integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==, - } - engines: { node: '>= 0.4' } - - which-builtin-type@1.2.1: - resolution: - { - integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==, - } - engines: { node: '>= 0.4' } - - which-collection@1.0.2: - resolution: - { - integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==, - } - engines: { node: '>= 0.4' } - - which-typed-array@1.1.19: - resolution: - { - integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==, - } - engines: { node: '>= 0.4' } - - which@2.0.2: - resolution: - { - integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, - } - engines: { node: '>= 8' } - hasBin: true - - word-wrap@1.2.5: - resolution: - { - integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, - } - engines: { node: '>=0.10.0' } - - wrap-ansi@9.0.2: - resolution: - { - integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==, - } - engines: { node: '>=18' } - - yaml@2.8.1: - resolution: - { - integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==, - } - engines: { node: '>= 14.6' } - hasBin: true - - yocto-queue@0.1.0: - resolution: - { - integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, - } - engines: { node: '>=10' } - - zod@3.25.76: - resolution: - { - integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==, - } - - zwitch@2.0.4: - resolution: - { - integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, - } - -snapshots: - '@alloc/quick-lru@5.2.0': {} - - '@babel/runtime@7.28.4': {} - - '@dnd-kit/accessibility@3.1.1(react@19.2.1)': dependencies: react: 19.2.1 tslib: 2.8.1 + dev: false - '@dnd-kit/core@6.3.1(react-dom@19.2.1)(react@19.2.1)': + /@dnd-kit/core@6.3.1(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' dependencies: '@dnd-kit/accessibility': 3.1.1(react@19.2.1) '@dnd-kit/utilities': 3.2.2(react@19.2.1) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) tslib: 2.8.1 + dev: false - '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1)(react@19.2.1)': + /@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1)(react@19.2.1): + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' dependencies: '@dnd-kit/core': 6.3.1(react-dom@19.2.1)(react@19.2.1) '@dnd-kit/utilities': 3.2.2(react@19.2.1) react: 19.2.1 tslib: 2.8.1 + dev: false - '@dnd-kit/utilities@3.2.2(react@19.2.1)': + /@dnd-kit/utilities@3.2.2(react@19.2.1): + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' dependencies: react: 19.2.1 tslib: 2.8.1 + dev: false - '@emnapi/core@1.7.1': + /@emnapi/core@1.7.1: + resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + requiresBuild: true dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 + dev: true optional: true - '@emnapi/runtime@1.7.1': + /@emnapi/runtime@1.7.1: + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + requiresBuild: true dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.1.0': + /@emnapi/wasi-threads@1.1.0: + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + requiresBuild: true dependencies: tslib: 2.8.1 + dev: true optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + /@eslint-community/eslint-utils@4.9.0(eslint@9.39.1): + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: eslint: 9.39.1 eslint-visitor-keys: 3.4.3 + dev: true - '@eslint-community/regexpp@4.12.2': {} + /@eslint-community/regexpp@4.12.2: + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true - '@eslint/config-array@0.21.1': + /@eslint/config-array@0.21.1: + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color + dev: true - '@eslint/config-helpers@0.4.2': + /@eslint/config-helpers@0.4.2: + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@eslint/core': 0.17.0 + dev: true - '@eslint/core@0.17.0': + /@eslint/core@0.17.0: + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@types/json-schema': 7.0.15 + dev: true - '@eslint/eslintrc@3.3.1': + /@eslint/eslintrc@3.3.1: + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: ajv: 6.12.6 debug: 4.4.3 @@ -5401,223 +370,493 @@ snapshots: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color + dev: true - '@eslint/js@9.39.1': {} + /@eslint/js@9.39.1: + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true - '@eslint/object-schema@2.1.7': {} + /@eslint/object-schema@2.1.7: + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true - '@eslint/plugin-kit@0.4.1': + /@eslint/plugin-kit@0.4.1: + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@eslint/core': 0.17.0 levn: 0.4.1 + dev: true - '@floating-ui/core@1.7.3': + /@floating-ui/core@1.7.3: + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} dependencies: '@floating-ui/utils': 0.2.10 + dev: false - '@floating-ui/dom@1.7.4': + /@floating-ui/dom@1.7.4: + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} dependencies: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 + dev: false - '@floating-ui/react-dom@2.1.6(react-dom@19.2.1)(react@19.2.1)': + /@floating-ui/react-dom@2.1.6(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' dependencies: '@floating-ui/dom': 1.7.4 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@floating-ui/utils@0.2.10': {} + /@floating-ui/utils@0.2.10: + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + dev: false - '@hookform/resolvers@5.2.2(react-hook-form@7.66.1)': + /@hookform/resolvers@5.2.2(react-hook-form@7.66.1): + resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} + peerDependencies: + react-hook-form: ^7.55.0 dependencies: '@standard-schema/utils': 0.3.0 react-hook-form: 7.66.1(react@19.2.1) + dev: false - '@humanfs/core@0.19.1': {} + /@humanfs/core@0.19.1: + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + dev: true - '@humanfs/node@0.16.7': + /@humanfs/node@0.16.7: + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} dependencies: '@humanfs/core': 0.19.1 '@humanwhocodes/retry': 0.4.3 + dev: true - '@humanwhocodes/module-importer@1.0.1': {} + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true - '@humanwhocodes/retry@0.4.3': {} + /@humanwhocodes/retry@0.4.3: + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + dev: true - '@img/colour@1.0.0': + /@img/colour@1.0.0: + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + requiresBuild: true + dev: false optional: true - '@img/sharp-darwin-arm64@0.34.5': + /@img/sharp-darwin-arm64@0.34.5: + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + requiresBuild: true optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.2.4 + dev: false optional: true - '@img/sharp-darwin-x64@0.34.5': + /@img/sharp-darwin-x64@0.34.5: + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + requiresBuild: true optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.2.4 + dev: false optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + /@img/sharp-libvips-darwin-arm64@1.2.4: + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + /@img/sharp-libvips-darwin-x64@1.2.4: + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + /@img/sharp-libvips-linux-arm64@1.2.4: + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + /@img/sharp-libvips-linux-arm@1.2.4: + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + /@img/sharp-libvips-linux-ppc64@1.2.4: + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + /@img/sharp-libvips-linux-riscv64@1.2.4: + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + /@img/sharp-libvips-linux-s390x@1.2.4: + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + /@img/sharp-libvips-linux-x64@1.2.4: + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + /@img/sharp-libvips-linuxmusl-arm64@1.2.4: + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': + /@img/sharp-libvips-linuxmusl-x64@1.2.4: + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@img/sharp-linux-arm64@0.34.5': + /@img/sharp-linux-arm64@0.34.5: + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.2.4 + dev: false optional: true - '@img/sharp-linux-arm@0.34.5': + /@img/sharp-linux-arm@0.34.5: + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-arm': 1.2.4 + dev: false optional: true - '@img/sharp-linux-ppc64@0.34.5': + /@img/sharp-linux-ppc64@0.34.5: + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-ppc64': 1.2.4 + dev: false optional: true - '@img/sharp-linux-riscv64@0.34.5': + /@img/sharp-linux-riscv64@0.34.5: + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-riscv64': 1.2.4 + dev: false optional: true - '@img/sharp-linux-s390x@0.34.5': + /@img/sharp-linux-s390x@0.34.5: + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.2.4 + dev: false optional: true - '@img/sharp-linux-x64@0.34.5': + /@img/sharp-linux-x64@0.34.5: + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-x64': 1.2.4 + dev: false optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': + /@img/sharp-linuxmusl-arm64@0.34.5: + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + requiresBuild: true optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + dev: false optional: true - '@img/sharp-linuxmusl-x64@0.34.5': + /@img/sharp-linuxmusl-x64@0.34.5: + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + requiresBuild: true optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + dev: false optional: true - '@img/sharp-wasm32@0.34.5': + /@img/sharp-wasm32@0.34.5: + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + requiresBuild: true dependencies: '@emnapi/runtime': 1.7.1 + dev: false optional: true - '@img/sharp-win32-arm64@0.34.5': + /@img/sharp-win32-arm64@0.34.5: + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false optional: true - '@img/sharp-win32-ia32@0.34.5': + /@img/sharp-win32-ia32@0.34.5: + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false optional: true - '@img/sharp-win32-x64@0.34.5': + /@img/sharp-win32-x64@0.34.5: + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false optional: true - '@jridgewell/gen-mapping@0.3.13': + /@jridgewell/gen-mapping@0.3.13: + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} dependencies: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 + dev: false - '@jridgewell/remapping@2.3.5': + /@jridgewell/remapping@2.3.5: + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} dependencies: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 + dev: false - '@jridgewell/resolve-uri@3.1.2': {} + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + dev: false - '@jridgewell/sourcemap-codec@1.5.5': {} + /@jridgewell/sourcemap-codec@1.5.5: + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + dev: false - '@jridgewell/trace-mapping@0.3.31': + /@jridgewell/trace-mapping@0.3.31: + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + dev: false - '@napi-rs/wasm-runtime@0.2.12': + /@napi-rs/wasm-runtime@0.2.12: + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + requiresBuild: true dependencies: '@emnapi/core': 1.7.1 '@emnapi/runtime': 1.7.1 '@tybys/wasm-util': 0.10.1 + dev: true optional: true - '@next/env@15.5.9': {} + /@next/env@15.5.9: + resolution: {integrity: sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==} + dev: false - '@next/eslint-plugin-next@15.2.4': + /@next/eslint-plugin-next@15.2.4: + resolution: {integrity: sha512-O8ScvKtnxkp8kL9TpJTTKnMqlkZnS+QxwoQnJwPGBxjBbzd6OVVPEJ5/pMNrktSyXQD/chEfzfFzYLM6JANOOQ==} dependencies: fast-glob: 3.3.1 + dev: true - '@next/swc-darwin-arm64@15.5.7': + /@next/swc-darwin-arm64@15.5.7: + resolution: {integrity: sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false optional: true - '@next/swc-darwin-x64@15.5.7': + /@next/swc-darwin-x64@15.5.7: + resolution: {integrity: sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-arm64-gnu@15.5.7': + /@next/swc-linux-arm64-gnu@15.5.7: + resolution: {integrity: sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-arm64-musl@15.5.7': + /@next/swc-linux-arm64-musl@15.5.7: + resolution: {integrity: sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-x64-gnu@15.5.7': + /@next/swc-linux-x64-gnu@15.5.7: + resolution: {integrity: sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-linux-x64-musl@15.5.7': + /@next/swc-linux-x64-musl@15.5.7: + resolution: {integrity: sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@next/swc-win32-arm64-msvc@15.5.7': + /@next/swc-win32-arm64-msvc@15.5.7: + resolution: {integrity: sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false optional: true - '@next/swc-win32-x64-msvc@15.5.7': + /@next/swc-win32-x64-msvc@15.5.7: + resolution: {integrity: sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false optional: true - '@nodelib/fs.scandir@2.1.5': + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 + dev: true - '@nodelib/fs.stat@2.0.5': {} + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true - '@nodelib/fs.walk@1.2.8': + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + dev: true - '@nolyfill/is-core-module@1.0.39': {} + /@nolyfill/is-core-module@1.0.39: + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + dev: true - '@pkgr/core@0.2.9': {} + /@pkgr/core@0.2.9: + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + dev: true - '@radix-ui/number@1.1.1': {} + /@radix-ui/number@1.1.1: + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + dev: false - '@radix-ui/primitive@1.1.3': {} + /@radix-ui/primitive@1.1.3: + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + dev: false - '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5629,16 +868,40 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5652,8 +915,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5663,13 +938,33 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5681,13 +976,33 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-context@1.1.2(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-context@1.1.2(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5707,13 +1022,33 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.1) + dev: false - '@radix-ui/react-direction@1.1.1(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-direction@1.1.1(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5724,8 +1059,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5738,13 +1085,33 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) @@ -5753,8 +1120,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5769,22 +1148,54 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-id@1.1.1(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-id@1.1.1(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) @@ -5808,8 +1219,20 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.1) + dev: false - '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5830,8 +1253,20 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.1) + dev: false - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@floating-ui/react-dom': 2.1.6(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) @@ -5847,8 +1282,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) @@ -5856,8 +1303,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) @@ -5865,24 +1324,60 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.1) '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/react-slot': 1.2.4(@types/react@19.2.7)(react@19.2.1) '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) @@ -5897,8 +1392,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 @@ -5913,8 +1420,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 @@ -5941,28 +1460,68 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.1) + dev: false - '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-slot@1.2.3(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-slot@1.2.3(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-slot@1.2.4(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-slot@1.2.4(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5975,8 +1534,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -5990,8 +1561,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -6004,8 +1587,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) @@ -6014,8 +1609,20 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -6033,74 +1640,162 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.2.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.1) '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-use-previous@1.1.1(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-use-rect@1.1.1(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@radix-ui/rect': 1.1.1 '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.7)(react@19.2.1)': + /@radix-ui/react-use-size@1.1.1(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) '@types/react': 19.2.7 react: 19.2.1 + dev: false - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1)': + /@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@radix-ui/rect@1.1.1': {} + /@radix-ui/rect@1.1.1: + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + dev: false - '@rtsao/scc@1.1.0': {} + /@rtsao/scc@1.1.0: + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + dev: true - '@rushstack/eslint-patch@1.15.0': {} + /@rushstack/eslint-patch@1.15.0: + resolution: {integrity: sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==} + dev: true - '@standard-schema/utils@0.3.0': {} + /@standard-schema/utils@0.3.0: + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + dev: false - '@swc/helpers@0.5.15': + /@swc/helpers@0.5.15: + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} dependencies: tslib: 2.8.1 + dev: false - '@tailwindcss/node@4.1.17': + /@tailwindcss/node@4.1.17: + resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} dependencies: '@jridgewell/remapping': 2.3.5 enhanced-resolve: 5.18.3 @@ -6109,44 +1804,125 @@ snapshots: magic-string: 0.30.21 source-map-js: 1.2.1 tailwindcss: 4.1.17 + dev: false - '@tailwindcss/oxide-android-arm64@4.1.17': + /@tailwindcss/oxide-android-arm64@4.1.17: + resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.17': + /@tailwindcss/oxide-darwin-arm64@4.1.17: + resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-darwin-x64@4.1.17': + /@tailwindcss/oxide-darwin-x64@4.1.17: + resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.17': + /@tailwindcss/oxide-freebsd-x64@4.1.17: + resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': + /@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17: + resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': + /@tailwindcss/oxide-linux-arm64-gnu@4.1.17: + resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.17': + /@tailwindcss/oxide-linux-arm64-musl@4.1.17: + resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.17': + /@tailwindcss/oxide-linux-x64-gnu@4.1.17: + resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.17': + /@tailwindcss/oxide-linux-x64-musl@4.1.17: + resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.17': + /@tailwindcss/oxide-wasm32-wasi@4.1.17: + resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + requiresBuild: true + dev: false + optional: true + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + /@tailwindcss/oxide-win32-arm64-msvc@4.1.17: + resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': + /@tailwindcss/oxide-win32-x64-msvc@4.1.17: + resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.17': - optional: true - - '@tailwindcss/oxide@4.1.17': + /@tailwindcss/oxide@4.1.17: + resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==} + engines: {node: '>= 10'} optionalDependencies: '@tailwindcss/oxide-android-arm64': 4.1.17 '@tailwindcss/oxide-darwin-arm64': 4.1.17 @@ -6160,77 +1936,129 @@ snapshots: '@tailwindcss/oxide-wasm32-wasi': 4.1.17 '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 + dev: false - '@tailwindcss/postcss@4.1.17': + /@tailwindcss/postcss@4.1.17: + resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==} dependencies: '@alloc/quick-lru': 5.2.0 '@tailwindcss/node': 4.1.17 '@tailwindcss/oxide': 4.1.17 postcss: 8.5.6 tailwindcss: 4.1.17 + dev: false - '@tanstack/react-table@8.21.3(react-dom@19.2.1)(react@19.2.1)': + /@tanstack/react-table@8.21.3(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' dependencies: '@tanstack/table-core': 8.21.3 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - '@tanstack/table-core@8.21.3': {} + /@tanstack/table-core@8.21.3: + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + dev: false - '@tybys/wasm-util@0.10.1': + /@tybys/wasm-util@0.10.1: + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + requiresBuild: true dependencies: tslib: 2.8.1 + dev: true optional: true - '@types/debug@4.1.12': + /@types/debug@4.1.12: + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} dependencies: '@types/ms': 2.1.0 - '@types/estree-jsx@1.0.5': + /@types/estree-jsx@1.0.5: + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} dependencies: '@types/estree': 1.0.8 - '@types/estree@1.0.8': {} + /@types/estree@1.0.8: + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/hast@3.0.4': + /@types/hast@3.0.4: + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} dependencies: '@types/unist': 3.0.3 - '@types/json-schema@7.0.15': {} + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true - '@types/json5@0.0.29': {} + /@types/json5@0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + dev: true - '@types/lodash@4.17.21': {} + /@types/lodash@4.17.21: + resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==} + dev: true - '@types/mdast@4.0.4': + /@types/mdast@4.0.4: + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} dependencies: '@types/unist': 3.0.3 - '@types/ms@2.1.0': {} + /@types/ms@2.1.0: + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@20.19.25': + /@types/node@20.19.25: + resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} dependencies: undici-types: 6.21.0 + dev: true - '@types/prismjs@1.26.5': {} + /@types/prismjs@1.26.5: + resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + dev: false - '@types/react-dom@19.2.3(@types/react@19.2.7)': + /@types/qrcode@1.5.6: + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + dependencies: + '@types/node': 20.19.25 + dev: true + + /@types/react-dom@19.2.3(@types/react@19.2.7): + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 dependencies: '@types/react': 19.2.7 - '@types/react-syntax-highlighter@15.5.13': + /@types/react-syntax-highlighter@15.5.13: + resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} dependencies: '@types/react': 19.2.7 + dev: true - '@types/react@19.2.7': + /@types/react@19.2.7: + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} dependencies: csstype: 3.2.3 - '@types/unist@2.0.11': {} + /@types/unist@2.0.11: + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + dev: false - '@types/unist@3.0.3': {} + /@types/unist@3.0.3: + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0)(eslint@9.39.1)(typescript@5.9.3)': + /@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0)(eslint@9.39.1)(typescript@5.9.3): + resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.48.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' dependencies: '@eslint-community/regexpp': 4.12.2 '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) @@ -6246,8 +2074,14 @@ snapshots: typescript: 5.9.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + /@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3): + resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' dependencies: '@typescript-eslint/scope-manager': 8.48.0 '@typescript-eslint/types': 8.48.0 @@ -6258,8 +2092,13 @@ snapshots: typescript: 5.9.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/project-service@8.48.0(typescript@5.9.3)': + /@typescript-eslint/project-service@8.48.0(typescript@5.9.3): + resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' dependencies: '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) '@typescript-eslint/types': 8.48.0 @@ -6267,17 +2106,31 @@ snapshots: typescript: 5.9.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/scope-manager@8.48.0': + /@typescript-eslint/scope-manager@8.48.0: + resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@typescript-eslint/types': 8.48.0 '@typescript-eslint/visitor-keys': 8.48.0 + dev: true - '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3)': + /@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3): + resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' dependencies: typescript: 5.9.3 + dev: true - '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + /@typescript-eslint/type-utils@8.48.0(eslint@9.39.1)(typescript@5.9.3): + resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' dependencies: '@typescript-eslint/types': 8.48.0 '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) @@ -6288,10 +2141,18 @@ snapshots: typescript: 5.9.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/types@8.48.0': {} + /@typescript-eslint/types@8.48.0: + resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true - '@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3)': + /@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3): + resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' dependencies: '@typescript-eslint/project-service': 8.48.0(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) @@ -6305,8 +2166,14 @@ snapshots: typescript: 5.9.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + /@typescript-eslint/utils@8.48.0(eslint@9.39.1)(typescript@5.9.3): + resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) '@typescript-eslint/scope-manager': 8.48.0 @@ -6316,112 +2183,252 @@ snapshots: typescript: 5.9.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/visitor-keys@8.48.0': + /@typescript-eslint/visitor-keys@8.48.0: + resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@typescript-eslint/types': 8.48.0 eslint-visitor-keys: 4.2.1 + dev: true - '@ungap/structured-clone@1.3.0': {} + /@ungap/structured-clone@1.3.0: + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + dev: false - '@unrs/resolver-binding-android-arm-eabi@1.11.1': + /@unrs/resolver-binding-android-arm-eabi@1.11.1: + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-android-arm64@1.11.1': + /@unrs/resolver-binding-android-arm64@1.11.1: + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-darwin-arm64@1.11.1': + /@unrs/resolver-binding-darwin-arm64@1.11.1: + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-darwin-x64@1.11.1': + /@unrs/resolver-binding-darwin-x64@1.11.1: + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-freebsd-x64@1.11.1': + /@unrs/resolver-binding-freebsd-x64@1.11.1: + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + /@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1: + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + /@unrs/resolver-binding-linux-arm-musleabihf@1.11.1: + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + /@unrs/resolver-binding-linux-arm64-gnu@1.11.1: + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + /@unrs/resolver-binding-linux-arm64-musl@1.11.1: + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@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==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + /@unrs/resolver-binding-linux-riscv64-gnu@1.11.1: + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@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==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + /@unrs/resolver-binding-linux-s390x-gnu@1.11.1: + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + /@unrs/resolver-binding-linux-x64-gnu@1.11.1: + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@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==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@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==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + requiresBuild: true dependencies: '@napi-rs/wasm-runtime': 0.2.12 + dev: true optional: true - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + /@unrs/resolver-binding-win32-arm64-msvc@1.11.1: + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + /@unrs/resolver-binding-win32-ia32-msvc@1.11.1: + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + /@unrs/resolver-binding-win32-x64-msvc@1.11.1: + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true optional: true - acorn-jsx@5.3.2(acorn@8.15.0): + /acorn-jsx@5.3.2(acorn@8.15.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.15.0 + dev: true - acorn@8.15.0: {} + /acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true - ajv@6.12.6: + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 + dev: true - ansi-escapes@7.2.0: + /ansi-escapes@7.2.0: + resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} + engines: {node: '>=18'} dependencies: environment: 1.1.0 + dev: true - ansi-regex@6.2.2: {} + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: false - ansi-styles@4.3.0: + /ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - ansi-styles@6.2.3: {} + /ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + dev: true - argparse@2.0.1: {} + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true - aria-hidden@1.2.6: + /aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} dependencies: tslib: 2.8.1 + dev: false - aria-query@5.3.2: {} + /aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + dev: true - array-buffer-byte-length@1.0.2: + /array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 is-array-buffer: 3.0.5 + dev: true - array-includes@3.1.9: + /array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -6431,8 +2438,11 @@ snapshots: get-intrinsic: 1.3.0 is-string: 1.1.1 math-intrinsics: 1.1.0 + dev: true - array.prototype.findlast@1.2.5: + /array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 @@ -6440,8 +2450,11 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 + dev: true - array.prototype.findlastindex@1.2.6: + /array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -6450,30 +2463,42 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 + dev: true - array.prototype.flat@1.3.3: + /array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 + dev: true - array.prototype.flatmap@1.3.3: + /array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 + dev: true - array.prototype.tosorted@1.1.4: + /array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 es-errors: 1.3.0 es-shim-unscopables: 1.1.0 + dev: true - arraybuffer.prototype.slice@1.0.4: + /arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 @@ -6482,209 +2507,394 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + dev: true - ast-types-flow@0.0.8: {} + /ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + dev: true - async-function@1.0.0: {} + /async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + dev: true - asynckit@0.4.0: {} + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false - available-typed-arrays@1.0.7: + /available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} dependencies: possible-typed-array-names: 1.1.0 + dev: true - axe-core@4.11.0: {} + /axe-core@4.11.0: + resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==} + engines: {node: '>=4'} + dev: true - axios@1.13.2: + /axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} dependencies: follow-redirects: 1.15.11 form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug + dev: false - axobject-query@4.1.0: {} + /axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + dev: true - bail@2.0.2: {} + /bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + dev: false - balanced-match@1.0.2: {} + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true - brace-expansion@1.1.12: + /brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 + dev: true - brace-expansion@2.0.2: + /brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} dependencies: balanced-match: 1.0.2 + dev: true - braces@3.0.3: + /braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} dependencies: fill-range: 7.1.1 + dev: true - call-bind-apply-helpers@1.0.2: + /call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + /call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 get-intrinsic: 1.3.0 set-function-length: 1.2.2 + dev: true - call-bound@1.0.4: + /call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + dev: true - callsites@3.1.0: {} + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true - caniuse-lite@1.0.30001760: {} + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: false - ccount@2.0.1: {} + /caniuse-lite@1.0.30001757: + resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} + dev: false - chalk@4.1.2: + /ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + dev: false + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + dev: true - chalk@5.6.2: {} + /chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true - character-entities-html4@2.1.0: {} + /character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + dev: false - character-entities-legacy@3.0.0: {} + /character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + dev: false - character-entities@2.0.2: {} + /character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + dev: false - character-reference-invalid@2.0.1: {} + /character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + dev: false - class-variance-authority@0.7.1: + /class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} dependencies: clsx: 2.1.1 + dev: false - cli-cursor@5.0.0: + /cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} dependencies: restore-cursor: 5.1.0 + dev: true - cli-truncate@4.0.0: + /cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} dependencies: slice-ansi: 5.0.0 string-width: 7.2.0 + dev: true - client-only@0.0.1: {} + /client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + dev: false - clsx@2.1.1: {} + /cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: false - color-convert@2.0.1: + /clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + dev: false + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - color-name@1.1.4: {} + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - colorette@2.0.20: {} + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + dev: true - combined-stream@1.0.8: + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 + dev: false - comma-separated-tokens@2.0.3: {} + /comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + dev: false - commander@13.1.0: {} + /commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + dev: true - concat-map@0.0.1: {} + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true - cross-spawn@7.0.6: + /cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + dev: true - csstype@3.2.3: {} + /csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - damerau-levenshtein@1.0.8: {} + /damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + dev: true - data-view-buffer@1.0.2: + /data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 + dev: true - data-view-byte-length@1.0.2: + /data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 + dev: true - data-view-byte-offset@1.0.1: + /data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 + dev: true - debug@3.2.7: + /debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: true + + /debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.3 - debug@4.4.3: - dependencies: - ms: 2.1.3 + /decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: false - decode-named-character-reference@1.2.0: + /decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} dependencies: character-entities: 2.0.2 + dev: false - deep-is@0.1.4: {} + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true - define-data-property@1.1.4: + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 + dev: true - define-properties@1.2.1: + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 + dev: true - delayed-stream@1.0.0: {} + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false - dequal@2.0.3: {} + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: false - detect-libc@2.1.2: {} + /detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + dev: false - detect-node-es@1.1.0: {} + /detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dev: false - devlop@1.1.0: + /devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} dependencies: dequal: 2.0.3 + dev: false - doctrine@2.1.0: + /dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + dev: false + + /doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} dependencies: esutils: 2.0.3 + dev: true - dunder-proto@1.0.1: + /dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 - emoji-regex@10.6.0: {} + /emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + dev: true - emoji-regex@9.2.2: {} + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: false - enhanced-resolve@5.18.3: + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + + /enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.11 tapable: 2.3.0 + dev: false - entities@6.0.1: {} + /entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + dev: false - environment@1.1.0: {} + /environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + dev: true - es-abstract@1.24.0: + /es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 @@ -6740,12 +2950,19 @@ snapshots: typed-array-length: 1.0.7 unbox-primitive: 1.1.0 which-typed-array: 1.1.19 + dev: true - es-define-property@1.0.1: {} + /es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} - es-errors@1.3.0: {} + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} - es-iterator-helpers@1.2.1: + /es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -6763,33 +2980,57 @@ snapshots: internal-slot: 1.1.0 iterator.prototype: 1.1.5 safe-array-concat: 1.1.3 + dev: true - es-object-atoms@1.1.1: + /es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.1.0: + /es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 - es-shim-unscopables@1.1.0: + /es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 + dev: true - es-to-primitive@1.3.0: + /es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} dependencies: is-callable: 1.2.7 is-date-object: 1.1.0 is-symbol: 1.1.1 + dev: true - escape-string-regexp@4.0.0: {} + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true - escape-string-regexp@5.0.0: {} + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: false - eslint-config-next@15.2.4(eslint@9.39.1)(typescript@5.9.3): + /eslint-config-next@15.2.4(eslint@9.39.1)(typescript@5.9.3): + resolution: {integrity: sha512-v4gYjd4eYIme8qzaJItpR5MMBXJ0/YV07u7eb50kEnlEmX7yhOjdUdzz70v4fiINYRjLf8X8TbogF0k7wlz6sA==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@next/eslint-plugin-next': 15.2.4 '@rushstack/eslint-patch': 1.15.0 @@ -6807,20 +3048,39 @@ snapshots: - eslint-import-resolver-webpack - eslint-plugin-import-x - supports-color + dev: true - eslint-config-prettier@10.1.8(eslint@9.39.1): + /eslint-config-prettier@10.1.8(eslint@9.39.1): + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' dependencies: eslint: 9.39.1 + dev: true - eslint-import-resolver-node@0.3.9: + /eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: debug: 3.2.7 is-core-module: 2.16.1 resolve: 1.22.11 transitivePeerDependencies: - supports-color + dev: true - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1): + /eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1): + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -6833,8 +3093,28 @@ snapshots: unrs-resolver: 1.11.1 transitivePeerDependencies: - supports-color + dev: true - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1): + /eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1): + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true dependencies: '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) debug: 3.2.7 @@ -6843,8 +3123,17 @@ snapshots: eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1) transitivePeerDependencies: - supports-color + dev: true - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1): + /eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1): + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true dependencies: '@rtsao/scc': 1.1.0 '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) @@ -6871,8 +3160,13 @@ snapshots: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color + dev: true - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1): + /eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1): + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -6890,20 +3184,43 @@ snapshots: object.fromentries: 2.0.8 safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 + dev: true - eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8)(eslint@9.39.1)(prettier@3.6.2): + /eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8)(eslint@9.39.1)(prettier@3.6.2): + resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true dependencies: eslint: 9.39.1 eslint-config-prettier: 10.1.8(eslint@9.39.1) prettier: 3.6.2 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 + dev: true - eslint-plugin-react-hooks@5.2.0(eslint@9.39.1): + /eslint-plugin-react-hooks@5.2.0(eslint@9.39.1): + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 dependencies: eslint: 9.39.1 + dev: true - eslint-plugin-react@7.37.5(eslint@9.39.1): + /eslint-plugin-react@7.37.5(eslint@9.39.1): + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -6924,17 +3241,35 @@ snapshots: semver: 6.3.1 string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + dev: true - eslint-scope@8.4.0: + /eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 + dev: true - eslint-visitor-keys@3.4.3: {} + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true - eslint-visitor-keys@4.2.1: {} + /eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true - eslint@9.39.1: + /eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) '@eslint-community/regexpp': 4.12.2 @@ -6972,30 +3307,52 @@ snapshots: optionator: 0.9.4 transitivePeerDependencies: - supports-color + dev: true - espree@10.4.0: + /espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: acorn: 8.15.0 acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 + dev: true - esquery@1.6.0: + /esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 + dev: true - esrecurse@4.3.0: + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 + dev: true - estraverse@5.3.0: {} + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true - estree-util-is-identifier-name@3.0.0: {} + /estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + dev: false - esutils@2.0.3: {} + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true - eventemitter3@5.0.1: {} + /eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + dev: true - execa@8.0.1: + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} dependencies: cross-spawn: 7.0.6 get-stream: 8.0.1 @@ -7006,76 +3363,144 @@ snapshots: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 + dev: true - extend@3.0.2: {} + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false - fast-deep-equal@3.1.3: {} + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true - fast-diff@1.3.0: {} + /fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + dev: true - fast-glob@3.3.1: + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.8 + dev: true - fast-json-stable-stringify@2.1.0: {} + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true - fast-levenshtein@2.0.6: {} + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true - fastq@1.19.1: + /fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} dependencies: reusify: 1.1.0 + dev: true - fault@1.0.4: + /fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} dependencies: format: 0.2.2 + dev: false - fdir@6.5.0(picomatch@4.0.3): + /fdir@6.5.0(picomatch@4.0.3): + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true dependencies: picomatch: 4.0.3 + dev: true - file-entry-cache@8.0.0: + /file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} dependencies: flat-cache: 4.0.1 + dev: true - fill-range@7.1.1: + /fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 + dev: true - find-up@5.0.0: + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + dev: false + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} dependencies: locate-path: 6.0.0 path-exists: 4.0.0 + dev: true - flat-cache@4.0.1: + /flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} dependencies: flatted: 3.3.3 keyv: 4.5.4 + dev: true - flatted@3.3.3: {} + /flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + dev: true - follow-redirects@1.15.11: {} + /follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false - for-each@0.3.5: + /for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} dependencies: is-callable: 1.2.7 + dev: true - form-data@4.0.5: + /form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 hasown: 2.0.2 mime-types: 2.1.35 + dev: false - format@0.2.2: {} + /format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + dev: false - function-bind@1.1.2: {} + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.8: + /function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -7083,14 +3508,30 @@ snapshots: functions-have-names: 1.2.3 hasown: 2.0.2 is-callable: 1.2.7 + dev: true - functions-have-names@1.2.3: {} + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true - generator-function@2.0.1: {} + /generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + dev: true - get-east-asian-width@1.4.0: {} + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: false - get-intrinsic@1.3.0: + /get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + dev: true + + /get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -7103,71 +3544,122 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 - get-nonce@1.0.1: {} + /get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + dev: false - get-proto@1.0.1: + /get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stream@8.0.1: {} + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true - get-symbol-description@1.1.0: + /get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 + dev: true - get-tsconfig@4.13.0: + /get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} dependencies: resolve-pkg-maps: 1.0.0 + dev: true - github-slugger@2.0.0: {} + /github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + dev: false - glob-parent@5.1.2: + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 + dev: true - glob-parent@6.0.2: + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 + dev: true - globals@14.0.0: {} + /globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + dev: true - globalthis@1.0.4: + /globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} dependencies: define-properties: 1.2.1 gopd: 1.2.0 + dev: true - gopd@1.2.0: {} + /gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} - graceful-fs@4.2.11: {} + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: false - graphemer@1.4.0: {} + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true - has-bigints@1.1.0: {} + /has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + dev: true - has-flag@4.0.0: {} + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true - has-property-descriptors@1.0.2: + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: es-define-property: 1.0.1 + dev: true - has-proto@1.2.0: + /has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} dependencies: dunder-proto: 1.0.1 + dev: true - has-symbols@1.1.0: {} + /has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} - has-tostringtag@1.0.2: + /has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} dependencies: has-symbols: 1.1.0 - hasown@2.0.2: + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - hast-util-from-parse5@8.0.3: + /hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.3 @@ -7177,20 +3669,28 @@ snapshots: vfile: 6.0.3 vfile-location: 5.0.3 web-namespaces: 2.0.1 + dev: false - hast-util-heading-rank@3.0.0: + /hast-util-heading-rank@3.0.0: + resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} dependencies: '@types/hast': 3.0.4 + dev: false - hast-util-is-element@3.0.0: + /hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} dependencies: '@types/hast': 3.0.4 + dev: false - hast-util-parse-selector@4.0.0: + /hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} dependencies: '@types/hast': 3.0.4 + dev: false - hast-util-raw@9.1.0: + /hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.3 @@ -7205,8 +3705,10 @@ snapshots: vfile: 6.0.3 web-namespaces: 2.0.1 zwitch: 2.0.4 + dev: false - hast-util-to-jsx-runtime@2.3.6: + /hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} dependencies: '@types/estree': 1.0.8 '@types/hast': 3.0.4 @@ -7225,8 +3727,10 @@ snapshots: vfile-message: 4.0.3 transitivePeerDependencies: - supports-color + dev: false - hast-util-to-parse5@8.0.0: + /hast-util-to-parse5@8.0.0: + resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} dependencies: '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 @@ -7235,217 +3739,381 @@ snapshots: space-separated-tokens: 2.0.2 web-namespaces: 2.0.1 zwitch: 2.0.4 + dev: false - hast-util-to-string@3.0.1: + /hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} dependencies: '@types/hast': 3.0.4 + dev: false - hast-util-to-text@4.0.2: + /hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.3 hast-util-is-element: 3.0.0 unist-util-find-after: 5.0.0 + dev: false - hast-util-whitespace@3.0.0: + /hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} dependencies: '@types/hast': 3.0.4 + dev: false - hastscript@9.0.1: + /hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} dependencies: '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 hast-util-parse-selector: 4.0.0 property-information: 7.1.0 space-separated-tokens: 2.0.2 + dev: false - highlight.js@10.7.3: {} + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false - highlight.js@11.11.1: {} + /highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + dev: false - highlightjs-vue@1.0.0: {} + /highlightjs-vue@1.0.0: + resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + dev: false - html-parse-stringify@3.0.1: + /html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} dependencies: void-elements: 3.1.0 + dev: false - html-url-attributes@3.0.1: {} + /html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + dev: false - html-void-elements@3.0.0: {} + /html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + dev: false - human-signals@5.0.0: {} + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true - i18next-browser-languagedetector@8.2.0: + /i18next-browser-languagedetector@8.2.0: + resolution: {integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==} dependencies: '@babel/runtime': 7.28.4 + dev: false - i18next@25.6.3(typescript@5.9.3): + /i18next@25.6.3(typescript@5.9.3): + resolution: {integrity: sha512-AEQvoPDljhp67a1+NsnG/Wb1Nh6YoSvtrmeEd24sfGn3uujCtXCF3cXpr7ulhMywKNFF7p3TX1u2j7y+caLOJg==} + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true dependencies: '@babel/runtime': 7.28.4 typescript: 5.9.3 + dev: false - ignore@5.3.2: {} + /ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + dev: true - ignore@7.0.5: {} + /ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + dev: true - import-fresh@3.3.1: + /import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 + dev: true - imurmurhash@0.1.4: {} + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true - inline-style-parser@0.2.7: {} + /inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + dev: false - input-otp@1.4.2(react-dom@19.2.1)(react@19.2.1): + /input-otp@1.4.2(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc dependencies: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - internal-slot@1.1.0: + /internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 hasown: 2.0.2 side-channel: 1.1.0 + dev: true - is-alphabetical@2.0.1: {} + /is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + dev: false - is-alphanumerical@2.0.1: + /is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} dependencies: is-alphabetical: 2.0.1 is-decimal: 2.0.1 + dev: false - is-array-buffer@3.0.5: + /is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 + dev: true - is-async-function@2.1.1: + /is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} dependencies: async-function: 1.0.0 call-bound: 1.0.4 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 + dev: true - is-bigint@1.1.0: + /is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} dependencies: has-bigints: 1.1.0 + dev: true - is-boolean-object@1.2.2: + /is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + dev: true - is-bun-module@2.0.0: + /is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} dependencies: semver: 7.7.3 + dev: true - is-callable@1.2.7: {} + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true - is-core-module@2.16.1: + /is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 + dev: true - is-data-view@1.0.2: + /is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 get-intrinsic: 1.3.0 is-typed-array: 1.1.15 + dev: true - is-date-object@1.1.0: + /is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + dev: true - is-decimal@2.0.1: {} + /is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + dev: false - is-extglob@2.1.1: {} + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true - is-finalizationregistry@1.1.1: + /is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 + dev: true - is-fullwidth-code-point@4.0.0: {} + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: false - is-fullwidth-code-point@5.1.0: + /is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + dev: true + + /is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} dependencies: get-east-asian-width: 1.4.0 + dev: true - is-generator-function@1.1.2: + /is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 generator-function: 2.0.1 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 + dev: true - is-glob@4.0.3: + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: true - is-hexadecimal@2.0.1: {} + /is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + dev: false - is-map@2.0.3: {} + /is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + dev: true - is-negative-zero@2.0.3: {} + /is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + dev: true - is-number-object@1.1.1: + /is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + dev: true - is-number@7.0.0: {} + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true - is-plain-obj@4.1.0: {} + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + dev: false - is-regex@1.2.1: + /is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 hasown: 2.0.2 + dev: true - is-set@2.0.3: {} + /is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + dev: true - is-shared-array-buffer@1.0.4: + /is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 + dev: true - is-stream@3.0.0: {} + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true - is-string@1.1.1: + /is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + dev: true - is-symbol@1.1.1: + /is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-symbols: 1.1.0 safe-regex-test: 1.1.0 + dev: true - is-typed-array@1.1.15: + /is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} dependencies: which-typed-array: 1.1.19 + dev: true - is-weakmap@2.0.2: {} + /is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + dev: true - is-weakref@1.1.1: + /is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 + dev: true - is-weakset@2.0.4: + /is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 get-intrinsic: 1.3.0 + dev: true - isarray@2.0.5: {} + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true - isexe@2.0.0: {} + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true - iterator.prototype@1.1.5: + /iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 es-object-atoms: 1.1.1 @@ -7453,81 +4121,180 @@ snapshots: get-proto: 1.0.1 has-symbols: 1.1.0 set-function-name: 2.0.2 + dev: true - jiti@2.6.1: {} + /jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + dev: false - js-tokens@4.0.0: {} + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true - js-yaml@4.1.1: + /js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true dependencies: argparse: 2.0.1 + dev: true - json-buffer@3.0.1: {} + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true - json-schema-traverse@0.4.1: {} + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true - json-stable-stringify-without-jsonify@1.0.1: {} + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true - json5@1.0.2: + /json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true dependencies: minimist: 1.2.8 + dev: true - jsx-ast-utils@3.3.5: + /jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} dependencies: array-includes: 3.1.9 array.prototype.flat: 1.3.3 object.assign: 4.1.7 object.values: 1.2.1 + dev: true - keyv@4.5.4: + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: json-buffer: 3.0.1 + dev: true - language-subtag-registry@0.3.23: {} + /language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + dev: true - language-tags@1.0.9: + /language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} dependencies: language-subtag-registry: 0.3.23 + dev: true - levn@0.4.1: + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + dev: true - lightningcss-android-arm64@1.30.2: + /lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false optional: true - lightningcss-darwin-arm64@1.30.2: + /lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false optional: true - lightningcss-darwin-x64@1.30.2: + /lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false optional: true - lightningcss-freebsd-x64@1.30.2: + /lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false optional: true - lightningcss-linux-arm-gnueabihf@1.30.2: + /lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false optional: true - lightningcss-linux-arm64-gnu@1.30.2: + /lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - lightningcss-linux-arm64-musl@1.30.2: + /lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true - lightningcss-linux-x64-gnu@1.30.2: + /lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - lightningcss-linux-x64-musl@1.30.2: + /lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true - lightningcss-win32-arm64-msvc@1.30.2: + /lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false optional: true - lightningcss-win32-x64-msvc@1.30.2: + /lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false optional: true - lightningcss@1.30.2: + /lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} dependencies: detect-libc: 2.1.2 optionalDependencies: @@ -7542,10 +4309,17 @@ snapshots: lightningcss-linux-x64-musl: 1.30.2 lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + dev: false - lilconfig@3.1.3: {} + /lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + dev: true - lint-staged@15.5.2: + /lint-staged@15.5.2: + resolution: {integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==} + engines: {node: '>=18.12.0'} + hasBin: true dependencies: chalk: 5.6.2 commander: 13.1.0 @@ -7559,8 +4333,11 @@ snapshots: yaml: 2.8.1 transitivePeerDependencies: - supports-color + dev: true - listr2@8.3.3: + /listr2@8.3.3: + resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} + engines: {node: '>=18.0.0'} dependencies: cli-truncate: 4.0.0 colorette: 2.0.20 @@ -7568,60 +4345,100 @@ snapshots: log-update: 6.1.0 rfdc: 1.4.1 wrap-ansi: 9.0.2 + dev: true - locate-path@6.0.0: + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + dev: false + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} dependencies: p-locate: 5.0.0 + dev: true - lodash.merge@4.6.2: {} + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true - lodash@4.17.21: {} + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false - log-update@6.1.0: + /log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} dependencies: ansi-escapes: 7.2.0 cli-cursor: 5.0.0 slice-ansi: 7.1.2 strip-ansi: 7.1.2 wrap-ansi: 9.0.2 + dev: true - longest-streak@3.1.0: {} + /longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + dev: false - loose-envify@1.4.0: + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true dependencies: js-tokens: 4.0.0 + dev: true - lowlight@1.20.0: + /lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} dependencies: fault: 1.0.4 highlight.js: 10.7.3 + dev: false - lowlight@3.3.0: + /lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} dependencies: '@types/hast': 3.0.4 devlop: 1.1.0 highlight.js: 11.11.1 + dev: false - lucide-react@0.507.0(react@19.2.1): + /lucide-react@0.507.0(react@19.2.1): + resolution: {integrity: sha512-XfgE6gvAHwAtnbUvWiTTHx4S3VGR+cUJHEc0vrh9Ogu672I1Tue2+Cp/8JJqpytgcBHAB1FVI297W4XGNwc2dQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 dependencies: react: 19.2.1 + dev: false - magic-string@0.30.21: + /magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + dev: false - markdown-table@3.0.4: {} + /markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + dev: false - math-intrinsics@1.1.0: {} + /math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} - mdast-util-find-and-replace@3.0.2: + /mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} dependencies: '@types/mdast': 4.0.4 escape-string-regexp: 5.0.0 unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + dev: false - mdast-util-from-markdown@2.0.2: + /mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 @@ -7637,16 +4454,20 @@ snapshots: unist-util-stringify-position: 4.0.0 transitivePeerDependencies: - supports-color + dev: false - mdast-util-gfm-autolink-literal@2.0.1: + /mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} dependencies: '@types/mdast': 4.0.4 ccount: 2.0.1 devlop: 1.1.0 mdast-util-find-and-replace: 3.0.2 micromark-util-character: 2.1.1 + dev: false - mdast-util-gfm-footnote@2.1.0: + /mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 @@ -7655,16 +4476,20 @@ snapshots: micromark-util-normalize-identifier: 2.0.1 transitivePeerDependencies: - supports-color + dev: false - mdast-util-gfm-strikethrough@2.0.0: + /mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} dependencies: '@types/mdast': 4.0.4 mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color + dev: false - mdast-util-gfm-table@2.0.0: + /mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 @@ -7673,8 +4498,10 @@ snapshots: mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color + dev: false - mdast-util-gfm-task-list-item@2.0.0: + /mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 @@ -7682,8 +4509,10 @@ snapshots: mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color + dev: false - mdast-util-gfm@3.1.0: + /mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} dependencies: mdast-util-from-markdown: 2.0.2 mdast-util-gfm-autolink-literal: 2.0.1 @@ -7694,8 +4523,10 @@ snapshots: mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color + dev: false - mdast-util-mdx-expression@2.0.1: + /mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} dependencies: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 @@ -7705,8 +4536,10 @@ snapshots: mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color + dev: false - mdast-util-mdx-jsx@3.2.0: + /mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} dependencies: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 @@ -7722,8 +4555,10 @@ snapshots: vfile-message: 4.0.3 transitivePeerDependencies: - supports-color + dev: false - mdast-util-mdxjs-esm@2.0.1: + /mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} dependencies: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 @@ -7733,13 +4568,17 @@ snapshots: mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color + dev: false - mdast-util-phrasing@4.1.0: + /mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} dependencies: '@types/mdast': 4.0.4 unist-util-is: 6.0.1 + dev: false - mdast-util-to-hast@13.2.1: + /mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 @@ -7750,8 +4589,10 @@ snapshots: unist-util-position: 5.0.0 unist-util-visit: 5.0.0 vfile: 6.0.3 + dev: false - mdast-util-to-markdown@2.1.2: + /mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 @@ -7762,16 +4603,25 @@ snapshots: micromark-util-decode-string: 2.0.1 unist-util-visit: 5.0.0 zwitch: 2.0.4 + dev: false - mdast-util-to-string@4.0.0: + /mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} dependencies: '@types/mdast': 4.0.4 + dev: false - merge-stream@2.0.0: {} + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true - merge2@1.4.1: {} + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true - micromark-core-commonmark@2.0.3: + /micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} dependencies: decode-named-character-reference: 1.2.0 devlop: 1.1.0 @@ -7789,15 +4639,19 @@ snapshots: micromark-util-subtokenize: 2.1.0 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-extension-gfm-autolink-literal@2.1.0: + /micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} dependencies: micromark-util-character: 2.1.1 micromark-util-sanitize-uri: 2.0.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-extension-gfm-footnote@2.1.0: + /micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} dependencies: devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -7807,8 +4661,10 @@ snapshots: micromark-util-sanitize-uri: 2.0.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-extension-gfm-strikethrough@2.1.0: + /micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} dependencies: devlop: 1.1.0 micromark-util-chunked: 2.0.1 @@ -7816,28 +4672,36 @@ snapshots: micromark-util-resolve-all: 2.0.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-extension-gfm-table@2.1.1: + /micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} dependencies: devlop: 1.1.0 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-extension-gfm-tagfilter@2.0.0: + /micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} dependencies: micromark-util-types: 2.0.2 + dev: false - micromark-extension-gfm-task-list-item@2.1.0: + /micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} dependencies: devlop: 1.1.0 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-extension-gfm@3.0.0: + /micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} dependencies: micromark-extension-gfm-autolink-literal: 2.1.0 micromark-extension-gfm-footnote: 2.1.0 @@ -7847,100 +4711,140 @@ snapshots: micromark-extension-gfm-task-list-item: 2.1.0 micromark-util-combine-extensions: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-factory-destination@2.0.1: + /micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-factory-label@2.0.1: + /micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} dependencies: devlop: 1.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-factory-space@2.0.1: + /micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} dependencies: micromark-util-character: 2.1.1 micromark-util-types: 2.0.2 + dev: false - micromark-factory-title@2.0.1: + /micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} dependencies: micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-factory-whitespace@2.0.1: + /micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} dependencies: micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-util-character@2.1.1: + /micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} dependencies: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-util-chunked@2.0.1: + /micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} dependencies: micromark-util-symbol: 2.0.1 + dev: false - micromark-util-classify-character@2.0.1: + /micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-util-combine-extensions@2.0.1: + /micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} dependencies: micromark-util-chunked: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-util-decode-numeric-character-reference@2.0.2: + /micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} dependencies: micromark-util-symbol: 2.0.1 + dev: false - micromark-util-decode-string@2.0.1: + /micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} dependencies: decode-named-character-reference: 1.2.0 micromark-util-character: 2.1.1 micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 + dev: false - micromark-util-encode@2.0.1: {} + /micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + dev: false - micromark-util-html-tag-name@2.0.1: {} + /micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + dev: false - micromark-util-normalize-identifier@2.0.1: + /micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} dependencies: micromark-util-symbol: 2.0.1 + dev: false - micromark-util-resolve-all@2.0.1: + /micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} dependencies: micromark-util-types: 2.0.2 + dev: false - micromark-util-sanitize-uri@2.0.1: + /micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} dependencies: micromark-util-character: 2.1.1 micromark-util-encode: 2.0.1 micromark-util-symbol: 2.0.1 + dev: false - micromark-util-subtokenize@2.1.0: + /micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} dependencies: devlop: 1.1.0 micromark-util-chunked: 2.0.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + dev: false - micromark-util-symbol@2.0.1: {} + /micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + dev: false - micromark-util-types@2.0.2: {} + /micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + dev: false - micromark@4.0.2: + /micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} dependencies: '@types/debug': 4.1.12 debug: 4.4.3 @@ -7961,50 +4865,108 @@ snapshots: micromark-util-types: 2.0.2 transitivePeerDependencies: - supports-color + dev: false - micromatch@4.0.8: + /micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} dependencies: braces: 3.0.3 picomatch: 2.3.1 + dev: true - mime-db@1.52.0: {} + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false - mime-types@2.1.35: + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 + dev: false - mimic-fn@4.0.0: {} + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true - mimic-function@5.0.1: {} + /mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + dev: true - minimatch@3.1.2: + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.12 + dev: true - minimatch@9.0.5: + /minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.2 + dev: true - minimist@1.2.8: {} + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true - ms@2.1.3: {} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.11: {} + /nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false - napi-postinstall@0.3.4: {} + /napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + dev: true - natural-compare@1.4.0: {} + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true - next-themes@0.4.6(react-dom@19.2.1)(react@19.2.1): + /next-themes@0.4.6(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc dependencies: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - next@15.5.9(react-dom@19.2.1)(react@19.2.1): + /next@15.5.9(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + 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 dependencies: '@next/env': 15.5.9 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001760 + caniuse-lite: 1.0.30001757 postcss: 8.4.31 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) @@ -8022,18 +4984,33 @@ snapshots: transitivePeerDependencies: - '@babel/core' - babel-plugin-macros + dev: false - npm-run-path@5.3.0: + /npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: path-key: 4.0.0 + dev: true - object-assign@4.1.1: {} + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: true - object-inspect@1.13.4: {} + /object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + dev: true - object-keys@1.1.1: {} + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true - object.assign@4.1.7: + /object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -8041,43 +5018,64 @@ snapshots: es-object-atoms: 1.1.1 has-symbols: 1.1.0 object-keys: 1.1.1 + dev: true - object.entries@1.1.9: + /object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 + dev: true - object.fromentries@2.0.8: + /object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 es-object-atoms: 1.1.1 + dev: true - object.groupby@1.0.3: + /object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 + dev: true - object.values@1.2.1: + /object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 + dev: true - onetime@6.0.0: + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} dependencies: mimic-fn: 4.0.0 + dev: true - onetime@7.0.0: + /onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} dependencies: mimic-function: 5.0.1 + dev: true - optionator@0.9.4: + /optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} dependencies: deep-is: 0.1.4 fast-levenshtein: 2.0.6 @@ -8085,26 +5083,59 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 word-wrap: 1.2.5 + dev: true - own-keys@1.0.1: + /own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} dependencies: get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 + dev: true - p-limit@3.1.0: + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + dev: false + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 + dev: true - p-locate@5.0.0: + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + dev: false + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} dependencies: p-limit: 3.1.0 + dev: true - parent-module@1.0.1: + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: false + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} dependencies: callsites: 3.1.0 + dev: true - parse-entities@4.0.2: + /parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} dependencies: '@types/unist': 2.0.11 character-entities-legacy: 3.0.0 @@ -8113,77 +5144,175 @@ snapshots: is-alphanumerical: 2.0.1 is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + dev: false - parse5@7.3.0: + /parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} dependencies: entities: 6.0.1 + dev: false - path-exists@4.0.0: {} + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} - path-key@3.1.1: {} + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true - path-key@4.0.0: {} + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true - path-parse@1.0.7: {} + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true - picocolors@1.1.1: {} + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dev: false - picomatch@2.3.1: {} + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true - picomatch@4.0.3: {} + /picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + dev: true - pidtree@0.6.0: {} + /pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + dev: true - possible-typed-array-names@1.1.0: {} + /pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + dev: false - postcss@8.4.31: + /possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + dev: true + + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + dev: false - postcss@8.5.6: + /postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + dev: false - prelude-ls@1.2.1: {} + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true - prettier-linter-helpers@1.0.0: + /prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} dependencies: fast-diff: 1.3.0 + dev: true - prettier@3.6.2: {} + /prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + dev: true - prismjs@1.30.0: {} + /prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + dev: false - prop-types@15.8.1: + /prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 + dev: true - property-information@6.5.0: {} + /property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + dev: false - property-information@7.1.0: {} + /property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + dev: false - proxy-from-env@1.1.0: {} + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false - punycode@2.3.1: {} + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: true - queue-microtask@1.2.3: {} + /qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + dev: false - react-dom@19.2.1(react@19.2.1): + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /react-dom@19.2.1(react@19.2.1): + resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} + peerDependencies: + react: ^19.2.1 dependencies: react: 19.2.1 scheduler: 0.27.0 + dev: false - react-hook-form@7.66.1(react@19.2.1): + /react-hook-form@7.66.1(react@19.2.1): + resolution: {integrity: sha512-2KnjpgG2Rhbi+CIiIBQQ9Df6sMGH5ExNyFl4Hw9qO7pIqMBR8Bvu9RQyjl3JM4vehzCh9soiNUM/xYMswb2EiA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 dependencies: react: 19.2.1 + dev: false - react-i18next@15.7.4(i18next@25.6.3)(react-dom@19.2.1)(react@19.2.1)(typescript@5.9.3): + /react-i18next@15.7.4(i18next@25.6.3)(react-dom@19.2.1)(react@19.2.1)(typescript@5.9.3): + resolution: {integrity: sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==} + peerDependencies: + i18next: '>= 23.4.0' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + typescript: ^5 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true dependencies: '@babel/runtime': 7.28.4 html-parse-stringify: 3.0.1 @@ -8191,10 +5320,17 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) typescript: 5.9.3 + dev: false - react-is@16.13.1: {} + /react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + dev: true - react-markdown@10.1.0(@types/react@19.2.7)(react@19.2.1): + /react-markdown@10.1.0(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 @@ -8211,20 +5347,43 @@ snapshots: vfile: 6.0.3 transitivePeerDependencies: - supports-color + dev: false - react-photo-view@1.2.7(react-dom@19.2.1)(react@19.2.1): + /react-photo-view@1.2.7(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-MfOWVPxuibncRLaycZUNxqYU8D9IA+rbGDDaq6GM8RIoGJal592hEJoRAyRSI7ZxyyJNJTLMUWWL3UIXHJJOpw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' dependencies: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.1): + /react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 react: 19.2.1 react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.1) tslib: 2.8.1 + dev: false - react-remove-scroll@2.7.1(@types/react@19.2.7)(react@19.2.1): + /react-remove-scroll@2.7.1(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 react: 19.2.1 @@ -8233,15 +5392,29 @@ snapshots: tslib: 2.8.1 use-callback-ref: 1.3.3(@types/react@19.2.7)(react@19.2.1) use-sidecar: 1.1.3(@types/react@19.2.7)(react@19.2.1) + dev: false - react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.2.1): + /react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 get-nonce: 1.0.1 react: 19.2.1 tslib: 2.8.1 + dev: false - react-syntax-highlighter@16.1.0(react@19.2.1): + /react-syntax-highlighter@16.1.0(react@19.2.1): + resolution: {integrity: sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==} + engines: {node: '>= 16.20.2'} + peerDependencies: + react: '>= 0.14.0' dependencies: '@babel/runtime': 7.28.4 highlight.js: 10.7.3 @@ -8250,10 +5423,16 @@ snapshots: prismjs: 1.30.0 react: 19.2.1 refractor: 5.0.0 + dev: false - react@19.2.1: {} + /react@19.2.1: + resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} + engines: {node: '>=0.10.0'} + dev: false - reflect.getprototypeof@1.0.10: + /reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 @@ -8263,15 +5442,20 @@ snapshots: get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 + dev: true - refractor@5.0.0: + /refractor@5.0.0: + resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} dependencies: '@types/hast': 3.0.4 '@types/prismjs': 1.26.5 hastscript: 9.0.1 parse-entities: 4.0.2 + dev: false - regexp.prototype.flags@1.5.4: + /regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 @@ -8279,8 +5463,10 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 set-function-name: 2.0.2 + dev: true - rehype-autolink-headings@7.1.0: + /rehype-autolink-headings@7.1.0: + resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} dependencies: '@types/hast': 3.0.4 '@ungap/structured-clone': 1.3.0 @@ -8288,30 +5474,38 @@ snapshots: hast-util-is-element: 3.0.0 unified: 11.0.5 unist-util-visit: 5.0.0 + dev: false - rehype-highlight@7.0.2: + /rehype-highlight@7.0.2: + resolution: {integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==} dependencies: '@types/hast': 3.0.4 hast-util-to-text: 4.0.2 lowlight: 3.3.0 unist-util-visit: 5.0.0 vfile: 6.0.3 + dev: false - rehype-raw@7.0.0: + /rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} dependencies: '@types/hast': 3.0.4 hast-util-raw: 9.1.0 vfile: 6.0.3 + dev: false - rehype-slug@6.0.0: + /rehype-slug@6.0.0: + resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} dependencies: '@types/hast': 3.0.4 github-slugger: 2.0.0 hast-util-heading-rank: 3.0.0 hast-util-to-string: 3.0.1 unist-util-visit: 5.0.0 + dev: false - remark-gfm@4.0.1: + /remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} dependencies: '@types/mdast': 4.0.4 mdast-util-gfm: 3.1.0 @@ -8321,8 +5515,10 @@ snapshots: unified: 11.0.5 transitivePeerDependencies: - supports-color + dev: false - remark-parse@11.0.0: + /remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} dependencies: '@types/mdast': 4.0.4 mdast-util-from-markdown: 2.0.2 @@ -8330,76 +5526,135 @@ snapshots: unified: 11.0.5 transitivePeerDependencies: - supports-color + dev: false - remark-rehype@11.1.2: + /remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 mdast-util-to-hast: 13.2.1 unified: 11.0.5 vfile: 6.0.3 + dev: false - remark-stringify@11.0.0: + /remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} dependencies: '@types/mdast': 4.0.4 mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + dev: false - resolve-from@4.0.0: {} + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: false - resolve-pkg-maps@1.0.0: {} + /require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + dev: false - resolve@1.22.11: + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + + /resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + dev: true - resolve@2.0.0-next.5: + /resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + dev: true - restore-cursor@5.1.0: + /restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} dependencies: onetime: 7.0.0 signal-exit: 4.1.0 + dev: true - reusify@1.1.0: {} + /reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true - rfdc@1.4.1: {} + /rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + dev: true - run-parallel@1.2.0: + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 + dev: true - safe-array-concat@1.1.3: + /safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 isarray: 2.0.5 + dev: true - safe-push-apply@1.0.0: + /safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 isarray: 2.0.5 + dev: true - safe-regex-test@1.1.0: + /safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-regex: 1.2.1 + dev: true - scheduler@0.27.0: {} + /scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + dev: false - semver@6.3.1: {} + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + dev: true - semver@7.7.3: {} + /semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true - set-function-length@1.2.2: + /set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: false + + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 @@ -8407,21 +5662,31 @@ snapshots: get-intrinsic: 1.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.2 + dev: true - set-function-name@2.0.2: + /set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + dev: true - set-proto@1.0.0: + /set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 + dev: true - sharp@0.34.5: + /sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + requiresBuild: true dependencies: '@img/colour': 1.0.0 detect-libc: 2.1.2 @@ -8451,85 +5716,148 @@ snapshots: '@img/sharp-win32-arm64': 0.34.5 '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 + dev: false optional: true - shebang-command@2.0.0: + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 + dev: true - shebang-regex@3.0.0: {} + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true - side-channel-list@1.0.0: + /side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 + dev: true - side-channel-map@1.0.1: + /side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 object-inspect: 1.13.4 + dev: true - side-channel-weakmap@1.0.2: + /side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 object-inspect: 1.13.4 side-channel-map: 1.0.1 + dev: true - side-channel@1.1.0: + /side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 side-channel-list: 1.0.0 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + dev: true - signal-exit@4.1.0: {} + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true - slice-ansi@5.0.0: + /slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} dependencies: ansi-styles: 6.2.3 is-fullwidth-code-point: 4.0.0 + dev: true - slice-ansi@7.1.2: + /slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} dependencies: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + dev: true - sonner@2.0.7(react-dom@19.2.1)(react@19.2.1): + /sonner@2.0.7(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc dependencies: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + dev: false - source-map-js@1.2.1: {} + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + dev: false - space-separated-tokens@2.0.2: {} + /space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + dev: false - stable-hash@0.0.5: {} + /stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + dev: true - stop-iteration-iterator@1.1.0: + /stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 + dev: true - string-argv@0.3.2: {} + /string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + dev: true - string-width@7.2.0: + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: false + + /string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} dependencies: emoji-regex: 10.6.0 get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 + dev: true - string.prototype.includes@2.0.1: + /string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 + dev: true - string.prototype.matchall@4.0.12: + /string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -8544,13 +5872,18 @@ snapshots: regexp.prototype.flags: 1.5.4 set-function-name: 2.0.2 side-channel: 1.1.0 + dev: true - string.prototype.repeat@1.0.0: + /string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} dependencies: define-properties: 1.2.1 es-abstract: 1.24.0 + dev: true - string.prototype.trim@1.2.10: + /string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -8559,111 +5892,202 @@ snapshots: es-abstract: 1.24.0 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 + dev: true - string.prototype.trimend@1.0.9: + /string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 + dev: true - string.prototype.trimstart@1.0.8: + /string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-object-atoms: 1.1.1 + dev: true - stringify-entities@4.0.4: + /stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} dependencies: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + dev: false - strip-ansi@7.1.2: + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: false + + /strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} dependencies: ansi-regex: 6.2.2 + dev: true - strip-bom@3.0.0: {} + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true - strip-final-newline@3.0.0: {} + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true - strip-json-comments@3.1.1: {} + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true - style-to-js@1.1.21: + /style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} dependencies: style-to-object: 1.0.14 + dev: false - style-to-object@1.0.14: + /style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} dependencies: inline-style-parser: 0.2.7 + dev: false - styled-jsx@5.1.6(react@19.2.1): + /styled-jsx@5.1.6(react@19.2.1): + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + 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 dependencies: client-only: 0.0.1 react: 19.2.1 + dev: false - supports-color@7.2.0: + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} dependencies: has-flag: 4.0.0 + dev: true - supports-preserve-symlinks-flag@1.0.0: {} + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true - synckit@0.11.11: + /synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} dependencies: '@pkgr/core': 0.2.9 + dev: true - tailwind-merge@3.4.0: {} + /tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + dev: false - tailwindcss@4.1.17: {} + /tailwindcss@4.1.17: + resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} + dev: false - tapable@2.3.0: {} + /tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + dev: false - tinyglobby@0.2.15: + /tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + dev: true - to-regex-range@5.0.1: + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 + dev: true - trim-lines@3.0.1: {} + /trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + dev: false - trough@2.2.0: {} + /trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + dev: false - ts-api-utils@2.1.0(typescript@5.9.3): + /ts-api-utils@2.1.0(typescript@5.9.3): + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' dependencies: typescript: 5.9.3 + dev: true - tsconfig-paths@3.15.0: + /tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} dependencies: '@types/json5': 0.0.29 json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 + dev: true - tslib@2.8.1: {} + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tw-animate-css@1.4.0: {} + /tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + dev: true - type-check@0.4.0: + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 + dev: true - typed-array-buffer@1.0.3: + /typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-typed-array: 1.1.15 + dev: true - typed-array-byte-length@1.0.3: + /typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 + dev: true - typed-array-byte-offset@1.0.4: + /typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 @@ -8672,8 +6096,11 @@ snapshots: has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 + dev: true - typed-array-length@1.0.7: + /typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 for-each: 0.3.5 @@ -8681,8 +6108,14 @@ snapshots: is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + dev: true - typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3): + /typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3): + resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' dependencies: '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0)(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) @@ -8692,19 +6125,29 @@ snapshots: typescript: 5.9.3 transitivePeerDependencies: - supports-color + dev: true - typescript@5.9.3: {} + /typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true - unbox-primitive@1.1.0: + /unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-bigints: 1.1.0 has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + dev: true - undici-types@6.21.0: {} + /undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + dev: true - unified@11.0.5: + /unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} dependencies: '@types/unist': 3.0.3 bail: 2.0.2 @@ -8713,36 +6156,51 @@ snapshots: is-plain-obj: 4.1.0 trough: 2.2.0 vfile: 6.0.3 + dev: false - unist-util-find-after@5.0.0: + /unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} dependencies: '@types/unist': 3.0.3 unist-util-is: 6.0.1 + dev: false - unist-util-is@6.0.1: + /unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} dependencies: '@types/unist': 3.0.3 + dev: false - unist-util-position@5.0.0: + /unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} dependencies: '@types/unist': 3.0.3 + dev: false - unist-util-stringify-position@4.0.0: + /unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} dependencies: '@types/unist': 3.0.3 + dev: false - unist-util-visit-parents@6.0.2: + /unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} dependencies: '@types/unist': 3.0.3 unist-util-is: 6.0.1 + dev: false - unist-util-visit@5.0.0: + /unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} dependencies: '@types/unist': 3.0.3 unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + dev: false - unrs-resolver@1.11.1: + /unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + requiresBuild: true dependencies: napi-postinstall: 0.3.4 optionalDependencies: @@ -8765,54 +6223,94 @@ snapshots: '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + dev: true - uri-js@4.4.1: + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.1 + dev: true - use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.1): + /use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 react: 19.2.1 tslib: 2.8.1 + dev: false - use-sidecar@1.1.3(@types/react@19.2.7)(react@19.2.1): + /use-sidecar@1.1.3(@types/react@19.2.7)(react@19.2.1): + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: '@types/react': 19.2.7 detect-node-es: 1.1.0 react: 19.2.1 tslib: 2.8.1 + dev: false - uuidjs@5.1.0: {} + /uuidjs@5.1.0: + resolution: {integrity: sha512-HAQPtUkr7t5Ud3uCwRcqtBRNagu/2aerrrBQE6PzgSluGijvFF75UaOq22Xw545GGviRjSLhc4c8CaSMI5h4Ng==} + hasBin: true + dev: false - vfile-location@5.0.3: + /vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} dependencies: '@types/unist': 3.0.3 vfile: 6.0.3 + dev: false - vfile-message@4.0.3: + /vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} dependencies: '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 + dev: false - vfile@6.0.3: + /vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} dependencies: '@types/unist': 3.0.3 vfile-message: 4.0.3 + dev: false - void-elements@3.1.0: {} + /void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + dev: false - web-namespaces@2.0.1: {} + /web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + dev: false - which-boxed-primitive@1.1.1: + /which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} dependencies: is-bigint: 1.1.0 is-boolean-object: 1.2.2 is-number-object: 1.1.1 is-string: 1.1.1 is-symbol: 1.1.1 + dev: true - which-builtin-type@1.2.1: + /which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 function.prototype.name: 1.1.8 @@ -8827,15 +6325,25 @@ snapshots: which-boxed-primitive: 1.1.1 which-collection: 1.0.2 which-typed-array: 1.1.19 + dev: true - which-collection@1.0.2: + /which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} dependencies: is-map: 2.0.3 is-set: 2.0.3 is-weakmap: 2.0.2 is-weakset: 2.0.4 + dev: true - which-typed-array@1.1.19: + /which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + dev: false + + /which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 @@ -8844,23 +6352,83 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-tostringtag: 1.0.2 + dev: true - which@2.0.2: + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true dependencies: isexe: 2.0.0 + dev: true - word-wrap@1.2.5: {} + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + dev: true - wrap-ansi@9.0.2: + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: false + + /wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 strip-ansi: 7.1.2 + dev: true - yaml@2.8.1: {} + /y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + dev: false - yocto-queue@0.1.0: {} + /yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + dev: true - zod@3.25.76: {} + /yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: false - zwitch@2.0.4: {} + /yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + dev: false + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true + + /zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + dev: false + + /zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + dev: false diff --git a/web/src/app/auth/space/callback/page.tsx b/web/src/app/auth/space/callback/page.tsx new file mode 100644 index 00000000..87ac2b9f --- /dev/null +++ b/web/src/app/auth/space/callback/page.tsx @@ -0,0 +1,122 @@ +'use client'; + +import { useEffect, useState, useCallback } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { httpClient } from '@/app/infra/http/HttpClient'; +import { toast } from 'sonner'; +import { useTranslation } from 'react-i18next'; +import { Loader2, AlertCircle, CheckCircle2 } from 'lucide-react'; +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import langbotIcon from '@/app/assets/langbot-logo.webp'; + +export default function SpaceOAuthCallback() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { t } = useTranslation(); + + const [status, setStatus] = useState<'loading' | 'success' | 'error'>( + 'loading', + ); + const [errorMessage, setErrorMessage] = useState(''); + + const handleOAuthCallback = useCallback( + async (code: string) => { + try { + const response = await httpClient.exchangeSpaceOAuthCode(code); + + // Store token and user info + localStorage.setItem('token', response.token); + if (response.user) { + localStorage.setItem('userEmail', response.user); + } + + setStatus('success'); + toast.success(t('common.spaceLoginSuccess')); + + // Redirect to home after a brief delay to show success state + setTimeout(() => { + router.push('/home'); + }, 1000); + } catch { + setStatus('error'); + setErrorMessage(t('common.spaceLoginFailed')); + } + }, + [router, t], + ); + + useEffect(() => { + const code = searchParams.get('code'); + const error = searchParams.get('error'); + const errorDescription = searchParams.get('error_description'); + + if (error) { + setStatus('error'); + setErrorMessage( + errorDescription || error || t('common.spaceLoginFailed'), + ); + return; + } + + if (!code) { + setStatus('error'); + setErrorMessage(t('common.spaceLoginNoCode')); + return; + } + + // Exchange code for token + handleOAuthCallback(code); + }, [searchParams, handleOAuthCallback, t]); + + return ( +
+ + + {/* eslint-disable-next-line @next/next/no-img-element */} + LangBot + + {status === 'loading' && t('common.spaceLoginProcessing')} + {status === 'success' && t('common.spaceLoginSuccess')} + {status === 'error' && t('common.spaceLoginError')} + + + {status === 'loading' && + t('common.spaceLoginProcessingDescription')} + {status === 'success' && t('common.spaceLoginSuccessDescription')} + {status === 'error' && errorMessage} + + + + {status === 'loading' && ( + + )} + {status === 'success' && ( + + )} + {status === 'error' && ( + <> + + + + )} + + +
+ ); +} diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index 6a624d17..b454a8e3 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -1,7 +1,17 @@ 'use client'; import { useState, useEffect } from 'react'; -import { Plus, MessageSquareText, Cpu, Info } from 'lucide-react'; +import { + Plus, + MessageSquareText, + Cpu, + Info, + RefreshCw, + ChevronLeft, + Cloud, + HardDrive, + Lock, +} from 'lucide-react'; import { LLMCardVO } from './component/llm-card/LLMCardVO'; import LLMCard from './component/llm-card/LLMCard'; import LLMForm from './component/llm-form/LLMForm'; @@ -21,80 +31,224 @@ import { extractI18nObject } from '@/i18n/I18nProvider'; import { EmbeddingCardVO } from './component/embedding-card/EmbeddingCardVO'; import EmbeddingCard from './component/embedding-card/EmbeddingCard'; import EmbeddingForm from './component/embedding-form/EmbeddingForm'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; interface ModelsDialogProps { open: boolean; onOpenChange: (open: boolean) => void; } +type ViewMode = 'providers' | 'space' | 'local'; + export default function ModelsDialog({ open, onOpenChange, }: ModelsDialogProps) { const { t } = useTranslation(); + const [viewMode, setViewMode] = useState('providers'); const [activeTab, setActiveTab] = useState('llm'); - const [cardList, setCardList] = useState([]); + + // User account type + const [accountType, setAccountType] = useState<'local' | 'space'>('local'); + + // Local models + const [localLLMList, setLocalLLMList] = useState([]); + const [localEmbeddingList, setLocalEmbeddingList] = useState< + EmbeddingCardVO[] + >([]); + + // Space models + const [spaceLLMList, setSpaceLLMList] = useState([]); + const [spaceEmbeddingList, setSpaceEmbeddingList] = useState< + EmbeddingCardVO[] + >([]); + + // Sync state + const [isSyncing, setIsSyncing] = useState(false); + + // Form modals const [modalOpen, setModalOpen] = useState(false); const [isEditForm, setIsEditForm] = useState(false); const [nowSelectedLLM, setNowSelectedLLM] = useState(null); - const [embeddingCardList, setEmbeddingCardList] = useState( - [], - ); const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false); const [isEditEmbeddingForm, setIsEditEmbeddingForm] = useState(false); const [nowSelectedEmbedding, setNowSelectedEmbedding] = useState(null); + // Requester name lists for display + const [llmRequesterNameList, setLLMRequesterNameList] = useState< + { label: string; value: string }[] + >([]); + const [embeddingRequesterNameList, setEmbeddingRequesterNameList] = useState< + { label: string; value: string }[] + >([]); + useEffect(() => { if (open) { - getLLMModelList(); - getEmbeddingModelList(); + loadUserInfo(); + loadRequesterLists(); + loadAllModels(); } }, [open]); - async function getLLMModelList() { - const requesterNameListResp = await httpClient.getProviderRequesters('llm'); - const requesterNameList = requesterNameListResp.requesters.map((item) => { - return { - label: extractI18nObject(item.label), - value: item.name, - }; - }); + async function loadUserInfo() { + try { + const userInfo = await httpClient.getUserInfo(); + setAccountType(userInfo.account_type); + } catch { + // Default to local if user info cannot be fetched + setAccountType('local'); + } + } - httpClient - .getProviderLLMModels() - .then((resp) => { - const llmModelList: LLMCardVO[] = resp.models.map((model: LLMModel) => { - return new LLMCardVO({ + async function loadRequesterLists() { + try { + const llmRequesters = await httpClient.getProviderRequesters('llm'); + setLLMRequesterNameList( + llmRequesters.requesters.map((item) => ({ + label: extractI18nObject(item.label), + value: item.name, + })), + ); + + const embeddingRequesters = + await httpClient.getProviderRequesters('text-embedding'); + setEmbeddingRequesterNameList( + embeddingRequesters.requesters.map((item) => ({ + label: extractI18nObject(item.label), + value: item.name, + })), + ); + } catch (err) { + console.error('Failed to load requester lists', err); + } + } + + async function loadAllModels() { + await Promise.all([loadLLMModels(), loadEmbeddingModels()]); + } + + async function loadLLMModels() { + try { + const resp = await httpClient.getProviderLLMModels(); + const localModels: LLMCardVO[] = []; + const spaceModels: LLMCardVO[] = []; + + resp.models.forEach((model: LLMModel & { source?: string }) => { + const cardVO = new LLMCardVO({ + id: model.uuid, + iconURL: httpClient.getProviderRequesterIconURL(model.requester), + name: model.name, + providerLabel: + llmRequesterNameList.find((item) => item.value === model.requester) + ?.label || model.requester.substring(0, 10), + baseURL: model.requester_config?.base_url, + abilities: model.abilities || [], + }); + + if (model.source === 'space') { + spaceModels.push(cardVO); + } else { + localModels.push(cardVO); + } + }); + + setLocalLLMList(localModels); + setSpaceLLMList(spaceModels); + } catch (err) { + console.error('Failed to load LLM models', err); + toast.error(t('models.getModelListError') + (err as Error).message); + } + } + + async function loadEmbeddingModels() { + try { + const resp = await httpClient.getProviderEmbeddingModels(); + const localModels: EmbeddingCardVO[] = []; + const spaceModels: EmbeddingCardVO[] = []; + + resp.models.forEach( + (model: { + uuid: string; + requester: string; + name: string; + requester_config?: { base_url?: string }; + source?: string; + }) => { + const cardVO = new EmbeddingCardVO({ id: model.uuid, iconURL: httpClient.getProviderRequesterIconURL(model.requester), name: model.name, providerLabel: - requesterNameList.find((item) => item.value === model.requester) - ?.label || model.requester.substring(0, 10), - baseURL: model.requester_config?.base_url, - abilities: model.abilities || [], + embeddingRequesterNameList.find( + (item) => item.value === model.requester, + )?.label || model.requester.substring(0, 10), + baseURL: model.requester_config?.base_url || '', }); - }); - setCardList(llmModelList); - }) - .catch((err) => { - console.error('get LLM model list error', err); - toast.error(t('models.getModelListError') + err.message); - }); + + if (model.source === 'space') { + spaceModels.push(cardVO); + } else { + localModels.push(cardVO); + } + }, + ); + + setLocalEmbeddingList(localModels); + setSpaceEmbeddingList(spaceModels); + } catch (err) { + console.error('Failed to load embedding models', err); + toast.error(t('embedding.getModelListError') + (err as Error).message); + } } - function selectLLM(cardVO: LLMCardVO) { + async function handleSyncSpaceModels() { + setIsSyncing(true); + try { + const stats = await httpClient.syncSpaceModels(); + toast.success( + t('models.syncSuccess', { + created: stats.created_llm + stats.created_embedding, + updated: stats.updated_llm + stats.updated_embedding, + }), + ); + await loadAllModels(); + } catch (err) { + toast.error(t('models.syncError') + (err as Error).message); + } finally { + setIsSyncing(false); + } + } + + function selectLLM(cardVO: LLMCardVO, isSpaceModel: boolean) { + if (isSpaceModel) { + // Space models are read-only, just show info + toast.info(t('models.spaceModelReadOnly')); + return; + } setIsEditForm(true); setNowSelectedLLM(cardVO); setModalOpen(true); } + function handleCreateModelClick() { setIsEditForm(false); setNowSelectedLLM(null); setModalOpen(true); } - function selectEmbedding(cardVO: EmbeddingCardVO) { + + function selectEmbedding(cardVO: EmbeddingCardVO, isSpaceModel: boolean) { + if (isSpaceModel) { + toast.info(t('models.spaceModelReadOnly')); + return; + } setIsEditEmbeddingForm(true); setNowSelectedEmbedding(cardVO); setEmbeddingModalOpen(true); @@ -105,80 +259,113 @@ export default function ModelsDialog({ setNowSelectedEmbedding(null); setEmbeddingModalOpen(true); } - async function getEmbeddingModelList() { - const requesterNameListResp = - await httpClient.getProviderRequesters('text-embedding'); - const requesterNameList = requesterNameListResp.requesters.map((item) => { - return { - label: extractI18nObject(item.label), - value: item.name, - }; - }); - httpClient - .getProviderEmbeddingModels() - .then((resp) => { - const embeddingModelList: EmbeddingCardVO[] = resp.models.map( - (model: { - uuid: string; - requester: string; - name: string; - requester_config?: { base_url?: string }; - }) => { - return new EmbeddingCardVO({ - id: model.uuid, - iconURL: httpClient.getProviderRequesterIconURL(model.requester), - name: model.name, - providerLabel: - requesterNameList.find((item) => item.value === model.requester) - ?.label || model.requester.substring(0, 10), - baseURL: model.requester_config?.base_url || '', - }); - }, - ); - setEmbeddingCardList(embeddingModelList); - }) - .catch((err) => { - console.error('get Embedding model list error', err); - toast.error(t('embedding.getModelListError') + err.message); - }); + function renderProviderCards() { + const isSpaceDisabled = accountType === 'local'; + + return ( +
+ {/* Space Provider Card */} + !isSpaceDisabled && setViewMode('space')} + > + +
+ +
+
+
+ Space + {isSpaceDisabled && ( + + )} +
+ + {isSpaceDisabled + ? t('models.spaceDisabledForLocalAccount') + : t('models.spaceProviderDescription')} + +
+
+ +
+ {spaceLLMList.length} LLM + + {spaceEmbeddingList.length} Embedding + +
+
+
+ + {/* Local Provider Card */} + setViewMode('local')} + > + +
+ +
+
+ {t('models.localProvider')} + + {t('models.localProviderDescription')} + +
+
+ +
+ {localLLMList.length} LLM + + {localEmbeddingList.length} Embedding + +
+
+
+
+ ); } - return ( - <> - { - if (!newOpen && (modalOpen || embeddingModalOpen)) { - return; - } - onOpenChange(newOpen); - }} + function renderModelList( + llmList: LLMCardVO[], + embeddingList: EmbeddingCardVO[], + isSpaceModel: boolean = false, + ) { + return ( + - - - {t('models.title')} - +
+ + + + {t('llm.llmModels')} + + + + {t('embedding.embeddingModels')} + + - -
- - - - {t('llm.llmModels')} - - - - {t('embedding.embeddingModels')} - - +
+ {isSpaceModel ? ( + + ) : (
+ )} +
+
-
- - {activeTab === 'llm' ? ( -

- {t('llm.description')} -

- ) : ( -

- {t('embedding.description')} -

+
+ + {activeTab === 'llm' ? ( +

+ {t('llm.description')} +

+ ) : ( +

+ {t('embedding.description')} +

+ )} +
+ + + {llmList.length === 0 ? ( +
+ {isSpaceModel + ? t('models.noSpaceModels') + : t('models.noLocalModels')} +
+ ) : ( +
+ {llmList.map((cardVO) => ( +
selectLLM(cardVO, isSpaceModel)} + className={isSpaceModel ? 'cursor-default' : 'cursor-pointer'} + > + +
+ ))} +
+ )} +
+ + + {embeddingList.length === 0 ? ( +
+ {isSpaceModel + ? t('models.noSpaceModels') + : t('models.noLocalModels')} +
+ ) : ( +
+ {embeddingList.map((cardVO) => ( +
selectEmbedding(cardVO, isSpaceModel)} + className={isSpaceModel ? 'cursor-default' : 'cursor-pointer'} + > + +
+ ))} +
+ )} +
+ + ); + } + + function getDialogTitle() { + switch (viewMode) { + case 'space': + return 'Space ' + t('models.title'); + case 'local': + return t('models.localProvider') + ' ' + t('models.title'); + default: + return t('models.title'); + } + } + + return ( + <> + { + if (!newOpen && (modalOpen || embeddingModalOpen)) { + return; + } + if (!newOpen) { + setViewMode('providers'); + } + onOpenChange(newOpen); + }} + > + + +
+ {viewMode !== 'providers' && ( + )} + {getDialogTitle()}
+
- -
- {cardList.map((cardVO) => { - return ( -
{ - selectLLM(cardVO); - }} - > - -
- ); - })} -
-
- - -
- {embeddingCardList.map((cardVO) => { - return ( -
{ - selectEmbedding(cardVO); - }} - > - -
- ); - })} -
-
- +
+ {viewMode === 'providers' && renderProviderCards()} + {viewMode === 'space' && + renderModelList(spaceLLMList, spaceEmbeddingList, true)} + {viewMode === 'local' && + renderModelList(localLLMList, localEmbeddingList, false)} +
@@ -259,14 +506,14 @@ export default function ModelsDialog({ initLLMId={nowSelectedLLM?.id} onFormSubmit={() => { setModalOpen(false); - getLLMModelList(); + loadAllModels(); }} onFormCancel={() => { setModalOpen(false); }} onLLMDeleted={() => { setModalOpen(false); - getLLMModelList(); + loadAllModels(); }} /> @@ -286,14 +533,14 @@ export default function ModelsDialog({ initEmbeddingId={nowSelectedEmbedding?.id} onFormSubmit={() => { setEmbeddingModalOpen(false); - getEmbeddingModelList(); + loadAllModels(); }} onFormCancel={() => { setEmbeddingModalOpen(false); }} onEmbeddingDeleted={() => { setEmbeddingModalOpen(false); - getEmbeddingModelList(); + loadAllModels(); }} /> diff --git a/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.tsx b/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.tsx index 8f2dfcd3..43feb9ba 100644 --- a/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.tsx +++ b/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.tsx @@ -1,5 +1,5 @@ import styles from './EmbeddingCard.module.css'; -import { EmbeddingCardVO } from '@/app/home/models/component/embedding-card/EmbeddingCardVO'; +import { EmbeddingCardVO } from './EmbeddingCardVO'; export default function EmbeddingCard({ cardVO }: { cardVO: EmbeddingCardVO }) { return ( diff --git a/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx b/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx index 41eeb635..00399359 100644 --- a/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx +++ b/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx @@ -1,6 +1,6 @@ -import { ICreateEmbeddingField } from '@/app/home/models/component/ICreateEmbeddingField'; +import { ICreateEmbeddingField } from '../ICreateEmbeddingField'; import { useEffect, useState } from 'react'; -import { IChooseRequesterEntity } from '@/app/home/models/component/ChooseRequesterEntity'; +import { IChooseRequesterEntity } from '../ChooseRequesterEntity'; import { httpClient } from '@/app/infra/http/HttpClient'; import { EmbeddingModel } from '@/app/infra/entities/api'; import { UUID } from 'uuidjs'; diff --git a/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx b/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx index 90dc3fab..5cca970a 100644 --- a/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx +++ b/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx @@ -1,5 +1,5 @@ import styles from './LLMCard.module.css'; -import { LLMCardVO } from '@/app/home/models/component/llm-card/LLMCardVO'; +import { LLMCardVO } from './LLMCardVO'; import { useTranslation } from 'react-i18next'; function AbilityBadges(abilities: string[]) { diff --git a/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx b/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx index 35b5757f..fe0d21b2 100644 --- a/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx +++ b/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx @@ -1,6 +1,6 @@ -import { ICreateLLMField } from '@/app/home/models/component/ICreateLLMField'; +import { ICreateLLMField } from '../ICreateLLMField'; import { useEffect, useState } from 'react'; -import { IChooseRequesterEntity } from '@/app/home/models/component/ChooseRequesterEntity'; +import { IChooseRequesterEntity } from '../ChooseRequesterEntity'; import { httpClient } from '@/app/infra/http/HttpClient'; import { LLMModel } from '@/app/infra/entities/api'; import { UUID } from 'uuidjs'; diff --git a/web/src/app/infra/http/BackendClient.ts b/web/src/app/infra/http/BackendClient.ts index 8dd7f1a0..541ec02b 100644 --- a/web/src/app/infra/http/BackendClient.ts +++ b/web/src/app/infra/http/BackendClient.ts @@ -688,4 +688,89 @@ export class BackendClient extends BaseHttpClient { new_password: newPassword, }); } + + public getUserInfo(): Promise<{ + user: string; + account_type: 'local' | 'space'; + }> { + return this.get('/api/v1/user/info'); + } + + // ============ Space OAuth API (Redirect Flow) ============ + public getSpaceAuthorizeUrl( + redirectUri: string, + state?: string, + ): Promise<{ + authorize_url: string; + }> { + const params: Record = { redirect_uri: redirectUri }; + if (state) { + params.state = state; + } + return this.get('/api/v1/user/space/authorize-url', params); + } + + public exchangeSpaceOAuthCode(code: string): Promise<{ + token: string; + user: string; + }> { + return this.post('/api/v1/user/space/callback', { code }); + } + + // ============ Space Models Sync API ============ + public syncSpaceModels(spaceUrl?: string): Promise<{ + created_llm: number; + updated_llm: number; + created_embedding: number; + updated_embedding: number; + skipped: number; + }> { + return this.post('/api/v1/space/models/sync', { space_url: spaceUrl }); + } + + public getSpaceModels(): Promise<{ + llm_models: Array<{ + uuid: string; + name: string; + description: string; + requester: string; + space_model_id: string; + source: string; + }>; + embedding_models: Array<{ + uuid: string; + name: string; + description: string; + requester: string; + space_model_id: string; + source: string; + }>; + }> { + return this.get('/api/v1/space/models'); + } + + public deleteSpaceModels(): Promise<{ + deleted_llm: number; + deleted_embedding: number; + }> { + return this.delete('/api/v1/space/models'); + } + + public getAvailableSpaceModels(spaceUrl?: string): Promise<{ + models: Array<{ + model_id: string; + display_name: { [key: string]: string }; + description: { [key: string]: string }; + category: string; + provider: string; + }>; + vendors: Array<{ + id: number; + name: string; + }>; + total: number; + }> { + const params = spaceUrl ? { space_url: spaceUrl } : {}; + return this.get('/api/v1/space/models/available', params); + } } diff --git a/web/src/app/login/page.tsx b/web/src/app/login/page.tsx index b47e24f4..60d0e118 100644 --- a/web/src/app/login/page.tsx +++ b/web/src/app/login/page.tsx @@ -20,10 +20,10 @@ import { FormLabel, FormMessage, } from '@/components/ui/form'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { httpClient } from '@/app/infra/http/HttpClient'; import { useRouter } from 'next/navigation'; -import { Mail, Lock } from 'lucide-react'; +import { Mail, Lock, Loader2 } from 'lucide-react'; import langbotIcon from '@/app/assets/langbot-logo.webp'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; @@ -39,6 +39,7 @@ const formSchema = (t: (key: string) => string) => export default function Login() { const router = useRouter(); const { t } = useTranslation(); + const [spaceLoading, setSpaceLoading] = useState(false); const form = useForm>>({ resolver: zodResolver(formSchema(t)), @@ -75,6 +76,7 @@ export default function Login() { }) .catch(() => {}); } + function onSubmit(values: z.infer>) { handleLogin(values.email, values.password); } @@ -93,6 +95,26 @@ export default function Login() { }); } + // Space OAuth redirect handler + const handleSpaceLoginClick = async () => { + setSpaceLoading(true); + + try { + // Build the redirect URI to the OAuth callback page + const currentOrigin = window.location.origin; + const redirectUri = `${currentOrigin}/auth/space/callback`; + + // Get the authorization URL from backend + const response = await httpClient.getSpaceAuthorizeUrl(redirectUri); + + // Redirect to Space authorization page + window.location.href = response.authorize_url; + } catch { + toast.error(t('common.spaceLoginFailed')); + setSpaceLoading(false); + } + }; + return (
@@ -113,7 +135,66 @@ export default function Login() { {t('common.continueToLogin')} - + + {/* Space Login - Recommended */} +
+ +

+ {t('common.spaceLoginRecommended')} +

+
+ +
+
+ +
+
+ + {t('common.or')} + +
+
+ + {/* Local Account Login */}
- diff --git a/web/src/app/register/page.tsx b/web/src/app/register/page.tsx index e0ca6aee..64c89356 100644 --- a/web/src/app/register/page.tsx +++ b/web/src/app/register/page.tsx @@ -20,10 +20,10 @@ import { FormLabel, FormMessage, } from '@/components/ui/form'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { httpClient } from '@/app/infra/http/HttpClient'; import { useRouter } from 'next/navigation'; -import { Mail, Lock } from 'lucide-react'; +import { Mail, Lock, Loader2 } from 'lucide-react'; import langbotIcon from '@/app/assets/langbot-logo.webp'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; @@ -38,6 +38,7 @@ const formSchema = (t: (key: string) => string) => export default function Register() { const router = useRouter(); const { t } = useTranslation(); + const [spaceLoading, setSpaceLoading] = useState(false); const form = useForm>>({ resolver: zodResolver(formSchema(t)), @@ -78,6 +79,26 @@ export default function Register() { }); } + // Space OAuth redirect handler + const handleSpaceLoginClick = async () => { + setSpaceLoading(true); + + try { + // Build the redirect URI to the OAuth callback page + const currentOrigin = window.location.origin; + const redirectUri = `${currentOrigin}/auth/space/callback`; + + // Get the authorization URL from backend + const response = await httpClient.getSpaceAuthorizeUrl(redirectUri); + + // Redirect to Space authorization page + window.location.href = response.authorize_url; + } catch { + toast.error(t('common.spaceLoginFailed')); + setSpaceLoading(false); + } + }; + return (
@@ -100,7 +121,66 @@ export default function Register() { {t('register.adminAccountNote')} - + + {/* Space Login - Recommended */} +
+ +

+ {t('register.spaceRecommended')} +

+
+ +
+
+ +
+
+ + {t('common.or')} + +
+
+ + {/* Local Account Registration */}
- diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index b53c9d34..1463d501 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -47,6 +47,30 @@ const enUS = { test: 'Test', forgotPassword: 'Forgot Password?', loading: 'Loading...', + or: 'or', + loginWithSpace: 'Login with Space', + spaceLoginRecommended: 'Recommended: Sync models and credits from Space', + loginLocal: 'Login with local account', + spaceLoginTitle: 'Login with Space', + spaceLoginDescription: + 'Scan the QR code or visit the link below to authorize', + spaceLoginUserCode: 'Your code', + spaceLoginExpires: 'Code expires in {{seconds}} seconds', + spaceLoginWaiting: 'Waiting for authorization...', + spaceLoginSuccess: 'Authorization successful', + spaceLoginFailed: 'Space login failed', + spaceLoginExpired: 'Authorization code expired, please try again', + spaceLoginCancel: 'Cancel', + spaceLoginVisitLink: 'Visit link', + spaceLoginProcessing: 'Logging in with Space', + spaceLoginProcessingDescription: + 'Please wait while we complete your login...', + spaceLoginSuccessDescription: 'Redirecting to LangBot...', + spaceLoginError: 'Login Failed', + spaceLoginNoCode: 'Missing authorization code', + backToLogin: 'Back to Login', + spaceAccountCannotChangePassword: + 'Space accounts cannot change password here', theme: 'Theme', changePassword: 'Change Password', currentPassword: 'Current Password', @@ -152,6 +176,16 @@ const enUS = { testSuccess: 'Test successful', testError: 'Test failed, please check your model configuration', llmModels: 'LLM Models', + localProvider: 'Local', + localProviderDescription: 'Models configured and managed locally', + spaceProviderDescription: 'Models synced from your Space account', + spaceDisabledForLocalAccount: 'Login with Space to use cloud models', + syncModels: 'Sync', + syncSuccess: 'Sync complete: {{created}} created, {{updated}} updated', + syncError: 'Sync failed: ', + spaceModelReadOnly: 'Space models are read-only', + noSpaceModels: 'No Space models. Click Sync to fetch models from Space.', + noLocalModels: 'No local models. Click Create to add a model.', }, bots: { title: 'Bots', @@ -640,6 +674,9 @@ const enUS = { adminAccountNote: 'The email and password you fill in will be used as the initial administrator account', register: 'Register', + initWithSpace: 'Initialize with Space', + spaceRecommended: 'Recommended: Sync models and credits from Space', + registerLocal: 'Register local account', initSuccess: 'Initialization successful, please login', initFailed: 'Initialization failed: ', }, diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 9d88f54d..2e435495 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -48,6 +48,31 @@ const jaJP = { test: 'テスト', forgotPassword: 'パスワードを忘れた?', loading: '読み込み中...', + or: 'または', + loginWithSpace: 'Space でログイン', + spaceLoginRecommended: 'おすすめ:Space からモデルとクレジットを同期', + loginLocal: 'ローカルアカウントでログイン', + spaceLoginTitle: 'Space でログイン', + spaceLoginDescription: + 'QRコードをスキャンするか、下のリンクにアクセスして認証してください', + spaceLoginUserCode: '認証コード', + spaceLoginExpires: 'コードは {{seconds}} 秒後に期限切れになります', + spaceLoginWaiting: '認証を待っています...', + spaceLoginSuccess: '認証に成功しました', + spaceLoginFailed: 'Space ログインに失敗しました', + spaceLoginExpired: + '認証コードの有効期限が切れました。もう一度お試しください', + spaceLoginCancel: 'キャンセル', + spaceLoginVisitLink: 'リンクにアクセス', + spaceLoginProcessing: 'Space でログイン中', + spaceLoginProcessingDescription: + 'ログインを完了しています。しばらくお待ちください...', + spaceLoginSuccessDescription: 'LangBot にリダイレクト中...', + spaceLoginError: 'ログインに失敗しました', + spaceLoginNoCode: '認証コードがありません', + backToLogin: 'ログインに戻る', + spaceAccountCannotChangePassword: + 'Space アカウントはここでパスワードを変更できません', theme: 'テーマ', changePassword: 'パスワードを変更', currentPassword: '現在のパスワード', @@ -154,6 +179,19 @@ const jaJP = { selectModel: 'モデルを選択してください', testSuccess: 'テストに成功しました', testError: 'テストに失敗しました。モデル設定を確認してください', + llmModels: 'LLM モデル', + localProvider: 'ローカル', + localProviderDescription: 'ローカルで設定・管理されているモデル', + spaceProviderDescription: 'Space アカウントから同期されたモデル', + spaceDisabledForLocalAccount: 'Space でログインしてクラウドモデルを使用', + syncModels: '同期', + syncSuccess: '同期完了:{{created}} 件作成、{{updated}} 件更新', + syncError: '同期に失敗しました:', + spaceModelReadOnly: 'Space モデルは読み取り専用です', + noSpaceModels: + 'Space モデルがありません。同期ボタンをクリックして Space からモデルを取得してください。', + noLocalModels: + 'ローカルモデルがありません。作成ボタンをクリックしてモデルを追加してください。', }, bots: { title: 'ボット', @@ -645,6 +683,9 @@ const jaJP = { adminAccountNote: '入力したメールアドレスとパスワードが初期管理者アカウントになります', register: '登録', + initWithSpace: 'Space で初期化', + spaceRecommended: 'おすすめ:Space からモデルとクレジットを同期', + registerLocal: 'ローカルアカウントを登録', initSuccess: '初期化に成功しました。ログインしてください', initFailed: '初期化に失敗しました:', }, diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 9756dd93..1767bcf5 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -47,6 +47,27 @@ const zhHans = { test: '测试', forgotPassword: '忘记密码?', loading: '加载中...', + or: '或', + loginWithSpace: '通过 Space 登录', + spaceLoginRecommended: '推荐:从 Space 同步模型和点数', + loginLocal: '使用本地账号登录', + spaceLoginTitle: '通过 Space 登录', + spaceLoginDescription: '扫描二维码或访问下方链接进行授权', + spaceLoginUserCode: '您的验证码', + spaceLoginExpires: '验证码将在 {{seconds}} 秒后过期', + spaceLoginWaiting: '等待授权中...', + spaceLoginSuccess: '授权成功', + spaceLoginFailed: 'Space 登录失败', + spaceLoginExpired: '验证码已过期,请重试', + spaceLoginCancel: '取消', + spaceLoginVisitLink: '访问链接', + spaceLoginProcessing: '正在通过 Space 登录', + spaceLoginProcessingDescription: '请稍候,正在完成登录...', + spaceLoginSuccessDescription: '正在跳转到 LangBot...', + spaceLoginError: '登录失败', + spaceLoginNoCode: '缺少授权码', + backToLogin: '返回登录', + spaceAccountCannotChangePassword: 'Space 账户无法在此修改密码', theme: '主题', changePassword: '修改密码', currentPassword: '当前密码', @@ -149,6 +170,16 @@ const zhHans = { testSuccess: '测试成功', testError: '测试失败,请检查模型配置', llmModels: '对话模型', + localProvider: '本地', + localProviderDescription: '在本地配置和管理的模型', + spaceProviderDescription: '从您的 Space 账户同步的模型', + spaceDisabledForLocalAccount: '使用 Space 登录以使用云端模型', + syncModels: '同步', + syncSuccess: '同步完成:创建 {{created}} 个,更新 {{updated}} 个', + syncError: '同步失败:', + spaceModelReadOnly: 'Space 模型为只读', + noSpaceModels: '暂无 Space 模型。点击同步按钮从 Space 获取模型。', + noLocalModels: '暂无本地模型。点击创建按钮添加模型。', }, bots: { title: '机器人', @@ -616,6 +647,9 @@ const zhHans = { description: '这是您首次启动 LangBot', adminAccountNote: '您填写的邮箱和密码将作为初始管理员账号', register: '注册', + initWithSpace: '通过 Space 初始化', + spaceRecommended: '推荐:从 Space 同步模型和点数', + registerLocal: '注册本地账号', initSuccess: '初始化成功 请登录', initFailed: '初始化失败:', }, diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index dcbbe1d4..55af4d62 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -47,6 +47,27 @@ const zhHant = { test: '測試', forgotPassword: '忘記密碼?', loading: '載入中...', + or: '或', + loginWithSpace: '透過 Space 登入', + spaceLoginRecommended: '推薦:從 Space 同步模型和點數', + loginLocal: '使用本地帳號登入', + spaceLoginTitle: '透過 Space 登入', + spaceLoginDescription: '掃描二維碼或訪問下方連結進行授權', + spaceLoginUserCode: '您的驗證碼', + spaceLoginExpires: '驗證碼將在 {{seconds}} 秒後過期', + spaceLoginWaiting: '等待授權中...', + spaceLoginSuccess: '授權成功', + spaceLoginFailed: 'Space 登入失敗', + spaceLoginExpired: '驗證碼已過期,請重試', + spaceLoginCancel: '取消', + spaceLoginVisitLink: '訪問連結', + spaceLoginProcessing: '正在透過 Space 登入', + spaceLoginProcessingDescription: '請稍候,正在完成登入...', + spaceLoginSuccessDescription: '正在跳轉到 LangBot...', + spaceLoginError: '登入失敗', + spaceLoginNoCode: '缺少授權碼', + backToLogin: '返回登入', + spaceAccountCannotChangePassword: 'Space 帳戶無法在此修改密碼', theme: '主題', changePassword: '修改密碼', currentPassword: '當前密碼', @@ -149,6 +170,16 @@ const zhHant = { testSuccess: '測試成功', testError: '測試失敗,請檢查模型設定', llmModels: '對話模型', + localProvider: '本地', + localProviderDescription: '在本地設定和管理的模型', + spaceProviderDescription: '從您的 Space 帳戶同步的模型', + spaceDisabledForLocalAccount: '使用 Space 登入以使用雲端模型', + syncModels: '同步', + syncSuccess: '同步完成:建立 {{created}} 個,更新 {{updated}} 個', + syncError: '同步失敗:', + spaceModelReadOnly: 'Space 模型為唯讀', + noSpaceModels: '暫無 Space 模型。點擊同步按鈕從 Space 取得模型。', + noLocalModels: '暫無本地模型。點擊建立按鈕新增模型。', }, bots: { title: '機器人', @@ -614,6 +645,9 @@ const zhHant = { description: '這是您首次啟動 LangBot', adminAccountNote: '您填寫的電子郵件和密碼將作為初始管理員帳號', register: '註冊', + initWithSpace: '透過 Space 初始化', + spaceRecommended: '推薦:從 Space 同步模型和點數', + registerLocal: '註冊本地帳號', initSuccess: '初始化成功 請登入', initFailed: '初始化失敗:', }, From 455e3db28d72d77b6e9d45e12354d644dd9299ad Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Fri, 26 Dec 2025 00:49:35 +0800 Subject: [PATCH 03/30] feat: add Radix UI collapsible component for enhanced UI interactions --- web/package.json | 1 + web/pnpm-lock.yaml | 30 ++++++++++++++++++++++++ web/src/components/ui/collapsible.tsx | 33 +++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 web/src/components/ui/collapsible.tsx diff --git a/web/package.json b/web/package.json index 90f25b55..1b3e04f4 100644 --- a/web/package.json +++ b/web/package.json @@ -25,6 +25,7 @@ "@hookform/resolvers": "^5.0.1", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.1", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-context-menu": "^2.2.15", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.16", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 45ec69d1..f60ecc2d 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: '@radix-ui/react-checkbox': specifier: ^1.3.1 version: 1.3.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-collapsible': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-context-menu': specifier: ^2.2.15 version: 2.2.16(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) @@ -917,6 +920,33 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + dev: false + /@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1): resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: diff --git a/web/src/components/ui/collapsible.tsx b/web/src/components/ui/collapsible.tsx new file mode 100644 index 00000000..672136a8 --- /dev/null +++ b/web/src/components/ui/collapsible.tsx @@ -0,0 +1,33 @@ +'use client'; + +import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; + +function Collapsible({ + ...props +}: React.ComponentProps) { + return ; +} + +function CollapsibleTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function CollapsibleContent({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; From 57fcec011d8518695034dc482c19c247468a7c08 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Fri, 26 Dec 2025 20:27:33 +0800 Subject: [PATCH 04/30] feat: refactor model management to introduce provider structure, enhancing model organization and retrieval --- .../http/controller/groups/provider/models.py | 18 +- .../controller/groups/provider/providers.py | 45 + .../pkg/api/http/controller/groups/space.py | 52 - src/langbot/pkg/api/http/service/model.py | 191 +++- src/langbot/pkg/api/http/service/provider.py | 152 +++ .../pkg/api/http/service/space_models.py | 247 ----- src/langbot/pkg/core/app.py | 6 +- src/langbot/pkg/core/stages/build_app.py | 8 +- src/langbot/pkg/entity/persistence/model.py | 39 +- .../dbm016_model_provider_refactor.py | 286 +++++ src/langbot/pkg/provider/modelmgr/modelmgr.py | 216 +++- src/langbot/pkg/utils/constants.py | 2 +- .../dynamic-form/DynamicFormItemComponent.tsx | 130 +-- .../components/models-dialog/ModelsDialog.tsx | 885 ++++++++-------- .../embedding-form/EmbeddingForm.tsx | 890 ++++++++-------- .../component/llm-form/LLMForm.tsx | 989 ++++++++---------- .../component/provider-form/ProviderForm.tsx | 242 +++++ .../knowledge/components/kb-form/KBForm.tsx | 119 +-- web/src/app/infra/entities/api/index.ts | 46 +- web/src/app/infra/http/BackendClient.ts | 101 +- web/src/i18n/locales/en-US.ts | 30 + web/src/i18n/locales/ja-JP.ts | 29 + web/src/i18n/locales/zh-Hans.ts | 30 + web/src/i18n/locales/zh-Hant.ts | 29 + 24 files changed, 2676 insertions(+), 2106 deletions(-) create mode 100644 src/langbot/pkg/api/http/controller/groups/provider/providers.py delete mode 100644 src/langbot/pkg/api/http/controller/groups/space.py create mode 100644 src/langbot/pkg/api/http/service/provider.py delete mode 100644 src/langbot/pkg/api/http/service/space_models.py create mode 100644 src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py create mode 100644 web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx diff --git a/src/langbot/pkg/api/http/controller/groups/provider/models.py b/src/langbot/pkg/api/http/controller/groups/provider/models.py index 25f16995..cec582ee 100644 --- a/src/langbot/pkg/api/http/controller/groups/provider/models.py +++ b/src/langbot/pkg/api/http/controller/groups/provider/models.py @@ -9,12 +9,15 @@ class LLMModelsRouterGroup(group.RouterGroup): @self.route('', methods=['GET', 'POST'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY) async def _() -> str: if quart.request.method == 'GET': + provider_uuid = quart.request.args.get('provider_uuid') + if provider_uuid: + return self.success( + data={'models': await self.ap.llm_model_service.get_llm_models_by_provider(provider_uuid)} + ) return self.success(data={'models': await self.ap.llm_model_service.get_llm_models()}) elif quart.request.method == 'POST': json_data = await quart.request.json - model_uuid = await self.ap.llm_model_service.create_llm_model(json_data) - return self.success(data={'uuid': model_uuid}) @self.route('/', methods=['GET', 'PUT', 'DELETE'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY) @@ -52,12 +55,19 @@ class EmbeddingModelsRouterGroup(group.RouterGroup): @self.route('', methods=['GET', 'POST'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY) async def _() -> str: if quart.request.method == 'GET': + provider_uuid = quart.request.args.get('provider_uuid') + if provider_uuid: + return self.success( + data={ + 'models': await self.ap.embedding_models_service.get_embedding_models_by_provider( + provider_uuid + ) + } + ) return self.success(data={'models': await self.ap.embedding_models_service.get_embedding_models()}) elif quart.request.method == 'POST': json_data = await quart.request.json - model_uuid = await self.ap.embedding_models_service.create_embedding_model(json_data) - return self.success(data={'uuid': model_uuid}) @self.route('/', methods=['GET', 'PUT', 'DELETE'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY) diff --git a/src/langbot/pkg/api/http/controller/groups/provider/providers.py b/src/langbot/pkg/api/http/controller/groups/provider/providers.py new file mode 100644 index 00000000..b28bb3e5 --- /dev/null +++ b/src/langbot/pkg/api/http/controller/groups/provider/providers.py @@ -0,0 +1,45 @@ +import quart + +from ... import group + + +@group.group_class('models/providers', '/api/v1/provider/providers') +class ModelProvidersRouterGroup(group.RouterGroup): + async def initialize(self) -> None: + @self.route('', methods=['GET', 'POST'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY) + async def _() -> str: + if quart.request.method == 'GET': + providers = await self.ap.provider_service.get_providers() + # Add model counts + for provider in providers: + counts = await self.ap.provider_service.get_provider_model_counts(provider['uuid']) + provider['llm_count'] = counts['llm_count'] + provider['embedding_count'] = counts['embedding_count'] + return self.success(data={'providers': providers}) + elif quart.request.method == 'POST': + json_data = await quart.request.json + provider_uuid = await self.ap.provider_service.create_provider(json_data) + return self.success(data={'uuid': provider_uuid}) + + @self.route( + '/', methods=['GET', 'PUT', 'DELETE'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY + ) + async def _(provider_uuid: str) -> str: + if quart.request.method == 'GET': + provider = await self.ap.provider_service.get_provider(provider_uuid) + if provider is None: + return self.http_status(404, -1, 'provider not found') + counts = await self.ap.provider_service.get_provider_model_counts(provider_uuid) + provider['llm_count'] = counts['llm_count'] + provider['embedding_count'] = counts['embedding_count'] + return self.success(data={'provider': provider}) + elif quart.request.method == 'PUT': + json_data = await quart.request.json + await self.ap.provider_service.update_provider(provider_uuid, json_data) + return self.success() + elif quart.request.method == 'DELETE': + try: + await self.ap.provider_service.delete_provider(provider_uuid) + return self.success() + except ValueError as e: + return self.http_status(400, -1, str(e)) diff --git a/src/langbot/pkg/api/http/controller/groups/space.py b/src/langbot/pkg/api/http/controller/groups/space.py deleted file mode 100644 index cefdce19..00000000 --- a/src/langbot/pkg/api/http/controller/groups/space.py +++ /dev/null @@ -1,52 +0,0 @@ -import quart - -from .. import group - - -DEFAULT_SPACE_URL = 'https://space.langbot.app' - - -@group.group_class('space', '/api/v1/space') -class SpaceRouterGroup(group.RouterGroup): - async def initialize(self) -> None: - @self.route('/models/sync', methods=['POST'], auth_type=group.AuthType.USER_TOKEN) - async def _(user_email: str) -> str: - """Sync models from Space MaaS to local database""" - json_data = await quart.request.json or {} - space_url = json_data.get('space_url', DEFAULT_SPACE_URL) - - try: - stats = await self.ap.space_models_service.sync_models_from_space(user_email, space_url) - return self.success(data=stats) - except ValueError as e: - return self.fail(1, str(e)) - except Exception as e: - return self.fail(2, f'Failed to sync models: {str(e)}') - - @self.route('/models', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) - async def _(user_email: str) -> str: - """Get all synced Space models""" - if quart.request.method == 'GET': - try: - models = await self.ap.space_models_service.get_space_models() - return self.success(data=models) - except Exception as e: - return self.fail(1, f'Failed to get Space models: {str(e)}') - elif quart.request.method == 'DELETE': - try: - stats = await self.ap.space_models_service.delete_space_models() - return self.success(data=stats) - except Exception as e: - return self.fail(1, f'Failed to delete Space models: {str(e)}') - - @self.route('/models/available', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) - async def _(user_email: str) -> str: - """Get available models from Space (preview before sync)""" - try: - space_url = quart.request.args.get('space_url', DEFAULT_SPACE_URL) - models_data = await self.ap.space_models_service.fetch_space_models(space_url) - return self.success(data=models_data) - except ValueError as e: - return self.fail(1, str(e)) - except Exception as e: - return self.fail(2, f'Failed to fetch available models: {str(e)}') diff --git a/src/langbot/pkg/api/http/service/model.py b/src/langbot/pkg/api/http/service/model.py index 288f07ad..03f42e3d 100644 --- a/src/langbot/pkg/api/http/service/model.py +++ b/src/langbot/pkg/api/http/service/model.py @@ -11,6 +11,18 @@ from ....entity.persistence import pipeline as persistence_pipeline from ....provider.modelmgr import requester as model_requester +def _parse_provider_api_keys(provider_dict: dict) -> dict: + """Parse api_keys if it's a JSON string""" + if isinstance(provider_dict.get('api_keys'), str): + import json + + try: + provider_dict['api_keys'] = json.loads(provider_dict['api_keys']) + except Exception: + provider_dict['api_keys'] = [] + return provider_dict + + class LLMModelsService: ap: app.Application @@ -18,29 +30,64 @@ class LLMModelsService: self.ap = ap async def get_llm_models(self, include_secret: bool = True) -> list[dict]: + """Get all LLM models with provider info""" result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_model.LLMModel)) - models = result.all() - masked_columns = [] - if not include_secret: - masked_columns = ['api_keys'] + # Get all providers for lookup + providers_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.ModelProvider) + ) + providers = {p.uuid: p for p in providers_result.all()} - return [ - self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, model, masked_columns) - for model in models - ] + models_list = [] + for model in models: + model_dict = self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, model) + provider = providers.get(model.provider_uuid) + if provider: + provider_dict = self.ap.persistence_mgr.serialize_model(persistence_model.ModelProvider, provider) + provider_dict = _parse_provider_api_keys(provider_dict) + if not include_secret: + provider_dict['api_keys'] = ['***'] * len(provider_dict.get('api_keys', [])) + model_dict['provider'] = provider_dict + models_list.append(model_dict) + + return models_list + + async def get_llm_models_by_provider(self, provider_uuid: str) -> list[dict]: + """Get LLM models by provider UUID""" + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.LLMModel).where( + persistence_model.LLMModel.provider_uuid == provider_uuid + ) + ) + models = result.all() + return [self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, m) for m in models] async def create_llm_model(self, model_data: dict) -> str: + """Create a new LLM model""" model_data['uuid'] = str(uuid.uuid4()) + # Handle provider creation if needed + if 'provider' in model_data: + provider_data = model_data.pop('provider') + if provider_data.get('uuid'): + model_data['provider_uuid'] = provider_data['uuid'] + else: + # Create new provider + provider_uuid = await self.ap.provider_service.find_or_create_provider( + requester=provider_data.get('requester', ''), + base_url=provider_data.get('base_url', ''), + api_keys=provider_data.get('api_keys', []), + ) + model_data['provider_uuid'] = provider_uuid + await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_model.LLMModel).values(**model_data)) llm_model = await self.get_llm_model(model_data['uuid']) - await self.ap.model_mgr.load_llm_model(llm_model) - # check if default pipeline has no model bound + # Check if default pipeline has no model bound result = await self.ap.persistence_mgr.execute_async( sqlalchemy.select(persistence_pipeline.LegacyPipeline).where( persistence_pipeline.LegacyPipeline.is_default == True @@ -56,21 +103,47 @@ class LLMModelsService: return model_data['uuid'] async def get_llm_model(self, model_uuid: str) -> dict | None: + """Get a single LLM model with provider info""" result = await self.ap.persistence_mgr.execute_async( sqlalchemy.select(persistence_model.LLMModel).where(persistence_model.LLMModel.uuid == model_uuid) ) - model = result.first() - if model is None: return None - return self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, model) + model_dict = self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, model) + + # Get provider + provider_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.ModelProvider).where( + persistence_model.ModelProvider.uuid == model.provider_uuid + ) + ) + provider = provider_result.first() + if provider: + provider_dict = self.ap.persistence_mgr.serialize_model(persistence_model.ModelProvider, provider) + model_dict['provider'] = _parse_provider_api_keys(provider_dict) + + return model_dict async def update_llm_model(self, model_uuid: str, model_data: dict) -> None: + """Update an existing LLM model""" if 'uuid' in model_data: del model_data['uuid'] + # Handle provider update if needed + if 'provider' in model_data: + provider_data = model_data.pop('provider') + if provider_data.get('uuid'): + model_data['provider_uuid'] = provider_data['uuid'] + else: + provider_uuid = await self.ap.provider_service.find_or_create_provider( + requester=provider_data.get('requester', ''), + base_url=provider_data.get('base_url', ''), + api_keys=provider_data.get('api_keys', []), + ) + model_data['provider_uuid'] = provider_uuid + await self.ap.persistence_mgr.execute_async( sqlalchemy.update(persistence_model.LLMModel) .where(persistence_model.LLMModel.uuid == model_uuid) @@ -78,19 +151,18 @@ class LLMModelsService: ) await self.ap.model_mgr.remove_llm_model(model_uuid) - llm_model = await self.get_llm_model(model_uuid) - await self.ap.model_mgr.load_llm_model(llm_model) async def delete_llm_model(self, model_uuid: str) -> None: + """Delete an LLM model""" await self.ap.persistence_mgr.execute_async( sqlalchemy.delete(persistence_model.LLMModel).where(persistence_model.LLMModel.uuid == model_uuid) ) - await self.ap.model_mgr.remove_llm_model(model_uuid) async def test_llm_model(self, model_uuid: str, model_data: dict) -> None: + """Test an LLM model""" runtime_llm_model: model_requester.RuntimeLLMModel | None = None if model_uuid != '_': @@ -98,18 +170,11 @@ class LLMModelsService: if model.model_entity.uuid == model_uuid: runtime_llm_model = model break - if runtime_llm_model is None: raise Exception('model not found') - else: runtime_llm_model = await self.ap.model_mgr.init_runtime_llm_model(model_data) - # Mon Nov 10 2025: Commented for some providers may not support thinking parameter - # # 有些模型厂商默认开启了思考功能,测试容易延迟 - # extra_args = model_data.get('extra_args', {}) - # if not extra_args or 'thinking' not in extra_args: - # extra_args['thinking'] = {'type': 'disabled'} extra_args = model_data.get('extra_args', {}) await runtime_llm_model.requester.invoke_llm( query=None, @@ -127,42 +192,103 @@ class EmbeddingModelsService: self.ap = ap async def get_embedding_models(self) -> list[dict]: + """Get all embedding models with provider info""" result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_model.EmbeddingModel)) - models = result.all() - return [self.ap.persistence_mgr.serialize_model(persistence_model.EmbeddingModel, model) for model in models] + + providers_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.ModelProvider) + ) + providers = {p.uuid: p for p in providers_result.all()} + + models_list = [] + for model in models: + model_dict = self.ap.persistence_mgr.serialize_model(persistence_model.EmbeddingModel, model) + provider = providers.get(model.provider_uuid) + if provider: + provider_dict = self.ap.persistence_mgr.serialize_model(persistence_model.ModelProvider, provider) + model_dict['provider'] = _parse_provider_api_keys(provider_dict) + models_list.append(model_dict) + + return models_list + + async def get_embedding_models_by_provider(self, provider_uuid: str) -> list[dict]: + """Get embedding models by provider UUID""" + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.EmbeddingModel).where( + persistence_model.EmbeddingModel.provider_uuid == provider_uuid + ) + ) + models = result.all() + return [self.ap.persistence_mgr.serialize_model(persistence_model.EmbeddingModel, m) for m in models] async def create_embedding_model(self, model_data: dict) -> str: + """Create a new embedding model""" model_data['uuid'] = str(uuid.uuid4()) + if 'provider' in model_data: + provider_data = model_data.pop('provider') + if provider_data.get('uuid'): + model_data['provider_uuid'] = provider_data['uuid'] + else: + provider_uuid = await self.ap.provider_service.find_or_create_provider( + requester=provider_data.get('requester', ''), + base_url=provider_data.get('base_url', ''), + api_keys=provider_data.get('api_keys', []), + ) + model_data['provider_uuid'] = provider_uuid + await self.ap.persistence_mgr.execute_async( sqlalchemy.insert(persistence_model.EmbeddingModel).values(**model_data) ) embedding_model = await self.get_embedding_model(model_data['uuid']) - await self.ap.model_mgr.load_embedding_model(embedding_model) return model_data['uuid'] async def get_embedding_model(self, model_uuid: str) -> dict | None: + """Get a single embedding model with provider info""" result = await self.ap.persistence_mgr.execute_async( sqlalchemy.select(persistence_model.EmbeddingModel).where( persistence_model.EmbeddingModel.uuid == model_uuid ) ) - model = result.first() - if model is None: return None - return self.ap.persistence_mgr.serialize_model(persistence_model.EmbeddingModel, model) + model_dict = self.ap.persistence_mgr.serialize_model(persistence_model.EmbeddingModel, model) + + provider_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.ModelProvider).where( + persistence_model.ModelProvider.uuid == model.provider_uuid + ) + ) + provider = provider_result.first() + if provider: + provider_dict = self.ap.persistence_mgr.serialize_model(persistence_model.ModelProvider, provider) + model_dict['provider'] = _parse_provider_api_keys(provider_dict) + + return model_dict async def update_embedding_model(self, model_uuid: str, model_data: dict) -> None: + """Update an existing embedding model""" if 'uuid' in model_data: del model_data['uuid'] + if 'provider' in model_data: + provider_data = model_data.pop('provider') + if provider_data.get('uuid'): + model_data['provider_uuid'] = provider_data['uuid'] + else: + provider_uuid = await self.ap.provider_service.find_or_create_provider( + requester=provider_data.get('requester', ''), + base_url=provider_data.get('base_url', ''), + api_keys=provider_data.get('api_keys', []), + ) + model_data['provider_uuid'] = provider_uuid + await self.ap.persistence_mgr.execute_async( sqlalchemy.update(persistence_model.EmbeddingModel) .where(persistence_model.EmbeddingModel.uuid == model_uuid) @@ -170,21 +296,20 @@ class EmbeddingModelsService: ) await self.ap.model_mgr.remove_embedding_model(model_uuid) - embedding_model = await self.get_embedding_model(model_uuid) - await self.ap.model_mgr.load_embedding_model(embedding_model) async def delete_embedding_model(self, model_uuid: str) -> None: + """Delete an embedding model""" await self.ap.persistence_mgr.execute_async( sqlalchemy.delete(persistence_model.EmbeddingModel).where( persistence_model.EmbeddingModel.uuid == model_uuid ) ) - await self.ap.model_mgr.remove_embedding_model(model_uuid) async def test_embedding_model(self, model_uuid: str, model_data: dict) -> None: + """Test an embedding model""" runtime_embedding_model: model_requester.RuntimeEmbeddingModel | None = None if model_uuid != '_': @@ -192,10 +317,8 @@ class EmbeddingModelsService: if model.model_entity.uuid == model_uuid: runtime_embedding_model = model break - if runtime_embedding_model is None: raise Exception('model not found') - else: runtime_embedding_model = await self.ap.model_mgr.init_runtime_embedding_model(model_data) diff --git a/src/langbot/pkg/api/http/service/provider.py b/src/langbot/pkg/api/http/service/provider.py new file mode 100644 index 00000000..eb99c092 --- /dev/null +++ b/src/langbot/pkg/api/http/service/provider.py @@ -0,0 +1,152 @@ +from __future__ import annotations + +import uuid + +import sqlalchemy + +from ....core import app +from ....entity.persistence import model as persistence_model + + +class ModelProviderService: + """Service for managing model providers""" + + ap: app.Application + + def __init__(self, ap: app.Application) -> None: + self.ap = ap + + async def get_providers(self) -> list[dict]: + """Get all providers""" + result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_model.ModelProvider)) + providers = result.all() + providers_list = [] + for p in providers: + provider_dict = self.ap.persistence_mgr.serialize_model(persistence_model.ModelProvider, p) + # Parse api_keys if it's a JSON string + if isinstance(provider_dict.get('api_keys'), str): + import json + + try: + provider_dict['api_keys'] = json.loads(provider_dict['api_keys']) + except Exception: + provider_dict['api_keys'] = [] + providers_list.append(provider_dict) + return providers_list + + async def get_provider(self, provider_uuid: str) -> dict | None: + """Get a single provider by UUID""" + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.ModelProvider).where( + persistence_model.ModelProvider.uuid == provider_uuid + ) + ) + provider = result.first() + if provider is None: + return None + provider_dict = self.ap.persistence_mgr.serialize_model(persistence_model.ModelProvider, provider) + # Parse api_keys if it's a JSON string + if isinstance(provider_dict.get('api_keys'), str): + import json + + try: + provider_dict['api_keys'] = json.loads(provider_dict['api_keys']) + except Exception: + provider_dict['api_keys'] = [] + return provider_dict + + async def create_provider(self, provider_data: dict) -> str: + """Create a new provider""" + provider_data['uuid'] = str(uuid.uuid4()) + await self.ap.persistence_mgr.execute_async( + sqlalchemy.insert(persistence_model.ModelProvider).values(**provider_data) + ) + return provider_data['uuid'] + + async def update_provider(self, provider_uuid: str, provider_data: dict) -> None: + """Update an existing provider""" + if 'uuid' in provider_data: + del provider_data['uuid'] + await self.ap.persistence_mgr.execute_async( + sqlalchemy.update(persistence_model.ModelProvider) + .where(persistence_model.ModelProvider.uuid == provider_uuid) + .values(**provider_data) + ) + # Reload all models using this provider + await self.ap.model_mgr.load_models_from_db() + + async def delete_provider(self, provider_uuid: str) -> None: + """Delete a provider (only if no models reference it)""" + # Check if any models use this provider + llm_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.LLMModel).where( + persistence_model.LLMModel.provider_uuid == provider_uuid + ) + ) + if llm_result.first() is not None: + raise ValueError('Cannot delete provider: LLM models still reference it') + + embedding_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.EmbeddingModel).where( + persistence_model.EmbeddingModel.provider_uuid == provider_uuid + ) + ) + if embedding_result.first() is not None: + raise ValueError('Cannot delete provider: Embedding models still reference it') + + await self.ap.persistence_mgr.execute_async( + sqlalchemy.delete(persistence_model.ModelProvider).where( + persistence_model.ModelProvider.uuid == provider_uuid + ) + ) + + async def get_provider_model_counts(self, provider_uuid: str) -> dict: + """Get count of models using this provider""" + llm_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(sqlalchemy.func.count()) + .select_from(persistence_model.LLMModel) + .where(persistence_model.LLMModel.provider_uuid == provider_uuid) + ) + llm_count = llm_result.scalar() or 0 + + embedding_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(sqlalchemy.func.count()) + .select_from(persistence_model.EmbeddingModel) + .where(persistence_model.EmbeddingModel.provider_uuid == provider_uuid) + ) + embedding_count = embedding_result.scalar() or 0 + + return {'llm_count': llm_count, 'embedding_count': embedding_count} + + async def find_or_create_provider(self, requester: str, base_url: str, api_keys: list) -> str: + """Find existing provider or create new one""" + # Try to find existing provider with same config + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.ModelProvider).where( + persistence_model.ModelProvider.requester == requester, + persistence_model.ModelProvider.base_url == base_url, + ) + ) + for provider in result.all(): + if sorted(provider.api_keys or []) == sorted(api_keys or []): + return provider.uuid + + # Create new provider + provider_name = requester + if base_url: + try: + from urllib.parse import urlparse + + parsed = urlparse(base_url) + provider_name = parsed.netloc or requester + except Exception: + pass + + return await self.create_provider( + { + 'name': provider_name, + 'requester': requester, + 'base_url': base_url, + 'api_keys': api_keys or [], + } + ) diff --git a/src/langbot/pkg/api/http/service/space_models.py b/src/langbot/pkg/api/http/service/space_models.py deleted file mode 100644 index 634c7e39..00000000 --- a/src/langbot/pkg/api/http/service/space_models.py +++ /dev/null @@ -1,247 +0,0 @@ -from __future__ import annotations - -import typing -import uuid as uuid_lib -import aiohttp -import sqlalchemy - -from ....core import app -from ....entity.persistence import model as persistence_model -from ....entity.persistence import user as persistence_user - - -DEFAULT_SPACE_URL = 'http://localhost:8383' - -# Space's base URL for model API requests (used for requester_config) -SPACE_API_BASE_URL = 'http://localhost:8383' - - -class SpaceModelsService: - """Service for syncing models from Space MaaS""" - - ap: app.Application - - def __init__(self, ap: app.Application) -> None: - self.ap = ap - - async def get_space_user_info(self, user_email: str) -> persistence_user.User | None: - """Get Space user info for sync operations""" - result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_user.User).where(persistence_user.User.user == user_email) - ) - result_list = result.all() - return result_list[0] if result_list else None - - async def fetch_space_models(self, space_url: str = DEFAULT_SPACE_URL) -> typing.Dict: - """Fetch available models from Space API""" - async with aiohttp.ClientSession() as session: - async with session.get(f'{space_url}/api/v1/models', params={'page_size': 100}) as response: - if response.status != 200: - raise ValueError(f'Failed to fetch models from Space: {await response.text()}') - data = await response.json() - if data.get('code') != 0: - raise ValueError(f'Failed to fetch models from Space: {data.get("msg")}') - return data.get('data', {}) - - async def sync_models_from_space( - self, user_email: str, space_url: str = DEFAULT_SPACE_URL - ) -> typing.Dict[str, typing.Any]: - """ - Sync models from Space to local database. - Returns statistics about the sync operation. - """ - # Get user info for API key - user_obj = await self.get_space_user_info(user_email) - if user_obj is None: - raise ValueError('User not found') - - if user_obj.account_type != 'space': - raise ValueError('User is not a Space account') - - if not user_obj.space_api_key: - raise ValueError('User does not have a Space API key configured') - - # Fetch models from Space - models_data = await self.fetch_space_models(space_url) - space_models = models_data.get('models', []) - - # Get existing Space models in local database - result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_model.LLMModel).where(persistence_model.LLMModel.source == 'space') - ) - existing_space_models = {m.space_model_id: m for m in result.all()} - - result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_model.EmbeddingModel).where( - persistence_model.EmbeddingModel.source == 'space' - ) - ) - existing_space_embedding_models = {m.space_model_id: m for m in result.all()} - - stats = {'created_llm': 0, 'updated_llm': 0, 'created_embedding': 0, 'updated_embedding': 0, 'skipped': 0} - - for model in space_models: - model_id = model.get('model_id') - category = model.get('category', '') - - if not model_id: - stats['skipped'] += 1 - continue - - if category == 'embedding': - # Handle embedding model - await self._sync_embedding_model(model, user_obj.space_api_key, existing_space_embedding_models, stats) - else: - # Handle LLM model (chat, completion, etc.) - await self._sync_llm_model(model, user_obj.space_api_key, existing_space_models, stats) - - return stats - - async def _sync_llm_model( - self, - model: typing.Dict, - api_key: str, - existing_models: typing.Dict[str, persistence_model.LLMModel], - stats: typing.Dict, - ) -> None: - """Sync a single LLM model from Space""" - model_id = model.get('model_id') - display_name = model.get('display_name', {}) - name = display_name.get('zh_Hans', display_name.get('en_US', model_id)) - description_obj = model.get('description', {}) - description = description_obj.get('zh_Hans', description_obj.get('en_US', '')) if description_obj else '' - - # Infer abilities from model capabilities - abilities = [] - supported_endpoints = model.get('supported_endpoints', []) - if 'vision' in str(supported_endpoints).lower() or 'vision' in model_id.lower(): - abilities.append('vision') - if 'function' in str(supported_endpoints).lower() or 'tool' in str(supported_endpoints).lower(): - abilities.append('function_call') - - model_data = { - 'name': name, - 'description': description[:255] if description else 'Model from Space MaaS', - 'requester': 'openai-chat-completions', # Space uses OpenAI-compatible API - 'requester_config': { - 'base-url': SPACE_API_BASE_URL, - 'args': {}, - 'timeout': 120, - }, - 'api_keys': [api_key], - 'abilities': abilities, - 'extra_args': {'model': model_id}, - 'source': 'space', - 'space_model_id': model_id, - } - - if model_id in existing_models: - # Update existing model - await self.ap.persistence_mgr.execute_async( - sqlalchemy.update(persistence_model.LLMModel) - .where(persistence_model.LLMModel.space_model_id == model_id) - .values(**model_data) - ) - stats['updated_llm'] += 1 - else: - # Create new model - model_data['uuid'] = str(uuid_lib.uuid4()) - await self.ap.persistence_mgr.execute_async( - sqlalchemy.insert(persistence_model.LLMModel).values(**model_data) - ) - stats['created_llm'] += 1 - - async def _sync_embedding_model( - self, - model: typing.Dict, - api_key: str, - existing_models: typing.Dict[str, persistence_model.EmbeddingModel], - stats: typing.Dict, - ) -> None: - """Sync a single embedding model from Space""" - model_id = model.get('model_id') - display_name = model.get('display_name', {}) - name = display_name.get('zh_Hans', display_name.get('en_US', model_id)) - description_obj = model.get('description', {}) - description = description_obj.get('zh_Hans', description_obj.get('en_US', '')) if description_obj else '' - - model_data = { - 'name': name, - 'description': description[:255] if description else 'Embedding model from Space MaaS', - 'requester': 'openai-embedding', # Space uses OpenAI-compatible API - 'requester_config': { - 'base-url': SPACE_API_BASE_URL, - 'args': {}, - 'timeout': 120, - }, - 'api_keys': [api_key], - 'extra_args': {'model': model_id}, - 'source': 'space', - 'space_model_id': model_id, - } - - if model_id in existing_models: - # Update existing model - await self.ap.persistence_mgr.execute_async( - sqlalchemy.update(persistence_model.EmbeddingModel) - .where(persistence_model.EmbeddingModel.space_model_id == model_id) - .values(**model_data) - ) - stats['updated_embedding'] += 1 - else: - # Create new model - model_data['uuid'] = str(uuid_lib.uuid4()) - await self.ap.persistence_mgr.execute_async( - sqlalchemy.insert(persistence_model.EmbeddingModel).values(**model_data) - ) - stats['created_embedding'] += 1 - - async def get_space_models(self) -> typing.Dict[str, typing.List]: - """Get all synced Space models""" - llm_result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_model.LLMModel).where(persistence_model.LLMModel.source == 'space') - ) - embedding_result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_model.EmbeddingModel).where( - persistence_model.EmbeddingModel.source == 'space' - ) - ) - - return { - 'llm_models': [ - self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, m) for m in llm_result.all() - ], - 'embedding_models': [ - self.ap.persistence_mgr.serialize_model(persistence_model.EmbeddingModel, m) - for m in embedding_result.all() - ], - } - - async def delete_space_models(self) -> typing.Dict[str, int]: - """Delete all synced Space models""" - # Remove from model manager first - llm_result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_model.LLMModel).where(persistence_model.LLMModel.source == 'space') - ) - for model in llm_result.all(): - await self.ap.model_mgr.remove_llm_model(model.uuid) - - embedding_result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_model.EmbeddingModel).where( - persistence_model.EmbeddingModel.source == 'space' - ) - ) - for model in embedding_result.all(): - await self.ap.model_mgr.remove_embedding_model(model.uuid) - - # Delete from database - llm_delete = await self.ap.persistence_mgr.execute_async( - sqlalchemy.delete(persistence_model.LLMModel).where(persistence_model.LLMModel.source == 'space') - ) - embedding_delete = await self.ap.persistence_mgr.execute_async( - sqlalchemy.delete(persistence_model.EmbeddingModel).where( - persistence_model.EmbeddingModel.source == 'space' - ) - ) - - return {'deleted_llm': llm_delete.rowcount, 'deleted_embedding': embedding_delete.rowcount} diff --git a/src/langbot/pkg/core/app.py b/src/langbot/pkg/core/app.py index 4b8d22a3..38f93daa 100644 --- a/src/langbot/pkg/core/app.py +++ b/src/langbot/pkg/core/app.py @@ -20,6 +20,7 @@ from ..persistence import mgr as persistencemgr from ..api.http.controller import main as http_controller from ..api.http.service import user as user_service from ..api.http.service import model as model_service +from ..api.http.service import provider as provider_service from ..api.http.service import pipeline as pipeline_service from ..api.http.service import bot as bot_service from ..api.http.service import knowledge as knowledge_service @@ -27,7 +28,6 @@ from ..api.http.service import mcp as mcp_service from ..api.http.service import apikey as apikey_service from ..api.http.service import webhook as webhook_service from ..api.http.service import external_kb as external_kb_service -from ..api.http.service import space_models as space_models_service from ..discover import engine as discover_engine from ..storage import mgr as storagemgr from ..utils import logcache @@ -119,6 +119,8 @@ class Application: embedding_models_service: model_service.EmbeddingModelsService = None + provider_service: provider_service.ModelProviderService = None + pipeline_service: pipeline_service.PipelineService = None bot_service: bot_service.BotService = None @@ -133,8 +135,6 @@ class Application: webhook_service: webhook_service.WebhookService = None - space_models_service: space_models_service.SpaceModelsService = None - def __init__(self): pass diff --git a/src/langbot/pkg/core/stages/build_app.py b/src/langbot/pkg/core/stages/build_app.py index 94a4c293..b2a054ed 100644 --- a/src/langbot/pkg/core/stages/build_app.py +++ b/src/langbot/pkg/core/stages/build_app.py @@ -17,6 +17,7 @@ from ...persistence import mgr as persistencemgr from ...api.http.controller import main as http_controller from ...api.http.service import user as user_service from ...api.http.service import model as model_service +from ...api.http.service import provider as provider_service from ...api.http.service import pipeline as pipeline_service from ...api.http.service import bot as bot_service from ...api.http.service import knowledge as knowledge_service @@ -24,7 +25,6 @@ from ...api.http.service import mcp as mcp_service from ...api.http.service import apikey as apikey_service from ...api.http.service import webhook as webhook_service from ...api.http.service import external_kb as external_kb_service -from ...api.http.service import space_models as space_models_service from ...discover import engine as discover_engine from ...storage import mgr as storagemgr from ...utils import logcache @@ -115,6 +115,9 @@ class BuildAppStage(stage.BootingStage): embedding_models_service_inst = model_service.EmbeddingModelsService(ap) ap.embedding_models_service = embedding_models_service_inst + provider_service_inst = provider_service.ModelProviderService(ap) + ap.provider_service = provider_service_inst + pipeline_service_inst = pipeline_service.PipelineService(ap) ap.pipeline_service = pipeline_service_inst @@ -136,9 +139,6 @@ class BuildAppStage(stage.BootingStage): webhook_service_inst = webhook_service.WebhookService(ap) ap.webhook_service = webhook_service_inst - space_models_service_inst = space_models_service.SpaceModelsService(ap) - ap.space_models_service = space_models_service_inst - async def runtime_disconnect_callback(connector: plugin_connector.PluginRuntimeConnector) -> None: await asyncio.sleep(3) await plugin_connector_inst.initialize() diff --git a/src/langbot/pkg/entity/persistence/model.py b/src/langbot/pkg/entity/persistence/model.py index 0dea51ff..e4459585 100644 --- a/src/langbot/pkg/entity/persistence/model.py +++ b/src/langbot/pkg/entity/persistence/model.py @@ -3,6 +3,25 @@ import sqlalchemy from .base import Base +class ModelProvider(Base): + """Model provider""" + + __tablename__ = 'model_providers' + + uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True) + name = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) + requester = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) + base_url = sqlalchemy.Column(sqlalchemy.String(512), nullable=False) + api_keys = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=[]) + created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) + updated_at = sqlalchemy.Column( + sqlalchemy.DateTime, + nullable=False, + server_default=sqlalchemy.func.now(), + onupdate=sqlalchemy.func.now(), + ) + + class LLMModel(Base): """LLM model""" @@ -10,16 +29,9 @@ class LLMModel(Base): uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True) name = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) - description = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) - requester = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) - requester_config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={}) - api_keys = sqlalchemy.Column(sqlalchemy.JSON, nullable=False) + provider_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) abilities = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=[]) extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={}) - # Source tracking for Space integration: 'local' or 'space' - source = sqlalchemy.Column(sqlalchemy.String(32), nullable=False, server_default='local') - # Space model ID for synced models (used to track and update synced models) - space_model_id = sqlalchemy.Column(sqlalchemy.String(255), nullable=True) created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) updated_at = sqlalchemy.Column( sqlalchemy.DateTime, @@ -30,21 +42,14 @@ class LLMModel(Base): class EmbeddingModel(Base): - """Embedding 模型""" + """Embedding model""" __tablename__ = 'embedding_models' uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True) name = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) - description = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) - requester = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) - requester_config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={}) - api_keys = sqlalchemy.Column(sqlalchemy.JSON, nullable=False) + provider_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={}) - # Source tracking for Space integration: 'local' or 'space' - source = sqlalchemy.Column(sqlalchemy.String(32), nullable=False, server_default='local') - # Space model ID for synced models (used to track and update synced models) - space_model_id = sqlalchemy.Column(sqlalchemy.String(255), nullable=True) created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) updated_at = sqlalchemy.Column( sqlalchemy.DateTime, diff --git a/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py b/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py new file mode 100644 index 00000000..88438409 --- /dev/null +++ b/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py @@ -0,0 +1,286 @@ +import uuid as uuid_lib + +import sqlalchemy +from .. import migration + + +@migration.migration_class(16) +class DBMigrateModelProviderRefactor(migration.DBMigration): + """Refactor model structure: create providers from existing models and update references""" + + async def upgrade(self): + """Upgrade""" + # Step 1: Create model_providers table if not exists + await self._create_providers_table() + + # Step 2: Migrate existing models to use providers + await self._migrate_llm_models() + await self._migrate_embedding_models() + + # Step 3: Remove deprecated columns + await self._cleanup_columns() + + async def _create_providers_table(self): + """Create model_providers table""" + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text(""" + CREATE TABLE IF NOT EXISTS model_providers ( + uuid VARCHAR(255) PRIMARY KEY, + name VARCHAR(255) NOT NULL, + requester VARCHAR(255) NOT NULL, + base_url VARCHAR(512) NOT NULL, + api_keys JSONB NOT NULL DEFAULT '[]', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ) + """) + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text(""" + CREATE TABLE IF NOT EXISTS model_providers ( + uuid VARCHAR(255) PRIMARY KEY, + name VARCHAR(255) NOT NULL, + requester VARCHAR(255) NOT NULL, + base_url VARCHAR(512) NOT NULL, + api_keys JSON NOT NULL DEFAULT '[]', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + ) + """) + ) + + async def _migrate_llm_models(self): + """Migrate LLM models to use providers""" + llm_columns = await self._get_columns('llm_models') + + # Add provider_uuid column if not exists + if 'provider_uuid' not in llm_columns: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE llm_models ADD COLUMN provider_uuid VARCHAR(255)') + ) + + # Only migrate if old columns exist + if 'requester' not in llm_columns: + return + + # Get all LLM models with old structure + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('SELECT uuid, name, requester, requester_config, api_keys FROM llm_models') + ) + models = result.fetchall() + + # Create providers and update models + provider_cache = {} # (requester, base_url, api_keys_str) -> provider_uuid + + for model in models: + model_uuid, model_name, requester, requester_config, api_keys = model + + # Extract base_url from requester_config + base_url = '' + if requester_config: + if isinstance(requester_config, str): + import json + + requester_config = json.loads(requester_config) + base_url = requester_config.get('base_url', '') or requester_config.get('base-url', '') + + # Parse api_keys if it's a string + if isinstance(api_keys, str): + import json + + try: + api_keys = json.loads(api_keys) + except Exception: + api_keys = [] + if not api_keys: + api_keys = [] + + # Create cache key + api_keys_str = str(sorted(api_keys)) if api_keys else '[]' + cache_key = (requester, base_url, api_keys_str) + + if cache_key in provider_cache: + provider_uuid = provider_cache[cache_key] + else: + # Create new provider + provider_uuid = str(uuid_lib.uuid4()) + provider_name = f'{requester}' + if base_url: + # Extract domain for name + try: + from urllib.parse import urlparse + + parsed = urlparse(base_url) + provider_name = parsed.netloc or requester + except Exception: + pass + + import json + + api_keys_json = json.dumps(api_keys) if api_keys else '[]' + + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text(""" + INSERT INTO model_providers (uuid, name, requester, base_url, api_keys) + VALUES (:uuid, :name, :requester, :base_url, :api_keys) + """), + { + 'uuid': provider_uuid, + 'name': provider_name, + 'requester': requester, + 'base_url': base_url, + 'api_keys': api_keys_json, + }, + ) + provider_cache[cache_key] = provider_uuid + + # Update model with provider_uuid + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('UPDATE llm_models SET provider_uuid = :provider_uuid WHERE uuid = :uuid'), + {'provider_uuid': provider_uuid, 'uuid': model_uuid}, + ) + + async def _migrate_embedding_models(self): + """Migrate embedding models to use providers""" + embedding_columns = await self._get_columns('embedding_models') + + # Add provider_uuid column if not exists + if 'provider_uuid' not in embedding_columns: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE embedding_models ADD COLUMN provider_uuid VARCHAR(255)') + ) + + # Only migrate if old columns exist + if 'requester' not in embedding_columns: + return + + # Get all embedding models with old structure + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('SELECT uuid, name, requester, requester_config, api_keys FROM embedding_models') + ) + models = result.fetchall() + + # Get existing providers + provider_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('SELECT uuid, requester, base_url, api_keys FROM model_providers') + ) + existing_providers = provider_result.fetchall() + + provider_cache = {} + for p in existing_providers: + p_uuid, p_requester, p_base_url, p_api_keys = p + api_keys_str = str(sorted(p_api_keys)) if p_api_keys else '[]' + provider_cache[(p_requester, p_base_url, api_keys_str)] = p_uuid + + for model in models: + model_uuid, model_name, requester, requester_config, api_keys = model + + base_url = '' + if requester_config: + if isinstance(requester_config, str): + import json + + requester_config = json.loads(requester_config) + base_url = requester_config.get('base_url', '') or requester_config.get('base-url', '') + + # Parse api_keys if it's a string + if isinstance(api_keys, str): + import json + + try: + api_keys = json.loads(api_keys) + except Exception: + api_keys = [] + if not api_keys: + api_keys = [] + + api_keys_str = str(sorted(api_keys)) if api_keys else '[]' + cache_key = (requester, base_url, api_keys_str) + + if cache_key in provider_cache: + provider_uuid = provider_cache[cache_key] + else: + provider_uuid = str(uuid_lib.uuid4()) + provider_name = f'{requester}' + if base_url: + try: + from urllib.parse import urlparse + + parsed = urlparse(base_url) + provider_name = parsed.netloc or requester + except Exception: + pass + + import json + + api_keys_json = json.dumps(api_keys) if api_keys else '[]' + + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text(""" + INSERT INTO model_providers (uuid, name, requester, base_url, api_keys) + VALUES (:uuid, :name, :requester, :base_url, :api_keys) + """), + { + 'uuid': provider_uuid, + 'name': provider_name, + 'requester': requester, + 'base_url': base_url, + 'api_keys': api_keys_json, + }, + ) + provider_cache[cache_key] = provider_uuid + + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('UPDATE embedding_models SET provider_uuid = :provider_uuid WHERE uuid = :uuid'), + {'provider_uuid': provider_uuid, 'uuid': model_uuid}, + ) + + async def _cleanup_columns(self): + """Remove deprecated columns from model tables""" + # SQLite doesn't support DROP COLUMN easily, so we skip for SQLite + if self.ap.persistence_mgr.db.name != 'postgresql': + return + + llm_columns = await self._get_columns('llm_models') + deprecated_llm_cols = ['requester', 'requester_config', 'api_keys', 'description', 'source', 'space_model_id'] + for col in deprecated_llm_cols: + if col in llm_columns: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text(f'ALTER TABLE llm_models DROP COLUMN IF EXISTS {col}') + ) + + embedding_columns = await self._get_columns('embedding_models') + deprecated_embedding_cols = [ + 'requester', + 'requester_config', + 'api_keys', + 'description', + 'source', + 'space_model_id', + ] + for col in deprecated_embedding_cols: + if col in embedding_columns: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text(f'ALTER TABLE embedding_models DROP COLUMN IF EXISTS {col}') + ) + + async def _get_columns(self, table_name: str) -> list: + """Get column names for a table""" + if self.ap.persistence_mgr.db.name == 'postgresql': + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.text( + f"SELECT column_name FROM information_schema.columns WHERE table_name = '{table_name}';" + ) + ) + all_result = result.fetchall() + return [row[0] for row in all_result] + else: + result = await self.ap.persistence_mgr.execute_async(sqlalchemy.text(f'PRAGMA table_info({table_name});')) + all_result = result.fetchall() + return [row[1] for row in all_result] + + async def downgrade(self): + """Downgrade""" + pass diff --git a/src/langbot/pkg/provider/modelmgr/modelmgr.py b/src/langbot/pkg/provider/modelmgr/modelmgr.py index f0bec0a5..3c369638 100644 --- a/src/langbot/pkg/provider/modelmgr/modelmgr.py +++ b/src/langbot/pkg/provider/modelmgr/modelmgr.py @@ -10,11 +10,9 @@ from . import token from ...entity.persistence import model as persistence_model from ...entity.errors import provider as provider_errors -FETCH_MODEL_LIST_URL = 'https://api.qchatgpt.rockchin.top/api/v2/fetch/model_list' - class ModelManager: - """模型管理器""" + """Model manager""" ap: app.Application @@ -24,7 +22,7 @@ class ModelManager: requester_components: list[engine.Component] - requester_dict: dict[str, type[requester.ProviderAPIRequester]] # cache + requester_dict: dict[str, type[requester.ProviderAPIRequester]] def __init__(self, ap: app.Application): self.ap = ap @@ -36,7 +34,6 @@ class ModelManager: async def initialize(self): self.requester_components = self.ap.discover.get_components_by_kind('LLMAPIRequester') - # forge requester class dict requester_dict: dict[str, type[requester.ProviderAPIRequester]] = {} for component in self.requester_components: requester_dict[component.metadata.name] = component.get_python_component_class() @@ -46,29 +43,45 @@ class ModelManager: await self.load_models_from_db() async def load_models_from_db(self): - """从数据库加载模型""" + """Load models from database""" self.ap.logger.info('Loading models from db...') self.llm_models = [] self.embedding_models = [] - # llm models + # Load all providers first + providers_result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.ModelProvider) + ) + providers = {p.uuid: p for p in providers_result.all()} + + # Load LLM models result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_model.LLMModel)) llm_models = result.all() for llm_model in llm_models: try: - await self.load_llm_model(llm_model) + provider = providers.get(llm_model.provider_uuid) + if provider is None: + self.ap.logger.warning(f'Provider {llm_model.provider_uuid} not found for model {llm_model.uuid}') + continue + await self.load_llm_model_with_provider(llm_model, provider) except provider_errors.RequesterNotFoundError as e: self.ap.logger.warning(f'Requester {e.requester_name} not found, skipping llm model {llm_model.uuid}') except Exception as e: self.ap.logger.error(f'Failed to load model {llm_model.uuid}: {e}\n{traceback.format_exc()}') - # embedding models + # Load embedding models result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_model.EmbeddingModel)) embedding_models = result.all() for embedding_model in embedding_models: try: - await self.load_embedding_model(embedding_model) + provider = providers.get(embedding_model.provider_uuid) + if provider is None: + self.ap.logger.warning( + f'Provider {embedding_model.provider_uuid} not found for model {embedding_model.uuid}' + ) + continue + await self.load_embedding_model_with_provider(embedding_model, provider) except provider_errors.RequesterNotFoundError as e: self.ap.logger.warning( f'Requester {e.requester_name} not found, skipping embedding model {embedding_model.uuid}' @@ -78,27 +91,33 @@ class ModelManager: async def init_runtime_llm_model( self, - model_info: persistence_model.LLMModel | sqlalchemy.Row[persistence_model.LLMModel] | dict, + model_info: dict, ): - """初始化运行时 LLM 模型""" - if isinstance(model_info, sqlalchemy.Row): - model_info = persistence_model.LLMModel(**model_info._mapping) - elif isinstance(model_info, dict): - model_info = persistence_model.LLMModel(**model_info) + """Initialize runtime LLM model from dict (for testing)""" + provider_info = model_info.get('provider', {}) + requester_name = provider_info.get('requester', '') + base_url = provider_info.get('base_url', '') + api_keys = provider_info.get('api_keys', []) - if model_info.requester not in self.requester_dict: - raise provider_errors.RequesterNotFoundError(model_info.requester) - - requester_inst = self.requester_dict[model_info.requester](ap=self.ap, config=model_info.requester_config) + if requester_name not in self.requester_dict: + raise provider_errors.RequesterNotFoundError(requester_name) + requester_cfg = {'base_url': base_url} + requester_inst = self.requester_dict[requester_name](ap=self.ap, config=requester_cfg) await requester_inst.initialize() + # Create a temporary model entity + model_entity = persistence_model.LLMModel( + uuid=model_info.get('uuid', ''), + name=model_info.get('name', ''), + provider_uuid='', + abilities=model_info.get('abilities', []), + extra_args=model_info.get('extra_args', {}), + ) + runtime_llm_model = requester.RuntimeLLMModel( - model_entity=model_info, - token_mgr=token.TokenManager( - name=model_info.uuid, - tokens=model_info.api_keys, - ), + model_entity=model_entity, + token_mgr=token.TokenManager(name=model_entity.uuid, tokens=api_keys), requester=requester_inst, ) @@ -106,78 +125,165 @@ class ModelManager: async def init_runtime_embedding_model( self, - model_info: persistence_model.EmbeddingModel | sqlalchemy.Row[persistence_model.EmbeddingModel] | dict, + model_info: dict, ): - """初始化运行时 Embedding 模型""" - if isinstance(model_info, sqlalchemy.Row): - model_info = persistence_model.EmbeddingModel(**model_info._mapping) - elif isinstance(model_info, dict): - model_info = persistence_model.EmbeddingModel(**model_info) + """Initialize runtime embedding model from dict (for testing)""" + provider_info = model_info.get('provider', {}) + requester_name = provider_info.get('requester', '') + base_url = provider_info.get('base_url', '') + api_keys = provider_info.get('api_keys', []) - if model_info.requester not in self.requester_dict: - raise provider_errors.RequesterNotFoundError(model_info.requester) - - requester_inst = self.requester_dict[model_info.requester](ap=self.ap, config=model_info.requester_config) + if requester_name not in self.requester_dict: + raise provider_errors.RequesterNotFoundError(requester_name) + requester_cfg = {'base_url': base_url} + requester_inst = self.requester_dict[requester_name](ap=self.ap, config=requester_cfg) await requester_inst.initialize() + model_entity = persistence_model.EmbeddingModel( + uuid=model_info.get('uuid', ''), + name=model_info.get('name', ''), + provider_uuid='', + extra_args=model_info.get('extra_args', {}), + ) + runtime_embedding_model = requester.RuntimeEmbeddingModel( - model_entity=model_info, - token_mgr=token.TokenManager( - name=model_info.uuid, - tokens=model_info.api_keys, - ), + model_entity=model_entity, + token_mgr=token.TokenManager(name=model_entity.uuid, tokens=api_keys), requester=requester_inst, ) return runtime_embedding_model - async def load_llm_model( + async def load_llm_model_with_provider( self, - model_info: persistence_model.LLMModel | sqlalchemy.Row[persistence_model.LLMModel] | dict, + model_info: persistence_model.LLMModel | sqlalchemy.Row, + provider: persistence_model.ModelProvider | sqlalchemy.Row, ): - """加载 LLM 模型""" - runtime_llm_model = await self.init_runtime_llm_model(model_info) + """Load LLM model with provider info""" + if isinstance(model_info, sqlalchemy.Row): + model_info = persistence_model.LLMModel(**model_info._mapping) + if isinstance(provider, sqlalchemy.Row): + provider = persistence_model.ModelProvider(**provider._mapping) + + if provider.requester not in self.requester_dict: + raise provider_errors.RequesterNotFoundError(provider.requester) + + requester_cfg = {'base_url': provider.base_url} + requester_inst = self.requester_dict[provider.requester](ap=self.ap, config=requester_cfg) + await requester_inst.initialize() + + runtime_llm_model = requester.RuntimeLLMModel( + model_entity=model_info, + token_mgr=token.TokenManager(name=model_info.uuid, tokens=provider.api_keys or []), + requester=requester_inst, + ) + self.llm_models.append(runtime_llm_model) - async def load_embedding_model( + async def load_embedding_model_with_provider( self, - model_info: persistence_model.EmbeddingModel | sqlalchemy.Row[persistence_model.EmbeddingModel] | dict, + model_info: persistence_model.EmbeddingModel | sqlalchemy.Row, + provider: persistence_model.ModelProvider | sqlalchemy.Row, ): - """加载 Embedding 模型""" - runtime_embedding_model = await self.init_runtime_embedding_model(model_info) + """Load embedding model with provider info""" + if isinstance(model_info, sqlalchemy.Row): + model_info = persistence_model.EmbeddingModel(**model_info._mapping) + if isinstance(provider, sqlalchemy.Row): + provider = persistence_model.ModelProvider(**provider._mapping) + + if provider.requester not in self.requester_dict: + raise provider_errors.RequesterNotFoundError(provider.requester) + + requester_cfg = {'base_url': provider.base_url} + requester_inst = self.requester_dict[provider.requester](ap=self.ap, config=requester_cfg) + await requester_inst.initialize() + + runtime_embedding_model = requester.RuntimeEmbeddingModel( + model_entity=model_info, + token_mgr=token.TokenManager(name=model_info.uuid, tokens=provider.api_keys or []), + requester=requester_inst, + ) + self.embedding_models.append(runtime_embedding_model) + async def load_llm_model(self, model_info: dict): + """Load LLM model from dict (with provider info)""" + provider_info = model_info.get('provider', {}) + if not provider_info: + raise ValueError('Provider info is required') + + model_entity = persistence_model.LLMModel( + uuid=model_info.get('uuid', ''), + name=model_info.get('name', ''), + provider_uuid=model_info.get('provider_uuid', ''), + abilities=model_info.get('abilities', []), + extra_args=model_info.get('extra_args', {}), + ) + + provider_entity = persistence_model.ModelProvider( + uuid=provider_info.get('uuid', ''), + name=provider_info.get('name', ''), + requester=provider_info.get('requester', ''), + base_url=provider_info.get('base_url', ''), + api_keys=provider_info.get('api_keys', []), + ) + + await self.load_llm_model_with_provider(model_entity, provider_entity) + + async def load_embedding_model(self, model_info: dict): + """Load embedding model from dict (with provider info)""" + provider_info = model_info.get('provider', {}) + if not provider_info: + raise ValueError('Provider info is required') + + model_entity = persistence_model.EmbeddingModel( + uuid=model_info.get('uuid', ''), + name=model_info.get('name', ''), + provider_uuid=model_info.get('provider_uuid', ''), + extra_args=model_info.get('extra_args', {}), + ) + + provider_entity = persistence_model.ModelProvider( + uuid=provider_info.get('uuid', ''), + name=provider_info.get('name', ''), + requester=provider_info.get('requester', ''), + base_url=provider_info.get('base_url', ''), + api_keys=provider_info.get('api_keys', []), + ) + + await self.load_embedding_model_with_provider(model_entity, provider_entity) + async def get_model_by_uuid(self, uuid: str) -> requester.RuntimeLLMModel: - """通过uuid获取 LLM 模型""" + """Get LLM model by uuid""" for model in self.llm_models: if model.model_entity.uuid == uuid: return model raise ValueError(f'LLM model {uuid} not found') async def get_embedding_model_by_uuid(self, uuid: str) -> requester.RuntimeEmbeddingModel: - """通过uuid获取 Embedding 模型""" + """Get embedding model by uuid""" for model in self.embedding_models: if model.model_entity.uuid == uuid: return model raise ValueError(f'Embedding model {uuid} not found') async def remove_llm_model(self, model_uuid: str): - """移除 LLM 模型""" + """Remove LLM model""" for model in self.llm_models: if model.model_entity.uuid == model_uuid: self.llm_models.remove(model) return async def remove_embedding_model(self, model_uuid: str): - """移除 Embedding 模型""" + """Remove embedding model""" for model in self.embedding_models: if model.model_entity.uuid == model_uuid: self.embedding_models.remove(model) return def get_available_requesters_info(self, model_type: str) -> list[dict]: - """获取所有可用的请求器""" + """Get all available requesters""" if model_type != '': return [ component.to_plain_dict() @@ -188,14 +294,14 @@ class ModelManager: return [component.to_plain_dict() for component in self.requester_components] def get_available_requester_info_by_name(self, name: str) -> dict | None: - """通过名称获取请求器信息""" + """Get requester info by name""" for component in self.requester_components: if component.metadata.name == name: return component.to_plain_dict() return None def get_available_requester_manifest_by_name(self, name: str) -> engine.Component | None: - """通过名称获取请求器清单""" + """Get requester manifest by name""" for component in self.requester_components: if component.metadata.name == name: return component diff --git a/src/langbot/pkg/utils/constants.py b/src/langbot/pkg/utils/constants.py index 66541ba8..a82d578c 100644 --- a/src/langbot/pkg/utils/constants.py +++ b/src/langbot/pkg/utils/constants.py @@ -2,7 +2,7 @@ import langbot semantic_version = f'v{langbot.__version__}' -required_database_version = 15 +required_database_version = 16 """Tag the version of the database schema, used to check if the database needs to be migrated""" debug_mode = False diff --git a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx index feda5e36..690d8de1 100644 --- a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx +++ b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx @@ -254,118 +254,36 @@ export default function DynamicFormItemComponent({ ); case DynamicFormItemType.LLM_MODEL_SELECTOR: + // Group models by provider + const groupedModels = llmModels.reduce( + (acc, model) => { + const providerName = + model.provider?.name || model.provider?.requester || 'Unknown'; + if (!acc[providerName]) acc[providerName] = []; + acc[providerName].push(model); + return acc; + }, + {} as Record, + ); + return ( ); diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index b454a8e3..2e5079a3 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -5,19 +5,19 @@ import { Plus, MessageSquareText, Cpu, - Info, - RefreshCw, - ChevronLeft, - Cloud, - HardDrive, - Lock, + ChevronDown, + ChevronRight, + Trash2, + Settings, + Sparkles, + LogIn, } from 'lucide-react'; -import { LLMCardVO } from './component/llm-card/LLMCardVO'; -import LLMCard from './component/llm-card/LLMCard'; -import LLMForm from './component/llm-form/LLMForm'; import { httpClient } from '@/app/infra/http/HttpClient'; -import { LLMModel } from '@/app/infra/entities/api'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { + LLMModel, + EmbeddingModel, + ModelProvider, +} from '@/app/infra/entities/api'; import { Dialog, DialogContent, @@ -25,68 +25,67 @@ import { DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from '@/components/ui/collapsible'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; import { extractI18nObject } from '@/i18n/I18nProvider'; -import { EmbeddingCardVO } from './component/embedding-card/EmbeddingCardVO'; -import EmbeddingCard from './component/embedding-card/EmbeddingCard'; -import EmbeddingForm from './component/embedding-form/EmbeddingForm'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; +import LLMForm from './component/llm-form/LLMForm'; +import EmbeddingForm from './component/embedding-form/EmbeddingForm'; +import ProviderForm from './component/provider-form/ProviderForm'; interface ModelsDialogProps { open: boolean; onOpenChange: (open: boolean) => void; } -type ViewMode = 'providers' | 'space' | 'local'; +const LANGBOT_MODELS_PROVIDER_NAME = 'LangBot Models'; export default function ModelsDialog({ open, onOpenChange, }: ModelsDialogProps) { const { t } = useTranslation(); - const [viewMode, setViewMode] = useState('providers'); - const [activeTab, setActiveTab] = useState('llm'); - // User account type + const [providers, setProviders] = useState([]); const [accountType, setAccountType] = useState<'local' | 'space'>('local'); + const [spaceBalance] = useState(null); - // Local models - const [localLLMList, setLocalLLMList] = useState([]); - const [localEmbeddingList, setLocalEmbeddingList] = useState< - EmbeddingCardVO[] - >([]); - - // Space models - const [spaceLLMList, setSpaceLLMList] = useState([]); - const [spaceEmbeddingList, setSpaceEmbeddingList] = useState< - EmbeddingCardVO[] - >([]); - - // Sync state - const [isSyncing, setIsSyncing] = useState(false); + // Expanded providers and their models + const [expandedProviders, setExpandedProviders] = useState>( + new Set(), + ); + const [providerModels, setProviderModels] = useState< + Record + >({}); + const [loadingProviders, setLoadingProviders] = useState>( + new Set(), + ); // Form modals - const [modalOpen, setModalOpen] = useState(false); - const [isEditForm, setIsEditForm] = useState(false); - const [nowSelectedLLM, setNowSelectedLLM] = useState(null); - const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false); - const [isEditEmbeddingForm, setIsEditEmbeddingForm] = useState(false); - const [nowSelectedEmbedding, setNowSelectedEmbedding] = - useState(null); + const [llmFormOpen, setLLMFormOpen] = useState(false); + const [embeddingFormOpen, setEmbeddingFormOpen] = useState(false); + const [providerFormOpen, setProviderFormOpen] = useState(false); + const [editingLLMId, setEditingLLMId] = useState(null); + const [editingEmbeddingId, setEditingEmbeddingId] = useState( + null, + ); + const [editingProviderId, setEditingProviderId] = useState( + null, + ); - // Requester name lists for display - const [llmRequesterNameList, setLLMRequesterNameList] = useState< - { label: string; value: string }[] - >([]); - const [embeddingRequesterNameList, setEmbeddingRequesterNameList] = useState< + const [requesterNameList, setRequesterNameList] = useState< { label: string; value: string }[] >([]); @@ -94,7 +93,7 @@ export default function ModelsDialog({ if (open) { loadUserInfo(); loadRequesterLists(); - loadAllModels(); + loadProviders(); } }, [open]); @@ -103,7 +102,6 @@ export default function ModelsDialog({ const userInfo = await httpClient.getUserInfo(); setAccountType(userInfo.account_type); } catch { - // Default to local if user info cannot be fetched setAccountType('local'); } } @@ -111,347 +109,406 @@ export default function ModelsDialog({ async function loadRequesterLists() { try { const llmRequesters = await httpClient.getProviderRequesters('llm'); - setLLMRequesterNameList( + setRequesterNameList( llmRequesters.requesters.map((item) => ({ label: extractI18nObject(item.label), value: item.name, })), ); - - const embeddingRequesters = - await httpClient.getProviderRequesters('text-embedding'); - setEmbeddingRequesterNameList( - embeddingRequesters.requesters.map((item) => ({ - label: extractI18nObject(item.label), - value: item.name, - })), - ); } catch (err) { console.error('Failed to load requester lists', err); } } - async function loadAllModels() { - await Promise.all([loadLLMModels(), loadEmbeddingModels()]); - } - - async function loadLLMModels() { + async function loadProviders() { try { - const resp = await httpClient.getProviderLLMModels(); - const localModels: LLMCardVO[] = []; - const spaceModels: LLMCardVO[] = []; - - resp.models.forEach((model: LLMModel & { source?: string }) => { - const cardVO = new LLMCardVO({ - id: model.uuid, - iconURL: httpClient.getProviderRequesterIconURL(model.requester), - name: model.name, - providerLabel: - llmRequesterNameList.find((item) => item.value === model.requester) - ?.label || model.requester.substring(0, 10), - baseURL: model.requester_config?.base_url, - abilities: model.abilities || [], - }); - - if (model.source === 'space') { - spaceModels.push(cardVO); - } else { - localModels.push(cardVO); - } - }); - - setLocalLLMList(localModels); - setSpaceLLMList(spaceModels); + const resp = await httpClient.getModelProviders(); + setProviders(resp.providers); } catch (err) { - console.error('Failed to load LLM models', err); - toast.error(t('models.getModelListError') + (err as Error).message); + console.error('Failed to load providers', err); + toast.error(t('models.loadError')); } } - async function loadEmbeddingModels() { + async function loadProviderModels(providerUuid: string) { + if (loadingProviders.has(providerUuid)) return; + + setLoadingProviders((prev) => new Set(prev).add(providerUuid)); try { - const resp = await httpClient.getProviderEmbeddingModels(); - const localModels: EmbeddingCardVO[] = []; - const spaceModels: EmbeddingCardVO[] = []; - - resp.models.forEach( - (model: { - uuid: string; - requester: string; - name: string; - requester_config?: { base_url?: string }; - source?: string; - }) => { - const cardVO = new EmbeddingCardVO({ - id: model.uuid, - iconURL: httpClient.getProviderRequesterIconURL(model.requester), - name: model.name, - providerLabel: - embeddingRequesterNameList.find( - (item) => item.value === model.requester, - )?.label || model.requester.substring(0, 10), - baseURL: model.requester_config?.base_url || '', - }); - - if (model.source === 'space') { - spaceModels.push(cardVO); - } else { - localModels.push(cardVO); - } + const [llmResp, embeddingResp] = await Promise.all([ + httpClient.getProviderLLMModels(providerUuid), + httpClient.getProviderEmbeddingModels(providerUuid), + ]); + setProviderModels((prev) => ({ + ...prev, + [providerUuid]: { + llm: llmResp.models, + embedding: embeddingResp.models, }, - ); - - setLocalEmbeddingList(localModels); - setSpaceEmbeddingList(spaceModels); + })); } catch (err) { - console.error('Failed to load embedding models', err); - toast.error(t('embedding.getModelListError') + (err as Error).message); - } - } - - async function handleSyncSpaceModels() { - setIsSyncing(true); - try { - const stats = await httpClient.syncSpaceModels(); - toast.success( - t('models.syncSuccess', { - created: stats.created_llm + stats.created_embedding, - updated: stats.updated_llm + stats.updated_embedding, - }), - ); - await loadAllModels(); - } catch (err) { - toast.error(t('models.syncError') + (err as Error).message); + console.error('Failed to load models', err); } finally { - setIsSyncing(false); + setLoadingProviders((prev) => { + const next = new Set(prev); + next.delete(providerUuid); + return next; + }); } } - function selectLLM(cardVO: LLMCardVO, isSpaceModel: boolean) { - if (isSpaceModel) { - // Space models are read-only, just show info - toast.info(t('models.spaceModelReadOnly')); - return; + function toggleProvider(providerUuid: string) { + setExpandedProviders((prev) => { + const next = new Set(prev); + if (next.has(providerUuid)) { + next.delete(providerUuid); + } else { + next.add(providerUuid); + if (!providerModels[providerUuid]) { + loadProviderModels(providerUuid); + } + } + return next; + }); + } + + function handleCreateLLM() { + setEditingLLMId(null); + setLLMFormOpen(true); + } + + function handleCreateEmbedding() { + setEditingEmbeddingId(null); + setEmbeddingFormOpen(true); + } + + function handleEditLLM(modelId: string) { + setEditingLLMId(modelId); + setLLMFormOpen(true); + } + + function handleEditEmbedding(modelId: string) { + setEditingEmbeddingId(modelId); + setEmbeddingFormOpen(true); + } + + function handleEditProvider(providerId: string) { + setEditingProviderId(providerId); + setProviderFormOpen(true); + } + + async function handleDeleteProvider(providerId: string) { + try { + await httpClient.deleteModelProvider(providerId); + toast.success(t('models.providerDeleted')); + loadProviders(); + } catch (err) { + toast.error(t('models.providerDeleteError') + (err as Error).message); } - setIsEditForm(true); - setNowSelectedLLM(cardVO); - setModalOpen(true); } - function handleCreateModelClick() { - setIsEditForm(false); - setNowSelectedLLM(null); - setModalOpen(true); - } - - function selectEmbedding(cardVO: EmbeddingCardVO, isSpaceModel: boolean) { - if (isSpaceModel) { - toast.info(t('models.spaceModelReadOnly')); - return; + async function handleDeleteLLM(modelId: string, providerUuid: string) { + try { + await httpClient.deleteProviderLLMModel(modelId); + toast.success(t('models.deleteSuccess')); + loadProviderModels(providerUuid); + loadProviders(); // Refresh counts + } catch (err) { + toast.error(t('models.deleteError') + (err as Error).message); } - setIsEditEmbeddingForm(true); - setNowSelectedEmbedding(cardVO); - setEmbeddingModalOpen(true); } - function handleCreateEmbeddingModelClick() { - setIsEditEmbeddingForm(false); - setNowSelectedEmbedding(null); - setEmbeddingModalOpen(true); + async function handleDeleteEmbedding(modelId: string, providerUuid: string) { + try { + await httpClient.deleteProviderEmbeddingModel(modelId); + toast.success(t('models.deleteSuccess')); + loadProviderModels(providerUuid); + loadProviders(); + } catch (err) { + toast.error(t('models.deleteError') + (err as Error).message); + } } - function renderProviderCards() { - const isSpaceDisabled = accountType === 'local'; + function handleSpaceLogin() { + window.location.href = '/auth/space'; + } + function getRequesterLabel(requester: string) { return ( -
- {/* Space Provider Card */} - !isSpaceDisabled && setViewMode('space')} - > - -
- -
-
-
- Space - {isSpaceDisabled && ( - - )} -
- - {isSpaceDisabled - ? t('models.spaceDisabledForLocalAccount') - : t('models.spaceProviderDescription')} - -
-
- -
- {spaceLLMList.length} LLM - - {spaceEmbeddingList.length} Embedding - -
-
-
- - {/* Local Provider Card */} - setViewMode('local')} - > - -
- -
-
- {t('models.localProvider')} - - {t('models.localProviderDescription')} - -
-
- -
- {localLLMList.length} LLM - - {localEmbeddingList.length} Embedding - -
-
-
-
+ requesterNameList.find((r) => r.value === requester)?.label || requester ); } - function renderModelList( - llmList: LLMCardVO[], - embeddingList: EmbeddingCardVO[], - isSpaceModel: boolean = false, - ) { - return ( - -
- - - - {t('llm.llmModels')} - - - - {t('embedding.embeddingModels')} - - + function maskApiKey(key: string): string { + if (!key) return ''; + if (key.length <= 8) return '****'; + return `${key.slice(0, 4)}...${key.slice(-4)}`; + } -
- {isSpaceModel ? ( - - ) : ( - + )} + {isLangBotModels && accountType === 'space' && ( + + {t('models.balance')}: {spaceBalance ?? '--'} + + )} + {!isLangBotModels && ( + <> + + {canDelete && ( + + )} + + )} +
+
+ + {isExpanded ? ( + + ) : ( + + )} + + {isExpanded + ? t('models.collapseModels') + : t('models.expandModels')} + + + + + + {isLoading ? ( +

+ {t('common.loading')}... +

+ ) : models ? ( +
+ {models.llm.map((model) => ( +
handleEditLLM(model.uuid)} + > +
+ + {model.name} + + + {t('models.chat')} + + {model.abilities?.includes('vision') && ( + + 👁 + + )} + {model.abilities?.includes('func_call') && ( + + 🔧 + + )} +
+ +
+ ))} + {models.embedding.map((model) => ( +
handleEditEmbedding(model.uuid)} + > +
+ + {model.name} + + + {t('models.embedding')} + +
+ +
+ ))} + {models.llm.length === 0 && models.embedding.length === 0 && ( +

+ {t('models.noModels')} +

+ )} +
+ ) : ( +

+ {t('models.noModels')} +

+ )} +
+
+ +
+ ); + } + + // Virtual LangBot Models card if not exists + function renderLangBotModelsCard() { + if (langbotProvider) { + return renderProviderCard(langbotProvider, true); + } + return ( + + +
+
+
+ +
+
+ + {LANGBOT_MODELS_PROVIDER_NAME} + +

+ {t('models.langbotModelsDescription')} +

+
+
+ {accountType !== 'space' && ( + )}
-
- -
- - {activeTab === 'llm' ? ( -

- {t('llm.description')} -

- ) : ( -

- {t('embedding.description')} -

- )} -
- - - {llmList.length === 0 ? ( -
- {isSpaceModel - ? t('models.noSpaceModels') - : t('models.noLocalModels')} -
- ) : ( -
- {llmList.map((cardVO) => ( -
selectLLM(cardVO, isSpaceModel)} - className={isSpaceModel ? 'cursor-default' : 'cursor-pointer'} - > - -
- ))} -
- )} -
- - - {embeddingList.length === 0 ? ( -
- {isSpaceModel - ? t('models.noSpaceModels') - : t('models.noLocalModels')} -
- ) : ( -
- {embeddingList.map((cardVO) => ( -
selectEmbedding(cardVO, isSpaceModel)} - className={isSpaceModel ? 'cursor-default' : 'cursor-pointer'} - > - -
- ))} -
- )} -
- + +
); } - function getDialogTitle() { - switch (viewMode) { - case 'space': - return 'Space ' + t('models.title'); - case 'local': - return t('models.localProvider') + ' ' + t('models.title'); - default: - return t('models.title'); - } + function handleFormClose() { + setLLMFormOpen(false); + setEmbeddingFormOpen(false); + setProviderFormOpen(false); + loadProviders(); + // Refresh expanded providers + expandedProviders.forEach((uuid) => loadProviderModels(uuid)); } return ( @@ -459,89 +516,101 @@ export default function ModelsDialog({ { - if (!newOpen && (modalOpen || embeddingModalOpen)) { + if ( + !newOpen && + (llmFormOpen || embeddingFormOpen || providerFormOpen) + ) return; - } - if (!newOpen) { - setViewMode('providers'); - } onOpenChange(newOpen); }} > - + -
- {viewMode !== 'providers' && ( - - )} - {getDialogTitle()} -
+ {t('models.title')}
-
- {viewMode === 'providers' && renderProviderCards()} - {viewMode === 'space' && - renderModelList(spaceLLMList, spaceEmbeddingList, true)} - {viewMode === 'local' && - renderModelList(localLLMList, localEmbeddingList, false)} +
+ {/* Fixed LangBot Models Card */} +
{renderLangBotModelsCard()}
+ + {/* Add Model Button */} +
+ + + + + + + + {t('models.addLLMModel')} + + + + {t('models.addEmbeddingModel')} + + + +
+ + {/* Scrollable Provider List */} +
+ {otherProviders.map((p) => renderProviderCard(p))} +
- - + + - {isEditForm ? t('models.editModel') : t('models.createModel')} + {editingLLMId ? t('models.editModel') : t('models.createModel')} { - setModalOpen(false); - loadAllModels(); - }} - onFormCancel={() => { - setModalOpen(false); - }} - onLLMDeleted={() => { - setModalOpen(false); - loadAllModels(); - }} + editMode={!!editingLLMId} + initLLMId={editingLLMId || undefined} + providers={providers} + onFormSubmit={handleFormClose} + onFormCancel={() => setLLMFormOpen(false)} + onLLMDeleted={handleFormClose} /> - - + + - {isEditEmbeddingForm + {editingEmbeddingId ? t('embedding.editModel') : t('embedding.createModel')} { - setEmbeddingModalOpen(false); - loadAllModels(); - }} - onFormCancel={() => { - setEmbeddingModalOpen(false); - }} - onEmbeddingDeleted={() => { - setEmbeddingModalOpen(false); - loadAllModels(); - }} + editMode={!!editingEmbeddingId} + initEmbeddingId={editingEmbeddingId || undefined} + providers={providers} + onFormSubmit={handleFormClose} + onFormCancel={() => setEmbeddingFormOpen(false)} + onEmbeddingDeleted={handleFormClose} + /> + + + + + + + {t('models.editProvider')} + + setProviderFormOpen(false)} /> diff --git a/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx b/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx index 00399359..5bd3b66a 100644 --- a/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx +++ b/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx @@ -1,9 +1,6 @@ -import { ICreateEmbeddingField } from '../ICreateEmbeddingField'; import { useEffect, useState } from 'react'; -import { IChooseRequesterEntity } from '../ChooseRequesterEntity'; import { httpClient } from '@/app/infra/http/HttpClient'; -import { EmbeddingModel } from '@/app/infra/entities/api'; -import { UUID } from 'uuidjs'; +import { ModelProvider } from '@/app/infra/entities/api'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; @@ -42,59 +39,43 @@ import { toast } from 'sonner'; import { extractI18nObject } from '@/i18n/I18nProvider'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { AlertCircle } from 'lucide-react'; - -const getExtraArgSchema = (t: (key: string) => string) => - z - .object({ - key: z.string().min(1, { message: t('models.keyNameRequired') }), - type: z.enum(['string', 'number', 'boolean']), - value: z.string(), - }) - .superRefine((data, ctx) => { - if (data.type === 'number' && isNaN(Number(data.value))) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: t('models.mustBeValidNumber'), - path: ['value'], - }); - } - if ( - data.type === 'boolean' && - data.value !== 'true' && - data.value !== 'false' - ) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: t('models.mustBeTrueOrFalse'), - path: ['value'], - }); - } - }); +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; const getFormSchema = (t: (key: string) => string) => z.object({ name: z.string().min(1, { message: t('models.modelNameRequired') }), - model_provider: z - .string() - .min(1, { message: t('models.modelProviderRequired') }), - url: z.string().optional(), - api_key: z.string().optional(), - extra_args: z.array(getExtraArgSchema(t)).optional(), + provider_uuid: z.string().optional(), + new_provider_requester: z.string().optional(), + new_provider_url: z.string().optional(), + new_provider_api_key: z.string().optional(), + extra_args: z + .array( + z.object({ + key: z.string(), + type: z.enum(['string', 'number', 'boolean']), + value: z.string(), + }), + ) + .optional(), }); +interface EmbeddingFormProps { + editMode: boolean; + initEmbeddingId?: string; + providers: ModelProvider[]; + onFormSubmit: () => void; + onFormCancel: () => void; + onEmbeddingDeleted: () => void; +} + export default function EmbeddingForm({ editMode, initEmbeddingId, + providers, onFormSubmit, onFormCancel, onEmbeddingDeleted, -}: { - editMode: boolean; - initEmbeddingId?: string; - onFormSubmit: () => void; - onFormCancel: () => void; - onEmbeddingDeleted: () => void; -}) { +}: EmbeddingFormProps) { const { t } = useTranslation(); const formSchema = getFormSchema(t); @@ -102,9 +83,10 @@ export default function EmbeddingForm({ resolver: zodResolver(formSchema), defaultValues: { name: '', - model_provider: '', - url: '', - api_key: '', + provider_uuid: '', + new_provider_requester: '', + new_provider_url: '', + new_provider_api_key: '', extra_args: [], }, }); @@ -112,54 +94,178 @@ export default function EmbeddingForm({ const [extraArgs, setExtraArgs] = useState< { key: string; type: 'string' | 'number' | 'boolean'; value: string }[] >([]); - const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false); - const [requesterNameList, setRequesterNameList] = useState< - IChooseRequesterEntity[] - >([]); - const [requesterDefaultURLList, setRequesterDefaultURLList] = useState< - string[] - >([]); const [modelTesting, setModelTesting] = useState(false); const [testErrorMessage, setTestErrorMessage] = useState(null); - const [currentModelProvider, setCurrentModelProvider] = useState(''); + const [providerMode, setProviderMode] = useState<'existing' | 'new'>( + 'existing', + ); + + const [requesterList, setRequesterList] = useState< + { label: string; value: string; category: string; defaultUrl: string }[] + >([]); useEffect(() => { - initEmbeddingModelFormComponent().then(() => { - if (editMode && initEmbeddingId) { - getEmbeddingConfig(initEmbeddingId).then((val) => { - form.setValue('name', val.name); - form.setValue('model_provider', val.model_provider); - setCurrentModelProvider(val.model_provider); - form.setValue('url', val.url); - form.setValue('api_key', val.api_key); - if (val.extra_args) { - const args = val.extra_args.map((arg) => { - const [key, value] = arg.split(':'); - let type: 'string' | 'number' | 'boolean' = 'string'; - if (!isNaN(Number(value))) { - type = 'number'; - } else if (value === 'true' || value === 'false') { - type = 'boolean'; - } - return { - key, - type, - value, - }; - }); - setExtraArgs(args); - form.setValue('extra_args', args); - } - }); - } else { - form.reset(); - } + loadRequesters(); + if (editMode && initEmbeddingId) { + loadModel(initEmbeddingId); + } + }, [editMode, initEmbeddingId]); + + async function loadRequesters() { + const resp = await httpClient.getProviderRequesters('text-embedding'); + setRequesterList( + resp.requesters.map((item) => ({ + label: extractI18nObject(item.label), + value: item.name, + category: item.spec.provider_category || 'manufacturer', + defaultUrl: + item.spec.config + .find((c) => c.name === 'base_url') + ?.default?.toString() || '', + })), + ); + } + + async function loadModel(id: string) { + const resp = await httpClient.getProviderEmbeddingModel(id); + const model = resp.model; + + form.setValue('name', model.name); + form.setValue('provider_uuid', model.provider_uuid); + + if (model.extra_args) { + const args = Object.entries(model.extra_args).map(([key, value]) => { + let type: 'string' | 'number' | 'boolean' = 'string'; + if (typeof value === 'number') type = 'number'; + else if (typeof value === 'boolean') type = 'boolean'; + return { key, type, value: String(value) }; + }); + setExtraArgs(args); + form.setValue('extra_args', args); + } + + setProviderMode('existing'); + } + + function handleFormSubmit(values: z.infer) { + const extraArgsObj: Record = {}; + values.extra_args?.forEach((arg) => { + if (arg.type === 'number') extraArgsObj[arg.key] = Number(arg.value); + else if (arg.type === 'boolean') + extraArgsObj[arg.key] = arg.value === 'true'; + else extraArgsObj[arg.key] = arg.value; }); - }, []); + + const modelData: Record = { + name: values.name, + extra_args: extraArgsObj, + }; + + if (providerMode === 'existing' && values.provider_uuid) { + modelData.provider_uuid = values.provider_uuid; + } else if (providerMode === 'new') { + modelData.provider = { + requester: values.new_provider_requester, + base_url: values.new_provider_url, + api_keys: values.new_provider_api_key + ? [values.new_provider_api_key] + : [], + }; + } + + if (editMode && initEmbeddingId) { + updateModel(initEmbeddingId, modelData); + } else { + createModel(modelData); + } + } + + async function createModel(data: Record) { + try { + await httpClient.createProviderEmbeddingModel(data as never); + toast.success(t('models.createSuccess')); + onFormSubmit(); + } catch (err) { + toast.error(t('models.createError') + (err as Error).message); + } + } + + async function updateModel(id: string, data: Record) { + try { + await httpClient.updateProviderEmbeddingModel(id, data as never); + toast.success(t('models.saveSuccess')); + onFormSubmit(); + } catch (err) { + toast.error(t('models.saveError') + (err as Error).message); + } + } + + async function deleteModel() { + if (!initEmbeddingId) return; + try { + await httpClient.deleteProviderEmbeddingModel(initEmbeddingId); + toast.success(t('models.deleteSuccess')); + onEmbeddingDeleted(); + } catch (err) { + toast.error(t('models.deleteError') + (err as Error).message); + } + } + + async function testModel() { + setModelTesting(true); + setTestErrorMessage(null); + + const values = form.getValues(); + const extraArgsObj: Record = {}; + values.extra_args?.forEach((arg) => { + if (arg.type === 'number') extraArgsObj[arg.key] = Number(arg.value); + else if (arg.type === 'boolean') + extraArgsObj[arg.key] = arg.value === 'true'; + else extraArgsObj[arg.key] = arg.value; + }); + + let provider: Record; + if (providerMode === 'existing' && values.provider_uuid) { + const p = providers.find((p) => p.uuid === values.provider_uuid); + provider = { + requester: p?.requester || '', + base_url: p?.base_url || '', + api_keys: p?.api_keys || [], + }; + } else { + provider = { + requester: values.new_provider_requester, + base_url: values.new_provider_url, + api_keys: values.new_provider_api_key + ? [values.new_provider_api_key] + : [], + }; + } + + try { + await httpClient.testEmbeddingModel('_', { + uuid: '', + name: values.name, + provider_uuid: '', + provider, + extra_args: extraArgsObj, + } as never); + toast.success(t('models.testSuccess')); + } catch (err) { + setTestErrorMessage((err as Error).message || t('models.testError')); + } finally { + setModelTesting(false); + } + } const addExtraArg = () => { - setExtraArgs([...extraArgs, { key: '', type: 'string', value: '' }]); + const newArgs = [ + ...extraArgs, + { key: '', type: 'string' as const, value: '' }, + ]; + setExtraArgs(newArgs); + form.setValue('extra_args', newArgs); }; const updateExtraArg = ( @@ -168,10 +274,7 @@ export default function EmbeddingForm({ value: string, ) => { const newArgs = [...extraArgs]; - newArgs[index] = { - ...newArgs[index], - [field]: value, - }; + newArgs[index] = { ...newArgs[index], [field]: value }; setExtraArgs(newArgs); form.setValue('extra_args', newArgs); }; @@ -182,167 +285,6 @@ export default function EmbeddingForm({ form.setValue('extra_args', newArgs); }; - async function initEmbeddingModelFormComponent() { - const requesterNameList = - await httpClient.getProviderRequesters('text-embedding'); - setRequesterNameList( - requesterNameList.requesters.map((item) => { - return { - label: extractI18nObject(item.label), - value: item.name, - provider_category: item.spec.provider_category || 'manufacturer', - description: extractI18nObject(item.description) || undefined, - }; - }), - ); - setRequesterDefaultURLList( - requesterNameList.requesters.map((item) => { - const config = item.spec.config; - for (let i = 0; i < config.length; i++) { - if (config[i].name == 'base_url') { - return config[i].default?.toString() || ''; - } - } - return ''; - }), - ); - } - - async function getEmbeddingConfig( - id: string, - ): Promise { - const embeddingModel = await httpClient.getProviderEmbeddingModel(id); - - const fakeExtraArgs = []; - const extraArgs = embeddingModel.model.extra_args as Record; - for (const key in extraArgs) { - fakeExtraArgs.push(`${key}:${extraArgs[key]}`); - } - return { - name: embeddingModel.model.name, - model_provider: embeddingModel.model.requester, - url: embeddingModel.model.requester_config?.base_url, - api_key: embeddingModel.model.api_keys[0], - extra_args: fakeExtraArgs, - }; - } - - function handleFormSubmit(value: z.infer) { - const extraArgsObj: Record = {}; - value.extra_args?.forEach( - (arg: { key: string; type: string; value: string }) => { - if (arg.type === 'number') { - extraArgsObj[arg.key] = Number(arg.value); - } else if (arg.type === 'boolean') { - extraArgsObj[arg.key] = arg.value === 'true'; - } else { - extraArgsObj[arg.key] = arg.value; - } - }, - ); - - const embeddingModel: EmbeddingModel = { - uuid: editMode ? initEmbeddingId || '' : UUID.generate(), - name: value.name, - description: '', - requester: value.model_provider, - requester_config: { - base_url: value.url || '', - timeout: 120, - }, - extra_args: extraArgsObj, - api_keys: value.api_key ? [value.api_key] : [], - }; - - if (editMode) { - onSaveEdit(embeddingModel).then(() => { - form.reset(); - }); - } else { - onCreateEmbedding(embeddingModel).then(() => { - form.reset(); - }); - } - } - - async function onCreateEmbedding(embeddingModel: EmbeddingModel) { - try { - await httpClient.createProviderEmbeddingModel(embeddingModel); - onFormSubmit(); - toast.success(t('models.createSuccess')); - } catch (err) { - toast.error(t('models.createError') + (err as Error).message); - } - } - - async function onSaveEdit(embeddingModel: EmbeddingModel) { - try { - await httpClient.updateProviderEmbeddingModel( - initEmbeddingId || '', - embeddingModel, - ); - onFormSubmit(); - toast.success(t('models.saveSuccess')); - } catch (err) { - toast.error(t('models.saveError') + (err as Error).message); - } - } - - function deleteModel() { - if (initEmbeddingId) { - httpClient - .deleteProviderEmbeddingModel(initEmbeddingId) - .then(() => { - onEmbeddingDeleted(); - toast.success(t('models.deleteSuccess')); - }) - .catch((err) => { - toast.error(t('models.deleteError') + err.message); - }); - } - } - - function testEmbeddingModelInForm() { - setModelTesting(true); - setTestErrorMessage(null); - const extraArgsObj: Record = {}; - form - .getValues('extra_args') - ?.forEach((arg: { key: string; type: string; value: string }) => { - if (arg.type === 'number') { - extraArgsObj[arg.key] = Number(arg.value); - } else if (arg.type === 'boolean') { - extraArgsObj[arg.key] = arg.value === 'true'; - } else { - extraArgsObj[arg.key] = arg.value; - } - }); - const apiKey = form.getValues('api_key'); - httpClient - .testEmbeddingModel('_', { - uuid: '', - name: form.getValues('name'), - description: '', - requester: form.getValues('model_provider'), - requester_config: { - base_url: form.getValues('url') ?? '', - timeout: 120, - }, - api_keys: apiKey ? [apiKey] : [], - extra_args: extraArgsObj, - }) - .then(() => { - toast.success(t('models.testSuccess')); - setTestErrorMessage(null); - }) - .catch((err: { message?: string }) => { - setTestErrorMessage(err?.message || t('models.testError')); - }) - .finally(() => { - setModelTesting(false); - }); - } - return (
-
- ( - - - {t('models.modelName')} - * - - - - - - - {t('models.modelProviderDescription')} - - - )} - /> - - ( - - - {t('models.modelProvider')} - * - - - - - {currentModelProvider && - requesterNameList.find( - (item) => item.value === currentModelProvider, - )?.description && ( - - { - requesterNameList.find( - (item) => item.value === currentModelProvider, - )?.description - } - - )} - - - )} - /> - - {!['seekdb-embedding'].includes(currentModelProvider) && ( - ( - - {t('models.requestURL')} - - - - - - )} - /> + ( + + + {t('models.modelName')} + * + + + + + + {t('models.modelProviderDescription')} + + + )} + /> - {!['ollama-chat', 'seekdb-embedding'].includes( - currentModelProvider, - ) && ( - ( - - {t('models.apiKey')} - - - - - - )} - /> - )} +
+ {t('models.provider')} + setProviderMode(v as 'existing' | 'new')} + className="mt-2" + > + + + {t('models.existingProvider')} + + {t('models.newProvider')} + - - {t('models.extraParameters')} -
- {extraArgs.map((arg, index) => ( -
- - updateExtraArg(index, 'key', e.target.value) - } - /> - - - updateExtraArg(index, 'value', e.target.value) - } - /> - -
- ))} - -
- - {t('embedding.extraParametersDescription')} - - -
+ + + + + {providers.map((p) => ( + + {p.name} ({p.base_url || 'default'}) + + ))} + + + + + )} + /> + + + + ( + + {t('models.requester')} + + + + )} + /> + + ( + + {t('models.requestURL')} + + + + + + )} + /> + + ( + + {t('models.apiKey')} + + + + + + )} + /> + +
+ + + {t('models.extraParameters')} +
+ {extraArgs.map((arg, index) => ( +
+ + updateExtraArg(index, 'key', e.target.value) + } + /> + + + updateExtraArg(index, 'value', e.target.value) + } + /> + +
+ ))} + +
+ + {t('embedding.extraParametersDescription')} + +
+ {testErrorMessage && ( @@ -612,6 +548,7 @@ export default function EmbeddingForm({ )} + {editMode && ( - - - diff --git a/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx b/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx index fe0d21b2..fb8a79ae 100644 --- a/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx +++ b/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx @@ -1,9 +1,6 @@ -import { ICreateLLMField } from '../ICreateLLMField'; import { useEffect, useState } from 'react'; -import { IChooseRequesterEntity } from '../ChooseRequesterEntity'; import { httpClient } from '@/app/infra/http/HttpClient'; -import { LLMModel } from '@/app/infra/entities/api'; -import { UUID } from 'uuidjs'; +import { ModelProvider } from '@/app/infra/entities/api'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; @@ -43,60 +40,45 @@ import { toast } from 'sonner'; import { extractI18nObject } from '@/i18n/I18nProvider'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { AlertCircle } from 'lucide-react'; - -const getExtraArgSchema = (t: (key: string) => string) => - z - .object({ - key: z.string().min(1, { message: t('models.keyNameRequired') }), - type: z.enum(['string', 'number', 'boolean']), - value: z.string(), - }) - .superRefine((data, ctx) => { - if (data.type === 'number' && isNaN(Number(data.value))) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: t('models.mustBeValidNumber'), - path: ['value'], - }); - } - if ( - data.type === 'boolean' && - data.value !== 'true' && - data.value !== 'false' - ) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: t('models.mustBeTrueOrFalse'), - path: ['value'], - }); - } - }); +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; const getFormSchema = (t: (key: string) => string) => z.object({ name: z.string().min(1, { message: t('models.modelNameRequired') }), - model_provider: z - .string() - .min(1, { message: t('models.modelProviderRequired') }), - url: z.string().min(1, { message: t('models.requestURLRequired') }), - api_key: z.string().optional(), + provider_uuid: z.string().optional(), + // New provider fields + new_provider_requester: z.string().optional(), + new_provider_url: z.string().optional(), + new_provider_api_key: z.string().optional(), abilities: z.array(z.string()), - extra_args: z.array(getExtraArgSchema(t)).optional(), + extra_args: z + .array( + z.object({ + key: z.string(), + type: z.enum(['string', 'number', 'boolean']), + value: z.string(), + }), + ) + .optional(), }); +interface LLMFormProps { + editMode: boolean; + initLLMId?: string; + providers: ModelProvider[]; + onFormSubmit: () => void; + onFormCancel: () => void; + onLLMDeleted: () => void; +} + export default function LLMForm({ editMode, initLLMId, + providers, onFormSubmit, onFormCancel, onLLMDeleted, -}: { - editMode: boolean; - initLLMId?: string; - onFormSubmit: () => void; - onFormCancel: () => void; - onLLMDeleted: () => void; -}) { +}: LLMFormProps) { const { t } = useTranslation(); const formSchema = getFormSchema(t); @@ -104,9 +86,10 @@ export default function LLMForm({ resolver: zodResolver(formSchema), defaultValues: { name: '', - model_provider: '', - url: '', - api_key: '', + provider_uuid: '', + new_provider_requester: '', + new_provider_url: '', + new_provider_api_key: '', abilities: [], extra_args: [], }, @@ -115,69 +98,186 @@ export default function LLMForm({ const [extraArgs, setExtraArgs] = useState< { key: string; type: 'string' | 'number' | 'boolean'; value: string }[] >([]); - const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false); - const abilityOptions: { label: string; value: string }[] = [ - { - label: t('models.visionAbility'), - value: 'vision', - }, - { - label: t('models.functionCallAbility'), - value: 'func_call', - }, - ]; - const [requesterNameList, setRequesterNameList] = useState< - IChooseRequesterEntity[] - >([]); - const [requesterDefaultURLList, setRequesterDefaultURLList] = useState< - string[] - >([]); const [modelTesting, setModelTesting] = useState(false); const [testErrorMessage, setTestErrorMessage] = useState(null); - const [currentModelProvider, setCurrentModelProvider] = useState(''); + const [providerMode, setProviderMode] = useState<'existing' | 'new'>( + 'existing', + ); + + const [requesterList, setRequesterList] = useState< + { label: string; value: string; category: string; defaultUrl: string }[] + >([]); + + const abilityOptions = [ + { label: t('models.visionAbility'), value: 'vision' }, + { label: t('models.functionCallAbility'), value: 'func_call' }, + ]; useEffect(() => { - initLLMModelFormComponent().then(() => { - if (editMode && initLLMId) { - getLLMConfig(initLLMId).then((val) => { - form.setValue('name', val.name); - form.setValue('model_provider', val.model_provider); - setCurrentModelProvider(val.model_provider); - form.setValue('url', val.url); - form.setValue('api_key', val.api_key); - form.setValue( - 'abilities', - val.abilities as ('vision' | 'func_call')[], - ); - // 转换extra_args为新格式 - if (val.extra_args) { - const args = val.extra_args.map((arg) => { - const [key, value] = arg.split(':'); - let type: 'string' | 'number' | 'boolean' = 'string'; - if (!isNaN(Number(value))) { - type = 'number'; - } else if (value === 'true' || value === 'false') { - type = 'boolean'; - } - return { - key, - type, - value, - }; - }); - setExtraArgs(args); - form.setValue('extra_args', args); - } - }); - } else { - form.reset(); - } + loadRequesters(); + if (editMode && initLLMId) { + loadModel(initLLMId); + } + }, [editMode, initLLMId]); + + async function loadRequesters() { + const resp = await httpClient.getProviderRequesters('llm'); + setRequesterList( + resp.requesters.map((item) => ({ + label: extractI18nObject(item.label), + value: item.name, + category: item.spec.provider_category || 'manufacturer', + defaultUrl: + item.spec.config + .find((c) => c.name === 'base_url') + ?.default?.toString() || '', + })), + ); + } + + async function loadModel(id: string) { + const resp = await httpClient.getProviderLLMModel(id); + const model = resp.model; + + form.setValue('name', model.name); + form.setValue('provider_uuid', model.provider_uuid); + form.setValue('abilities', model.abilities || []); + + if (model.extra_args) { + const args = Object.entries(model.extra_args).map(([key, value]) => { + let type: 'string' | 'number' | 'boolean' = 'string'; + if (typeof value === 'number') type = 'number'; + else if (typeof value === 'boolean') type = 'boolean'; + return { key, type, value: String(value) }; + }); + setExtraArgs(args); + form.setValue('extra_args', args); + } + + setProviderMode('existing'); + } + + function handleFormSubmit(values: z.infer) { + const extraArgsObj: Record = {}; + values.extra_args?.forEach((arg) => { + if (arg.type === 'number') extraArgsObj[arg.key] = Number(arg.value); + else if (arg.type === 'boolean') + extraArgsObj[arg.key] = arg.value === 'true'; + else extraArgsObj[arg.key] = arg.value; }); - }, []); + + const modelData: Record = { + name: values.name, + abilities: values.abilities, + extra_args: extraArgsObj, + }; + + if (providerMode === 'existing' && values.provider_uuid) { + modelData.provider_uuid = values.provider_uuid; + } else if (providerMode === 'new') { + modelData.provider = { + requester: values.new_provider_requester, + base_url: values.new_provider_url, + api_keys: values.new_provider_api_key + ? [values.new_provider_api_key] + : [], + }; + } + + if (editMode && initLLMId) { + updateModel(initLLMId, modelData); + } else { + createModel(modelData); + } + } + + async function createModel(data: Record) { + try { + await httpClient.createProviderLLMModel(data as never); + toast.success(t('models.createSuccess')); + onFormSubmit(); + } catch (err) { + toast.error(t('models.createError') + (err as Error).message); + } + } + + async function updateModel(id: string, data: Record) { + try { + await httpClient.updateProviderLLMModel(id, data as never); + toast.success(t('models.saveSuccess')); + onFormSubmit(); + } catch (err) { + toast.error(t('models.saveError') + (err as Error).message); + } + } + + async function deleteModel() { + if (!initLLMId) return; + try { + await httpClient.deleteProviderLLMModel(initLLMId); + toast.success(t('models.deleteSuccess')); + onLLMDeleted(); + } catch (err) { + toast.error(t('models.deleteError') + (err as Error).message); + } + } + + async function testModel() { + setModelTesting(true); + setTestErrorMessage(null); + + const values = form.getValues(); + const extraArgsObj: Record = {}; + values.extra_args?.forEach((arg) => { + if (arg.type === 'number') extraArgsObj[arg.key] = Number(arg.value); + else if (arg.type === 'boolean') + extraArgsObj[arg.key] = arg.value === 'true'; + else extraArgsObj[arg.key] = arg.value; + }); + + let provider: Record; + if (providerMode === 'existing' && values.provider_uuid) { + const p = providers.find((p) => p.uuid === values.provider_uuid); + provider = { + requester: p?.requester || '', + base_url: p?.base_url || '', + api_keys: p?.api_keys || [], + }; + } else { + provider = { + requester: values.new_provider_requester, + base_url: values.new_provider_url, + api_keys: values.new_provider_api_key + ? [values.new_provider_api_key] + : [], + }; + } + + try { + await httpClient.testLLMModel('_', { + uuid: '', + name: values.name, + provider_uuid: '', + provider, + abilities: values.abilities, + extra_args: extraArgsObj, + } as never); + toast.success(t('models.testSuccess')); + } catch (err) { + setTestErrorMessage((err as Error).message || t('models.testError')); + } finally { + setModelTesting(false); + } + } const addExtraArg = () => { - setExtraArgs([...extraArgs, { key: '', type: 'string', value: '' }]); + const newArgs = [ + ...extraArgs, + { key: '', type: 'string' as const, value: '' }, + ]; + setExtraArgs(newArgs); + form.setValue('extra_args', newArgs); }; const updateExtraArg = ( @@ -186,10 +286,7 @@ export default function LLMForm({ value: string, ) => { const newArgs = [...extraArgs]; - newArgs[index] = { - ...newArgs[index], - [field]: value, - }; + newArgs[index] = { ...newArgs[index], [field]: value }; setExtraArgs(newArgs); form.setValue('extra_args', newArgs); }; @@ -200,163 +297,6 @@ export default function LLMForm({ form.setValue('extra_args', newArgs); }; - async function initLLMModelFormComponent() { - const requesterNameList = await httpClient.getProviderRequesters('llm'); - setRequesterNameList( - requesterNameList.requesters.map((item) => { - return { - label: extractI18nObject(item.label), - value: item.name, - provider_category: item.spec.provider_category || 'manufacturer', - }; - }), - ); - setRequesterDefaultURLList( - requesterNameList.requesters.map((item) => { - const config = item.spec.config; - for (let i = 0; i < config.length; i++) { - if (config[i].name == 'base_url') { - return config[i].default?.toString() || ''; - } - } - return ''; - }), - ); - } - - async function getLLMConfig(id: string): Promise { - const llmModel = await httpClient.getProviderLLMModel(id); - - const fakeExtraArgs = []; - const extraArgs = llmModel.model.extra_args as Record; - for (const key in extraArgs) { - fakeExtraArgs.push(`${key}:${extraArgs[key]}`); - } - return { - name: llmModel.model.name, - model_provider: llmModel.model.requester, - url: llmModel.model.requester_config?.base_url, - api_key: llmModel.model.api_keys[0], - abilities: llmModel.model.abilities || [], - extra_args: fakeExtraArgs, - }; - } - - function handleFormSubmit(value: z.infer) { - const extraArgsObj: Record = {}; - value.extra_args?.forEach( - (arg: { key: string; type: string; value: string }) => { - if (arg.type === 'number') { - extraArgsObj[arg.key] = Number(arg.value); - } else if (arg.type === 'boolean') { - extraArgsObj[arg.key] = arg.value === 'true'; - } else { - extraArgsObj[arg.key] = arg.value; - } - }, - ); - - const llmModel: LLMModel = { - uuid: editMode ? initLLMId || '' : UUID.generate(), - name: value.name, - description: '', - requester: value.model_provider, - requester_config: { - base_url: value.url, - timeout: 120, - }, - extra_args: extraArgsObj, - api_keys: value.api_key ? [value.api_key] : [], - abilities: value.abilities, - }; - - if (editMode) { - onSaveEdit(llmModel).then(() => { - form.reset(); - }); - } else { - onCreateLLM(llmModel).then(() => { - form.reset(); - }); - } - } - - async function onCreateLLM(llmModel: LLMModel) { - try { - await httpClient.createProviderLLMModel(llmModel); - onFormSubmit(); - toast.success(t('models.createSuccess')); - } catch (err) { - toast.error(t('models.createError') + (err as Error).message); - } - } - - async function onSaveEdit(llmModel: LLMModel) { - try { - await httpClient.updateProviderLLMModel(initLLMId || '', llmModel); - onFormSubmit(); - toast.success(t('models.saveSuccess')); - } catch (err) { - toast.error(t('models.saveError') + (err as Error).message); - } - } - - function deleteModel() { - if (initLLMId) { - httpClient - .deleteProviderLLMModel(initLLMId) - .then(() => { - onLLMDeleted(); - toast.success(t('models.deleteSuccess')); - }) - .catch((err) => { - toast.error(t('models.deleteError') + err.message); - }); - } - } - - function testLLMModelInForm() { - setModelTesting(true); - setTestErrorMessage(null); - const extraArgsObj: Record = {}; - form - .getValues('extra_args') - ?.forEach((arg: { key: string; type: string; value: string }) => { - if (arg.type === 'number') { - extraArgsObj[arg.key] = Number(arg.value); - } else if (arg.type === 'boolean') { - extraArgsObj[arg.key] = arg.value === 'true'; - } else { - extraArgsObj[arg.key] = arg.value; - } - }); - const apiKey = form.getValues('api_key'); - httpClient - .testLLMModel('_', { - uuid: '', - name: form.getValues('name'), - description: '', - requester: form.getValues('model_provider'), - requester_config: { - base_url: form.getValues('url'), - timeout: 120, - }, - api_keys: apiKey ? [apiKey] : [], - abilities: form.getValues('abilities'), - extra_args: extraArgsObj, - }) - .then(() => { - toast.success(t('models.testSuccess')); - setTestErrorMessage(null); - }) - .catch((err: { message?: string }) => { - setTestErrorMessage(err?.message || t('models.testError')); - }) - .finally(() => { - setModelTesting(false); - }); - } - return (
-
- ( - - - {t('models.modelName')} - * - - - - - - - {t('models.modelProviderDescription')} - - - )} - /> - - ( - - - {t('models.modelProvider')} - * - - - - - - - )} - /> - - ( - - - {t('models.requestURL')} - * - - - - - - - )} - /> - - {!['lmstudio-chat-completions', 'ollama-chat'].includes( - currentModelProvider, - ) && ( - ( - - {t('models.apiKey')} - - - - - - )} - /> + ( + + + {t('models.modelName')} + * + + + + + + {t('models.modelProviderDescription')} + + + )} + /> - ( - - {t('models.abilities')} -
- - {t('models.selectModelAbilities')} - -
- {abilityOptions.map((item) => ( - { - return ( - - - { - return checked - ? field.onChange([ - ...(field.value || []), - item.value, - ]) - : field.onChange( - field.value?.filter( - (value: string) => - value !== item.value, - ), - ); - }} - /> - - - {item.label} - - - ); - }} - /> - ))} - -
- )} - /> +
+ {t('models.provider')} + setProviderMode(v as 'existing' | 'new')} + className="mt-2" + > + + + {t('models.existingProvider')} + + {t('models.newProvider')} + - - {t('models.extraParameters')} -
- {extraArgs.map((arg, index) => ( -
- - updateExtraArg(index, 'key', e.target.value) - } - /> - - - updateExtraArg(index, 'value', e.target.value) - } - /> - -
- ))} - -
- - {t('llm.extraParametersDescription')} - - -
+ + + + + {providers.map((p) => ( + + {p.name} ({p.base_url || 'default'}) + + ))} + + + + + )} + /> + + + + ( + + {t('models.requester')} + + + + )} + /> + + ( + + {t('models.requestURL')} + + + + + + )} + /> + + ( + + {t('models.apiKey')} + + + + + + )} + /> + +
+ + ( + + {t('models.abilities')} + + {t('models.selectModelAbilities')} + + {abilityOptions.map((item) => ( + ( + + + { + if (checked) { + field.onChange([ + ...(field.value || []), + item.value, + ]); + } else { + field.onChange( + field.value?.filter( + (v: string) => v !== item.value, + ), + ); + } + }} + /> + + + {item.label} + + + )} + /> + ))} + + )} + /> + + + {t('models.extraParameters')} +
+ {extraArgs.map((arg, index) => ( +
+ + updateExtraArg(index, 'key', e.target.value) + } + /> + + + updateExtraArg(index, 'value', e.target.value) + } + /> + +
+ ))} + +
+ + {t('llm.extraParametersDescription')} + +
+ {testErrorMessage && ( @@ -659,6 +606,7 @@ export default function LLMForm({ )} + {editMode && ( - - - diff --git a/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx b/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx new file mode 100644 index 00000000..70afb369 --- /dev/null +++ b/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx @@ -0,0 +1,242 @@ +import { useEffect, useState } from 'react'; +import { httpClient } from '@/app/infra/http/HttpClient'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { useTranslation } from 'react-i18next'; + +import { Button } from '@/components/ui/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { DialogFooter } from '@/components/ui/dialog'; +import { toast } from 'sonner'; +import { extractI18nObject } from '@/i18n/I18nProvider'; + +const getFormSchema = (t: (key: string) => string) => + z.object({ + name: z.string().min(1, { message: t('models.providerNameRequired') }), + requester: z.string().min(1, { message: t('models.requesterRequired') }), + base_url: z.string(), + api_key: z.string().optional(), + }); + +interface ProviderFormProps { + providerId?: string; + onFormSubmit: () => void; + onFormCancel: () => void; +} + +export default function ProviderForm({ + providerId, + onFormSubmit, + onFormCancel, +}: ProviderFormProps) { + const { t } = useTranslation(); + const formSchema = getFormSchema(t); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: '', + requester: '', + base_url: '', + api_key: '', + }, + }); + + const [requesterList, setRequesterList] = useState< + { label: string; value: string; category: string; defaultUrl: string }[] + >([]); + + useEffect(() => { + loadRequesters(); + if (providerId) { + loadProvider(providerId); + } + }, [providerId]); + + async function loadRequesters() { + const resp = await httpClient.getProviderRequesters('llm'); + setRequesterList( + resp.requesters.map((item) => ({ + label: extractI18nObject(item.label), + value: item.name, + category: item.spec.provider_category || 'manufacturer', + defaultUrl: + item.spec.config + .find((c) => c.name === 'base_url') + ?.default?.toString() || '', + })), + ); + } + + async function loadProvider(id: string) { + const resp = await httpClient.getModelProvider(id); + const provider = resp.provider; + + form.setValue('name', provider.name); + form.setValue('requester', provider.requester); + form.setValue('base_url', provider.base_url); + form.setValue('api_key', provider.api_keys?.[0] || ''); + } + + async function handleFormSubmit(values: z.infer) { + const data = { + name: values.name, + requester: values.requester, + base_url: values.base_url, + api_keys: values.api_key ? [values.api_key] : [], + }; + + try { + if (providerId) { + await httpClient.updateModelProvider(providerId, data); + toast.success(t('models.providerSaved')); + } else { + await httpClient.createModelProvider(data); + toast.success(t('models.providerCreated')); + } + onFormSubmit(); + } catch (err) { + toast.error(t('models.providerSaveError') + (err as Error).message); + } + } + + return ( + + + ( + + + {t('models.providerName')} + * + + + + + + + )} + /> + + ( + + + {t('models.requester')} + * + + + + + )} + /> + + ( + + {t('models.requestURL')} + + + + + + )} + /> + + ( + + {t('models.apiKey')} + + + + + + )} + /> + + + + + + + + ); +} diff --git a/web/src/app/home/knowledge/components/kb-form/KBForm.tsx b/web/src/app/home/knowledge/components/kb-form/KBForm.tsx index 6ed5173c..3f9d443f 100644 --- a/web/src/app/home/knowledge/components/kb-form/KBForm.tsx +++ b/web/src/app/home/knowledge/components/kb-form/KBForm.tsx @@ -19,16 +19,12 @@ import { SelectContent, SelectGroup, SelectItem, + SelectLabel, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { KnowledgeBase, EmbeddingModel } from '@/app/infra/entities/api'; import { toast } from 'sonner'; -import { - HoverCard, - HoverCardContent, - HoverCardTrigger, -} from '@/components/ui/hover-card'; const getFormSchema = (t: (key: string) => string) => z.object({ @@ -205,90 +201,35 @@ export default function KBForm({ /> - - {embeddingModels.map((model) => ( - - - - {model.name} - - - -
-
- icon -

- {model.name} -

-
-

- {model.description} -

- {model.requester_config && ( -
- - - - - Base URL: - - {model.requester_config.base_url} -
- )} - {model.extra_args && - Object.keys(model.extra_args).length > - 0 && ( -
-
- {t('models.extraParameters')} -
-
- {Object.entries( - model.extra_args as Record< - string, - unknown - >, - ).map(([key, value]) => ( -
- - {key}: - - - {JSON.stringify(value)} - -
- ))} -
-
- )} -
-
-
- ))} -
+ {(() => { + const grouped = embeddingModels.reduce( + (acc, model) => { + const providerName = + model.provider?.name || + model.provider?.requester || + 'Unknown'; + if (!acc[providerName]) acc[providerName] = []; + acc[providerName].push(model); + return acc; + }, + {} as Record, + ); + return Object.entries(grouped).map( + ([providerName, models]) => ( + + {providerName} + {models.map((model) => ( + + {model.name} + + ))} + + ), + ); + })()}
diff --git a/web/src/app/infra/entities/api/index.ts b/web/src/app/infra/entities/api/index.ts index 9ae309ac..407bb6fc 100644 --- a/web/src/app/infra/entities/api/index.ts +++ b/web/src/app/infra/entities/api/index.ts @@ -41,20 +41,33 @@ export interface ApiRespProviderLLMModel { model: LLMModel; } -export interface LLMModel { - name: string; - description: string; +export interface ModelProvider { uuid: string; + name: string; requester: string; - requester_config: { - base_url: string; - timeout: number; - }; - extra_args?: object; + base_url: string; api_keys: string[]; + llm_count?: number; + embedding_count?: number; + created_at?: string; + updated_at?: string; +} + +export interface ApiRespModelProviders { + providers: ModelProvider[]; +} + +export interface ApiRespModelProvider { + provider: ModelProvider; +} + +export interface LLMModel { + uuid: string; + name: string; + provider_uuid: string; + provider?: ModelProvider; abilities?: string[]; - // created_at: string; - // updated_at: string; + extra_args?: object; } export interface KnowledgeBase { @@ -76,18 +89,11 @@ export interface ApiRespProviderEmbeddingModel { } export interface EmbeddingModel { - name: string; - description: string; uuid: string; - requester: string; - requester_config: { - base_url: string; - timeout: number; - }; + name: string; + provider_uuid: string; + provider?: ModelProvider; extra_args?: object; - api_keys: string[]; - // created_at: string; - // updated_at: string; } export interface ApiRespPipelines { diff --git a/web/src/app/infra/http/BackendClient.ts b/web/src/app/infra/http/BackendClient.ts index 541ec02b..69da4e9d 100644 --- a/web/src/app/infra/http/BackendClient.ts +++ b/web/src/app/infra/http/BackendClient.ts @@ -38,6 +38,9 @@ import { ExternalKnowledgeBase, ApiRespExternalKnowledgeBases, ApiRespExternalKnowledgeBase, + ApiRespModelProviders, + ApiRespModelProvider, + ModelProvider, } from '@/app/infra/entities/api'; import { Plugin } from '@/app/infra/entities/plugin'; import { GetBotLogsRequest } from '@/app/infra/http/requestParam/bots/GetBotLogsRequest'; @@ -65,7 +68,6 @@ export class BackendClient extends BaseHttpClient { public getProviderRequesterIconURL(name: string): string { if (this.instance.defaults.baseURL === '/') { - // 获取用户访问的URL const url = window.location.href; const baseURL = url.split('/').slice(0, 3).join('/'); return `${baseURL}/api/v1/provider/requesters/${name}/icon`; @@ -76,9 +78,38 @@ export class BackendClient extends BaseHttpClient { ); } + // ============ Model Providers ============ + public getModelProviders(): Promise { + return this.get('/api/v1/provider/providers'); + } + + public getModelProvider(uuid: string): Promise { + return this.get(`/api/v1/provider/providers/${uuid}`); + } + + public createModelProvider( + provider: Omit, + ): Promise<{ uuid: string }> { + return this.post('/api/v1/provider/providers', provider); + } + + public updateModelProvider( + uuid: string, + provider: Partial, + ): Promise { + return this.put(`/api/v1/provider/providers/${uuid}`, provider); + } + + public deleteModelProvider(uuid: string): Promise { + return this.delete(`/api/v1/provider/providers/${uuid}`); + } + // ============ Provider Model LLM ============ - public getProviderLLMModels(): Promise { - return this.get('/api/v1/provider/models/llm'); + public getProviderLLMModels( + providerUuid?: string, + ): Promise { + const params = providerUuid ? { provider_uuid: providerUuid } : {}; + return this.get('/api/v1/provider/models/llm', params); } public getProviderLLMModel(uuid: string): Promise { @@ -105,8 +136,11 @@ export class BackendClient extends BaseHttpClient { } // ============ Provider Model Embedding ============ - public getProviderEmbeddingModels(): Promise { - return this.get('/api/v1/provider/models/embedding'); + public getProviderEmbeddingModels( + providerUuid?: string, + ): Promise { + const params = providerUuid ? { provider_uuid: providerUuid } : {}; + return this.get('/api/v1/provider/models/embedding', params); } public getProviderEmbeddingModel( @@ -716,61 +750,4 @@ export class BackendClient extends BaseHttpClient { }> { return this.post('/api/v1/user/space/callback', { code }); } - - // ============ Space Models Sync API ============ - public syncSpaceModels(spaceUrl?: string): Promise<{ - created_llm: number; - updated_llm: number; - created_embedding: number; - updated_embedding: number; - skipped: number; - }> { - return this.post('/api/v1/space/models/sync', { space_url: spaceUrl }); - } - - public getSpaceModels(): Promise<{ - llm_models: Array<{ - uuid: string; - name: string; - description: string; - requester: string; - space_model_id: string; - source: string; - }>; - embedding_models: Array<{ - uuid: string; - name: string; - description: string; - requester: string; - space_model_id: string; - source: string; - }>; - }> { - return this.get('/api/v1/space/models'); - } - - public deleteSpaceModels(): Promise<{ - deleted_llm: number; - deleted_embedding: number; - }> { - return this.delete('/api/v1/space/models'); - } - - public getAvailableSpaceModels(spaceUrl?: string): Promise<{ - models: Array<{ - model_id: string; - display_name: { [key: string]: string }; - description: { [key: string]: string }; - category: string; - provider: string; - }>; - vendors: Array<{ - id: number; - name: string; - }>; - total: number; - }> { - const params = spaceUrl ? { space_url: spaceUrl } : {}; - return this.get('/api/v1/space/models/available', params); - } } diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 1463d501..64cafc47 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -186,6 +186,36 @@ const enUS = { spaceModelReadOnly: 'Space models are read-only', noSpaceModels: 'No Space models. Click Sync to fetch models from Space.', noLocalModels: 'No local models. Click Create to add a model.', + // New keys for provider-based structure + addModel: 'Add Model', + addLLMModel: 'Add LLM Model', + addEmbeddingModel: 'Add Embedding Model', + provider: 'Provider', + existingProvider: 'Existing Provider', + newProvider: 'New Provider', + selectProvider: 'Select Provider', + requester: 'Requester', + selectRequester: 'Select Requester', + langbotModelsDescription: 'Cloud models powered by LangBot Space', + balance: 'Balance', + loginWithSpace: 'Login with Space', + loginToUseModels: 'Login with Space to use cloud models', + noModels: 'No models configured', + editProvider: 'Edit Provider', + providerName: 'Provider Name', + providerNameRequired: 'Provider name is required', + requesterRequired: 'Requester is required', + providerSaved: 'Provider saved', + providerCreated: 'Provider created', + providerSaveError: 'Failed to save provider: ', + providerDeleted: 'Provider deleted', + providerDeleteError: 'Failed to delete provider: ', + loadError: 'Failed to load data', + chat: 'Chat', + embedding: 'Embedding', + modelsCount: '{{count}} model(s)', + expandModels: 'Expand', + collapseModels: 'Collapse', }, bots: { title: 'Bots', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 2e435495..38dbea4a 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -192,6 +192,35 @@ const jaJP = { 'Space モデルがありません。同期ボタンをクリックして Space からモデルを取得してください。', noLocalModels: 'ローカルモデルがありません。作成ボタンをクリックしてモデルを追加してください。', + addModel: 'モデルを追加', + addLLMModel: 'LLMモデルを追加', + addEmbeddingModel: '埋め込みモデルを追加', + provider: 'プロバイダー', + existingProvider: '既存のプロバイダー', + newProvider: '新規プロバイダー', + selectProvider: 'プロバイダーを選択', + requester: 'リクエスター', + selectRequester: 'リクエスターを選択', + langbotModelsDescription: 'LangBot Space が提供するクラウドモデル', + balance: '残高', + loginWithSpace: 'Space でログイン', + loginToUseModels: 'Space でログインしてクラウドモデルを使用', + noModels: 'モデルがありません', + editProvider: 'プロバイダーを編集', + providerName: 'プロバイダー名', + providerNameRequired: 'プロバイダー名は必須です', + requesterRequired: 'リクエスターは必須です', + providerSaved: 'プロバイダーを保存しました', + providerCreated: 'プロバイダーを作成しました', + providerSaveError: 'プロバイダーの保存に失敗しました:', + providerDeleted: 'プロバイダーを削除しました', + providerDeleteError: 'プロバイダーの削除に失敗しました:', + loadError: 'データの読み込みに失敗しました', + chat: 'チャット', + embedding: '埋め込み', + modelsCount: '{{count}} 個のモデル', + expandModels: '展開', + collapseModels: '折りたたむ', }, bots: { title: 'ボット', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 1767bcf5..8c98c0f6 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -180,6 +180,36 @@ const zhHans = { spaceModelReadOnly: 'Space 模型为只读', noSpaceModels: '暂无 Space 模型。点击同步按钮从 Space 获取模型。', noLocalModels: '暂无本地模型。点击创建按钮添加模型。', + // 供应商结构新增键 + addModel: '添加模型', + addLLMModel: '添加对话模型', + addEmbeddingModel: '添加嵌入模型', + provider: '供应商', + existingProvider: '已有供应商', + newProvider: '新建供应商', + selectProvider: '选择供应商', + requester: '请求器', + selectRequester: '选择请求器', + langbotModelsDescription: 'LangBot Space 提供的云端模型', + balance: '余额', + loginWithSpace: '通过 Space 登录', + loginToUseModels: '通过 Space 登录以使用云端模型', + noModels: '暂无模型', + editProvider: '编辑供应商', + providerName: '供应商名称', + providerNameRequired: '供应商名称不能为空', + requesterRequired: '请求器不能为空', + providerSaved: '供应商已保存', + providerCreated: '供应商已创建', + providerSaveError: '保存供应商失败:', + providerDeleted: '供应商已删除', + providerDeleteError: '删除供应商失败:', + loadError: '加载数据失败', + chat: '对话', + embedding: '嵌入', + modelsCount: '{{count}} 个模型', + expandModels: '展开', + collapseModels: '收起', }, bots: { title: '机器人', diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index 55af4d62..ad4e5ced 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -180,6 +180,35 @@ const zhHant = { spaceModelReadOnly: 'Space 模型為唯讀', noSpaceModels: '暫無 Space 模型。點擊同步按鈕從 Space 取得模型。', noLocalModels: '暫無本地模型。點擊建立按鈕新增模型。', + addModel: '新增模型', + addLLMModel: '新增對話模型', + addEmbeddingModel: '新增嵌入模型', + provider: '供應商', + existingProvider: '現有供應商', + newProvider: '新供應商', + selectProvider: '選擇供應商', + requester: '請求器', + selectRequester: '選擇請求器', + langbotModelsDescription: '由 LangBot Space 提供的雲端模型', + balance: '餘額', + loginWithSpace: '使用 Space 登入', + loginToUseModels: '使用 Space 登入以使用雲端模型', + noModels: '暫無模型', + editProvider: '編輯供應商', + providerName: '供應商名稱', + providerNameRequired: '供應商名稱不能為空', + requesterRequired: '請求器不能為空', + providerSaved: '供應商已儲存', + providerCreated: '供應商已建立', + providerSaveError: '儲存供應商失敗:', + providerDeleted: '供應商已刪除', + providerDeleteError: '刪除供應商失敗:', + loadError: '載入資料失敗', + chat: '對話', + embedding: '嵌入', + modelsCount: '{{count}} 個模型', + expandModels: '展開', + collapseModels: '收起', }, bots: { title: '機器人', From 1d4c5bbdf1d24936c536a832d7bf5242c6ab1768 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Fri, 26 Dec 2025 20:57:12 +0800 Subject: [PATCH 05/30] feat: enhance model abilities display in DynamicFormItem and ModelsDialog components with icons for vision and function call --- .../dynamic-form/DynamicFormItemComponent.tsx | 14 ++++++++++---- .../home/components/models-dialog/ModelsDialog.tsx | 10 ++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx index 690d8de1..96369bf3 100644 --- a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx +++ b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx @@ -43,7 +43,7 @@ import { DialogFooter, } from '@/components/ui/dialog'; import { Checkbox } from '@/components/ui/checkbox'; -import { Plus, X } from 'lucide-react'; +import { Plus, X, Eye, Wrench } from 'lucide-react'; export default function DynamicFormItemComponent({ config, @@ -277,9 +277,15 @@ export default function DynamicFormItemComponent({ {providerName} {models.map((model) => ( - {model.name} - {model.abilities?.includes('vision') && ' 👁'} - {model.abilities?.includes('func_call') && ' 🔧'} + + {model.name} + {model.abilities?.includes('vision') && ( + + )} + {model.abilities?.includes('func_call') && ( + + )} + ))} diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index 2e5079a3..f8dc9ca2 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -11,6 +11,8 @@ import { Settings, Sparkles, LogIn, + Eye, + Wrench, } from 'lucide-react'; import { httpClient } from '@/app/infra/http/HttpClient'; import { @@ -400,13 +402,13 @@ export default function ModelsDialog({ {t('models.chat')} {model.abilities?.includes('vision') && ( - - 👁 + + )} {model.abilities?.includes('func_call') && ( - - 🔧 + + )} From 24c15b4479ce5e52279e70b008a3c21cf3a1fb79 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Fri, 26 Dec 2025 23:20:51 +0800 Subject: [PATCH 06/30] feat: implement account settings dialog for managing user passwords and binding Space accounts --- AGENTS.md | 1 + .../pkg/api/http/controller/groups/user.py | 82 +++++ src/langbot/pkg/api/http/service/user.py | 147 +++++++-- web/src/app/auth/space/callback/page.tsx | 134 ++++++-- .../AccountSettingsDialog.tsx | 274 ++++++++++++++++ .../components/home-sidebar/HomeSidebar.tsx | 57 ++-- web/src/app/infra/http/BackendClient.ts | 30 ++ web/src/app/login/page.tsx | 296 ++++++++++-------- web/src/app/register/page.tsx | 2 +- web/src/i18n/locales/en-US.ts | 23 ++ web/src/i18n/locales/ja-JP.ts | 23 ++ web/src/i18n/locales/zh-Hans.ts | 19 ++ web/src/i18n/locales/zh-Hant.ts | 19 ++ 13 files changed, 901 insertions(+), 206 deletions(-) create mode 100644 web/src/app/home/components/account-settings-dialog/AccountSettingsDialog.tsx diff --git a/AGENTS.md b/AGENTS.md index aa6acf23..c9c2e2b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,6 +70,7 @@ Plugin Runtime automatically starts each installed plugin and interacts through - type: must be a specific type, such as feat (new feature), fix (bug fix), docs (documentation), style (code style), refactor (refactoring), perf (performance optimization), etc. - scope: the scope of the commit, such as the package name, the file name, the function name, the class name, the module name, etc. - subject: the subject of the commit, such as the description of the commit, the reason for the commit, the impact of the commit, etc. +- If you changed the definition of database entities, please update the migration file in `src/langbot/pkg/persistence/migrations/` and update the constants.py file in `src/langbot/pkg/utils/constants.py` with the new migration number. ## Some Principles diff --git a/src/langbot/pkg/api/http/controller/groups/user.py b/src/langbot/pkg/api/http/controller/groups/user.py index 16aa9a51..89fd6507 100644 --- a/src/langbot/pkg/api/http/controller/groups/user.py +++ b/src/langbot/pkg/api/http/controller/groups/user.py @@ -152,5 +152,87 @@ class UserRouterGroup(group.RouterGroup): data={ 'user': user_obj.user, 'account_type': user_obj.account_type, + 'has_password': bool(user_obj.password and user_obj.password.strip()), } ) + + @self.route('/account-info', methods=['GET'], auth_type=group.AuthType.NONE) + async def _() -> str: + """Get account info for login page (account type and has_password)""" + if not await self.ap.user_service.is_initialized(): + return self.success(data={'initialized': False}) + + user_obj = await self.ap.user_service.get_first_user() + if user_obj is None: + return self.success(data={'initialized': False}) + + return self.success( + data={ + 'initialized': True, + 'account_type': user_obj.account_type, + 'has_password': bool(user_obj.password and user_obj.password.strip()), + } + ) + + @self.route('/set-password', methods=['POST'], auth_type=group.AuthType.USER_TOKEN) + async def _(user_email: str) -> str: + """Set password for Space account (first time) or change password""" + json_data = await quart.request.json + new_password = json_data.get('new_password') + current_password = json_data.get('current_password') + + if not new_password: + return self.http_status(400, -1, 'New password is required') + + user_obj = await self.ap.user_service.get_user_by_email(user_email) + if user_obj is None: + return self.http_status(404, -1, 'User not found') + + try: + await self.ap.user_service.set_password(user_email, new_password, current_password) + return self.success(data={'user': user_email}) + except ValueError as e: + return self.http_status(400, -1, str(e)) + except argon2.exceptions.VerifyMismatchError: + return self.http_status(400, -1, 'Current password is incorrect') + + @self.route('/bind-space', methods=['POST'], auth_type=group.AuthType.NONE) + async def _() -> str: + """Bind Space account to existing local account""" + json_data = await quart.request.json + code = json_data.get('code') + state = json_data.get('state') # JWT token passed as state + + if not code: + return self.http_status(400, -1, 'Missing authorization code') + + if not state: + return self.http_status(400, -1, 'Missing state parameter') + + # Verify state is a valid JWT token + try: + user_email = await self.ap.user_service.verify_jwt_token(state) + except Exception: + return self.http_status(401, -1, 'Invalid or expired state') + + user_obj = await self.ap.user_service.get_user_by_email(user_email) + if user_obj is None: + return self.http_status(404, -1, 'User not found') + + if user_obj.account_type != 'local': + return self.http_status(400, -1, 'Only local accounts can bind to Space') + + try: + updated_user = await self.ap.user_service.bind_space_account(user_email, code) + jwt_token = await self.ap.user_service.generate_jwt_token(updated_user.user) + return self.success( + data={ + 'token': jwt_token, + 'user': updated_user.user, + 'account_type': updated_user.account_type, + } + ) + except ValueError as e: + return self.http_status(400, -1, str(e)) + except Exception as e: + return self.http_status(500, -1, f'Failed to bind Space account: {str(e)}') diff --git a/src/langbot/pkg/api/http/service/user.py b/src/langbot/pkg/api/http/service/user.py index 5f98dc6f..4db1e013 100644 --- a/src/langbot/pkg/api/http/service/user.py +++ b/src/langbot/pkg/api/http/service/user.py @@ -6,6 +6,7 @@ import jwt import datetime import aiohttp import typing +import asyncio from ....core import app from ....entity.persistence import user @@ -14,9 +15,11 @@ from ....utils import constants class UserService: ap: app.Application + _create_user_lock: asyncio.Lock def __init__(self, ap: app.Application) -> None: self.ap = ap + self._create_user_lock = asyncio.Lock() def _get_space_config(self) -> typing.Dict[str, str]: """Get Space configuration from config file""" @@ -197,43 +200,63 @@ class UserService: refresh_token: str, api_key: str, ) -> user.User: - """Create or update a Space user account""" - # Check if user with this Space UUID already exists - existing_user = await self.get_user_by_space_account_uuid(space_account_uuid) + """Create or update a Space user account (only if system not initialized or user exists)""" + async with self._create_user_lock: + # Check if user with this Space UUID already exists + existing_user = await self.get_user_by_space_account_uuid(space_account_uuid) - if existing_user: - # Update existing user's tokens + if existing_user: + # Update existing user's tokens + await self.ap.persistence_mgr.execute_async( + sqlalchemy.update(user.User) + .where(user.User.space_account_uuid == space_account_uuid) + .values( + space_access_token=access_token, + space_refresh_token=refresh_token, + space_api_key=api_key, + ) + ) + return await self.get_user_by_space_account_uuid(space_account_uuid) + + # Check if user with same email exists + existing_email_user = await self.get_user_by_email(email) + if existing_email_user: + # Update existing user to link with Space account + await self.ap.persistence_mgr.execute_async( + sqlalchemy.update(user.User) + .where(user.User.user == email) + .values( + account_type='space', + space_account_uuid=space_account_uuid, + space_access_token=access_token, + space_refresh_token=refresh_token, + space_api_key=api_key, + ) + ) + return await self.get_user_by_email(email) + + # Check if system is already initialized + is_initialized = await self.is_initialized() + if is_initialized: + raise ValueError( + 'This LangBot instance already has an account. Please use the existing account to login.' + ) + + # Create new Space user (first time initialization) await self.ap.persistence_mgr.execute_async( - sqlalchemy.update(user.User) - .where(user.User.space_account_uuid == space_account_uuid) - .values( + sqlalchemy.insert(user.User).values( + user=email, + password='', # Space users don't have local password + account_type='space', + space_account_uuid=space_account_uuid, space_access_token=access_token, space_refresh_token=refresh_token, space_api_key=api_key, ) ) + return await self.get_user_by_space_account_uuid(space_account_uuid) - # Check if user with same email exists as local account - existing_email_user = await self.get_user_by_email(email) - if existing_email_user and existing_email_user.account_type == 'local': - raise ValueError('A local account with this email already exists. Please use a different email.') - - # Create new Space user - await self.ap.persistence_mgr.execute_async( - sqlalchemy.insert(user.User).values( - user=email, - password='', # Space users don't have local password - account_type='space', - space_account_uuid=space_account_uuid, - space_access_token=access_token, - space_refresh_token=refresh_token, - space_api_key=api_key, - ) - ) - - return await self.get_user_by_space_account_uuid(space_account_uuid) - async def authenticate_space_user(self, access_token: str, refresh_token: str) -> typing.Tuple[str, user.User]: """Authenticate with Space and return JWT token""" # Get user info from Space @@ -261,3 +284,71 @@ class UserService: jwt_token = await self.generate_jwt_token(email) return jwt_token, user_obj + + async def get_first_user(self) -> user.User | None: + """Get the first user (for single-user mode)""" + result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(user.User).limit(1)) + result_list = result.all() + return result_list[0] if result_list else None + + async def set_password(self, user_email: str, new_password: str, current_password: str | None = None) -> None: + """Set or change password for a user""" + ph = argon2.PasswordHasher() + user_obj = await self.get_user_by_email(user_email) + + if user_obj is None: + raise ValueError('User not found') + + # If user already has a password, verify current password + has_password = bool(user_obj.password and user_obj.password.strip()) + if has_password: + if not current_password: + raise ValueError('Current password is required') + ph.verify(user_obj.password, current_password) + + hashed_password = ph.hash(new_password) + await self.ap.persistence_mgr.execute_async( + sqlalchemy.update(user.User).where(user.User.user == user_email).values(password=hashed_password) + ) + + async def bind_space_account(self, user_email: str, code: str) -> user.User: + """Bind Space account to existing local account""" + # Exchange code for tokens + token_data = await self.exchange_space_oauth_code(code) + access_token = token_data.get('access_token') + refresh_token = token_data.get('refresh_token') + + if not access_token: + raise ValueError('Failed to get access token from Space') + + # Get Space user info + user_info = await self.get_space_user_info(access_token) + account = user_info.get('account', {}) + api_key = user_info.get('api_key', '') + + space_account_uuid = account.get('uuid') + space_email = account.get('email') + + if not space_account_uuid or not space_email: + raise ValueError('Invalid Space user info') + + # Check if this Space account is already bound to another user + existing_space_user = await self.get_user_by_space_account_uuid(space_account_uuid) + if existing_space_user and existing_space_user.user != user_email: + raise ValueError('This Space account is already bound to another user') + + # Update local account to Space account + await self.ap.persistence_mgr.execute_async( + sqlalchemy.update(user.User) + .where(user.User.user == user_email) + .values( + user=space_email, # Update email to Space email + account_type='space', + space_account_uuid=space_account_uuid, + space_access_token=access_token, + space_refresh_token=refresh_token, + space_api_key=api_key, + ) + ) + + return await self.get_user_by_email(space_email) diff --git a/web/src/app/auth/space/callback/page.tsx b/web/src/app/auth/space/callback/page.tsx index 87ac2b9f..d0e7ddc4 100644 --- a/web/src/app/auth/space/callback/page.tsx +++ b/web/src/app/auth/space/callback/page.tsx @@ -5,7 +5,12 @@ import { useRouter, useSearchParams } from 'next/navigation'; import { httpClient } from '@/app/infra/http/HttpClient'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; -import { Loader2, AlertCircle, CheckCircle2 } from 'lucide-react'; +import { + Loader2, + AlertCircle, + CheckCircle2, + AlertTriangle, +} from 'lucide-react'; import { Card, CardContent, @@ -21,26 +26,24 @@ export default function SpaceOAuthCallback() { const searchParams = useSearchParams(); const { t } = useTranslation(); - const [status, setStatus] = useState<'loading' | 'success' | 'error'>( - 'loading', - ); + const [status, setStatus] = useState< + 'loading' | 'confirm' | 'success' | 'error' + >('loading'); const [errorMessage, setErrorMessage] = useState(''); + const [isBindMode, setIsBindMode] = useState(false); + const [code, setCode] = useState(null); + const [isProcessing, setIsProcessing] = useState(false); const handleOAuthCallback = useCallback( - async (code: string) => { + async (authCode: string) => { try { - const response = await httpClient.exchangeSpaceOAuthCode(code); - - // Store token and user info + const response = await httpClient.exchangeSpaceOAuthCode(authCode); localStorage.setItem('token', response.token); if (response.user) { localStorage.setItem('userEmail', response.user); } - setStatus('success'); toast.success(t('common.spaceLoginSuccess')); - - // Redirect to home after a brief delay to show success state setTimeout(() => { router.push('/home'); }, 1000); @@ -52,10 +55,40 @@ export default function SpaceOAuthCallback() { [router, t], ); + const [bindState, setBindState] = useState(null); + + const handleBindAccount = useCallback( + async (authCode: string, state: string) => { + setIsProcessing(true); + try { + const response = await httpClient.bindSpaceAccount(authCode, state); + localStorage.setItem('token', response.token); + if (response.user) { + localStorage.setItem('userEmail', response.user); + } + setStatus('success'); + toast.success(t('account.bindSpaceSuccess')); + setTimeout(() => { + router.push('/home'); + }, 1000); + } catch (err) { + setStatus('error'); + setErrorMessage( + err instanceof Error ? err.message : t('account.bindSpaceFailed'), + ); + } finally { + setIsProcessing(false); + } + }, + [router, t], + ); + useEffect(() => { - const code = searchParams.get('code'); + const authCode = searchParams.get('code'); const error = searchParams.get('error'); const errorDescription = searchParams.get('error_description'); + const mode = searchParams.get('mode'); + const state = searchParams.get('state'); if (error) { setStatus('error'); @@ -65,21 +98,44 @@ export default function SpaceOAuthCallback() { return; } - if (!code) { + if (!authCode) { setStatus('error'); setErrorMessage(t('common.spaceLoginNoCode')); return; } - // Exchange code for token - handleOAuthCallback(code); + setCode(authCode); + + if (mode === 'bind') { + // Bind mode - verify state (token) exists + if (!state) { + setStatus('error'); + setErrorMessage(t('account.bindSpaceInvalidState')); + return; + } + setBindState(state); + setIsBindMode(true); + setStatus('confirm'); + } else { + // Normal login/register mode + handleOAuthCallback(authCode); + } }, [searchParams, handleOAuthCallback, t]); + const handleConfirmBind = () => { + if (code && bindState) { + handleBindAccount(code, bindState); + } + }; + + const handleCancelBind = () => { + router.push('/home'); + }; + return (
- + - {/* eslint-disable-next-line @next/next/no-img-element */} LangBot {status === 'loading' && t('common.spaceLoginProcessing')} - {status === 'success' && t('common.spaceLoginSuccess')} - {status === 'error' && t('common.spaceLoginError')} + {status === 'confirm' && t('account.bindSpaceConfirmTitle')} + {status === 'success' && + (isBindMode + ? t('account.bindSpaceSuccess') + : t('common.spaceLoginSuccess'))} + {status === 'error' && + (isBindMode + ? t('account.bindSpaceFailed') + : t('common.spaceLoginError'))} {status === 'loading' && t('common.spaceLoginProcessingDescription')} + {status === 'confirm' && t('account.bindSpaceConfirmDescription')} {status === 'success' && t('common.spaceLoginSuccessDescription')} {status === 'error' && errorMessage} @@ -101,6 +165,34 @@ export default function SpaceOAuthCallback() { {status === 'loading' && ( )} + {status === 'confirm' && ( + <> + +

+ {t('account.bindSpaceWarning')} +

+
+ + +
+ + )} {status === 'success' && ( )} @@ -108,10 +200,10 @@ export default function SpaceOAuthCallback() { <> )} diff --git a/web/src/app/home/components/account-settings-dialog/AccountSettingsDialog.tsx b/web/src/app/home/components/account-settings-dialog/AccountSettingsDialog.tsx new file mode 100644 index 00000000..29b49d0a --- /dev/null +++ b/web/src/app/home/components/account-settings-dialog/AccountSettingsDialog.tsx @@ -0,0 +1,274 @@ +'use client'; + +import * as React from 'react'; +import { useState, useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { toast } from 'sonner'; +import { useTranslation } from 'react-i18next'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Separator } from '@/components/ui/separator'; +import { httpClient } from '@/app/infra/http/HttpClient'; +import { Loader2, ExternalLink } from 'lucide-react'; + +interface AccountSettingsDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export default function AccountSettingsDialog({ + open, + onOpenChange, +}: AccountSettingsDialogProps) { + const { t } = useTranslation(); + const [isSubmitting, setIsSubmitting] = useState(false); + const [accountType, setAccountType] = useState<'local' | 'space'>('local'); + const [hasPassword, setHasPassword] = useState(false); + const [userEmail, setUserEmail] = useState(''); + const [loading, setLoading] = useState(true); + const [spaceBindLoading, setSpaceBindLoading] = useState(false); + + // Schema with optional currentPassword + const formSchema = z + .object({ + currentPassword: z.string().optional(), + newPassword: z + .string() + .min(1, { message: t('common.newPasswordRequired') }), + confirmNewPassword: z + .string() + .min(1, { message: t('common.confirmPasswordRequired') }), + }) + .refine((data) => data.newPassword === data.confirmNewPassword, { + message: t('common.passwordsDoNotMatch'), + path: ['confirmNewPassword'], + }) + .refine( + (data) => + !hasPassword || + (data.currentPassword && data.currentPassword.length > 0), + { + message: t('common.currentPasswordRequired'), + path: ['currentPassword'], + }, + ); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + currentPassword: '', + newPassword: '', + confirmNewPassword: '', + }, + }); + + useEffect(() => { + if (open) { + loadUserInfo(); + } + }, [open]); + + useEffect(() => { + form.reset({ + currentPassword: '', + newPassword: '', + confirmNewPassword: '', + }); + }, [hasPassword, form]); + + async function loadUserInfo() { + setLoading(true); + try { + const info = await httpClient.getUserInfo(); + setAccountType(info.account_type); + setHasPassword(info.has_password); + setUserEmail(info.user); + } catch { + toast.error(t('common.error')); + } finally { + setLoading(false); + } + } + + const onSubmit = async (values: z.infer) => { + setIsSubmitting(true); + try { + await httpClient.setPassword(values.newPassword, values.currentPassword); + toast.success(t('account.passwordSetSuccess')); + form.reset(); + setHasPassword(true); + } catch { + toast.error(t('common.changePasswordFailed')); + } finally { + setIsSubmitting(false); + } + }; + + const handleBindSpace = async () => { + setSpaceBindLoading(true); + try { + const token = localStorage.getItem('token'); + if (!token) { + toast.error(t('common.error')); + setSpaceBindLoading(false); + return; + } + const currentOrigin = window.location.origin; + const redirectUri = `${currentOrigin}/auth/space/callback?mode=bind`; + // Pass token as state for security verification + const response = await httpClient.getSpaceAuthorizeUrl( + redirectUri, + token, + ); + window.location.href = response.authorize_url; + } catch { + toast.error(t('common.spaceLoginFailed')); + setSpaceBindLoading(false); + } + }; + + return ( + + + + {t('account.settings')} + {userEmail} + + + {loading ? ( +
+ +
+ ) : ( +
+ {/* Password Section */} +
+

+ {hasPassword + ? t('common.changePassword') + : t('account.setPassword')} +

+ {!hasPassword && ( +

+ {t('account.setPasswordHint')} +

+ )} +
+ + {hasPassword && ( + ( + + {t('common.currentPassword')} + + + + + + )} + /> + )} + ( + + {t('common.newPassword')} + + + + + + )} + /> + ( + + {t('common.confirmNewPassword')} + + + + + + )} + /> + + + +
+ + {/* Bind Space Account - only for local accounts */} + {accountType === 'local' && ( + <> + +
+

+ {t('account.bindSpace')} +

+

+ {t('account.bindSpaceDescription')} +

+ +
+ + )} +
+ )} +
+
+ ); +} diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 764acfe2..6ab5b3a9 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -9,7 +9,7 @@ import { import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConfigList'; import langbotIcon from '@/app/assets/langbot-logo.webp'; -import { systemInfo } from '@/app/infra/http/HttpClient'; +import { systemInfo, httpClient } from '@/app/infra/http/HttpClient'; import { getCloudServiceClientSync } from '@/app/infra/http'; import { useTranslation } from 'react-i18next'; import { @@ -18,9 +18,9 @@ import { Monitor, CircleHelp, Lightbulb, - Lock, LogOut, KeyRound, + User, } from 'lucide-react'; import { useTheme } from 'next-themes'; @@ -33,7 +33,7 @@ import { Button } from '@/components/ui/button'; import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; import { LanguageSelector } from '@/components/ui/language-selector'; import { Badge } from '@/components/ui/badge'; -import PasswordChangeDialog from '@/app/home/components/password-change-dialog/PasswordChangeDialog'; +import AccountSettingsDialog from '@/app/home/components/account-settings-dialog/AccountSettingsDialog'; import ApiIntegrationDialog from '@/app/home/components/api-integration-dialog/ApiIntegrationDialog'; import NewVersionDialog from '@/app/home/components/new-version-dialog/NewVersionDialog'; import ModelsDialog from '@/app/home/components/models-dialog/ModelsDialog'; @@ -85,7 +85,7 @@ export default function HomeSidebar({ const { theme, setTheme } = useTheme(); const { t } = useTranslation(); const [popoverOpen, setPopoverOpen] = useState(false); - const [passwordChangeOpen, setPasswordChangeOpen] = useState(false); + const [accountSettingsOpen, setAccountSettingsOpen] = useState(false); const [apiKeyDialogOpen, setApiKeyDialogOpen] = useState(false); const [languageSelectorOpen, setLanguageSelectorOpen] = useState(false); const [starCount, setStarCount] = useState(null); @@ -95,6 +95,7 @@ export default function HomeSidebar({ const [hasNewVersion, setHasNewVersion] = useState(false); const [versionDialogOpen, setVersionDialogOpen] = useState(false); const [modelsDialogOpen, setModelsDialogOpen] = useState(false); + const [userEmail, setUserEmail] = useState(''); // 处理模型对话框的打开和关闭,同时更新 URL function handleModelsDialogChange(open: boolean) { @@ -120,6 +121,21 @@ export default function HomeSidebar({ localStorage.setItem('userEmail', 'test@example.com'); } + // Load user email + const storedEmail = localStorage.getItem('userEmail'); + if (storedEmail) { + setUserEmail(storedEmail); + } else { + // Fetch from API if not in localStorage + httpClient + .getUserInfo() + .then((info) => { + setUserEmail(info.user); + localStorage.setItem('userEmail', info.user); + }) + .catch(() => {}); + } + getCloudServiceClientSync() .get('/api/v1/dist/info/repo') .then((response) => { @@ -374,6 +390,20 @@ export default function HomeSidebar({
{t('common.account')} + {/* User email display */} + - {systemInfo?.allow_change_password && ( - - )}
- { return this.get('/api/v1/user/info'); } + public getAccountInfo(): Promise<{ + initialized: boolean; + account_type?: 'local' | 'space'; + has_password?: boolean; + }> { + return this.get('/api/v1/user/account-info'); + } + + public setPassword( + newPassword: string, + currentPassword?: string, + ): Promise<{ user: string }> { + return this.post('/api/v1/user/set-password', { + new_password: newPassword, + current_password: currentPassword, + }); + } + + public bindSpaceAccount( + code: string, + state: string, + ): Promise<{ + token: string; + user: string; + account_type: 'local' | 'space'; + }> { + return this.post('/api/v1/user/bind-space', { code, state }); + } + // ============ Space OAuth API (Redirect Flow) ============ public getSpaceAuthorizeUrl( redirectUri: string, diff --git a/web/src/app/login/page.tsx b/web/src/app/login/page.tsx index 60d0e118..4ce2f722 100644 --- a/web/src/app/login/page.tsx +++ b/web/src/app/login/page.tsx @@ -36,10 +36,15 @@ const formSchema = (t: (key: string) => string) => password: z.string().min(1, t('common.emptyPassword')), }); +type AccountType = 'local' | 'space'; + export default function Login() { const router = useRouter(); const { t } = useTranslation(); const [spaceLoading, setSpaceLoading] = useState(false); + const [accountType, setAccountType] = useState(null); + const [hasPassword, setHasPassword] = useState(false); + const [loading, setLoading] = useState(true); const form = useForm>>({ resolver: zodResolver(formSchema(t)), @@ -50,19 +55,25 @@ export default function Login() { }); useEffect(() => { - getIsInitialized(); - checkIfAlreadyLoggedIn(); + checkAccountInfo(); }, []); - function getIsInitialized() { - httpClient - .checkIfInited() - .then((res) => { - if (!res.initialized) { - router.push('/register'); - } - }) - .catch(() => {}); + async function checkAccountInfo() { + try { + const res = await httpClient.getAccountInfo(); + if (!res.initialized) { + router.push('/register'); + return; + } + setAccountType(res.account_type || 'local'); + setHasPassword(res.has_password || false); + setLoading(false); + + // Also check if already logged in + checkIfAlreadyLoggedIn(); + } catch { + setLoading(false); + } } function checkIfAlreadyLoggedIn() { @@ -95,19 +106,12 @@ export default function Login() { }); } - // Space OAuth redirect handler const handleSpaceLoginClick = async () => { setSpaceLoading(true); - try { - // Build the redirect URI to the OAuth callback page const currentOrigin = window.location.origin; const redirectUri = `${currentOrigin}/auth/space/callback`; - - // Get the authorization URL from backend const response = await httpClient.getSpaceAuthorizeUrl(redirectUri); - - // Redirect to Space authorization page window.location.href = response.authorize_url; } catch { toast.error(t('common.spaceLoginFailed')); @@ -115,6 +119,19 @@ export default function Login() { } }; + if (loading) { + return ( +
+ +
+ ); + } + + // Determine what to show based on account type + const showLocalLogin = + accountType === 'local' || (accountType === 'space' && hasPassword); + const showSpaceLogin = accountType === 'space'; + return (
@@ -136,128 +153,135 @@ export default function Login() { - {/* Space Login - Recommended */} -
- -

- {t('common.spaceLoginRecommended')} -

-
- -
-
- -
-
- - {t('common.or')} - -
-
- - {/* Local Account Login */} -
- - ( - - {t('common.email')} - -
- - -
-
- -
- )} - /> - - ( - -
- {t('common.password')} - - {t('common.forgotPassword')} - -
- - -
- - -
-
- -
- )} - /> - + {/* Space Login - only show for space accounts */} + {showSpaceLogin && ( +
- - +
+ )} + + {/* Divider - only show if both login methods are available */} + {showSpaceLogin && showLocalLogin && ( +
+
+ +
+
+ + {t('common.or')} + +
+
+ )} + + {/* Local Account Login - show for local accounts or space accounts with password */} + {showLocalLogin && ( +
+ + ( + + {t('common.email')} + +
+ + +
+
+ +
+ )} + /> + + ( + +
+ {t('common.password')} + + {t('common.forgotPassword')} + +
+ + +
+ + +
+
+ +
+ )} + /> + + + + + )}
diff --git a/web/src/app/register/page.tsx b/web/src/app/register/page.tsx index 64c89356..5ec3b249 100644 --- a/web/src/app/register/page.tsx +++ b/web/src/app/register/page.tsx @@ -231,7 +231,7 @@ export default function Register() { variant="outline" className="w-full cursor-pointer" > - {t('register.registerLocal')} + {t('register.registerWithPassword')} diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 64cafc47..4a85db8a 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -51,6 +51,7 @@ const enUS = { loginWithSpace: 'Login with Space', spaceLoginRecommended: 'Recommended: Sync models and credits from Space', loginLocal: 'Login with local account', + loginWithPassword: 'Login with password', spaceLoginTitle: 'Login with Space', spaceLoginDescription: 'Scan the QR code or visit the link below to authorize', @@ -69,6 +70,7 @@ const enUS = { spaceLoginError: 'Login Failed', spaceLoginNoCode: 'Missing authorization code', backToLogin: 'Back to Login', + backToHome: 'Back to Home', spaceAccountCannotChangePassword: 'Space accounts cannot change password here', theme: 'Theme', @@ -707,6 +709,7 @@ const enUS = { initWithSpace: 'Initialize with Space', spaceRecommended: 'Recommended: Sync models and credits from Space', registerLocal: 'Register local account', + registerWithPassword: 'Register with email and password', initSuccess: 'Initialization successful, please login', initFailed: 'Initialization failed: ', }, @@ -749,6 +752,26 @@ const enUS = { viewUpdateGuide: 'View Update Guide', noReleaseNotes: 'No release notes available', }, + account: { + settings: 'Account Settings', + setPassword: 'Set Password', + passwordSetSuccess: 'Password set successfully', + bindSpace: 'Bind Space Account', + bindSpaceDescription: + 'Link your local account to a Space account to sync models and credits', + bindSpaceButton: 'Bind Space Account', + bindSpaceConfirmTitle: 'Confirm Binding', + bindSpaceConfirmDescription: + 'You are about to bind your local account to a Space account', + bindSpaceWarning: + 'After binding, your login email will be changed to the Space account email. You can still use email/password login if you have set a password.', + bindSpaceSuccess: 'Space account bound successfully', + bindSpaceFailed: 'Failed to bind Space account', + bindSpaceInvalidState: + 'Invalid bind request. Please try again from account settings.', + setPasswordHint: + 'After setting a password, you can login with email and password', + }, }; export default enUS; diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 38dbea4a..f78687d2 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -52,6 +52,7 @@ const jaJP = { loginWithSpace: 'Space でログイン', spaceLoginRecommended: 'おすすめ:Space からモデルとクレジットを同期', loginLocal: 'ローカルアカウントでログイン', + loginWithPassword: 'パスワードでログイン', spaceLoginTitle: 'Space でログイン', spaceLoginDescription: 'QRコードをスキャンするか、下のリンクにアクセスして認証してください', @@ -71,6 +72,7 @@ const jaJP = { spaceLoginError: 'ログインに失敗しました', spaceLoginNoCode: '認証コードがありません', backToLogin: 'ログインに戻る', + backToHome: 'ホームに戻る', spaceAccountCannotChangePassword: 'Space アカウントはここでパスワードを変更できません', theme: 'テーマ', @@ -715,6 +717,7 @@ const jaJP = { initWithSpace: 'Space で初期化', spaceRecommended: 'おすすめ:Space からモデルとクレジットを同期', registerLocal: 'ローカルアカウントを登録', + registerWithPassword: 'メールアドレスとパスワードで登録', initSuccess: '初期化に成功しました。ログインしてください', initFailed: '初期化に失敗しました:', }, @@ -757,6 +760,26 @@ const jaJP = { viewUpdateGuide: 'アップデート方法を見る', noReleaseNotes: 'リリースノートはありません', }, + account: { + settings: 'アカウント設定', + setPassword: 'パスワードを設定', + passwordSetSuccess: 'パスワードの設定に成功しました', + bindSpace: 'Space アカウントを連携', + bindSpaceDescription: + 'ローカルアカウントを Space アカウントに連携して、モデルとクレジットを同期します', + bindSpaceButton: 'Space アカウントを連携', + bindSpaceConfirmTitle: '連携を確認', + bindSpaceConfirmDescription: + 'ローカルアカウントを Space アカウントに連携しようとしています', + bindSpaceWarning: + '連携後、ログインメールアドレスは Space アカウントのメールアドレスに変更されます。パスワードを設定している場合は、引き続きメールアドレスとパスワードでログインできます。', + bindSpaceSuccess: 'Space アカウントの連携に成功しました', + bindSpaceFailed: 'Space アカウントの連携に失敗しました', + bindSpaceInvalidState: + '無効な連携リクエストです。アカウント設定から再度お試しください。', + setPasswordHint: + 'パスワードを設定すると、メールアドレスとパスワードでログインできます', + }, }; export default jaJP; diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 8c98c0f6..c60b9506 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -51,6 +51,7 @@ const zhHans = { loginWithSpace: '通过 Space 登录', spaceLoginRecommended: '推荐:从 Space 同步模型和点数', loginLocal: '使用本地账号登录', + loginWithPassword: '通过密码登录', spaceLoginTitle: '通过 Space 登录', spaceLoginDescription: '扫描二维码或访问下方链接进行授权', spaceLoginUserCode: '您的验证码', @@ -67,6 +68,7 @@ const zhHans = { spaceLoginError: '登录失败', spaceLoginNoCode: '缺少授权码', backToLogin: '返回登录', + backToHome: '返回首页', spaceAccountCannotChangePassword: 'Space 账户无法在此修改密码', theme: '主题', changePassword: '修改密码', @@ -680,6 +682,7 @@ const zhHans = { initWithSpace: '通过 Space 初始化', spaceRecommended: '推荐:从 Space 同步模型和点数', registerLocal: '注册本地账号', + registerWithPassword: '通过邮箱密码组合注册', initSuccess: '初始化成功 请登录', initFailed: '初始化失败:', }, @@ -720,6 +723,22 @@ const zhHans = { viewUpdateGuide: '查看更新方式', noReleaseNotes: '暂无更新日志', }, + account: { + settings: '账户设置', + setPassword: '设置密码', + passwordSetSuccess: '密码设置成功', + bindSpace: '绑定 Space 账户', + bindSpaceDescription: '将本地账户关联到 Space 账户,以同步模型和点数', + bindSpaceButton: '绑定 Space 账户', + bindSpaceConfirmTitle: '确认绑定', + bindSpaceConfirmDescription: '您即将把本地账户绑定到 Space 账户', + bindSpaceWarning: + '绑定后,您的登录邮箱将更改为 Space 账户的邮箱。如果您已设置密码,仍可使用邮箱密码登录。', + bindSpaceSuccess: 'Space 账户绑定成功', + bindSpaceFailed: '绑定 Space 账户失败', + bindSpaceInvalidState: '无效的绑定请求,请从账户设置重新发起', + setPasswordHint: '设置密码后,您可使用邮箱、密码组合登录', + }, }; export default zhHans; diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index ad4e5ced..5b8fbb97 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -51,6 +51,7 @@ const zhHant = { loginWithSpace: '透過 Space 登入', spaceLoginRecommended: '推薦:從 Space 同步模型和點數', loginLocal: '使用本地帳號登入', + loginWithPassword: '透過密碼登入', spaceLoginTitle: '透過 Space 登入', spaceLoginDescription: '掃描二維碼或訪問下方連結進行授權', spaceLoginUserCode: '您的驗證碼', @@ -67,6 +68,7 @@ const zhHant = { spaceLoginError: '登入失敗', spaceLoginNoCode: '缺少授權碼', backToLogin: '返回登入', + backToHome: '返回首頁', spaceAccountCannotChangePassword: 'Space 帳戶無法在此修改密碼', theme: '主題', changePassword: '修改密碼', @@ -677,6 +679,7 @@ const zhHant = { initWithSpace: '透過 Space 初始化', spaceRecommended: '推薦:從 Space 同步模型和點數', registerLocal: '註冊本地帳號', + registerWithPassword: '透過電子郵件密碼組合註冊', initSuccess: '初始化成功 請登入', initFailed: '初始化失敗:', }, @@ -717,6 +720,22 @@ const zhHant = { viewUpdateGuide: '查看更新方式', noReleaseNotes: '暫無更新日誌', }, + account: { + settings: '帳戶設定', + setPassword: '設定密碼', + passwordSetSuccess: '密碼設定成功', + bindSpace: '綁定 Space 帳戶', + bindSpaceDescription: '將本地帳戶關聯到 Space 帳戶,以同步模型和點數', + bindSpaceButton: '綁定 Space 帳戶', + bindSpaceConfirmTitle: '確認綁定', + bindSpaceConfirmDescription: '您即將把本地帳戶綁定到 Space 帳戶', + bindSpaceWarning: + '綁定後,您的登入電子郵件將更改為 Space 帳戶的電子郵件。如果您已設定密碼,仍可使用電子郵件密碼登入。', + bindSpaceSuccess: 'Space 帳戶綁定成功', + bindSpaceFailed: '綁定 Space 帳戶失敗', + bindSpaceInvalidState: '無效的綁定請求,請從帳戶設定重新發起', + setPasswordHint: '設定密碼後,您可使用電子郵件、密碼組合登入', + }, }; export default zhHant; From 07ad846e963be51294a397858370e8868971b5df Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 28 Dec 2025 22:38:11 +0800 Subject: [PATCH 07/30] feat: update dependencies and enhance account settings dialog with password management and improved UI elements --- web/package.json | 2 +- web/pnpm-lock.yaml | 2 +- web/src/app/auth/space/callback/page.tsx | 6 +- .../AccountSettingsDialog.tsx | 310 +++++++----------- .../ApiIntegrationDialog.tsx | 39 ++- .../components/home-sidebar/HomeSidebar.tsx | 127 ++++--- .../PasswordChangeDialog.tsx | 111 ++++--- web/src/components/ui/item.tsx | 193 +++++++++++ web/src/i18n/locales/en-US.ts | 28 +- web/src/i18n/locales/ja-JP.ts | 27 +- web/src/i18n/locales/zh-Hans.ts | 22 +- web/src/i18n/locales/zh-Hant.ts | 22 +- 12 files changed, 549 insertions(+), 340 deletions(-) create mode 100644 web/src/components/ui/item.tsx diff --git a/web/package.json b/web/package.json index 1b3e04f4..5709fc10 100644 --- a/web/package.json +++ b/web/package.json @@ -34,7 +34,7 @@ "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-scroll-area": "^1.2.9", "@radix-ui/react-select": "^2.2.4", - "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.4", "@radix-ui/react-tabs": "^1.1.11", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index f60ecc2d..e73782bc 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -51,7 +51,7 @@ dependencies: specifier: ^2.2.4 version: 2.2.6(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-separator': - specifier: ^1.1.7 + specifier: ^1.1.8 version: 1.1.8(@types/react-dom@19.2.3)(@types/react@19.2.7)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-slot': specifier: ^1.2.3 diff --git a/web/src/app/auth/space/callback/page.tsx b/web/src/app/auth/space/callback/page.tsx index d0e7ddc4..a9898fbc 100644 --- a/web/src/app/auth/space/callback/page.tsx +++ b/web/src/app/auth/space/callback/page.tsx @@ -33,6 +33,7 @@ export default function SpaceOAuthCallback() { const [isBindMode, setIsBindMode] = useState(false); const [code, setCode] = useState(null); const [isProcessing, setIsProcessing] = useState(false); + const [localEmail, setLocalEmail] = useState(''); const handleOAuthCallback = useCallback( async (authCode: string) => { @@ -115,6 +116,7 @@ export default function SpaceOAuthCallback() { } setBindState(state); setIsBindMode(true); + setLocalEmail(localStorage.getItem('userEmail') || ''); setStatus('confirm'); } else { // Normal login/register mode @@ -169,7 +171,9 @@ export default function SpaceOAuthCallback() { <>

- {t('account.bindSpaceWarning')} + {t('account.bindSpaceWarning', { + localEmail: localEmail || '-', + })}

- - + {loading ? ( +
+
- - {/* Bind Space Account - only for local accounts */} - {accountType === 'local' && ( - <> - -
-

- {t('account.bindSpace')} -

-

- {t('account.bindSpaceDescription')} -

+ ) : ( +
+ {/* Password Item */} + + + + + + {t('account.passwordStatus')} + + {hasPassword + ? t('account.passwordSetDescription') + : t('account.setPasswordHint')} + + + -
- - )} -
- )} - - + + + + {/* Space Account Item */} + + + + + + + + + + {t('account.spaceStatus')} + + {accountType === 'space' + ? t('account.spaceBoundDescription') + : t('account.bindSpaceDescription')} + + + {accountType === 'local' && ( + + + + )} + +
+ )} + + + + + ); } diff --git a/web/src/app/home/components/api-integration-dialog/ApiIntegrationDialog.tsx b/web/src/app/home/components/api-integration-dialog/ApiIntegrationDialog.tsx index 68b36417..b39f9144 100644 --- a/web/src/app/home/components/api-integration-dialog/ApiIntegrationDialog.tsx +++ b/web/src/app/home/components/api-integration-dialog/ApiIntegrationDialog.tsx @@ -5,6 +5,7 @@ import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; import { Copy, Trash2, Plus } from 'lucide-react'; +import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import { Dialog, DialogContent, @@ -66,6 +67,9 @@ export default function ApiIntegrationDialog({ onOpenChange, }: ApiIntegrationDialogProps) { const { t } = useTranslation(); + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); const [activeTab, setActiveTab] = useState('apikeys'); const [apiKeys, setApiKeys] = useState([]); const [webhooks, setWebhooks] = useState([]); @@ -84,6 +88,30 @@ export default function ApiIntegrationDialog({ const [newWebhookEnabled, setNewWebhookEnabled] = useState(true); const [deleteWebhookId, setDeleteWebhookId] = useState(null); + // Sync URL with dialog state + useEffect(() => { + if (open) { + const params = new URLSearchParams(searchParams.toString()); + params.set('action', 'showApiIntegrationSettings'); + router.replace(`${pathname}?${params.toString()}`, { scroll: false }); + } + }, [open]); + + const handleOpenChange = (newOpen: boolean) => { + if (!newOpen && (deleteKeyId || deleteWebhookId)) { + return; + } + if (!newOpen) { + const params = new URLSearchParams(searchParams.toString()); + params.delete('action'); + const newUrl = params.toString() + ? `${pathname}?${params.toString()}` + : pathname; + router.replace(newUrl, { scroll: false }); + } + onOpenChange(newOpen); + }; + // 清理 body 样式,防止对话框关闭后页面无法交互 useEffect(() => { if (!deleteKeyId && !deleteWebhookId) { @@ -231,16 +259,7 @@ export default function ApiIntegrationDialog({ return ( <> - { - // 如果删除确认框是打开的,不允许关闭主对话框 - if (!newOpen && (deleteKeyId || deleteWebhookId)) { - return; - } - onOpenChange(newOpen); - }} - > + {t('common.manageApiIntegration')} diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 6ab5b3a9..bfca8a35 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -20,7 +20,6 @@ import { Lightbulb, LogOut, KeyRound, - User, } from 'lucide-react'; import { useTheme } from 'next-themes'; @@ -79,6 +78,12 @@ export default function HomeSidebar({ if (searchParams.get('action') === 'showModelSettings') { setModelsDialogOpen(true); } + if (searchParams.get('action') === 'showAccountSettings') { + setAccountSettingsOpen(true); + } + if (searchParams.get('action') === 'showApiIntegrationSettings') { + setApiKeyDialogOpen(true); + } }, [searchParams]); const [selectedChild, setSelectedChild] = useState(); @@ -114,6 +119,23 @@ export default function HomeSidebar({ } } + // 处理账户设置对话框的打开和关闭,同时更新 URL + function handleAccountSettingsChange(open: boolean) { + setAccountSettingsOpen(open); + if (open) { + const params = new URLSearchParams(searchParams.toString()); + params.set('action', 'showAccountSettings'); + router.replace(`${pathname}?${params.toString()}`, { scroll: false }); + } else { + const params = new URLSearchParams(searchParams.toString()); + params.delete('action'); + const newUrl = params.toString() + ? `${pathname}?${params.toString()}` + : pathname; + router.replace(newUrl, { scroll: false }); + } + } + useEffect(() => { initSelect(); if (!localStorage.getItem('token')) { @@ -337,44 +359,47 @@ export default function HomeSidebar({ -
- {t('common.theme')} - { - if (value) setTheme(value); - }} - className="justify-start" - > - - - - - - - - - - +
{ + handleAccountSettingsChange(true); + setPopoverOpen(false); + }} + > +
+ {userEmail ? userEmail.charAt(0).toUpperCase() : 'U'} +
+ + {userEmail || t('account.settings')} +
-
- - {t('common.language')} - - -
+ + { + if (value) setTheme(value); + }} + className="w-full justify-start" + > + + + + + + + + + + -
- - {t('common.integration')} - +
-
- -
- {t('common.account')} - {/* User email display */} -
string) => - z - .object({ - currentPassword: z - .string() - .min(1, { message: t('common.currentPasswordRequired') }), - newPassword: z - .string() - .min(1, { message: t('common.newPasswordRequired') }), - confirmNewPassword: z - .string() - .min(1, { message: t('common.confirmPasswordRequired') }), - }) - .refine((data) => data.newPassword === data.confirmNewPassword, { - message: t('common.passwordsDoNotMatch'), - path: ['confirmNewPassword'], - }); - interface PasswordChangeDialogProps { open: boolean; onOpenChange: (open: boolean) => void; + hasPassword?: boolean; } export default function PasswordChangeDialog({ open, onOpenChange, + hasPassword = true, }: PasswordChangeDialogProps) { const { t } = useTranslation(); const [isSubmitting, setIsSubmitting] = useState(false); - const formSchema = getFormSchema(t); + + const getFormSchema = () => + z + .object({ + currentPassword: hasPassword + ? z.string().min(1, { message: t('common.currentPasswordRequired') }) + : z.string().optional(), + newPassword: z + .string() + .min(1, { message: t('common.newPasswordRequired') }), + confirmNewPassword: z + .string() + .min(1, { message: t('common.confirmPasswordRequired') }), + }) + .refine((data) => data.newPassword === data.confirmNewPassword, { + message: t('common.passwordsDoNotMatch'), + path: ['confirmNewPassword'], + }); + + const formSchema = getFormSchema(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -66,14 +69,30 @@ export default function PasswordChangeDialog({ }, }); + // Reset form when dialog opens/closes or hasPassword changes + useEffect(() => { + if (open) { + form.reset({ + currentPassword: '', + newPassword: '', + confirmNewPassword: '', + }); + } + }, [open, hasPassword, form]); + const onSubmit = async (values: z.infer) => { setIsSubmitting(true); try { - await httpClient.changePassword( - values.currentPassword, - values.newPassword, - ); - toast.success(t('common.changePasswordSuccess')); + if (hasPassword) { + await httpClient.changePassword( + values.currentPassword!, + values.newPassword, + ); + toast.success(t('common.changePasswordSuccess')); + } else { + await httpClient.setPassword(values.newPassword, undefined); + toast.success(t('account.passwordSetSuccess')); + } form.reset(); onOpenChange(false); } catch { @@ -87,27 +106,33 @@ export default function PasswordChangeDialog({ - {t('common.changePassword')} + + {hasPassword + ? t('common.changePassword') + : t('account.setPassword')} +
- ( - - {t('common.currentPassword')} - - - - - - )} - /> + {hasPassword && ( + ( + + {t('common.currentPassword')} + + + + + + )} + /> + )} ) { + return ( +
+ ); +} + +function ItemSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +const itemVariants = cva( + 'group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]', + { + variants: { + variant: { + default: 'bg-transparent', + outline: 'border-border', + muted: 'bg-muted/50', + }, + size: { + default: 'p-4 gap-4 ', + sm: 'py-3 px-4 gap-2.5', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +); + +function Item({ + className, + variant = 'default', + size = 'default', + asChild = false, + ...props +}: React.ComponentProps<'div'> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'div'; + return ( + + ); +} + +const itemMediaVariants = cva( + 'flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5', + { + variants: { + variant: { + default: 'bg-transparent', + icon: "size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4", + image: + 'size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +); + +function ItemMedia({ + className, + variant = 'default', + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ); +} + +function ItemContent({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function ItemTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function ItemDescription({ className, ...props }: React.ComponentProps<'p'>) { + return ( +

a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4', + className, + )} + {...props} + /> + ); +} + +function ItemActions({ className, ...props }: React.ComponentProps<'div'>) { + return ( +

+ ); +} + +function ItemHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function ItemFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +export { + Item, + ItemMedia, + ItemContent, + ItemActions, + ItemGroup, + ItemSeparator, + ItemTitle, + ItemDescription, + ItemHeader, + ItemFooter, +}; diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 4a85db8a..a04abc67 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -49,7 +49,8 @@ const enUS = { loading: 'Loading...', or: 'or', loginWithSpace: 'Login with Space', - spaceLoginRecommended: 'Recommended: Sync models and credits from Space', + spaceLoginRecommended: + 'Recommended: Use official stable model APIs and cloud services', loginLocal: 'Login with local account', loginWithPassword: 'Login with password', spaceLoginTitle: 'Login with Space', @@ -707,7 +708,8 @@ const enUS = { 'The email and password you fill in will be used as the initial administrator account', register: 'Register', initWithSpace: 'Initialize with Space', - spaceRecommended: 'Recommended: Sync models and credits from Space', + spaceRecommended: + 'Recommended: Use official stable model APIs and cloud services', registerLocal: 'Register local account', registerWithPassword: 'Register with email and password', initSuccess: 'Initialization successful, please login', @@ -756,21 +758,29 @@ const enUS = { settings: 'Account Settings', setPassword: 'Set Password', passwordSetSuccess: 'Password set successfully', + passwordStatus: 'Local Password', + passwordSet: 'Set', + passwordNotSet: 'Not Set', + passwordSetDescription: + 'Password is set, you can login with email and password', + spaceStatus: 'Space Account', + spaceBound: 'Bound', + spaceNotBound: 'Not Bound', + spaceBoundDescription: + 'Space account bound, official model APIs and cloud services available', bindSpace: 'Bind Space Account', - bindSpaceDescription: - 'Link your local account to a Space account to sync models and credits', - bindSpaceButton: 'Bind Space Account', + bindSpaceDescription: 'Bind to use official model APIs and cloud services', + bindSpaceButton: 'Bind', bindSpaceConfirmTitle: 'Confirm Binding', bindSpaceConfirmDescription: - 'You are about to bind your local account to a Space account', + 'You are about to bind your local instance to a Space account', bindSpaceWarning: - 'After binding, your login email will be changed to the Space account email. You can still use email/password login if you have set a password.', + 'After binding, your login email will be changed from {{localEmail}} to the Space account email.', bindSpaceSuccess: 'Space account bound successfully', bindSpaceFailed: 'Failed to bind Space account', bindSpaceInvalidState: 'Invalid bind request. Please try again from account settings.', - setPasswordHint: - 'After setting a password, you can login with email and password', + setPasswordHint: 'Set a password to login with email and password', }, }; diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index f78687d2..39067992 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -50,7 +50,8 @@ const jaJP = { loading: '読み込み中...', or: 'または', loginWithSpace: 'Space でログイン', - spaceLoginRecommended: 'おすすめ:Space からモデルとクレジットを同期', + spaceLoginRecommended: + 'おすすめ:公式の安定したモデル API とクラウドサービスを利用', loginLocal: 'ローカルアカウントでログイン', loginWithPassword: 'パスワードでログイン', spaceLoginTitle: 'Space でログイン', @@ -715,7 +716,8 @@ const jaJP = { '入力したメールアドレスとパスワードが初期管理者アカウントになります', register: '登録', initWithSpace: 'Space で初期化', - spaceRecommended: 'おすすめ:Space からモデルとクレジットを同期', + spaceRecommended: + 'おすすめ:公式の安定したモデル API とクラウドサービスを利用', registerLocal: 'ローカルアカウントを登録', registerWithPassword: 'メールアドレスとパスワードで登録', initSuccess: '初期化に成功しました。ログインしてください', @@ -764,21 +766,30 @@ const jaJP = { settings: 'アカウント設定', setPassword: 'パスワードを設定', passwordSetSuccess: 'パスワードの設定に成功しました', + passwordStatus: 'ローカルパスワード', + passwordSet: '設定済み', + passwordNotSet: '未設定', + passwordSetDescription: + 'パスワードが設定されています。メールとパスワードでログインできます', + spaceStatus: 'Space アカウント', + spaceBound: '連携済み', + spaceNotBound: '未連携', + spaceBoundDescription: + 'Space アカウントと連携済み、公式モデル API とクラウドサービスが利用可能', bindSpace: 'Space アカウントを連携', - bindSpaceDescription: - 'ローカルアカウントを Space アカウントに連携して、モデルとクレジットを同期します', - bindSpaceButton: 'Space アカウントを連携', + bindSpaceDescription: '連携して公式モデル API とクラウドサービスを利用', + bindSpaceButton: '連携', bindSpaceConfirmTitle: '連携を確認', bindSpaceConfirmDescription: - 'ローカルアカウントを Space アカウントに連携しようとしています', + 'ローカルインスタンスを Space アカウントに連携しようとしています', bindSpaceWarning: - '連携後、ログインメールアドレスは Space アカウントのメールアドレスに変更されます。パスワードを設定している場合は、引き続きメールアドレスとパスワードでログインできます。', + '連携後、ログインメールアドレスは {{localEmail}} から Space アカウントのメールアドレスに変更されます。', bindSpaceSuccess: 'Space アカウントの連携に成功しました', bindSpaceFailed: 'Space アカウントの連携に失敗しました', bindSpaceInvalidState: '無効な連携リクエストです。アカウント設定から再度お試しください。', setPasswordHint: - 'パスワードを設定すると、メールアドレスとパスワードでログインできます', + 'パスワードを設定するとメールとパスワードでログインできます', }, }; diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index c60b9506..f929326f 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -49,7 +49,7 @@ const zhHans = { loading: '加载中...', or: '或', loginWithSpace: '通过 Space 登录', - spaceLoginRecommended: '推荐:从 Space 同步模型和点数', + spaceLoginRecommended: '推荐:使用官方提供的稳定模型 API 和云服务', loginLocal: '使用本地账号登录', loginWithPassword: '通过密码登录', spaceLoginTitle: '通过 Space 登录', @@ -680,7 +680,7 @@ const zhHans = { adminAccountNote: '您填写的邮箱和密码将作为初始管理员账号', register: '注册', initWithSpace: '通过 Space 初始化', - spaceRecommended: '推荐:从 Space 同步模型和点数', + spaceRecommended: '推荐:使用官方提供的稳定模型 API 和云服务', registerLocal: '注册本地账号', registerWithPassword: '通过邮箱密码组合注册', initSuccess: '初始化成功 请登录', @@ -727,17 +727,25 @@ const zhHans = { settings: '账户设置', setPassword: '设置密码', passwordSetSuccess: '密码设置成功', + passwordStatus: '本地密码', + passwordSet: '已设置', + passwordNotSet: '未设置', + passwordSetDescription: '您已设置本地密码,可使用邮箱密码登录', + spaceStatus: 'Space 账户', + spaceBound: '已绑定', + spaceNotBound: '未绑定', + spaceBoundDescription: '已绑定 Space 账户,可使用官方模型 API 和云服务', bindSpace: '绑定 Space 账户', - bindSpaceDescription: '将本地账户关联到 Space 账户,以同步模型和点数', - bindSpaceButton: '绑定 Space 账户', + bindSpaceDescription: '绑定后可使用官方模型 API 和云服务', + bindSpaceButton: '绑定', bindSpaceConfirmTitle: '确认绑定', - bindSpaceConfirmDescription: '您即将把本地账户绑定到 Space 账户', + bindSpaceConfirmDescription: '您即将把本地实例绑定到 Space 账户', bindSpaceWarning: - '绑定后,您的登录邮箱将更改为 Space 账户的邮箱。如果您已设置密码,仍可使用邮箱密码登录。', + '绑定后,您的登录邮箱将从 {{localEmail}} 更改为 Space 账户的邮箱。', bindSpaceSuccess: 'Space 账户绑定成功', bindSpaceFailed: '绑定 Space 账户失败', bindSpaceInvalidState: '无效的绑定请求,请从账户设置重新发起', - setPasswordHint: '设置密码后,您可使用邮箱、密码组合登录', + setPasswordHint: '设置密码后可使用邮箱密码登录', }, }; diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index 5b8fbb97..e4a8267b 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -49,7 +49,7 @@ const zhHant = { loading: '載入中...', or: '或', loginWithSpace: '透過 Space 登入', - spaceLoginRecommended: '推薦:從 Space 同步模型和點數', + spaceLoginRecommended: '推薦:使用官方提供的穩定模型 API 和雲服務', loginLocal: '使用本地帳號登入', loginWithPassword: '透過密碼登入', spaceLoginTitle: '透過 Space 登入', @@ -677,7 +677,7 @@ const zhHant = { adminAccountNote: '您填寫的電子郵件和密碼將作為初始管理員帳號', register: '註冊', initWithSpace: '透過 Space 初始化', - spaceRecommended: '推薦:從 Space 同步模型和點數', + spaceRecommended: '推薦:使用官方提供的穩定模型 API 和雲服務', registerLocal: '註冊本地帳號', registerWithPassword: '透過電子郵件密碼組合註冊', initSuccess: '初始化成功 請登入', @@ -724,17 +724,25 @@ const zhHant = { settings: '帳戶設定', setPassword: '設定密碼', passwordSetSuccess: '密碼設定成功', + passwordStatus: '本地密碼', + passwordSet: '已設定', + passwordNotSet: '未設定', + passwordSetDescription: '您已設定本地密碼,可使用電子郵件密碼登入', + spaceStatus: 'Space 帳戶', + spaceBound: '已綁定', + spaceNotBound: '未綁定', + spaceBoundDescription: '已綁定 Space 帳戶,可使用官方模型 API 和雲服務', bindSpace: '綁定 Space 帳戶', - bindSpaceDescription: '將本地帳戶關聯到 Space 帳戶,以同步模型和點數', - bindSpaceButton: '綁定 Space 帳戶', + bindSpaceDescription: '綁定後可使用官方模型 API 和雲服務', + bindSpaceButton: '綁定', bindSpaceConfirmTitle: '確認綁定', - bindSpaceConfirmDescription: '您即將把本地帳戶綁定到 Space 帳戶', + bindSpaceConfirmDescription: '您即將把本地實例綁定到 Space 帳戶', bindSpaceWarning: - '綁定後,您的登入電子郵件將更改為 Space 帳戶的電子郵件。如果您已設定密碼,仍可使用電子郵件密碼登入。', + '綁定後,您的登入電子郵件將從 {{localEmail}} 更改為 Space 帳戶的電子郵件。', bindSpaceSuccess: 'Space 帳戶綁定成功', bindSpaceFailed: '綁定 Space 帳戶失敗', bindSpaceInvalidState: '無效的綁定請求,請從帳戶設定重新發起', - setPasswordHint: '設定密碼後,您可使用電子郵件、密碼組合登入', + setPasswordHint: '設定密碼後可使用電子郵件密碼登入', }, }; From ba5b4816170bc1c35b957865ff4cd5bc7ed6546c Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 28 Dec 2025 22:43:05 +0800 Subject: [PATCH 08/30] refactor: simplify theme toggle implementation in HomeSidebar and ThemeToggle components --- .../components/home-sidebar/HomeSidebar.tsx | 49 ++++++++++--------- web/src/components/ui/theme-toggle.tsx | 13 +++-- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index bfca8a35..2b1ae764 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -29,7 +29,6 @@ import { PopoverTrigger, } from '@/components/ui/popover'; import { Button } from '@/components/ui/button'; -import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; import { LanguageSelector } from '@/components/ui/language-selector'; import { Badge } from '@/components/ui/badge'; import AccountSettingsDialog from '@/app/home/components/account-settings-dialog/AccountSettingsDialog'; @@ -376,28 +375,32 @@ export default function HomeSidebar({
- - { - if (value) setTheme(value); - }} - className="w-full justify-start" - > - - - - - - - - - - +
+ + +
); } From de8a7df6c2e19cd8082044048185378a035b7e2e Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 29 Dec 2025 00:35:31 +0800 Subject: [PATCH 09/30] feat: implement instance ID management and integrate with OAuth token exchange --- src/langbot/pkg/api/http/service/user.py | 4 +++- src/langbot/pkg/core/app.py | 2 ++ src/langbot/pkg/core/stages/load_config.py | 19 +++++++++++++++++++ src/langbot/pkg/utils/constants.py | 2 ++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/langbot/pkg/api/http/service/user.py b/src/langbot/pkg/api/http/service/user.py index 4db1e013..3996caad 100644 --- a/src/langbot/pkg/api/http/service/user.py +++ b/src/langbot/pkg/api/http/service/user.py @@ -145,13 +145,15 @@ class UserService: async def exchange_space_oauth_code(self, code: str) -> typing.Dict: """Exchange OAuth authorization code for tokens""" + from langbot.pkg.utils import constants + space_config = self._get_space_config() space_url = space_config['url'] async with aiohttp.ClientSession() as session: async with session.post( f'{space_url}/api/v1/accounts/oauth/token', - json={'code': code}, + json={'code': code, 'instance_id': constants.instance_id}, ) as response: if response.status != 200: raise ValueError(f'Failed to exchange OAuth code: {await response.text()}') diff --git a/src/langbot/pkg/core/app.py b/src/langbot/pkg/core/app.py index 38f93daa..1397f233 100644 --- a/src/langbot/pkg/core/app.py +++ b/src/langbot/pkg/core/app.py @@ -76,6 +76,8 @@ class Application: instance_config: config_mgr.ConfigManager = None + instance_id: config_mgr.ConfigManager = None # used to identify the instance + # ======= Metadata config manager ======= sensitive_meta: config_mgr.ConfigManager = None diff --git a/src/langbot/pkg/core/stages/load_config.py b/src/langbot/pkg/core/stages/load_config.py index b2b5abba..ccf816bd 100644 --- a/src/langbot/pkg/core/stages/load_config.py +++ b/src/langbot/pkg/core/stages/load_config.py @@ -2,8 +2,11 @@ from __future__ import annotations import os from typing import Any +from langbot.pkg.utils import constants import yaml import importlib.resources as resources +import uuid +import time from .. import stage, app from ..bootutils import config @@ -142,6 +145,22 @@ class LoadConfigStage(stage.BootingStage): await ap.instance_config.dump_config() + # load or generate instance id + ap.instance_id = await config.load_json_config( + 'data/labels/instance_id.json', + template_data={ + 'instance_id': f'instance_{str(uuid.uuid4())}', + 'instance_create_ts': int(time.time()), + }, + completion=False, + ) + + constants.instance_id = ap.instance_id.data['instance_id'] + + print(f'LangBot instance id: {constants.instance_id}') + + await ap.instance_id.dump_config() + ap.sensitive_meta = await config.load_json_config( 'data/metadata/sensitive-words.json', 'metadata/sensitive-words.json', diff --git a/src/langbot/pkg/utils/constants.py b/src/langbot/pkg/utils/constants.py index a82d578c..1bb088a6 100644 --- a/src/langbot/pkg/utils/constants.py +++ b/src/langbot/pkg/utils/constants.py @@ -8,3 +8,5 @@ required_database_version = 16 debug_mode = False edition = 'community' + +instance_id = '' From b788858f9e05f69b97234289830f642e1decdcf8 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 29 Dec 2025 12:18:45 +0800 Subject: [PATCH 10/30] fix: handle case of empty token list in TokenManager to prevent errors --- src/langbot/pkg/provider/modelmgr/token.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/langbot/pkg/provider/modelmgr/token.py b/src/langbot/pkg/provider/modelmgr/token.py index 9f477243..e1a71614 100644 --- a/src/langbot/pkg/provider/modelmgr/token.py +++ b/src/langbot/pkg/provider/modelmgr/token.py @@ -18,6 +18,8 @@ class TokenManager: self.using_token_index = 0 def get_token(self) -> str: + if len(self.tokens) == 0: + return '' return self.tokens[self.using_token_index] def next_token(self): From 863b26c3faf88c13c9a0ca51b398b35fd399e4c4 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 29 Dec 2025 20:42:06 +0800 Subject: [PATCH 11/30] refactor: update column drop logic in DBMigrateModelProviderRefactor for PostgreSQL compatibility --- .../dbm016_model_provider_refactor.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py b/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py index 88438409..286967c3 100644 --- a/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py +++ b/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py @@ -247,9 +247,14 @@ class DBMigrateModelProviderRefactor(migration.DBMigration): deprecated_llm_cols = ['requester', 'requester_config', 'api_keys', 'description', 'source', 'space_model_id'] for col in deprecated_llm_cols: if col in llm_columns: - await self.ap.persistence_mgr.execute_async( - sqlalchemy.text(f'ALTER TABLE llm_models DROP COLUMN IF EXISTS {col}') - ) + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text(f'ALTER TABLE llm_models DROP COLUMN IF EXISTS {col}') + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text(f'ALTER TABLE llm_models DROP COLUMN {col}') + ) embedding_columns = await self._get_columns('embedding_models') deprecated_embedding_cols = [ @@ -262,9 +267,14 @@ class DBMigrateModelProviderRefactor(migration.DBMigration): ] for col in deprecated_embedding_cols: if col in embedding_columns: - await self.ap.persistence_mgr.execute_async( - sqlalchemy.text(f'ALTER TABLE embedding_models DROP COLUMN IF EXISTS {col}') - ) + if self.ap.persistence_mgr.db.name == 'postgresql': + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text(f'ALTER TABLE embedding_models DROP COLUMN IF EXISTS {col}') + ) + else: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text(f'ALTER TABLE embedding_models DROP COLUMN {col}') + ) async def _get_columns(self, table_name: str) -> list: """Get column names for a table""" From f11e01b549e7f36f46a38e6a11e270379225584d Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 29 Dec 2025 21:14:05 +0800 Subject: [PATCH 12/30] refactor: rename 'allow_change_password' to 'allow_modify_login_info' and update related logic across the application --- .../pkg/api/http/controller/groups/system.py | 4 ++-- .../pkg/api/http/controller/groups/user.py | 15 ++++++++++++--- src/langbot/pkg/api/http/service/user.py | 2 +- src/langbot/templates/config.yaml | 4 ++-- .../AccountSettingsDialog.tsx | 6 +++++- web/src/app/infra/entities/api/index.ts | 2 +- web/src/app/infra/http/index.ts | 2 +- 7 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/langbot/pkg/api/http/controller/groups/system.py b/src/langbot/pkg/api/http/controller/groups/system.py index ddbb2fda..03b45e35 100644 --- a/src/langbot/pkg/api/http/controller/groups/system.py +++ b/src/langbot/pkg/api/http/controller/groups/system.py @@ -23,8 +23,8 @@ class SystemRouterGroup(group.RouterGroup): if 'cloud_service_url' in self.ap.instance_config.data.get('plugin', {}) else 'https://space.langbot.app' ), - 'allow_change_password': self.ap.instance_config.data.get('system', {}).get( - 'allow_change_password', True + 'allow_modify_login_info': self.ap.instance_config.data.get('system', {}).get( + 'allow_modify_login_info', True ), } ) diff --git a/src/langbot/pkg/api/http/controller/groups/user.py b/src/langbot/pkg/api/http/controller/groups/user.py index 89fd6507..981b1bd8 100644 --- a/src/langbot/pkg/api/http/controller/groups/user.py +++ b/src/langbot/pkg/api/http/controller/groups/user.py @@ -73,9 +73,11 @@ class UserRouterGroup(group.RouterGroup): @self.route('/change-password', methods=['POST'], auth_type=group.AuthType.USER_TOKEN) async def _(user_email: str) -> str: # Check if password change is allowed - allow_change_password = self.ap.instance_config.data.get('system', {}).get('allow_change_password', True) - if not allow_change_password: - return self.http_status(403, -1, 'Password change is disabled') + allow_modify_login_info = self.ap.instance_config.data.get('system', {}).get( + 'allow_modify_login_info', True + ) + if not allow_modify_login_info: + return self.http_status(403, -1, 'Modifying login info is disabled') json_data = await quart.request.json @@ -199,6 +201,13 @@ class UserRouterGroup(group.RouterGroup): @self.route('/bind-space', methods=['POST'], auth_type=group.AuthType.NONE) async def _() -> str: """Bind Space account to existing local account""" + # Check if modifying login info is allowed + allow_modify_login_info = self.ap.instance_config.data.get('system', {}).get( + 'allow_modify_login_info', True + ) + if not allow_modify_login_info: + return self.http_status(403, -1, 'Modifying login info is disabled') + json_data = await quart.request.json code = json_data.get('code') state = json_data.get('state') # JWT token passed as state diff --git a/src/langbot/pkg/api/http/service/user.py b/src/langbot/pkg/api/http/service/user.py index 3996caad..6235ca1a 100644 --- a/src/langbot/pkg/api/http/service/user.py +++ b/src/langbot/pkg/api/http/service/user.py @@ -26,7 +26,7 @@ class UserService: space_config = self.ap.instance_config.data.get('space', {}) return { 'url': space_config.get('url', 'https://space.langbot.app'), - 'api_url': space_config.get('api_url', 'https://api.langbot.app'), + 'models_gateway_api_url': space_config.get('models_gateway_api_url', 'https://api.langbot.cloud'), 'oauth_authorize_url': space_config.get('oauth_authorize_url', 'https://space.langbot.app/auth/authorize'), } diff --git a/src/langbot/templates/config.yaml b/src/langbot/templates/config.yaml index bd0a2ff7..5925a95d 100644 --- a/src/langbot/templates/config.yaml +++ b/src/langbot/templates/config.yaml @@ -16,7 +16,7 @@ proxy: https: '' system: recovery_key: '' - allow_change_password: true + allow_modify_login_info: true jwt: expire: 604800 secret: '' @@ -75,6 +75,6 @@ space: # Space service URL for OAuth and API url: 'https://space.langbot.app' # Space API URL for model requests (MaaS) - api_url: 'https://api.langbot.app' + models_gateway_api_url: 'https://api.langbot.cloud' # OAuth authorization page URL (user will be redirected here) oauth_authorize_url: 'https://space.langbot.app/auth/authorize' diff --git a/web/src/app/home/components/account-settings-dialog/AccountSettingsDialog.tsx b/web/src/app/home/components/account-settings-dialog/AccountSettingsDialog.tsx index 8a82a0cd..aec4d2dc 100644 --- a/web/src/app/home/components/account-settings-dialog/AccountSettingsDialog.tsx +++ b/web/src/app/home/components/account-settings-dialog/AccountSettingsDialog.tsx @@ -21,6 +21,7 @@ import { ItemActions, } from '@/components/ui/item'; import { httpClient } from '@/app/infra/http/HttpClient'; +import { systemInfo } from '@/app/infra/http'; import { Loader2, ExternalLink, KeyRound } from 'lucide-react'; import PasswordChangeDialog from '../password-change-dialog/PasswordChangeDialog'; @@ -125,6 +126,7 @@ export default function AccountSettingsDialog({ variant="outline" size="sm" onClick={() => setPasswordDialogOpen(true)} + disabled={!systemInfo.allow_modify_login_info} > {hasPassword ? t('common.changePassword') @@ -179,7 +181,9 @@ export default function AccountSettingsDialog({ variant="outline" size="sm" onClick={handleBindSpace} - disabled={spaceBindLoading} + disabled={ + spaceBindLoading || !systemInfo.allow_modify_login_info + } > {spaceBindLoading ? ( diff --git a/web/src/app/infra/entities/api/index.ts b/web/src/app/infra/entities/api/index.ts index 407bb6fc..1c3b7b5c 100644 --- a/web/src/app/infra/entities/api/index.ts +++ b/web/src/app/infra/entities/api/index.ts @@ -241,7 +241,7 @@ export interface ApiRespSystemInfo { version: string; cloud_service_url: string; enable_marketplace: boolean; - allow_change_password: boolean; + allow_modify_login_info: boolean; } export interface ApiRespPluginSystemStatus { diff --git a/web/src/app/infra/http/index.ts b/web/src/app/infra/http/index.ts index f14188cc..aab696dc 100644 --- a/web/src/app/infra/http/index.ts +++ b/web/src/app/infra/http/index.ts @@ -8,7 +8,7 @@ export let systemInfo: ApiRespSystemInfo = { version: '', enable_marketplace: true, cloud_service_url: '', - allow_change_password: true, + allow_modify_login_info: true, }; /** From 9c82eeddeb9f08934fd5f4103321fd52707c3b78 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 29 Dec 2025 22:23:11 +0800 Subject: [PATCH 13/30] feat: add endpoint for retrieving user space credits and implement caching mechanism in UserService --- .../pkg/api/http/controller/groups/user.py | 6 ++ src/langbot/pkg/api/http/service/user.py | 26 ++++++ .../components/models-dialog/ModelsDialog.tsx | 84 +++++++++++++++---- web/src/app/infra/http/BackendClient.ts | 4 + web/src/i18n/locales/en-US.ts | 9 +- web/src/i18n/locales/ja-JP.ts | 9 +- web/src/i18n/locales/zh-Hans.ts | 9 +- web/src/i18n/locales/zh-Hant.ts | 9 +- 8 files changed, 125 insertions(+), 31 deletions(-) diff --git a/src/langbot/pkg/api/http/controller/groups/user.py b/src/langbot/pkg/api/http/controller/groups/user.py index 981b1bd8..91aa4f47 100644 --- a/src/langbot/pkg/api/http/controller/groups/user.py +++ b/src/langbot/pkg/api/http/controller/groups/user.py @@ -158,6 +158,12 @@ class UserRouterGroup(group.RouterGroup): } ) + @self.route('/space-credits', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) + async def _(user_email: str) -> str: + """Get Space credits balance for current user""" + credits = await self.ap.user_service.get_space_credits(user_email) + return self.success(data={'credits': credits}) + @self.route('/account-info', methods=['GET'], auth_type=group.AuthType.NONE) async def _() -> str: """Get account info for login page (account type and has_password)""" diff --git a/src/langbot/pkg/api/http/service/user.py b/src/langbot/pkg/api/http/service/user.py index 6235ca1a..46410e9b 100644 --- a/src/langbot/pkg/api/http/service/user.py +++ b/src/langbot/pkg/api/http/service/user.py @@ -16,10 +16,12 @@ from ....utils import constants class UserService: ap: app.Application _create_user_lock: asyncio.Lock + _space_credits_cache: typing.Dict[str, typing.Tuple[int, float]] # {user_email: (credits, timestamp)} def __init__(self, ap: app.Application) -> None: self.ap = ap self._create_user_lock = asyncio.Lock() + self._space_credits_cache = {} def _get_space_config(self) -> typing.Dict[str, str]: """Get Space configuration from config file""" @@ -178,6 +180,30 @@ class UserService: raise ValueError(f'Failed to get user info: {data.get("msg")}') return data.get('data', {}) + async def get_space_credits(self, user_email: str, force_refresh: bool = False) -> int | None: + """Get Space credits for user with caching (60s TTL)""" + import time + + cache_ttl = 60 + + if not force_refresh and user_email in self._space_credits_cache: + credits, ts = self._space_credits_cache[user_email] + if time.time() - ts < cache_ttl: + return credits + + user_obj = await self.get_user_by_email(user_email) + if not user_obj or user_obj.account_type != 'space' or not user_obj.space_access_token: + return None + + try: + info = await self.get_space_user_info(user_obj.space_access_token) + credits = info.get('credits') + if credits is not None: + self._space_credits_cache[user_email] = (credits, time.time()) + return credits + except Exception: + return self._space_credits_cache.get(user_email, (None, 0))[0] + async def refresh_space_token(self, refresh_token: str) -> typing.Dict: """Refresh Space access token""" space_config = self._get_space_config() diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index f8dc9ca2..6579ca9c 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -9,12 +9,11 @@ import { ChevronRight, Trash2, Settings, - Sparkles, LogIn, Eye, Wrench, } from 'lucide-react'; -import { httpClient } from '@/app/infra/http/HttpClient'; +import { httpClient, systemInfo } from '@/app/infra/http/HttpClient'; import { LLMModel, EmbeddingModel, @@ -46,6 +45,7 @@ import { Badge } from '@/components/ui/badge'; import LLMForm from './component/llm-form/LLMForm'; import EmbeddingForm from './component/embedding-form/EmbeddingForm'; import ProviderForm from './component/provider-form/ProviderForm'; +import langbotIcon from '@/app/assets/langbot-logo.webp'; interface ModelsDialogProps { open: boolean; @@ -62,7 +62,7 @@ export default function ModelsDialog({ const [providers, setProviders] = useState([]); const [accountType, setAccountType] = useState<'local' | 'space'>('local'); - const [spaceBalance] = useState(null); + const [spaceCredits, setSpaceCredits] = useState(null); // Expanded providers and their models const [expandedProviders, setExpandedProviders] = useState>( @@ -103,6 +103,10 @@ export default function ModelsDialog({ try { const userInfo = await httpClient.getUserInfo(); setAccountType(userInfo.account_type); + if (userInfo.account_type === 'space') { + const creditsInfo = await httpClient.getSpaceCredits(); + setSpaceCredits(creditsInfo.credits); + } } catch { setAccountType('local'); } @@ -279,8 +283,12 @@ export default function ModelsDialog({
{isLangBotModels ? ( -
- +
+ LangBot
) : ( )} - {isLangBotModels && accountType === 'space' && ( - - {t('models.balance')}: {spaceBalance ?? '--'} - - )} + {isLangBotModels && + accountType === 'space' && + spaceCredits !== null && ( +
+ + {(spaceCredits / 5000).toFixed(2)} {t('models.credits')} + + +
+ )} {!isLangBotModels && ( <> + ) : ( + spaceCredits !== null && ( +
+ + {(spaceCredits / 5000).toFixed(2)} {t('models.credits')} + + +
+ ) )}
@@ -531,12 +582,15 @@ export default function ModelsDialog({ {t('models.title')} -
+
{/* Fixed LangBot Models Card */}
{renderLangBotModelsCard()}
{/* Add Model Button */} -
+
+ + {t('models.providerCount', { count: otherProviders.length })} + - ) : ( - spaceCredits !== null && ( -
- - {(spaceCredits / 5000).toFixed(2)} {t('models.credits')} - - -
- ) - )} -
- - - ); } function handleFormClose() { diff --git a/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx b/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx index 5bd3b66a..3e7d942e 100644 --- a/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx +++ b/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx @@ -373,11 +373,15 @@ export default function EmbeddingForm({ /> - {providers.map((p) => ( - - {p.name} ({p.base_url || 'default'}) - - ))} + {providers + .filter( + (p) => p.requester !== 'space-chat-completions', + ) + .map((p) => ( + + {p.name} ({p.base_url || 'default'}) + + ))} @@ -413,7 +417,11 @@ export default function EmbeddingForm({ {t('models.modelManufacturer')} {requesterList - .filter((r) => r.category === 'manufacturer') + .filter( + (r) => + r.category === 'manufacturer' && + r.value !== 'space-chat-completions', + ) .map((r) => ( {r.label} @@ -425,7 +433,11 @@ export default function EmbeddingForm({ {t('models.aggregationPlatform')} {requesterList - .filter((r) => r.category === 'maas') + .filter( + (r) => + r.category === 'maas' && + r.value !== 'space-chat-completions', + ) .map((r) => ( {r.label} @@ -437,7 +449,11 @@ export default function EmbeddingForm({ {t('models.selfDeployed')} {requesterList - .filter((r) => r.category === 'self-hosted') + .filter( + (r) => + r.category === 'self-hosted' && + r.value !== 'space-chat-completions', + ) .map((r) => ( {r.label} diff --git a/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx b/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx index fb8a79ae..7d816d3a 100644 --- a/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx +++ b/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx @@ -385,11 +385,15 @@ export default function LLMForm({ /> - {providers.map((p) => ( - - {p.name} ({p.base_url || 'default'}) - - ))} + {providers + .filter( + (p) => p.requester !== 'space-chat-completions', + ) + .map((p) => ( + + {p.name} ({p.base_url || 'default'}) + + ))} @@ -425,7 +429,11 @@ export default function LLMForm({ {t('models.modelManufacturer')} {requesterList - .filter((r) => r.category === 'manufacturer') + .filter( + (r) => + r.category === 'manufacturer' && + r.value !== 'space-chat-completions', + ) .map((r) => ( {r.label} @@ -437,7 +445,11 @@ export default function LLMForm({ {t('models.aggregationPlatform')} {requesterList - .filter((r) => r.category === 'maas') + .filter( + (r) => + r.category === 'maas' && + r.value !== 'space-chat-completions', + ) .map((r) => ( {r.label} @@ -449,7 +461,11 @@ export default function LLMForm({ {t('models.selfDeployed')} {requesterList - .filter((r) => r.category === 'self-hosted') + .filter( + (r) => + r.category === 'self-hosted' && + r.value !== 'space-chat-completions', + ) .map((r) => ( {r.label} From 96e40eaf259e448900ddf8bbee43486df121c4d4 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Wed, 31 Dec 2025 22:25:07 +0800 Subject: [PATCH 16/30] feat: enhance model creation with UUID preservation option and implement Space model synchronization in ModelManager --- src/langbot/pkg/api/http/service/model.py | 10 +- src/langbot/pkg/api/http/service/space.py | 17 +++ src/langbot/pkg/core/stages/build_app.py | 124 +++++++++--------- src/langbot/pkg/entity/dto/__init__.py | 0 src/langbot/pkg/entity/dto/space_model.py | 49 +++++++ src/langbot/pkg/entity/errors/provider.py | 8 ++ src/langbot/pkg/entity/persistence/model.py | 2 + src/langbot/pkg/persistence/mgr.py | 2 +- .../dbm015_model_source_tracking.py | 67 +--------- .../dbm016_model_provider_refactor.py | 12 ++ src/langbot/pkg/provider/modelmgr/modelmgr.py | 60 +++++++++ 11 files changed, 219 insertions(+), 132 deletions(-) create mode 100644 src/langbot/pkg/entity/dto/__init__.py create mode 100644 src/langbot/pkg/entity/dto/space_model.py diff --git a/src/langbot/pkg/api/http/service/model.py b/src/langbot/pkg/api/http/service/model.py index 03f42e3d..e12498cf 100644 --- a/src/langbot/pkg/api/http/service/model.py +++ b/src/langbot/pkg/api/http/service/model.py @@ -64,9 +64,10 @@ class LLMModelsService: models = result.all() return [self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, m) for m in models] - async def create_llm_model(self, model_data: dict) -> str: + async def create_llm_model(self, model_data: dict, preserve_uuid: bool = False) -> str: """Create a new LLM model""" - model_data['uuid'] = str(uuid.uuid4()) + if not preserve_uuid: + model_data['uuid'] = str(uuid.uuid4()) # Handle provider creation if needed if 'provider' in model_data: @@ -222,9 +223,10 @@ class EmbeddingModelsService: models = result.all() return [self.ap.persistence_mgr.serialize_model(persistence_model.EmbeddingModel, m) for m in models] - async def create_embedding_model(self, model_data: dict) -> str: + async def create_embedding_model(self, model_data: dict, preserve_uuid: bool = False) -> str: """Create a new embedding model""" - model_data['uuid'] = str(uuid.uuid4()) + if not preserve_uuid: + model_data['uuid'] = str(uuid.uuid4()) if 'provider' in model_data: provider_data = model_data.pop('provider') diff --git a/src/langbot/pkg/api/http/service/space.py b/src/langbot/pkg/api/http/service/space.py index 1961dd4c..cd694883 100644 --- a/src/langbot/pkg/api/http/service/space.py +++ b/src/langbot/pkg/api/http/service/space.py @@ -8,6 +8,7 @@ import sqlalchemy from ....core import app from ....entity.persistence import user +from ....entity.dto.space_model import SpaceModel class SpaceService: @@ -170,3 +171,19 @@ class SpaceService: return credits except Exception: return self._credits_cache.get(user_email, (None, 0))[0] + + async def get_models(self) -> typing.List[SpaceModel]: + """Get models from Space""" + + space_config = self._get_space_config() + space_url = space_config['url'] + + async with aiohttp.ClientSession() as session: + async with session.get(f'{space_url}/api/v1/models') as response: + if response.status != 200: + raise ValueError(f'Failed to get models: {await response.text()}') + data = await response.json() + if data.get('code') != 0: + raise ValueError(f'Failed to get models: {data.get("msg")}') + models_data = data.get('data', {}).get('models', []) + return [SpaceModel.model_validate(model_dict) for model_dict in models_data] diff --git a/src/langbot/pkg/core/stages/build_app.py b/src/langbot/pkg/core/stages/build_app.py index b1695fd5..1ce4ddbd 100644 --- a/src/langbot/pkg/core/stages/build_app.py +++ b/src/langbot/pkg/core/stages/build_app.py @@ -45,68 +45,6 @@ class BuildAppStage(stage.BootingStage): discover.discover_blueprint('templates/components.yaml') ap.discover = discover - proxy_mgr = proxy.ProxyManager(ap) - await proxy_mgr.initialize() - ap.proxy_mgr = proxy_mgr - - ver_mgr = version.VersionManager(ap) - await ver_mgr.initialize() - ap.ver_mgr = ver_mgr - - ap.query_pool = pool.QueryPool() - - log_cache = logcache.LogCache() - ap.log_cache = log_cache - - storage_mgr_inst = storagemgr.StorageMgr(ap) - await storage_mgr_inst.initialize() - ap.storage_mgr = storage_mgr_inst - - persistence_mgr_inst = persistencemgr.PersistenceManager(ap) - ap.persistence_mgr = persistence_mgr_inst - await persistence_mgr_inst.initialize() - - cmd_mgr_inst = cmdmgr.CommandManager(ap) - await cmd_mgr_inst.initialize() - ap.cmd_mgr = cmd_mgr_inst - - llm_model_mgr_inst = llm_model_mgr.ModelManager(ap) - await llm_model_mgr_inst.initialize() - ap.model_mgr = llm_model_mgr_inst - - llm_session_mgr_inst = llm_session_mgr.SessionManager(ap) - await llm_session_mgr_inst.initialize() - ap.sess_mgr = llm_session_mgr_inst - - llm_tool_mgr_inst = llm_tool_mgr.ToolManager(ap) - await llm_tool_mgr_inst.initialize() - ap.tool_mgr = llm_tool_mgr_inst - - im_mgr_inst = im_mgr.PlatformManager(ap=ap) - await im_mgr_inst.initialize() - ap.platform_mgr = im_mgr_inst - - # Initialize webhook pusher - webhook_pusher_inst = WebhookPusher(ap) - ap.webhook_pusher = webhook_pusher_inst - - pipeline_mgr = pipelinemgr.PipelineManager(ap) - await pipeline_mgr.initialize() - ap.pipeline_mgr = pipeline_mgr - - rag_mgr_inst = rag_mgr.RAGManager(ap) - await rag_mgr_inst.initialize() - ap.rag_mgr = rag_mgr_inst - - # 初始化向量数据库管理器 - vectordb_mgr_inst = vectordb_mgr.VectorDBManager(ap) - await vectordb_mgr_inst.initialize() - ap.vector_db_mgr = vectordb_mgr_inst - - http_ctrl = http_controller.HTTPController(ap) - await http_ctrl.initialize() - ap.http_ctrl = http_ctrl - user_service_inst = user_service.UserService(ap) ap.user_service = user_service_inst @@ -143,6 +81,68 @@ class BuildAppStage(stage.BootingStage): webhook_service_inst = webhook_service.WebhookService(ap) ap.webhook_service = webhook_service_inst + proxy_mgr = proxy.ProxyManager(ap) + await proxy_mgr.initialize() + ap.proxy_mgr = proxy_mgr + + ver_mgr = version.VersionManager(ap) + await ver_mgr.initialize() + ap.ver_mgr = ver_mgr + + ap.query_pool = pool.QueryPool() + + log_cache = logcache.LogCache() + ap.log_cache = log_cache + + storage_mgr_inst = storagemgr.StorageMgr(ap) + await storage_mgr_inst.initialize() + ap.storage_mgr = storage_mgr_inst + + persistence_mgr_inst = persistencemgr.PersistenceManager(ap) + ap.persistence_mgr = persistence_mgr_inst + await persistence_mgr_inst.initialize() + + cmd_mgr_inst = cmdmgr.CommandManager(ap) + await cmd_mgr_inst.initialize() + ap.cmd_mgr = cmd_mgr_inst + + llm_model_mgr_inst = llm_model_mgr.ModelManager(ap) + ap.model_mgr = llm_model_mgr_inst + await llm_model_mgr_inst.initialize() + + llm_session_mgr_inst = llm_session_mgr.SessionManager(ap) + await llm_session_mgr_inst.initialize() + ap.sess_mgr = llm_session_mgr_inst + + llm_tool_mgr_inst = llm_tool_mgr.ToolManager(ap) + await llm_tool_mgr_inst.initialize() + ap.tool_mgr = llm_tool_mgr_inst + + im_mgr_inst = im_mgr.PlatformManager(ap=ap) + await im_mgr_inst.initialize() + ap.platform_mgr = im_mgr_inst + + # Initialize webhook pusher + webhook_pusher_inst = WebhookPusher(ap) + ap.webhook_pusher = webhook_pusher_inst + + pipeline_mgr = pipelinemgr.PipelineManager(ap) + await pipeline_mgr.initialize() + ap.pipeline_mgr = pipeline_mgr + + rag_mgr_inst = rag_mgr.RAGManager(ap) + await rag_mgr_inst.initialize() + ap.rag_mgr = rag_mgr_inst + + # 初始化向量数据库管理器 + vectordb_mgr_inst = vectordb_mgr.VectorDBManager(ap) + await vectordb_mgr_inst.initialize() + ap.vector_db_mgr = vectordb_mgr_inst + + http_ctrl = http_controller.HTTPController(ap) + await http_ctrl.initialize() + ap.http_ctrl = http_ctrl + async def runtime_disconnect_callback(connector: plugin_connector.PluginRuntimeConnector) -> None: await asyncio.sleep(3) await plugin_connector_inst.initialize() diff --git a/src/langbot/pkg/entity/dto/__init__.py b/src/langbot/pkg/entity/dto/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/langbot/pkg/entity/dto/space_model.py b/src/langbot/pkg/entity/dto/space_model.py new file mode 100644 index 00000000..62b9f2b0 --- /dev/null +++ b/src/langbot/pkg/entity/dto/space_model.py @@ -0,0 +1,49 @@ +# [ +# { +# "uuid": "7652ebdb-54dc-412c-a830-e9268ac88471", +# "model_id": "claude-opus-4-5-20251101", +# "display_name": { +# "en_US": "claude-opus-4-5-20251101", +# "zh_Hans": "claude-opus-4-5-20251101" +# }, +# "description": {}, +# "provider": "anthropic", +# "category": "chat", +# "icon_url": "Claude.Color", +# "tags": {}, +# "is_featured": true, +# "featured_order": 999, +# "model_ratio": 2.5, +# "completion_ratio": 5, +# "quota_type": 0, +# "model_price": 0, +# "input_credits": 500, +# "output_credits": 2500, +# "vendor_id": 1, +# "vendor_name": "Anthropic", +# "vendor_icon": "Claude.Color", +# "supported_endpoints": [ +# "anthropic", +# "openai" +# ], +# "status": "active", +# "metadata": null, +# "created_at": "2025-12-30T22:23:38.337207+08:00", +# "updated_at": "2025-12-30T22:23:38.337207+08:00" +# } +# ] + +import pydantic + + +class SpaceModel(pydantic.BaseModel): + uuid: str + model_id: str + provider: str + category: str # chat / embedding + llm_abilities: list[str] | None = None + is_featured: bool = False + featured_order: int = 0 + status: str + created_at: str | None = None + updated_at: str | None = None diff --git a/src/langbot/pkg/entity/errors/provider.py b/src/langbot/pkg/entity/errors/provider.py index b495a6b0..c7831cef 100644 --- a/src/langbot/pkg/entity/errors/provider.py +++ b/src/langbot/pkg/entity/errors/provider.py @@ -7,3 +7,11 @@ class RequesterNotFoundError(Exception): def __str__(self): return f'Requester {self.requester_name} not found' + + +class ProviderNotFoundError(Exception): + def __init__(self, provider_name: str): + self.provider_name = provider_name + + def __str__(self): + return f'Provider {self.provider_name} not found' diff --git a/src/langbot/pkg/entity/persistence/model.py b/src/langbot/pkg/entity/persistence/model.py index e4459585..8ac3bd18 100644 --- a/src/langbot/pkg/entity/persistence/model.py +++ b/src/langbot/pkg/entity/persistence/model.py @@ -32,6 +32,7 @@ class LLMModel(Base): provider_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) abilities = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=[]) extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={}) + prefered_ranking = sqlalchemy.Column(sqlalchemy.Integer, nullable=False, default=0) created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) updated_at = sqlalchemy.Column( sqlalchemy.DateTime, @@ -50,6 +51,7 @@ class EmbeddingModel(Base): name = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) provider_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={}) + prefered_ranking = sqlalchemy.Column(sqlalchemy.Integer, nullable=False, default=0) created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) updated_at = sqlalchemy.Column( sqlalchemy.DateTime, diff --git a/src/langbot/pkg/persistence/mgr.py b/src/langbot/pkg/persistence/mgr.py index 92be6a77..1311ff4f 100644 --- a/src/langbot/pkg/persistence/mgr.py +++ b/src/langbot/pkg/persistence/mgr.py @@ -134,7 +134,7 @@ class PersistenceManager: if result.first() is None: self.ap.logger.info('Creating space model providers...') space_chat_completions_model_provider = { - 'uuid': str(uuid.uuid4()), + 'uuid': '00000000-0000-0000-0000-000000000000', 'name': 'LangBot Models', 'requester': 'space-chat-completions', 'base_url': 'https://api.langbot.cloud/v1', diff --git a/src/langbot/pkg/persistence/migrations/dbm015_model_source_tracking.py b/src/langbot/pkg/persistence/migrations/dbm015_model_source_tracking.py index 05182b36..363ff666 100644 --- a/src/langbot/pkg/persistence/migrations/dbm015_model_source_tracking.py +++ b/src/langbot/pkg/persistence/migrations/dbm015_model_source_tracking.py @@ -1,77 +1,14 @@ -import sqlalchemy from .. import migration +# this is a deprecated migration @migration.migration_class(15) class DBMigrateModelSourceTracking(migration.DBMigration): """Add source tracking fields to models tables for Space integration""" async def upgrade(self): """Upgrade""" - # Add source column to llm_models table - llm_columns = await self._get_columns('llm_models') - - if 'source' not in llm_columns: - if self.ap.persistence_mgr.db.name == 'postgresql': - await self.ap.persistence_mgr.execute_async( - sqlalchemy.text("ALTER TABLE llm_models ADD COLUMN source VARCHAR(32) DEFAULT 'local' NOT NULL") - ) - else: - await self.ap.persistence_mgr.execute_async( - sqlalchemy.text("ALTER TABLE llm_models ADD COLUMN source VARCHAR(32) DEFAULT 'local' NOT NULL") - ) - - if 'space_model_id' not in llm_columns: - if self.ap.persistence_mgr.db.name == 'postgresql': - await self.ap.persistence_mgr.execute_async( - sqlalchemy.text('ALTER TABLE llm_models ADD COLUMN space_model_id VARCHAR(255)') - ) - else: - await self.ap.persistence_mgr.execute_async( - sqlalchemy.text('ALTER TABLE llm_models ADD COLUMN space_model_id VARCHAR(255)') - ) - - # Add source column to embedding_models table - embedding_columns = await self._get_columns('embedding_models') - - if 'source' not in embedding_columns: - if self.ap.persistence_mgr.db.name == 'postgresql': - await self.ap.persistence_mgr.execute_async( - sqlalchemy.text( - "ALTER TABLE embedding_models ADD COLUMN source VARCHAR(32) DEFAULT 'local' NOT NULL" - ) - ) - else: - await self.ap.persistence_mgr.execute_async( - sqlalchemy.text( - "ALTER TABLE embedding_models ADD COLUMN source VARCHAR(32) DEFAULT 'local' NOT NULL" - ) - ) - - if 'space_model_id' not in embedding_columns: - if self.ap.persistence_mgr.db.name == 'postgresql': - await self.ap.persistence_mgr.execute_async( - sqlalchemy.text('ALTER TABLE embedding_models ADD COLUMN space_model_id VARCHAR(255)') - ) - else: - await self.ap.persistence_mgr.execute_async( - sqlalchemy.text('ALTER TABLE embedding_models ADD COLUMN space_model_id VARCHAR(255)') - ) - - async def _get_columns(self, table_name: str) -> list: - """Get column names for a table""" - if self.ap.persistence_mgr.db.name == 'postgresql': - result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.text( - f"SELECT column_name FROM information_schema.columns WHERE table_name = '{table_name}';" - ) - ) - all_result = result.fetchall() - return [row[0] for row in all_result] - else: - result = await self.ap.persistence_mgr.execute_async(sqlalchemy.text(f'PRAGMA table_info({table_name});')) - all_result = result.fetchall() - return [row[1] for row in all_result] + pass async def downgrade(self): """Downgrade""" diff --git a/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py b/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py index 286967c3..223f2883 100644 --- a/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py +++ b/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py @@ -61,6 +61,12 @@ class DBMigrateModelProviderRefactor(migration.DBMigration): sqlalchemy.text('ALTER TABLE llm_models ADD COLUMN provider_uuid VARCHAR(255)') ) + # Add prefered_ranking column if not exists + if 'prefered_ranking' not in llm_columns: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE llm_models ADD COLUMN prefered_ranking INTEGER NOT NULL DEFAULT 0') + ) + # Only migrate if old columns exist if 'requester' not in llm_columns: return @@ -152,6 +158,12 @@ class DBMigrateModelProviderRefactor(migration.DBMigration): sqlalchemy.text('ALTER TABLE embedding_models ADD COLUMN provider_uuid VARCHAR(255)') ) + # Add prefered_ranking column if not exists + if 'prefered_ranking' not in embedding_columns: + await self.ap.persistence_mgr.execute_async( + sqlalchemy.text('ALTER TABLE embedding_models ADD COLUMN prefered_ranking INTEGER NOT NULL DEFAULT 0') + ) + # Only migrate if old columns exist if 'requester' not in embedding_columns: return diff --git a/src/langbot/pkg/provider/modelmgr/modelmgr.py b/src/langbot/pkg/provider/modelmgr/modelmgr.py index 3c369638..0c84eaad 100644 --- a/src/langbot/pkg/provider/modelmgr/modelmgr.py +++ b/src/langbot/pkg/provider/modelmgr/modelmgr.py @@ -41,6 +41,7 @@ class ModelManager: self.requester_dict = requester_dict await self.load_models_from_db() + await self.sync_new_models_from_space() async def load_models_from_db(self): """Load models from database""" @@ -89,6 +90,65 @@ class ModelManager: except Exception as e: self.ap.logger.error(f'Failed to load model {embedding_model.uuid}: {e}\n{traceback.format_exc()}') + async def sync_new_models_from_space(self): + """Sync models from Space""" + space_model_provider = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.ModelProvider).where( + persistence_model.ModelProvider.requester == 'space-chat-completions' + ) + ) + result = space_model_provider.first() + if result is None: + raise provider_errors.ProviderNotFoundError('LangBot Models') + + space_model_provider = result + + # get the latest models from space + space_models = await self.ap.space_service.get_models() + + exists_llm_models_uuids = [m['uuid'] for m in await self.ap.llm_model_service.get_llm_models()] + exists_embedding_models_uuids = [ + m['uuid'] for m in await self.ap.embedding_models_service.get_embedding_models() + ] + + for space_model in space_models: + if space_model.category == 'chat': + uuid = space_model.uuid + + if uuid in exists_llm_models_uuids: + continue + + # model will be automatically loaded + await self.ap.llm_model_service.create_llm_model( + { + 'uuid': space_model.uuid, + 'name': space_model.model_id, + 'provider_uuid': space_model_provider.uuid, + 'abilities': space_model.llm_abilities or [], + 'extra_args': {}, + 'prefered_ranking': space_model.featured_order, + }, + preserve_uuid=True, + ) + + elif space_model.category == 'embedding': + uuid = space_model.uuid + + if uuid in exists_embedding_models_uuids: + continue + + # model will be automatically loaded + await self.ap.embedding_models_service.create_embedding_model( + { + 'uuid': space_model.uuid, + 'name': space_model.model_id, + 'provider_uuid': space_model_provider.uuid, + 'extra_args': {}, + 'prefered_ranking': space_model.featured_order, + }, + preserve_uuid=True, + ) + async def init_runtime_llm_model( self, model_info: dict, From 4528000fc4059d2c88b88d523798ab8dd7d106c6 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 1 Jan 2026 02:00:24 +0800 Subject: [PATCH 17/30] refactor: model management --- .../pkg/api/http/controller/groups/user.py | 3 + src/langbot/pkg/api/http/service/model.py | 56 +++-- src/langbot/pkg/api/http/service/provider.py | 18 +- src/langbot/pkg/api/http/service/user.py | 3 + src/langbot/pkg/persistence/mgr.py | 18 +- src/langbot/pkg/plugin/handler.py | 2 +- src/langbot/pkg/provider/modelmgr/modelmgr.py | 192 ++++++++++-------- .../pkg/provider/modelmgr/requester.py | 43 ++-- .../modelmgr/requesters/anthropicmsgs.py | 4 +- .../modelmgr/requesters/bailianchatcmpl.py | 4 +- .../provider/modelmgr/requesters/chatcmpl.py | 6 +- .../modelmgr/requesters/deepseekchatcmpl.py | 2 +- .../modelmgr/requesters/geminichatcmpl.py | 2 +- .../modelmgr/requesters/jiekouaichatcmpl.py | 2 +- .../modelmgr/requesters/modelscopechatcmpl.py | 4 +- .../modelmgr/requesters/moonshotchatcmpl.py | 2 +- .../modelmgr/requesters/ppiochatcmpl.py | 2 +- .../pkg/provider/runners/localagent.py | 8 +- .../pkg/rag/knowledge/services/embedder.py | 2 +- .../pkg/rag/knowledge/services/retriever.py | 2 +- 20 files changed, 238 insertions(+), 137 deletions(-) diff --git a/src/langbot/pkg/api/http/controller/groups/user.py b/src/langbot/pkg/api/http/controller/groups/user.py index 086f9c28..b23a6c32 100644 --- a/src/langbot/pkg/api/http/controller/groups/user.py +++ b/src/langbot/pkg/api/http/controller/groups/user.py @@ -1,6 +1,7 @@ import quart import argon2 import asyncio +import traceback from .. import group @@ -141,8 +142,10 @@ class UserRouterGroup(group.RouterGroup): } ) except ValueError as e: + traceback.print_exc() return self.fail(1, str(e)) except Exception as e: + traceback.print_exc() return self.fail(2, f'OAuth callback failed: {str(e)}') @self.route('/info', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) diff --git a/src/langbot/pkg/api/http/service/model.py b/src/langbot/pkg/api/http/service/model.py index e12498cf..d6250ff6 100644 --- a/src/langbot/pkg/api/http/service/model.py +++ b/src/langbot/pkg/api/http/service/model.py @@ -85,10 +85,17 @@ class LLMModelsService: await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_model.LLMModel).values(**model_data)) - llm_model = await self.get_llm_model(model_data['uuid']) - await self.ap.model_mgr.load_llm_model(llm_model) + runtime_provider = self.ap.model_mgr.provider_dict.get(model_data['provider_uuid']) + if runtime_provider is None: + raise Exception('provider not found') - # Check if default pipeline has no model bound + runtime_llm_model = await self.ap.model_mgr.load_llm_model_with_provider( + persistence_model.LLMModel(**model_data), + runtime_provider, + ) + self.ap.model_mgr.llm_models.append(runtime_llm_model) + + # set the default pipeline model to this model result = await self.ap.persistence_mgr.execute_async( sqlalchemy.select(persistence_pipeline.LegacyPipeline).where( persistence_pipeline.LegacyPipeline.is_default == True @@ -152,8 +159,16 @@ class LLMModelsService: ) await self.ap.model_mgr.remove_llm_model(model_uuid) - llm_model = await self.get_llm_model(model_uuid) - await self.ap.model_mgr.load_llm_model(llm_model) + + runtime_provider = self.ap.model_mgr.provider_dict.get(model_data['provider_uuid']) + if runtime_provider is None: + raise Exception('provider not found') + + runtime_llm_model = await self.ap.model_mgr.load_llm_model_with_provider( + persistence_model.LLMModel(**model_data), + runtime_provider, + ) + self.ap.model_mgr.llm_models.append(runtime_llm_model) async def delete_llm_model(self, model_uuid: str) -> None: """Delete an LLM model""" @@ -174,10 +189,10 @@ class LLMModelsService: if runtime_llm_model is None: raise Exception('model not found') else: - runtime_llm_model = await self.ap.model_mgr.init_runtime_llm_model(model_data) + runtime_llm_model = await self.ap.model_mgr.init_temporary_runtime_llm_model(model_data) extra_args = model_data.get('extra_args', {}) - await runtime_llm_model.requester.invoke_llm( + await runtime_llm_model.provider.requester.invoke_llm( query=None, model=runtime_llm_model, messages=[provider_message.Message(role='user', content='Hello, world! Please just reply a "Hello".')], @@ -244,8 +259,15 @@ class EmbeddingModelsService: sqlalchemy.insert(persistence_model.EmbeddingModel).values(**model_data) ) - embedding_model = await self.get_embedding_model(model_data['uuid']) - await self.ap.model_mgr.load_embedding_model(embedding_model) + runtime_provider = self.ap.model_mgr.provider_dict.get(model_data['provider_uuid']) + if runtime_provider is None: + raise Exception('provider not found') + + runtime_embedding_model = await self.ap.model_mgr.load_embedding_model_with_provider( + persistence_model.EmbeddingModel(**model_data), + runtime_provider, + ) + self.ap.model_mgr.embedding_models.append(runtime_embedding_model) return model_data['uuid'] @@ -298,8 +320,16 @@ class EmbeddingModelsService: ) await self.ap.model_mgr.remove_embedding_model(model_uuid) - embedding_model = await self.get_embedding_model(model_uuid) - await self.ap.model_mgr.load_embedding_model(embedding_model) + + runtime_provider = self.ap.model_mgr.provider_dict.get(model_data['provider_uuid']) + if runtime_provider is None: + raise Exception('provider not found') + + runtime_embedding_model = await self.ap.model_mgr.load_embedding_model_with_provider( + persistence_model.EmbeddingModel(**model_data), + runtime_provider, + ) + self.ap.model_mgr.embedding_models.append(runtime_embedding_model) async def delete_embedding_model(self, model_uuid: str) -> None: """Delete an embedding model""" @@ -322,9 +352,9 @@ class EmbeddingModelsService: if runtime_embedding_model is None: raise Exception('model not found') else: - runtime_embedding_model = await self.ap.model_mgr.init_runtime_embedding_model(model_data) + runtime_embedding_model = await self.ap.model_mgr.init_temporary_runtime_embedding_model(model_data) - await runtime_embedding_model.requester.invoke_embedding( + await runtime_embedding_model.provider.requester.invoke_embedding( model=runtime_embedding_model, input_text=['Hello, world!'], extra_args={}, diff --git a/src/langbot/pkg/api/http/service/provider.py b/src/langbot/pkg/api/http/service/provider.py index eb99c092..1abb6e9f 100644 --- a/src/langbot/pkg/api/http/service/provider.py +++ b/src/langbot/pkg/api/http/service/provider.py @@ -61,6 +61,10 @@ class ModelProviderService: await self.ap.persistence_mgr.execute_async( sqlalchemy.insert(persistence_model.ModelProvider).values(**provider_data) ) + + # load to runtime + runtime_provider = await self.ap.model_mgr.load_provider(provider_data) + self.ap.model_mgr.provider_dict[runtime_provider.provider_entity.uuid] = runtime_provider return provider_data['uuid'] async def update_provider(self, provider_uuid: str, provider_data: dict) -> None: @@ -72,8 +76,7 @@ class ModelProviderService: .where(persistence_model.ModelProvider.uuid == provider_uuid) .values(**provider_data) ) - # Reload all models using this provider - await self.ap.model_mgr.load_models_from_db() + await self.ap.model_mgr.reload_provider(provider_uuid) async def delete_provider(self, provider_uuid: str) -> None: """Delete a provider (only if no models reference it)""" @@ -100,6 +103,8 @@ class ModelProviderService: ) ) + await self.ap.model_mgr.remove_provider(provider_uuid) + async def get_provider_model_counts(self, provider_uuid: str) -> dict: """Get count of models using this provider""" llm_result = await self.ap.persistence_mgr.execute_async( @@ -150,3 +155,12 @@ class ModelProviderService: 'api_keys': api_keys or [], } ) + + async def update_space_model_provider_api_keys(self, api_key: str) -> None: + """Update Space model provider API keys""" + await self.ap.persistence_mgr.execute_async( + sqlalchemy.update(persistence_model.ModelProvider) + .where(persistence_model.ModelProvider.uuid == '00000000-0000-0000-0000-000000000000') + .values(api_keys=[api_key]) + ) + await self.ap.model_mgr.reload_provider('00000000-0000-0000-0000-000000000000') diff --git a/src/langbot/pkg/api/http/service/user.py b/src/langbot/pkg/api/http/service/user.py index 2b37d794..d83ee0ff 100644 --- a/src/langbot/pkg/api/http/service/user.py +++ b/src/langbot/pkg/api/http/service/user.py @@ -149,6 +149,7 @@ class UserService: space_access_token_expires_at=expires_at, ) ) + await self.ap.provider_service.update_space_model_provider_api_keys(api_key) return await self.get_user_by_space_account_uuid(space_account_uuid) # Check if user with same email exists @@ -167,6 +168,7 @@ class UserService: space_access_token_expires_at=expires_at, ) ) + await self.ap.provider_service.update_space_model_provider_api_keys(api_key) return await self.get_user_by_email(email) # Check if system is already initialized @@ -189,6 +191,7 @@ class UserService: space_access_token_expires_at=expires_at, ) ) + await self.ap.provider_service.update_space_model_provider_api_keys(api_key) return await self.get_user_by_space_account_uuid(space_account_uuid) diff --git a/src/langbot/pkg/persistence/mgr.py b/src/langbot/pkg/persistence/mgr.py index 1311ff4f..8e147799 100644 --- a/src/langbot/pkg/persistence/mgr.py +++ b/src/langbot/pkg/persistence/mgr.py @@ -125,25 +125,39 @@ class PersistenceManager: await self.execute_async(sqlalchemy.insert(pipeline.LegacyPipeline).values(pipeline_data)) async def write_space_model_providers(self): + space_models_gateway_api_url = self.ap.instance_config.data.get('space', {}).get( + 'models_gateway_api_url', 'https://api.langbot.cloud/v1' + ) + # write space model providers result = await self.execute_async( sqlalchemy.select(persistence_model.ModelProvider).where( persistence_model.ModelProvider.requester == 'space-chat-completions' ) ) - if result.first() is None: + exists_space_chat_completions_model_provider = result.first() + + # api keys will be set/updated when the oauth callback + if exists_space_chat_completions_model_provider is None: self.ap.logger.info('Creating space model providers...') space_chat_completions_model_provider = { 'uuid': '00000000-0000-0000-0000-000000000000', 'name': 'LangBot Models', 'requester': 'space-chat-completions', - 'base_url': 'https://api.langbot.cloud/v1', + 'base_url': space_models_gateway_api_url, 'api_keys': [], } await self.execute_async( sqlalchemy.insert(persistence_model.ModelProvider).values(space_chat_completions_model_provider) ) + else: + if exists_space_chat_completions_model_provider.base_url != space_models_gateway_api_url: + await self.execute_async( + sqlalchemy.update(persistence_model.ModelProvider) + .where(persistence_model.ModelProvider.uuid == exists_space_chat_completions_model_provider.uuid) + .values({'base_url': space_models_gateway_api_url}) + ) # ================================= diff --git a/src/langbot/pkg/plugin/handler.py b/src/langbot/pkg/plugin/handler.py index 35eeec50..e9819e0d 100644 --- a/src/langbot/pkg/plugin/handler.py +++ b/src/langbot/pkg/plugin/handler.py @@ -324,7 +324,7 @@ class RuntimeConnectionHandler(handler.Handler): messages_obj = [provider_message.Message.model_validate(message) for message in messages] funcs_obj = [resource_tool.LLMTool.model_validate(func) for func in funcs] - result = await llm_model.requester.invoke_llm( + result = await llm_model.provider.requester.invoke_llm( query=None, model=llm_model, messages=messages_obj, diff --git a/src/langbot/pkg/provider/modelmgr/modelmgr.py b/src/langbot/pkg/provider/modelmgr/modelmgr.py index 0c84eaad..de72f1bc 100644 --- a/src/langbot/pkg/provider/modelmgr/modelmgr.py +++ b/src/langbot/pkg/provider/modelmgr/modelmgr.py @@ -16,6 +16,9 @@ class ModelManager: ap: app.Application + provider_dict: dict[str, requester.RuntimeProvider] + """运行时模型提供商字典, uuid -> RuntimeProvider""" + llm_models: list[requester.RuntimeLLMModel] embedding_models: list[requester.RuntimeEmbeddingModel] @@ -51,23 +54,31 @@ class ModelManager: self.embedding_models = [] # Load all providers first + self.provider_dict = {} providers_result = await self.ap.persistence_mgr.execute_async( sqlalchemy.select(persistence_model.ModelProvider) ) - providers = {p.uuid: p for p in providers_result.all()} + for provider in providers_result.all(): + try: + runtime_provider = await self.load_provider(provider) + self.provider_dict[provider.uuid] = runtime_provider + except provider_errors.RequesterNotFoundError as e: + self.ap.logger.warning(f'Requester {e.requester_name} not found, skipping provider {provider.uuid}') + continue + except Exception as e: + self.ap.logger.error(f'Failed to load provider {provider.uuid}: {e}\n{traceback.format_exc()}') # Load LLM models result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_model.LLMModel)) llm_models = result.all() for llm_model in llm_models: try: - provider = providers.get(llm_model.provider_uuid) + provider = self.provider_dict.get(llm_model.provider_uuid) if provider is None: self.ap.logger.warning(f'Provider {llm_model.provider_uuid} not found for model {llm_model.uuid}') continue - await self.load_llm_model_with_provider(llm_model, provider) - except provider_errors.RequesterNotFoundError as e: - self.ap.logger.warning(f'Requester {e.requester_name} not found, skipping llm model {llm_model.uuid}') + runtime_llm_model = await self.load_llm_model_with_provider(llm_model, provider) + self.llm_models.append(runtime_llm_model) except Exception as e: self.ap.logger.error(f'Failed to load model {llm_model.uuid}: {e}\n{traceback.format_exc()}') @@ -76,17 +87,14 @@ class ModelManager: embedding_models = result.all() for embedding_model in embedding_models: try: - provider = providers.get(embedding_model.provider_uuid) + provider = self.provider_dict.get(embedding_model.provider_uuid) if provider is None: self.ap.logger.warning( f'Provider {embedding_model.provider_uuid} not found for model {embedding_model.uuid}' ) continue - await self.load_embedding_model_with_provider(embedding_model, provider) - except provider_errors.RequesterNotFoundError as e: - self.ap.logger.warning( - f'Requester {e.requester_name} not found, skipping embedding model {embedding_model.uuid}' - ) + runtime_embedding_model = await self.load_embedding_model_with_provider(embedding_model, provider) + self.embedding_models.append(runtime_embedding_model) except Exception as e: self.ap.logger.error(f'Failed to load model {embedding_model.uuid}: {e}\n{traceback.format_exc()}') @@ -149,123 +157,139 @@ class ModelManager: preserve_uuid=True, ) - async def init_runtime_llm_model( + async def init_temporary_runtime_llm_model( self, model_info: dict, - ): + ) -> requester.RuntimeLLMModel: """Initialize runtime LLM model from dict (for testing)""" provider_info = model_info.get('provider', {}) - requester_name = provider_info.get('requester', '') - base_url = provider_info.get('base_url', '') - api_keys = provider_info.get('api_keys', []) - if requester_name not in self.requester_dict: - raise provider_errors.RequesterNotFoundError(requester_name) - - requester_cfg = {'base_url': base_url} - requester_inst = self.requester_dict[requester_name](ap=self.ap, config=requester_cfg) - await requester_inst.initialize() - - # Create a temporary model entity - model_entity = persistence_model.LLMModel( - uuid=model_info.get('uuid', ''), - name=model_info.get('name', ''), - provider_uuid='', - abilities=model_info.get('abilities', []), - extra_args=model_info.get('extra_args', {}), - ) + runtime_provider = await self.load_provider(provider_info) runtime_llm_model = requester.RuntimeLLMModel( - model_entity=model_entity, - token_mgr=token.TokenManager(name=model_entity.uuid, tokens=api_keys), - requester=requester_inst, + model_entity=persistence_model.LLMModel( + uuid=model_info.get('uuid', ''), + name=model_info.get('name', ''), + provider_uuid='', + abilities=model_info.get('abilities', []), + extra_args=model_info.get('extra_args', {}), + ), + provider=runtime_provider, ) return runtime_llm_model - async def init_runtime_embedding_model( + async def init_temporary_runtime_embedding_model( self, model_info: dict, - ): + ) -> requester.RuntimeEmbeddingModel: """Initialize runtime embedding model from dict (for testing)""" provider_info = model_info.get('provider', {}) - requester_name = provider_info.get('requester', '') - base_url = provider_info.get('base_url', '') - api_keys = provider_info.get('api_keys', []) - - if requester_name not in self.requester_dict: - raise provider_errors.RequesterNotFoundError(requester_name) - - requester_cfg = {'base_url': base_url} - requester_inst = self.requester_dict[requester_name](ap=self.ap, config=requester_cfg) - await requester_inst.initialize() - - model_entity = persistence_model.EmbeddingModel( - uuid=model_info.get('uuid', ''), - name=model_info.get('name', ''), - provider_uuid='', - extra_args=model_info.get('extra_args', {}), - ) + runtime_provider = await self.load_provider(provider_info) runtime_embedding_model = requester.RuntimeEmbeddingModel( - model_entity=model_entity, - token_mgr=token.TokenManager(name=model_entity.uuid, tokens=api_keys), - requester=requester_inst, + model_entity=persistence_model.EmbeddingModel( + uuid=model_info.get('uuid', ''), + name=model_info.get('name', ''), + provider_uuid='', + extra_args=model_info.get('extra_args', {}), + ), + provider=runtime_provider, ) return runtime_embedding_model + async def load_provider( + self, provider_info: persistence_model.ModelProvider | sqlalchemy.Row | dict + ) -> requester.RuntimeProvider: + """Load provider from dict""" + if isinstance(provider_info, sqlalchemy.Row): + provider_entity = persistence_model.ModelProvider(**provider_info._mapping) + elif isinstance(provider_info, dict): + provider_entity = persistence_model.ModelProvider(**provider_info) + else: + provider_entity = provider_info + + if provider_entity.requester not in self.requester_dict: + raise provider_errors.RequesterNotFoundError(provider_entity.requester) + + requester_inst = self.requester_dict[provider_entity.requester]( + ap=self.ap, config={'base_url': provider_entity.base_url} + ) + await requester_inst.initialize() + + token_mgr = token.TokenManager(name=provider_entity.uuid, tokens=provider_entity.api_keys or []) + + provider = requester.RuntimeProvider( + provider_entity=provider_entity, + token_mgr=token_mgr, + requester=requester_inst, + ) + return provider + + async def remove_provider(self, provider_uuid: str): + """Remove provider + + This method will not consider the models using this provider, + because the models should be removed by the caller. + """ + del self.provider_dict[provider_uuid] + + async def reload_provider(self, provider_uuid: str): + """Reload provider""" + provider_entity = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_model.ModelProvider).where( + persistence_model.ModelProvider.uuid == provider_uuid + ) + ) + provider_entity = provider_entity.first() + if provider_entity is None: + raise provider_errors.ProviderNotFoundError(provider_uuid) + + new_runtime_provider = await self.load_provider(provider_entity) + + # update refs in runtime models + for model in self.llm_models: + if model.provider.provider_entity.uuid == provider_uuid: + model.provider = new_runtime_provider + for model in self.embedding_models: + if model.provider.provider_entity.uuid == provider_uuid: + model.provider = new_runtime_provider + + # update ref in provider dict + self.provider_dict[provider_uuid] = new_runtime_provider + async def load_llm_model_with_provider( self, model_info: persistence_model.LLMModel | sqlalchemy.Row, - provider: persistence_model.ModelProvider | sqlalchemy.Row, - ): + provider: requester.RuntimeProvider, + ) -> requester.RuntimeLLMModel: """Load LLM model with provider info""" if isinstance(model_info, sqlalchemy.Row): model_info = persistence_model.LLMModel(**model_info._mapping) - if isinstance(provider, sqlalchemy.Row): - provider = persistence_model.ModelProvider(**provider._mapping) - - if provider.requester not in self.requester_dict: - raise provider_errors.RequesterNotFoundError(provider.requester) - - requester_cfg = {'base_url': provider.base_url} - requester_inst = self.requester_dict[provider.requester](ap=self.ap, config=requester_cfg) - await requester_inst.initialize() runtime_llm_model = requester.RuntimeLLMModel( model_entity=model_info, - token_mgr=token.TokenManager(name=model_info.uuid, tokens=provider.api_keys or []), - requester=requester_inst, + provider=provider, ) - self.llm_models.append(runtime_llm_model) + return runtime_llm_model async def load_embedding_model_with_provider( self, model_info: persistence_model.EmbeddingModel | sqlalchemy.Row, - provider: persistence_model.ModelProvider | sqlalchemy.Row, - ): + provider: requester.RuntimeProvider, + ) -> requester.RuntimeEmbeddingModel: """Load embedding model with provider info""" if isinstance(model_info, sqlalchemy.Row): model_info = persistence_model.EmbeddingModel(**model_info._mapping) - if isinstance(provider, sqlalchemy.Row): - provider = persistence_model.ModelProvider(**provider._mapping) - - if provider.requester not in self.requester_dict: - raise provider_errors.RequesterNotFoundError(provider.requester) - - requester_cfg = {'base_url': provider.base_url} - requester_inst = self.requester_dict[provider.requester](ap=self.ap, config=requester_cfg) - await requester_inst.initialize() runtime_embedding_model = requester.RuntimeEmbeddingModel( model_entity=model_info, - token_mgr=token.TokenManager(name=model_info.uuid, tokens=provider.api_keys or []), - requester=requester_inst, + provider=provider, ) - self.embedding_models.append(runtime_embedding_model) + return runtime_embedding_model async def load_llm_model(self, model_info: dict): """Load LLM model from dict (with provider info)""" diff --git a/src/langbot/pkg/provider/modelmgr/requester.py b/src/langbot/pkg/provider/modelmgr/requester.py index 52d73eea..3052a62f 100644 --- a/src/langbot/pkg/provider/modelmgr/requester.py +++ b/src/langbot/pkg/provider/modelmgr/requester.py @@ -11,11 +11,11 @@ import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query import langbot_plugin.api.entities.builtin.provider.message as provider_message -class RuntimeLLMModel: - """运行时模型""" +class RuntimeProvider: + """运行时模型提供商""" - model_entity: persistence_model.LLMModel - """模型数据""" + provider_entity: persistence_model.ModelProvider + """提供商数据""" token_mgr: token.TokenManager """api key管理器""" @@ -25,36 +25,49 @@ class RuntimeLLMModel: def __init__( self, - model_entity: persistence_model.LLMModel, + provider_entity: persistence_model.ModelProvider, token_mgr: token.TokenManager, requester: ProviderAPIRequester, ): - self.model_entity = model_entity + self.provider_entity = provider_entity self.token_mgr = token_mgr self.requester = requester +class RuntimeLLMModel: + """运行时模型""" + + model_entity: persistence_model.LLMModel + """模型数据""" + + provider: RuntimeProvider + """提供商实例""" + + def __init__( + self, + model_entity: persistence_model.LLMModel, + provider: RuntimeProvider, + ): + self.model_entity = model_entity + self.provider = provider + + class RuntimeEmbeddingModel: """运行时 Embedding 模型""" model_entity: persistence_model.EmbeddingModel """模型数据""" - token_mgr: token.TokenManager - """api key管理器""" - - requester: ProviderAPIRequester - """请求器实例""" + provider: RuntimeProvider + """提供商实例""" def __init__( self, model_entity: persistence_model.EmbeddingModel, - token_mgr: token.TokenManager, - requester: ProviderAPIRequester, + provider: RuntimeProvider, ): self.model_entity = model_entity - self.token_mgr = token_mgr - self.requester = requester + self.provider = provider class ProviderAPIRequester(metaclass=abc.ABCMeta): diff --git a/src/langbot/pkg/provider/modelmgr/requesters/anthropicmsgs.py b/src/langbot/pkg/provider/modelmgr/requesters/anthropicmsgs.py index 3a1b9384..9394b73d 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/anthropicmsgs.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/anthropicmsgs.py @@ -56,7 +56,7 @@ class AnthropicMessages(requester.ProviderAPIRequester): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.Message: - self.client.api_key = model.token_mgr.get_token() + self.client.api_key = model.provider.token_mgr.get_token() args = extra_args.copy() args['model'] = model.model_entity.name @@ -190,7 +190,7 @@ class AnthropicMessages(requester.ProviderAPIRequester): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.Message: - self.client.api_key = model.token_mgr.get_token() + self.client.api_key = model.provider.token_mgr.get_token() args = extra_args.copy() args['model'] = model.model_entity.name diff --git a/src/langbot/pkg/provider/modelmgr/requesters/bailianchatcmpl.py b/src/langbot/pkg/provider/modelmgr/requesters/bailianchatcmpl.py index c60165bb..9da6e1b4 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/bailianchatcmpl.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/bailianchatcmpl.py @@ -30,7 +30,7 @@ class BailianChatCompletions(modelscopechatcmpl.ModelScopeChatCompletions): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.Message | typing.AsyncGenerator[provider_message.MessageChunk, None]: - self.client.api_key = use_model.token_mgr.get_token() + self.client.api_key = use_model.provider.token_mgr.get_token() args = {} args['model'] = use_model.model_entity.name @@ -117,7 +117,7 @@ class BailianChatCompletions(modelscopechatcmpl.ModelScopeChatCompletions): if is_use_dashscope_call: response = dashscope.MultiModalConversation.call( # 若没有配置环境变量,请用百炼API Key将下行替换为:api_key = "sk-xxx" - api_key=use_model.token_mgr.get_token(), + api_key=use_model.provider.token_mgr.get_token(), model=use_model.model_entity.name, messages=messages, result_format='message', diff --git a/src/langbot/pkg/provider/modelmgr/requesters/chatcmpl.py b/src/langbot/pkg/provider/modelmgr/requesters/chatcmpl.py index b940859e..beb45936 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/chatcmpl.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/chatcmpl.py @@ -130,7 +130,7 @@ class OpenAIChatCompletions(requester.ProviderAPIRequester): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.MessageChunk: - self.client.api_key = use_model.token_mgr.get_token() + self.client.api_key = use_model.provider.token_mgr.get_token() args = {} args['model'] = use_model.model_entity.name @@ -251,7 +251,7 @@ class OpenAIChatCompletions(requester.ProviderAPIRequester): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.Message: - self.client.api_key = use_model.token_mgr.get_token() + self.client.api_key = use_model.provider.token_mgr.get_token() args = {} args['model'] = use_model.model_entity.name @@ -337,7 +337,7 @@ class OpenAIChatCompletions(requester.ProviderAPIRequester): extra_args: dict[str, typing.Any] = {}, ) -> list[list[float]]: """调用 Embedding API""" - self.client.api_key = model.token_mgr.get_token() + self.client.api_key = model.provider.token_mgr.get_token() args = { 'model': model.model_entity.name, diff --git a/src/langbot/pkg/provider/modelmgr/requesters/deepseekchatcmpl.py b/src/langbot/pkg/provider/modelmgr/requesters/deepseekchatcmpl.py index 83b2bfa4..a95371da 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/deepseekchatcmpl.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/deepseekchatcmpl.py @@ -26,7 +26,7 @@ class DeepseekChatCompletions(chatcmpl.OpenAIChatCompletions): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.Message: - self.client.api_key = use_model.token_mgr.get_token() + self.client.api_key = use_model.provider.token_mgr.get_token() args = {} args['model'] = use_model.model_entity.name diff --git a/src/langbot/pkg/provider/modelmgr/requesters/geminichatcmpl.py b/src/langbot/pkg/provider/modelmgr/requesters/geminichatcmpl.py index 9741e6b3..f934145e 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/geminichatcmpl.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/geminichatcmpl.py @@ -29,7 +29,7 @@ class GeminiChatCompletions(chatcmpl.OpenAIChatCompletions): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.MessageChunk: - self.client.api_key = use_model.token_mgr.get_token() + self.client.api_key = use_model.provider.token_mgr.get_token() args = {} args['model'] = use_model.model_entity.name diff --git a/src/langbot/pkg/provider/modelmgr/requesters/jiekouaichatcmpl.py b/src/langbot/pkg/provider/modelmgr/requesters/jiekouaichatcmpl.py index 60001037..305ae21f 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/jiekouaichatcmpl.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/jiekouaichatcmpl.py @@ -109,7 +109,7 @@ class JieKouAIChatCompletions(chatcmpl.OpenAIChatCompletions): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.Message | typing.AsyncGenerator[provider_message.MessageChunk, None]: - self.client.api_key = use_model.token_mgr.get_token() + self.client.api_key = use_model.provider.token_mgr.get_token() args = {} args['model'] = use_model.model_entity.name diff --git a/src/langbot/pkg/provider/modelmgr/requesters/modelscopechatcmpl.py b/src/langbot/pkg/provider/modelmgr/requesters/modelscopechatcmpl.py index 8684a677..0d92bb51 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/modelscopechatcmpl.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/modelscopechatcmpl.py @@ -131,7 +131,7 @@ class ModelScopeChatCompletions(requester.ProviderAPIRequester): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.Message: - self.client.api_key = use_model.token_mgr.get_token() + self.client.api_key = use_model.provider.token_mgr.get_token() args = {} args['model'] = use_model.model_entity.name @@ -181,7 +181,7 @@ class ModelScopeChatCompletions(requester.ProviderAPIRequester): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.Message | typing.AsyncGenerator[provider_message.MessageChunk, None]: - self.client.api_key = use_model.token_mgr.get_token() + self.client.api_key = use_model.provider.token_mgr.get_token() args = {} args['model'] = use_model.model_entity.name diff --git a/src/langbot/pkg/provider/modelmgr/requesters/moonshotchatcmpl.py b/src/langbot/pkg/provider/modelmgr/requesters/moonshotchatcmpl.py index aa3d0f4f..969392b4 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/moonshotchatcmpl.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/moonshotchatcmpl.py @@ -27,7 +27,7 @@ class MoonshotChatCompletions(chatcmpl.OpenAIChatCompletions): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.Message: - self.client.api_key = use_model.token_mgr.get_token() + self.client.api_key = use_model.provider.token_mgr.get_token() args = {} args['model'] = use_model.model_entity.name diff --git a/src/langbot/pkg/provider/modelmgr/requesters/ppiochatcmpl.py b/src/langbot/pkg/provider/modelmgr/requesters/ppiochatcmpl.py index 9658312b..1836bd62 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/ppiochatcmpl.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/ppiochatcmpl.py @@ -109,7 +109,7 @@ class PPIOChatCompletions(chatcmpl.OpenAIChatCompletions): extra_args: dict[str, typing.Any] = {}, remove_think: bool = False, ) -> provider_message.Message | typing.AsyncGenerator[provider_message.MessageChunk, None]: - self.client.api_key = use_model.token_mgr.get_token() + self.client.api_key = use_model.provider.token_mgr.get_token() args = {} args['model'] = use_model.model_entity.name diff --git a/src/langbot/pkg/provider/runners/localagent.py b/src/langbot/pkg/provider/runners/localagent.py index 6c29415e..4197f076 100644 --- a/src/langbot/pkg/provider/runners/localagent.py +++ b/src/langbot/pkg/provider/runners/localagent.py @@ -130,7 +130,7 @@ class LocalAgentRunner(runner.RequestRunner): if not is_stream: # 非流式输出,直接请求 - msg = await use_llm_model.requester.invoke_llm( + msg = await use_llm_model.provider.requester.invoke_llm( query, use_llm_model, req_messages, @@ -147,7 +147,7 @@ class LocalAgentRunner(runner.RequestRunner): accumulated_content = '' # 从开始累积的所有内容 last_role = 'assistant' msg_sequence = 1 - async for msg in use_llm_model.requester.invoke_llm_stream( + async for msg in use_llm_model.provider.requester.invoke_llm_stream( query, use_llm_model, req_messages, @@ -250,7 +250,7 @@ class LocalAgentRunner(runner.RequestRunner): last_role = 'assistant' msg_sequence = first_end_sequence - async for msg in use_llm_model.requester.invoke_llm_stream( + async for msg in use_llm_model.provider.requester.invoke_llm_stream( query, use_llm_model, req_messages, @@ -306,7 +306,7 @@ class LocalAgentRunner(runner.RequestRunner): ) else: # 处理完所有调用,再次请求 - msg = await use_llm_model.requester.invoke_llm( + msg = await use_llm_model.provider.requester.invoke_llm( query, use_llm_model, req_messages, diff --git a/src/langbot/pkg/rag/knowledge/services/embedder.py b/src/langbot/pkg/rag/knowledge/services/embedder.py index c8a1c3d3..a067c90c 100644 --- a/src/langbot/pkg/rag/knowledge/services/embedder.py +++ b/src/langbot/pkg/rag/knowledge/services/embedder.py @@ -33,7 +33,7 @@ class Embedder(BaseService): await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_rag.Chunk).values(chunk_dicts)) # get embeddings - embeddings_list: list[list[float]] = await embedding_model.requester.invoke_embedding( + embeddings_list: list[list[float]] = await embedding_model.provider.requester.invoke_embedding( model=embedding_model, input_text=chunks, extra_args={}, # TODO: add extra args diff --git a/src/langbot/pkg/rag/knowledge/services/retriever.py b/src/langbot/pkg/rag/knowledge/services/retriever.py index dada8d5f..f2f4bef5 100644 --- a/src/langbot/pkg/rag/knowledge/services/retriever.py +++ b/src/langbot/pkg/rag/knowledge/services/retriever.py @@ -19,7 +19,7 @@ class Retriever(base_service.BaseService): f"Retrieving for query: '{query[:10]}' with k={k} using {embedding_model.model_entity.uuid}" ) - query_embedding: list[float] = await embedding_model.requester.invoke_embedding( + query_embedding: list[float] = await embedding_model.provider.requester.invoke_embedding( model=embedding_model, input_text=[query], extra_args={}, # TODO: add extra args From b99c5561fc322e367360831867b8321061c4053e Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 1 Jan 2026 12:50:26 +0800 Subject: [PATCH 18/30] fix: update cloud service URL retrieval and enhance model synchronization error handling --- src/langbot/pkg/api/http/controller/groups/system.py | 6 +----- src/langbot/pkg/provider/modelmgr/modelmgr.py | 7 ++++++- web/src/app/home/components/models-dialog/ModelsDialog.tsx | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/langbot/pkg/api/http/controller/groups/system.py b/src/langbot/pkg/api/http/controller/groups/system.py index 03b45e35..e5d3bef0 100644 --- a/src/langbot/pkg/api/http/controller/groups/system.py +++ b/src/langbot/pkg/api/http/controller/groups/system.py @@ -17,11 +17,7 @@ class SystemRouterGroup(group.RouterGroup): 'enable_marketplace', True ), 'cloud_service_url': ( - self.ap.instance_config.data.get('plugin', {}).get( - 'cloud_service_url', 'https://space.langbot.app' - ) - if 'cloud_service_url' in self.ap.instance_config.data.get('plugin', {}) - else 'https://space.langbot.app' + self.ap.instance_config.data.get('space', {}).get('url', 'https://space.langbot.app') ), 'allow_modify_login_info': self.ap.instance_config.data.get('system', {}).get( 'allow_modify_login_info', True diff --git a/src/langbot/pkg/provider/modelmgr/modelmgr.py b/src/langbot/pkg/provider/modelmgr/modelmgr.py index de72f1bc..1b0d4a38 100644 --- a/src/langbot/pkg/provider/modelmgr/modelmgr.py +++ b/src/langbot/pkg/provider/modelmgr/modelmgr.py @@ -44,7 +44,12 @@ class ModelManager: self.requester_dict = requester_dict await self.load_models_from_db() - await self.sync_new_models_from_space() + + try: + await self.sync_new_models_from_space() + except Exception as e: + self.ap.logger.warning('Failed to sync new models from LangBot Space, model list may not be updated.') + self.ap.logger.warning(f' - Error: {e}') async def load_models_from_db(self): """Load models from database""" diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index 4a9404b3..e631c95e 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -355,7 +355,7 @@ export default function ModelsDialog({ onClick={(e) => { e.stopPropagation(); window.open( - `${systemInfo.cloud_service_url}/billing`, + `${systemInfo.cloud_service_url}/profile?tab=billing`, '_blank', ); }} From 898144e9f43f649bef1a956bb4c6cf989116a239 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 1 Jan 2026 12:53:39 +0800 Subject: [PATCH 19/30] fix: remove unused HoverCard imports from DynamicFormItemComponent and clean up ModelsDialog constants --- .../components/dynamic-form/DynamicFormItemComponent.tsx | 5 ----- web/src/app/home/components/models-dialog/ModelsDialog.tsx | 1 - 2 files changed, 6 deletions(-) diff --git a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx index 96369bf3..41586b43 100644 --- a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx +++ b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx @@ -26,11 +26,6 @@ import { ApiRespPluginSystemStatus, } from '@/app/infra/entities/api'; import { toast } from 'sonner'; -import { - HoverCard, - HoverCardContent, - HoverCardTrigger, -} from '@/components/ui/hover-card'; import { useTranslation } from 'react-i18next'; import { extractI18nObject } from '@/i18n/I18nProvider'; import { Textarea } from '@/components/ui/textarea'; diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index e631c95e..10def6ba 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -52,7 +52,6 @@ interface ModelsDialogProps { onOpenChange: (open: boolean) => void; } -const LANGBOT_MODELS_PROVIDER_NAME = 'LangBot Models'; const LANGBOT_MODELS_PROVIDER_REQUESTER = 'space-chat-completions'; export default function ModelsDialog({ From 679e549b1de9cfdc41d21cbf94908854e76a3ed8 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 1 Jan 2026 13:06:04 +0800 Subject: [PATCH 20/30] feat: implement loading states in SpaceOAuthCallback and HomeSidebar components using Suspense --- web/src/app/auth/space/callback/page.tsx | 24 +++++++++++++++-- .../components/home-sidebar/HomeSidebar.tsx | 27 +++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/web/src/app/auth/space/callback/page.tsx b/web/src/app/auth/space/callback/page.tsx index a9898fbc..bce384cf 100644 --- a/web/src/app/auth/space/callback/page.tsx +++ b/web/src/app/auth/space/callback/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState, useCallback } from 'react'; +import { useEffect, useState, useCallback, Suspense } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { httpClient } from '@/app/infra/http/HttpClient'; import { toast } from 'sonner'; @@ -21,7 +21,7 @@ import { import { Button } from '@/components/ui/button'; import langbotIcon from '@/app/assets/langbot-logo.webp'; -export default function SpaceOAuthCallback() { +function SpaceOAuthCallbackContent() { const router = useRouter(); const searchParams = useSearchParams(); const { t } = useTranslation(); @@ -216,3 +216,23 @@ export default function SpaceOAuthCallback() {
); } + +function LoadingFallback() { + return ( +
+ + + + + +
+ ); +} + +export default function SpaceOAuthCallback() { + return ( + }> + + + ); +} diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 2b1ae764..0b853683 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -1,7 +1,7 @@ 'use client'; import styles from './HomeSidebar.module.css'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, Suspense } from 'react'; import { SidebarChild, SidebarChildVO, @@ -20,6 +20,7 @@ import { Lightbulb, LogOut, KeyRound, + Loader2, } from 'lucide-react'; import { useTheme } from 'next-themes'; @@ -58,7 +59,7 @@ function compareVersions(v1: string, v2: string): boolean { } // TODO 侧边导航栏要加动画 -export default function HomeSidebar({ +function HomeSidebarContent({ onSelectedChangeAction, }: { onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void; @@ -482,3 +483,25 @@ export default function HomeSidebar({
); } + +function SidebarLoadingFallback() { + return ( +
+
+ +
+
+ ); +} + +export default function HomeSidebar({ + onSelectedChangeAction, +}: { + onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void; +}) { + return ( + }> + + + ); +} From b4773c4e48ae82e925d9abf9f5cb2714feac2ef9 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 1 Jan 2026 14:58:06 +0800 Subject: [PATCH 21/30] refactor: update model management components and enhance provider functionality --- .../provider/modelmgr/requesters/chatcmpl.py | 9 +- .../models-dialog/LLMConfig.module.css | 19 - .../components/models-dialog/ModelsDialog.tsx | 706 ++++++++---------- .../component/ChooseRequesterEntity.ts | 6 - .../component/ICreateEmbeddingField.ts | 7 - .../component/ICreateLLMField.ts | 8 - .../embedding-card/EmbeddingCard.module.css | 128 ---- .../embedding-card/EmbeddingCard.tsx | 55 -- .../embedding-card/EmbeddingCardVO.ts | 23 - .../embedding-form/EmbeddingForm.tsx | 597 --------------- .../component/llm-card/LLMCard.module.css | 173 ----- .../component/llm-card/LLMCard.tsx | 98 --- .../component/llm-card/LLMCardVO.ts | 26 - .../component/llm-form/LLMForm.tsx | 655 ---------------- .../component/provider-form/ProviderForm.tsx | 4 +- .../components/AddModelPopover.tsx | 235 ++++++ .../components/ExtraArgsEditor.tsx | 114 +++ .../models-dialog/components/ModelItem.tsx | 294 ++++++++ .../models-dialog/components/ProviderCard.tsx | 371 +++++++++ .../models-dialog/components/index.ts | 4 + .../home/components/models-dialog/types.ts | 102 +++ web/src/i18n/locales/en-US.ts | 4 + web/src/i18n/locales/ja-JP.ts | 5 + web/src/i18n/locales/zh-Hans.ts | 6 +- web/src/i18n/locales/zh-Hant.ts | 4 + 25 files changed, 1444 insertions(+), 2209 deletions(-) delete mode 100644 web/src/app/home/components/models-dialog/LLMConfig.module.css delete mode 100644 web/src/app/home/components/models-dialog/component/ChooseRequesterEntity.ts delete mode 100644 web/src/app/home/components/models-dialog/component/ICreateEmbeddingField.ts delete mode 100644 web/src/app/home/components/models-dialog/component/ICreateLLMField.ts delete mode 100644 web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.module.css delete mode 100644 web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.tsx delete mode 100644 web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCardVO.ts delete mode 100644 web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx delete mode 100644 web/src/app/home/components/models-dialog/component/llm-card/LLMCard.module.css delete mode 100644 web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx delete mode 100644 web/src/app/home/components/models-dialog/component/llm-card/LLMCardVO.ts delete mode 100644 web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx create mode 100644 web/src/app/home/components/models-dialog/components/AddModelPopover.tsx create mode 100644 web/src/app/home/components/models-dialog/components/ExtraArgsEditor.tsx create mode 100644 web/src/app/home/components/models-dialog/components/ModelItem.tsx create mode 100644 web/src/app/home/components/models-dialog/components/ProviderCard.tsx create mode 100644 web/src/app/home/components/models-dialog/components/index.ts create mode 100644 web/src/app/home/components/models-dialog/types.ts diff --git a/src/langbot/pkg/provider/modelmgr/requesters/chatcmpl.py b/src/langbot/pkg/provider/modelmgr/requesters/chatcmpl.py index beb45936..97e61c89 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/chatcmpl.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/chatcmpl.py @@ -4,7 +4,7 @@ import asyncio import typing import openai -import openai.types.chat.chat_completion as chat_completion +import openai.types.chat.chat_completion as chat_completion_module import httpx from .. import errors, requester @@ -35,7 +35,7 @@ class OpenAIChatCompletions(requester.ProviderAPIRequester): self, args: dict, extra_body: dict = {}, - ) -> chat_completion.ChatCompletion: + ) -> chat_completion_module.ChatCompletion: return await self.client.chat.completions.create(**args, extra_body=extra_body) async def _req_stream( @@ -48,9 +48,12 @@ class OpenAIChatCompletions(requester.ProviderAPIRequester): async def _make_msg( self, - chat_completion: chat_completion.ChatCompletion, + chat_completion: chat_completion_module.ChatCompletion, remove_think: bool = False, ) -> provider_message.Message: + if not isinstance(chat_completion, chat_completion_module.ChatCompletion): + raise TypeError(f'Expected ChatCompletion, got {type(chat_completion).__name__}: {chat_completion[:16]}') + chatcmpl_message = chat_completion.choices[0].message.model_dump() # 确保 role 字段存在且不为 None diff --git a/web/src/app/home/components/models-dialog/LLMConfig.module.css b/web/src/app/home/components/models-dialog/LLMConfig.module.css deleted file mode 100644 index ce6c689a..00000000 --- a/web/src/app/home/components/models-dialog/LLMConfig.module.css +++ /dev/null @@ -1,19 +0,0 @@ -.modelListContainer { - width: 100%; - padding-left: 0.8rem; - padding-right: 0.8rem; - display: grid; - grid-template-columns: repeat(auto-fill, minmax(24rem, 1fr)); - gap: 2rem; - justify-items: stretch; - align-items: start; -} - -.emptyContainer { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index 10def6ba..2d67dafc 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -1,24 +1,9 @@ 'use client'; import { useState, useEffect } from 'react'; -import { - Plus, - MessageSquareText, - Cpu, - ChevronDown, - ChevronRight, - Trash2, - Settings, - LogIn, - Eye, - Wrench, -} from 'lucide-react'; -import { httpClient, systemInfo } from '@/app/infra/http/HttpClient'; -import { - LLMModel, - EmbeddingModel, - ModelProvider, -} from '@/app/infra/entities/api'; +import { Plus, Boxes } from 'lucide-react'; +import { httpClient } from '@/app/infra/http/HttpClient'; +import { ModelProvider } from '@/app/infra/entities/api'; import { Dialog, DialogContent, @@ -26,33 +11,37 @@ import { DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from '@/components/ui/collapsible'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; import { extractI18nObject } from '@/i18n/I18nProvider'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import LLMForm from './component/llm-form/LLMForm'; -import EmbeddingForm from './component/embedding-form/EmbeddingForm'; import ProviderForm from './component/provider-form/ProviderForm'; -import langbotIcon from '@/app/assets/langbot-logo.webp'; +import { ProviderCard } from './components'; +import { + ExtraArg, + ModelType, + TestResult, + ProviderModels, + LANGBOT_MODELS_PROVIDER_REQUESTER, +} from './types'; interface ModelsDialogProps { open: boolean; onOpenChange: (open: boolean) => void; } -const LANGBOT_MODELS_PROVIDER_REQUESTER = 'space-chat-completions'; +function convertExtraArgsToObject( + args: ExtraArg[], +): Record { + const obj: Record = {}; + args.forEach((arg) => { + if (arg.key.trim()) { + if (arg.type === 'number') obj[arg.key] = Number(arg.value); + else if (arg.type === 'boolean') obj[arg.key] = arg.value === 'true'; + else obj[arg.key] = arg.value; + } + }); + return obj; +} export default function ModelsDialog({ open, @@ -69,28 +58,49 @@ export default function ModelsDialog({ new Set(), ); const [providerModels, setProviderModels] = useState< - Record + Record >({}); const [loadingProviders, setLoadingProviders] = useState>( new Set(), ); - // Form modals - const [llmFormOpen, setLLMFormOpen] = useState(false); - const [embeddingFormOpen, setEmbeddingFormOpen] = useState(false); + // Provider form modal const [providerFormOpen, setProviderFormOpen] = useState(false); - const [editingLLMId, setEditingLLMId] = useState(null); - const [editingEmbeddingId, setEditingEmbeddingId] = useState( - null, - ); const [editingProviderId, setEditingProviderId] = useState( null, ); + // Popover states + const [addModelPopoverOpen, setAddModelPopoverOpen] = useState( + null, + ); + const [editModelPopoverOpen, setEditModelPopoverOpen] = useState< + string | null + >(null); + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState( + null, + ); + + // Form states + const [isSubmitting, setIsSubmitting] = useState(false); + const [isTesting, setIsTesting] = useState(false); + const [testResult, setTestResult] = useState(null); + const [requesterNameList, setRequesterNameList] = useState< { label: string; value: string }[] >([]); + // Track if providers have been loaded initially + const [providersLoaded, setProvidersLoaded] = useState(false); + + // Separate LangBot Models provider + const langbotProvider = providers.find( + (p) => p.requester === LANGBOT_MODELS_PROVIDER_REQUESTER, + ); + const otherProviders = providers.filter( + (p) => p.requester !== LANGBOT_MODELS_PROVIDER_REQUESTER, + ); + useEffect(() => { if (open) { loadUserInfo(); @@ -99,6 +109,18 @@ export default function ModelsDialog({ } }, [open]); + // Auto-expand LangBot Models when no external providers exist + useEffect(() => { + if (providersLoaded && langbotProvider && otherProviders.length === 0) { + if (!expandedProviders.has(langbotProvider.uuid)) { + setExpandedProviders(new Set([langbotProvider.uuid])); + if (!providerModels[langbotProvider.uuid]) { + loadProviderModels(langbotProvider.uuid); + } + } + } + }, [providersLoaded, providers]); + async function loadUserInfo() { try { const userInfo = await httpClient.getUserInfo(); @@ -130,16 +152,19 @@ export default function ModelsDialog({ try { const resp = await httpClient.getModelProviders(); setProviders(resp.providers); + setProvidersLoaded(true); } catch (err) { console.error('Failed to load providers', err); toast.error(t('models.loadError')); } } - async function loadProviderModels(providerUuid: string) { + async function loadProviderModels(providerUuid: string, silent = false) { if (loadingProviders.has(providerUuid)) return; - setLoadingProviders((prev) => new Set(prev).add(providerUuid)); + if (!silent) { + setLoadingProviders((prev) => new Set(prev).add(providerUuid)); + } try { const [llmResp, embeddingResp] = await Promise.all([ httpClient.getProviderLLMModels(providerUuid), @@ -155,11 +180,13 @@ export default function ModelsDialog({ } catch (err) { console.error('Failed to load models', err); } finally { - setLoadingProviders((prev) => { - const next = new Set(prev); - next.delete(providerUuid); - return next; - }); + if (!silent) { + setLoadingProviders((prev) => { + const next = new Set(prev); + next.delete(providerUuid); + return next; + }); + } } } @@ -178,24 +205,9 @@ export default function ModelsDialog({ }); } - function handleCreateLLM() { - setEditingLLMId(null); - setLLMFormOpen(true); - } - - function handleCreateEmbedding() { - setEditingEmbeddingId(null); - setEmbeddingFormOpen(true); - } - - function handleEditLLM(modelId: string) { - setEditingLLMId(modelId); - setLLMFormOpen(true); - } - - function handleEditEmbedding(modelId: string) { - setEditingEmbeddingId(modelId); - setEmbeddingFormOpen(true); + function handleCreateProvider() { + setEditingProviderId(null); + setProviderFormOpen(true); } function handleEditProvider(providerId: string) { @@ -213,315 +225,225 @@ export default function ModelsDialog({ } } - async function handleDeleteLLM(modelId: string, providerUuid: string) { + function handleSpaceLogin() { + window.location.href = '/auth/space'; + } + + async function handleAddModel( + providerUuid: string, + modelType: ModelType, + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) { + if (!name.trim()) { + toast.error(t('models.modelNameRequired')); + return; + } + setIsSubmitting(true); try { - await httpClient.deleteProviderLLMModel(modelId); - toast.success(t('models.deleteSuccess')); - loadProviderModels(providerUuid); - loadProviders(); // Refresh counts + const extraArgsObj = convertExtraArgsToObject(extraArgs); + + if (modelType === 'llm') { + await httpClient.createProviderLLMModel({ + name, + provider_uuid: providerUuid, + abilities, + extra_args: extraArgsObj, + } as never); + } else { + await httpClient.createProviderEmbeddingModel({ + name, + provider_uuid: providerUuid, + extra_args: extraArgsObj, + } as never); + } + setAddModelPopoverOpen(null); + loadProviderModels(providerUuid, true); + loadProviders(); } catch (err) { - toast.error(t('models.deleteError') + (err as Error).message); + toast.error(t('models.createError') + (err as Error).message); + } finally { + setIsSubmitting(false); } } - async function handleDeleteEmbedding(modelId: string, providerUuid: string) { + async function handleUpdateModel( + providerUuid: string, + modelId: string, + modelType: ModelType, + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) { + if (!name.trim()) { + toast.error(t('models.modelNameRequired')); + return; + } + setIsSubmitting(true); try { - await httpClient.deleteProviderEmbeddingModel(modelId); + const extraArgsObj = convertExtraArgsToObject(extraArgs); + + if (modelType === 'llm') { + await httpClient.updateProviderLLMModel(modelId, { + name, + provider_uuid: providerUuid, + abilities, + extra_args: extraArgsObj, + } as never); + } else { + await httpClient.updateProviderEmbeddingModel(modelId, { + name, + provider_uuid: providerUuid, + extra_args: extraArgsObj, + } as never); + } + setEditModelPopoverOpen(null); + loadProviderModels(providerUuid, true); + loadProviders(); + } catch (err) { + toast.error(t('models.saveError') + (err as Error).message); + } finally { + setIsSubmitting(false); + } + } + + async function handleDeleteModel( + providerUuid: string, + modelId: string, + modelType: ModelType, + ) { + try { + if (modelType === 'llm') { + await httpClient.deleteProviderLLMModel(modelId); + } else { + await httpClient.deleteProviderEmbeddingModel(modelId); + } toast.success(t('models.deleteSuccess')); - loadProviderModels(providerUuid); + loadProviderModels(providerUuid, true); loadProviders(); } catch (err) { toast.error(t('models.deleteError') + (err as Error).message); } } - function handleSpaceLogin() { - window.location.href = '/auth/space'; + async function handleTestModel( + providerUuid: string, + name: string, + modelType: ModelType, + abilities: string[], + extraArgs: ExtraArg[], + ) { + setIsTesting(true); + setTestResult(null); + const startTime = Date.now(); + try { + const extraArgsObj = convertExtraArgsToObject(extraArgs); + + // Get the provider info + const provider = providers.find((p) => p.uuid === providerUuid); + const providerData = { + requester: provider?.requester || '', + base_url: provider?.base_url || '', + api_keys: provider?.api_keys || [], + }; + + if (modelType === 'llm') { + await httpClient.testLLMModel('_', { + uuid: '', + name, + provider_uuid: '', + provider: providerData, + abilities, + extra_args: extraArgsObj, + } as never); + } else { + await httpClient.testEmbeddingModel('_', { + uuid: '', + name, + provider_uuid: '', + provider: providerData, + extra_args: extraArgsObj, + } as never); + } + const duration = Date.now() - startTime; + setTestResult({ success: true, duration }); + } catch (err) { + toast.error(t('models.testError') + ': ' + (err as Error).message); + setTestResult(null); + } finally { + setIsTesting(false); + } } - function getRequesterLabel(requester: string) { - return ( - requesterNameList.find((r) => r.value === requester)?.label || requester - ); + function handleFormClose() { + setProviderFormOpen(false); + loadProviders(); + // Refresh expanded providers + expandedProviders.forEach((uuid) => loadProviderModels(uuid)); } - function maskApiKey(key: string): string { - if (!key) return ''; - if (key.length <= 8) return '****'; - return `${key.slice(0, 4)}...${key.slice(-4)}`; - } - - // Separate LangBot Models provider - const langbotProvider = providers.find( - (p) => p.requester === LANGBOT_MODELS_PROVIDER_REQUESTER, - ); - const otherProviders = providers.filter( - (p) => p.requester !== LANGBOT_MODELS_PROVIDER_REQUESTER, - ); - function renderProviderCard( provider: ModelProvider, isLangBotModels: boolean = false, ) { - const isExpanded = expandedProviders.has(provider.uuid); - const isLoading = loadingProviders.has(provider.uuid); - const models = providerModels[provider.uuid]; - const canDelete = - !isLangBotModels && - (provider.llm_count || 0) === 0 && - (provider.embedding_count || 0) === 0; - const totalModels = - (provider.llm_count || 0) + (provider.embedding_count || 0); - return ( - - toggleProvider(provider.uuid)} - > - -
-
- {isLangBotModels ? ( -
- LangBot -
- ) : ( - {provider.name} - )} -
-
- - {isLangBotModels - ? provider.name - : getRequesterLabel(provider.requester)} - - - {t('models.modelsCount', { count: totalModels })} - -
-

- {isLangBotModels ? ( - t('models.langbotModelsDescription') - ) : ( - <> - {provider.base_url} - {provider.base_url && - provider.api_keys?.length > 0 && - ' · '} - {provider.api_keys?.length > 0 && - maskApiKey(provider.api_keys[0])} - - )} -

-
-
-
- {isLangBotModels && accountType !== 'space' && ( - - )} - {isLangBotModels && - accountType === 'space' && - spaceCredits !== null && ( -
- - {(spaceCredits / 5000).toFixed(2)} {t('models.credits')} - - -
- )} - {!isLangBotModels && ( - <> - - {canDelete && ( - - )} - - )} -
-
- - {isExpanded ? ( - - ) : ( - - )} - - {isExpanded - ? t('models.collapseModels') - : t('models.expandModels')} - - -
- - - {isLoading ? ( -

- {t('common.loading')}... -

- ) : models ? ( -
- {models.llm.map((model) => ( -
handleEditLLM(model.uuid)} - > -
- - {model.name} - - - {t('models.chat')} - - {model.abilities?.includes('vision') && ( - - - - )} - {model.abilities?.includes('func_call') && ( - - - - )} -
- -
- ))} - {models.embedding.map((model) => ( -
handleEditEmbedding(model.uuid)} - > -
- - {model.name} - - - {t('models.embedding')} - -
- -
- ))} - {models.llm.length === 0 && models.embedding.length === 0 && ( -

- {t('models.noModels')} -

- )} -
- ) : ( -

- {t('models.noModels')} -

- )} -
-
-
-
+ toggleProvider(provider.uuid)} + onEditProvider={() => handleEditProvider(provider.uuid)} + onDeleteProvider={() => handleDeleteProvider(provider.uuid)} + onSpaceLogin={handleSpaceLogin} + onOpenAddModel={() => setAddModelPopoverOpen(provider.uuid)} + onCloseAddModel={() => setAddModelPopoverOpen(null)} + onAddModel={(modelType, name, abilities, extraArgs) => + handleAddModel(provider.uuid, modelType, name, abilities, extraArgs) + } + onOpenEditModel={(modelId) => setEditModelPopoverOpen(modelId)} + onCloseEditModel={() => setEditModelPopoverOpen(null)} + onUpdateModel={(modelId, modelType, name, abilities, extraArgs) => + handleUpdateModel( + provider.uuid, + modelId, + modelType, + name, + abilities, + extraArgs, + ) + } + onOpenDeleteConfirm={(modelId) => setDeleteConfirmOpen(modelId)} + onCloseDeleteConfirm={() => setDeleteConfirmOpen(null)} + onDeleteModel={(modelId, modelType) => + handleDeleteModel(provider.uuid, modelId, modelType) + } + onTestModel={(name, modelType, abilities, extraArgs) => + handleTestModel(provider.uuid, name, modelType, abilities, extraArgs) + } + isSubmitting={isSubmitting} + isTesting={isTesting} + testResult={testResult} + onResetTestResult={() => setTestResult(null)} + /> ); } - // Virtual LangBot Models card if not exists - function renderLangBotModelsCard() { - if (langbotProvider) { - return renderProviderCard(langbotProvider, true); - } - } - - function handleFormClose() { - setLLMFormOpen(false); - setEmbeddingFormOpen(false); - setProviderFormOpen(false); - loadProviders(); - // Refresh expanded providers - expandedProviders.forEach((uuid) => loadProviderModels(uuid)); - } - return ( <> { - if ( - !newOpen && - (llmFormOpen || embeddingFormOpen || providerFormOpen) - ) - return; + if (!newOpen && providerFormOpen) return; onOpenChange(newOpen); }} > @@ -532,84 +454,50 @@ export default function ModelsDialog({
{/* Fixed LangBot Models Card */} -
{renderLangBotModelsCard()}
+
+ {langbotProvider && renderProviderCard(langbotProvider, true)} +
- {/* Add Model Button */} + {/* Add Provider Button */}
- {t('models.providerCount', { count: otherProviders.length })} + {otherProviders.length === 0 + ? t('models.addProviderHint') + : t('models.providerCount', { count: otherProviders.length })} - - - - - - - - {t('models.addLLMModel')} - - - - {t('models.addEmbeddingModel')} - - - +
{/* Scrollable Provider List */}
- {otherProviders.map((p) => renderProviderCard(p))} + {otherProviders.length === 0 ? ( +
+ +

{t('models.noProviders')}

+
+ ) : ( + otherProviders.map((p) => renderProviderCard(p)) + )}
- - - - - {editingLLMId ? t('models.editModel') : t('models.createModel')} - - - setLLMFormOpen(false)} - onLLMDeleted={handleFormClose} - /> - - - - - - - - {editingEmbeddingId - ? t('embedding.editModel') - : t('embedding.createModel')} - - - setEmbeddingFormOpen(false)} - onEmbeddingDeleted={handleFormClose} - /> - - - - {t('models.editProvider')} + + {editingProviderId + ? t('models.editProvider') + : t('models.addProvider')} + -
- icon - -
- {/* 名称 */} -
- {cardVO.name} -
- {/* 厂商 */} -
- - - - - {cardVO.providerLabel} - -
- {/* baseURL */} - {cardVO.baseURL && ( -
- - - - {cardVO.baseURL} -
- )} -
-
-
- ); -} diff --git a/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCardVO.ts b/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCardVO.ts deleted file mode 100644 index f6d960f6..00000000 --- a/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCardVO.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface IEmbeddingCardVO { - id: string; - iconURL: string; - name: string; - providerLabel: string; - baseURL: string; -} - -export class EmbeddingCardVO implements IEmbeddingCardVO { - id: string; - iconURL: string; - providerLabel: string; - name: string; - baseURL: string; - - constructor(props: IEmbeddingCardVO) { - this.id = props.id; - this.iconURL = props.iconURL; - this.providerLabel = props.providerLabel; - this.name = props.name; - this.baseURL = props.baseURL; - } -} diff --git a/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx b/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx deleted file mode 100644 index 3e7d942e..00000000 --- a/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx +++ /dev/null @@ -1,597 +0,0 @@ -import { useEffect, useState } from 'react'; -import { httpClient } from '@/app/infra/http/HttpClient'; -import { ModelProvider } from '@/app/infra/entities/api'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; -import { useTranslation } from 'react-i18next'; - -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogFooter, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { toast } from 'sonner'; -import { extractI18nObject } from '@/i18n/I18nProvider'; -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; -import { AlertCircle } from 'lucide-react'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; - -const getFormSchema = (t: (key: string) => string) => - z.object({ - name: z.string().min(1, { message: t('models.modelNameRequired') }), - provider_uuid: z.string().optional(), - new_provider_requester: z.string().optional(), - new_provider_url: z.string().optional(), - new_provider_api_key: z.string().optional(), - extra_args: z - .array( - z.object({ - key: z.string(), - type: z.enum(['string', 'number', 'boolean']), - value: z.string(), - }), - ) - .optional(), - }); - -interface EmbeddingFormProps { - editMode: boolean; - initEmbeddingId?: string; - providers: ModelProvider[]; - onFormSubmit: () => void; - onFormCancel: () => void; - onEmbeddingDeleted: () => void; -} - -export default function EmbeddingForm({ - editMode, - initEmbeddingId, - providers, - onFormSubmit, - onFormCancel, - onEmbeddingDeleted, -}: EmbeddingFormProps) { - const { t } = useTranslation(); - const formSchema = getFormSchema(t); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - name: '', - provider_uuid: '', - new_provider_requester: '', - new_provider_url: '', - new_provider_api_key: '', - extra_args: [], - }, - }); - - const [extraArgs, setExtraArgs] = useState< - { key: string; type: 'string' | 'number' | 'boolean'; value: string }[] - >([]); - const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false); - const [modelTesting, setModelTesting] = useState(false); - const [testErrorMessage, setTestErrorMessage] = useState(null); - const [providerMode, setProviderMode] = useState<'existing' | 'new'>( - 'existing', - ); - - const [requesterList, setRequesterList] = useState< - { label: string; value: string; category: string; defaultUrl: string }[] - >([]); - - useEffect(() => { - loadRequesters(); - if (editMode && initEmbeddingId) { - loadModel(initEmbeddingId); - } - }, [editMode, initEmbeddingId]); - - async function loadRequesters() { - const resp = await httpClient.getProviderRequesters('text-embedding'); - setRequesterList( - resp.requesters.map((item) => ({ - label: extractI18nObject(item.label), - value: item.name, - category: item.spec.provider_category || 'manufacturer', - defaultUrl: - item.spec.config - .find((c) => c.name === 'base_url') - ?.default?.toString() || '', - })), - ); - } - - async function loadModel(id: string) { - const resp = await httpClient.getProviderEmbeddingModel(id); - const model = resp.model; - - form.setValue('name', model.name); - form.setValue('provider_uuid', model.provider_uuid); - - if (model.extra_args) { - const args = Object.entries(model.extra_args).map(([key, value]) => { - let type: 'string' | 'number' | 'boolean' = 'string'; - if (typeof value === 'number') type = 'number'; - else if (typeof value === 'boolean') type = 'boolean'; - return { key, type, value: String(value) }; - }); - setExtraArgs(args); - form.setValue('extra_args', args); - } - - setProviderMode('existing'); - } - - function handleFormSubmit(values: z.infer) { - const extraArgsObj: Record = {}; - values.extra_args?.forEach((arg) => { - if (arg.type === 'number') extraArgsObj[arg.key] = Number(arg.value); - else if (arg.type === 'boolean') - extraArgsObj[arg.key] = arg.value === 'true'; - else extraArgsObj[arg.key] = arg.value; - }); - - const modelData: Record = { - name: values.name, - extra_args: extraArgsObj, - }; - - if (providerMode === 'existing' && values.provider_uuid) { - modelData.provider_uuid = values.provider_uuid; - } else if (providerMode === 'new') { - modelData.provider = { - requester: values.new_provider_requester, - base_url: values.new_provider_url, - api_keys: values.new_provider_api_key - ? [values.new_provider_api_key] - : [], - }; - } - - if (editMode && initEmbeddingId) { - updateModel(initEmbeddingId, modelData); - } else { - createModel(modelData); - } - } - - async function createModel(data: Record) { - try { - await httpClient.createProviderEmbeddingModel(data as never); - toast.success(t('models.createSuccess')); - onFormSubmit(); - } catch (err) { - toast.error(t('models.createError') + (err as Error).message); - } - } - - async function updateModel(id: string, data: Record) { - try { - await httpClient.updateProviderEmbeddingModel(id, data as never); - toast.success(t('models.saveSuccess')); - onFormSubmit(); - } catch (err) { - toast.error(t('models.saveError') + (err as Error).message); - } - } - - async function deleteModel() { - if (!initEmbeddingId) return; - try { - await httpClient.deleteProviderEmbeddingModel(initEmbeddingId); - toast.success(t('models.deleteSuccess')); - onEmbeddingDeleted(); - } catch (err) { - toast.error(t('models.deleteError') + (err as Error).message); - } - } - - async function testModel() { - setModelTesting(true); - setTestErrorMessage(null); - - const values = form.getValues(); - const extraArgsObj: Record = {}; - values.extra_args?.forEach((arg) => { - if (arg.type === 'number') extraArgsObj[arg.key] = Number(arg.value); - else if (arg.type === 'boolean') - extraArgsObj[arg.key] = arg.value === 'true'; - else extraArgsObj[arg.key] = arg.value; - }); - - let provider: Record; - if (providerMode === 'existing' && values.provider_uuid) { - const p = providers.find((p) => p.uuid === values.provider_uuid); - provider = { - requester: p?.requester || '', - base_url: p?.base_url || '', - api_keys: p?.api_keys || [], - }; - } else { - provider = { - requester: values.new_provider_requester, - base_url: values.new_provider_url, - api_keys: values.new_provider_api_key - ? [values.new_provider_api_key] - : [], - }; - } - - try { - await httpClient.testEmbeddingModel('_', { - uuid: '', - name: values.name, - provider_uuid: '', - provider, - extra_args: extraArgsObj, - } as never); - toast.success(t('models.testSuccess')); - } catch (err) { - setTestErrorMessage((err as Error).message || t('models.testError')); - } finally { - setModelTesting(false); - } - } - - const addExtraArg = () => { - const newArgs = [ - ...extraArgs, - { key: '', type: 'string' as const, value: '' }, - ]; - setExtraArgs(newArgs); - form.setValue('extra_args', newArgs); - }; - - const updateExtraArg = ( - index: number, - field: 'key' | 'type' | 'value', - value: string, - ) => { - const newArgs = [...extraArgs]; - newArgs[index] = { ...newArgs[index], [field]: value }; - setExtraArgs(newArgs); - form.setValue('extra_args', newArgs); - }; - - const removeExtraArg = (index: number) => { - const newArgs = extraArgs.filter((_, i) => i !== index); - setExtraArgs(newArgs); - form.setValue('extra_args', newArgs); - }; - - return ( -
- - - - {t('common.confirmDelete')} - - - {t('models.deleteConfirmation')} - - - - - - - - - - - ( - - - {t('models.modelName')} - * - - - - - - {t('models.modelProviderDescription')} - - - - )} - /> - -
- {t('models.provider')} - setProviderMode(v as 'existing' | 'new')} - className="mt-2" - > - - - {t('models.existingProvider')} - - {t('models.newProvider')} - - - - ( - - - - - )} - /> - - - - ( - - {t('models.requester')} - - - - )} - /> - - ( - - {t('models.requestURL')} - - - - - - )} - /> - - ( - - {t('models.apiKey')} - - - - - - )} - /> - - -
- - - {t('models.extraParameters')} -
- {extraArgs.map((arg, index) => ( -
- - updateExtraArg(index, 'key', e.target.value) - } - /> - - - updateExtraArg(index, 'value', e.target.value) - } - /> - -
- ))} - -
- - {t('embedding.extraParametersDescription')} - -
- - {testErrorMessage && ( - - - {t('models.testError')} - - {testErrorMessage} - - - )} - - - {editMode && ( - - )} - - - - - - -
- ); -} diff --git a/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.module.css b/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.module.css deleted file mode 100644 index c6eed0b7..00000000 --- a/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.module.css +++ /dev/null @@ -1,173 +0,0 @@ -.cardContainer { - width: 100%; - height: 10rem; - background-color: #fff; - border-radius: 10px; - box-shadow: 0px 2px 2px 0 rgba(0, 0, 0, 0.2); - padding: 1.2rem; - cursor: pointer; - transition: all 0.2s ease; -} - -:global(.dark) .cardContainer { - background-color: #1f1f22; - box-shadow: 0; -} - -.cardContainer:hover { - box-shadow: 0px 2px 8px 0 rgba(0, 0, 0, 0.1); -} - -:global(.dark) .cardContainer:hover { - box-shadow: 0; -} - -.iconBasicInfoContainer { - width: 100%; - height: 100%; - display: flex; - flex-direction: row; - gap: 0.8rem; - user-select: none; - /* background-color: aqua; */ -} - -.iconImage { - width: 3.8rem; - height: 3.8rem; - margin: 0.2rem; - border-radius: 8%; -} - -.basicInfoContainer { - display: flex; - flex-direction: column; - gap: 0.2rem; - min-width: 0; - width: 100%; -} - -.basicInfoText { - font-size: 1.4rem; - font-weight: bold; - color: #1a1a1a; -} - -:global(.dark) .basicInfoText { - color: #f0f0f0; -} - -.providerContainer { - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - gap: 0.2rem; -} - -.providerIcon { - width: 1.2rem; - height: 1.2rem; - margin-top: 0.2rem; - color: #626262; -} - -:global(.dark) .providerIcon { - color: #a0a0a0; -} - -.providerLabel { - font-size: 1.2rem; - font-weight: 600; - color: #626262; -} - -:global(.dark) .providerLabel { - color: #a0a0a0; -} - -.baseURLContainer { - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - gap: 0.2rem; - width: calc(100% - 3rem); -} - -.baseURLIcon { - width: 1.2rem; - height: 1.2rem; - color: #626262; -} - -:global(.dark) .baseURLIcon { - color: #a0a0a0; -} - -.baseURLText { - font-size: 1rem; - width: 100%; - color: #626262; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 100%; -} - -:global(.dark) .baseURLText { - color: #a0a0a0; -} - -.abilitiesContainer { - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - gap: 0.4rem; -} - -.abilityBadge { - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - gap: 0.2rem; - height: 1.5rem; - padding: 0.5rem; - border-radius: 0.8rem; - background-color: #66baff80; -} - -:global(.dark) .abilityBadge { - background-color: rgba(34, 136, 238, 0.3); -} - -.abilityIcon { - width: 1rem; - height: 1rem; - color: #2288ee; -} - -:global(.dark) .abilityIcon { - color: #66baff; -} - -.abilityLabel { - font-size: 0.8rem; - font-weight: 400; - color: #2288ee; -} - -:global(.dark) .abilityLabel { - color: #66baff; -} - -.bigText { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: 1.4rem; - font-weight: bold; - max-width: 100%; -} diff --git a/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx b/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx deleted file mode 100644 index 5cca970a..00000000 --- a/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import styles from './LLMCard.module.css'; -import { LLMCardVO } from './LLMCardVO'; -import { useTranslation } from 'react-i18next'; - -function AbilityBadges(abilities: string[]) { - const { t } = useTranslation(); - const abilityBadges = { - vision: ( -
- - - - - {t('models.visionAbility')} - -
- ), - func_call: ( -
- - - - - {t('models.functionCallAbility')} - -
- ), - }; - - return abilities.map((ability) => { - return abilityBadges[ability as keyof typeof abilityBadges]; - }); -} - -export default function LLMCard({ cardVO }: { cardVO: LLMCardVO }) { - return ( -
-
- icon - -
- {/* 名称 */} -
- {cardVO.name} -
- {/* 厂商 */} -
- - - - - {cardVO.providerLabel} - -
- {/* baseURL */} -
- - - - {cardVO.baseURL} -
- {/* 能力 */} -
- {AbilityBadges(cardVO.abilities)} -
-
-
-
- ); -} diff --git a/web/src/app/home/components/models-dialog/component/llm-card/LLMCardVO.ts b/web/src/app/home/components/models-dialog/component/llm-card/LLMCardVO.ts deleted file mode 100644 index 274cede1..00000000 --- a/web/src/app/home/components/models-dialog/component/llm-card/LLMCardVO.ts +++ /dev/null @@ -1,26 +0,0 @@ -export interface ILLMCardVO { - id: string; - iconURL: string; - name: string; - providerLabel: string; - baseURL: string; - abilities: string[]; -} - -export class LLMCardVO implements ILLMCardVO { - id: string; - iconURL: string; - providerLabel: string; - name: string; - baseURL: string; - abilities: string[]; - - constructor(props: ILLMCardVO) { - this.id = props.id; - this.iconURL = props.iconURL; - this.providerLabel = props.providerLabel; - this.name = props.name; - this.baseURL = props.baseURL; - this.abilities = props.abilities; - } -} diff --git a/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx b/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx deleted file mode 100644 index 7d816d3a..00000000 --- a/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx +++ /dev/null @@ -1,655 +0,0 @@ -import { useEffect, useState } from 'react'; -import { httpClient } from '@/app/infra/http/HttpClient'; -import { ModelProvider } from '@/app/infra/entities/api'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; -import { useTranslation } from 'react-i18next'; - -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogFooter, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { Checkbox } from '@/components/ui/checkbox'; -import { toast } from 'sonner'; -import { extractI18nObject } from '@/i18n/I18nProvider'; -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; -import { AlertCircle } from 'lucide-react'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; - -const getFormSchema = (t: (key: string) => string) => - z.object({ - name: z.string().min(1, { message: t('models.modelNameRequired') }), - provider_uuid: z.string().optional(), - // New provider fields - new_provider_requester: z.string().optional(), - new_provider_url: z.string().optional(), - new_provider_api_key: z.string().optional(), - abilities: z.array(z.string()), - extra_args: z - .array( - z.object({ - key: z.string(), - type: z.enum(['string', 'number', 'boolean']), - value: z.string(), - }), - ) - .optional(), - }); - -interface LLMFormProps { - editMode: boolean; - initLLMId?: string; - providers: ModelProvider[]; - onFormSubmit: () => void; - onFormCancel: () => void; - onLLMDeleted: () => void; -} - -export default function LLMForm({ - editMode, - initLLMId, - providers, - onFormSubmit, - onFormCancel, - onLLMDeleted, -}: LLMFormProps) { - const { t } = useTranslation(); - const formSchema = getFormSchema(t); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - name: '', - provider_uuid: '', - new_provider_requester: '', - new_provider_url: '', - new_provider_api_key: '', - abilities: [], - extra_args: [], - }, - }); - - const [extraArgs, setExtraArgs] = useState< - { key: string; type: 'string' | 'number' | 'boolean'; value: string }[] - >([]); - const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false); - const [modelTesting, setModelTesting] = useState(false); - const [testErrorMessage, setTestErrorMessage] = useState(null); - const [providerMode, setProviderMode] = useState<'existing' | 'new'>( - 'existing', - ); - - const [requesterList, setRequesterList] = useState< - { label: string; value: string; category: string; defaultUrl: string }[] - >([]); - - const abilityOptions = [ - { label: t('models.visionAbility'), value: 'vision' }, - { label: t('models.functionCallAbility'), value: 'func_call' }, - ]; - - useEffect(() => { - loadRequesters(); - if (editMode && initLLMId) { - loadModel(initLLMId); - } - }, [editMode, initLLMId]); - - async function loadRequesters() { - const resp = await httpClient.getProviderRequesters('llm'); - setRequesterList( - resp.requesters.map((item) => ({ - label: extractI18nObject(item.label), - value: item.name, - category: item.spec.provider_category || 'manufacturer', - defaultUrl: - item.spec.config - .find((c) => c.name === 'base_url') - ?.default?.toString() || '', - })), - ); - } - - async function loadModel(id: string) { - const resp = await httpClient.getProviderLLMModel(id); - const model = resp.model; - - form.setValue('name', model.name); - form.setValue('provider_uuid', model.provider_uuid); - form.setValue('abilities', model.abilities || []); - - if (model.extra_args) { - const args = Object.entries(model.extra_args).map(([key, value]) => { - let type: 'string' | 'number' | 'boolean' = 'string'; - if (typeof value === 'number') type = 'number'; - else if (typeof value === 'boolean') type = 'boolean'; - return { key, type, value: String(value) }; - }); - setExtraArgs(args); - form.setValue('extra_args', args); - } - - setProviderMode('existing'); - } - - function handleFormSubmit(values: z.infer) { - const extraArgsObj: Record = {}; - values.extra_args?.forEach((arg) => { - if (arg.type === 'number') extraArgsObj[arg.key] = Number(arg.value); - else if (arg.type === 'boolean') - extraArgsObj[arg.key] = arg.value === 'true'; - else extraArgsObj[arg.key] = arg.value; - }); - - const modelData: Record = { - name: values.name, - abilities: values.abilities, - extra_args: extraArgsObj, - }; - - if (providerMode === 'existing' && values.provider_uuid) { - modelData.provider_uuid = values.provider_uuid; - } else if (providerMode === 'new') { - modelData.provider = { - requester: values.new_provider_requester, - base_url: values.new_provider_url, - api_keys: values.new_provider_api_key - ? [values.new_provider_api_key] - : [], - }; - } - - if (editMode && initLLMId) { - updateModel(initLLMId, modelData); - } else { - createModel(modelData); - } - } - - async function createModel(data: Record) { - try { - await httpClient.createProviderLLMModel(data as never); - toast.success(t('models.createSuccess')); - onFormSubmit(); - } catch (err) { - toast.error(t('models.createError') + (err as Error).message); - } - } - - async function updateModel(id: string, data: Record) { - try { - await httpClient.updateProviderLLMModel(id, data as never); - toast.success(t('models.saveSuccess')); - onFormSubmit(); - } catch (err) { - toast.error(t('models.saveError') + (err as Error).message); - } - } - - async function deleteModel() { - if (!initLLMId) return; - try { - await httpClient.deleteProviderLLMModel(initLLMId); - toast.success(t('models.deleteSuccess')); - onLLMDeleted(); - } catch (err) { - toast.error(t('models.deleteError') + (err as Error).message); - } - } - - async function testModel() { - setModelTesting(true); - setTestErrorMessage(null); - - const values = form.getValues(); - const extraArgsObj: Record = {}; - values.extra_args?.forEach((arg) => { - if (arg.type === 'number') extraArgsObj[arg.key] = Number(arg.value); - else if (arg.type === 'boolean') - extraArgsObj[arg.key] = arg.value === 'true'; - else extraArgsObj[arg.key] = arg.value; - }); - - let provider: Record; - if (providerMode === 'existing' && values.provider_uuid) { - const p = providers.find((p) => p.uuid === values.provider_uuid); - provider = { - requester: p?.requester || '', - base_url: p?.base_url || '', - api_keys: p?.api_keys || [], - }; - } else { - provider = { - requester: values.new_provider_requester, - base_url: values.new_provider_url, - api_keys: values.new_provider_api_key - ? [values.new_provider_api_key] - : [], - }; - } - - try { - await httpClient.testLLMModel('_', { - uuid: '', - name: values.name, - provider_uuid: '', - provider, - abilities: values.abilities, - extra_args: extraArgsObj, - } as never); - toast.success(t('models.testSuccess')); - } catch (err) { - setTestErrorMessage((err as Error).message || t('models.testError')); - } finally { - setModelTesting(false); - } - } - - const addExtraArg = () => { - const newArgs = [ - ...extraArgs, - { key: '', type: 'string' as const, value: '' }, - ]; - setExtraArgs(newArgs); - form.setValue('extra_args', newArgs); - }; - - const updateExtraArg = ( - index: number, - field: 'key' | 'type' | 'value', - value: string, - ) => { - const newArgs = [...extraArgs]; - newArgs[index] = { ...newArgs[index], [field]: value }; - setExtraArgs(newArgs); - form.setValue('extra_args', newArgs); - }; - - const removeExtraArg = (index: number) => { - const newArgs = extraArgs.filter((_, i) => i !== index); - setExtraArgs(newArgs); - form.setValue('extra_args', newArgs); - }; - - return ( -
- - - - {t('common.confirmDelete')} - - - {t('models.deleteConfirmation')} - - - - - - - - -
- - ( - - - {t('models.modelName')} - * - - - - - - {t('models.modelProviderDescription')} - - - - )} - /> - -
- {t('models.provider')} - setProviderMode(v as 'existing' | 'new')} - className="mt-2" - > - - - {t('models.existingProvider')} - - {t('models.newProvider')} - - - - ( - - - - - )} - /> - - - - ( - - {t('models.requester')} - - - - )} - /> - - ( - - {t('models.requestURL')} - - - - - - )} - /> - - ( - - {t('models.apiKey')} - - - - - - )} - /> - - -
- - ( - - {t('models.abilities')} - - {t('models.selectModelAbilities')} - - {abilityOptions.map((item) => ( - ( - - - { - if (checked) { - field.onChange([ - ...(field.value || []), - item.value, - ]); - } else { - field.onChange( - field.value?.filter( - (v: string) => v !== item.value, - ), - ); - } - }} - /> - - - {item.label} - - - )} - /> - ))} - - )} - /> - - - {t('models.extraParameters')} -
- {extraArgs.map((arg, index) => ( -
- - updateExtraArg(index, 'key', e.target.value) - } - /> - - - updateExtraArg(index, 'value', e.target.value) - } - /> - -
- ))} - -
- - {t('llm.extraParametersDescription')} - -
- - {testErrorMessage && ( - - - {t('models.testError')} - - {testErrorMessage} - - - )} - - - {editMode && ( - - )} - - - - - - -
- ); -} diff --git a/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx b/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx index 70afb369..022a44db 100644 --- a/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx +++ b/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx @@ -155,7 +155,9 @@ export default function ProviderForm({ onValueChange={(v) => { field.onChange(v); const req = requesterList.find((r) => r.value === v); - if (req && !form.getValues('base_url')) { + // Auto-fill default URL when creating new provider + // or when base_url is empty in edit mode + if (req && (!providerId || !form.getValues('base_url'))) { form.setValue('base_url', req.defaultUrl); } }} diff --git a/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx b/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx new file mode 100644 index 00000000..0afb438c --- /dev/null +++ b/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx @@ -0,0 +1,235 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Plus, MessageSquareText, Cpu, Eye, Wrench, Check } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Checkbox } from '@/components/ui/checkbox'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { useTranslation } from 'react-i18next'; +import { ExtraArg, ModelType, TestResult } from '../types'; +import ExtraArgsEditor from './ExtraArgsEditor'; + +interface AddModelPopoverProps { + providerUuid: string; + isOpen: boolean; + onOpen: () => void; + onClose: () => void; + onAddModel: ( + modelType: ModelType, + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + onTestModel: ( + name: string, + modelType: ModelType, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + isSubmitting: boolean; + isTesting: boolean; + testResult: TestResult | null; + onResetTestResult: () => void; +} + +export default function AddModelPopover({ + providerUuid, + isOpen, + onOpen, + onClose, + onAddModel, + onTestModel, + isSubmitting, + isTesting, + testResult, + onResetTestResult, +}: AddModelPopoverProps) { + const { t } = useTranslation(); + + const [tab, setTab] = useState('llm'); + const [name, setName] = useState(''); + const [abilities, setAbilities] = useState([]); + const [extraArgs, setExtraArgs] = useState([]); + + // Reset form when popover opens + useEffect(() => { + if (isOpen) { + setTab('llm'); + setName(''); + setAbilities([]); + setExtraArgs([]); + onResetTestResult(); + } + }, [isOpen]); + + const handleAdd = async () => { + await onAddModel(tab, name, abilities, extraArgs); + }; + + const handleTest = async () => { + await onTestModel(name, tab, tab === 'llm' ? abilities : [], extraArgs); + }; + + const toggleAbility = (ability: string, checked: boolean) => { + if (checked) { + setAbilities([...abilities, ability]); + } else { + setAbilities(abilities.filter((a) => a !== ability)); + } + }; + + return ( + (open ? onOpen() : onClose())} + > + + + + e.stopPropagation()} + > + setTab(v as ModelType)}> + + + + {t('models.chat')} + + + + {t('models.embedding')} + + + + +
+ + setName(e.target.value)} + /> +
+
+ +
+
+ + toggleAbility('vision', checked as boolean) + } + /> + +
+
+ + toggleAbility('func_call', checked as boolean) + } + /> + +
+
+
+ +
+ + +
+
+ + +
+ + setName(e.target.value)} + /> +
+ +
+ + +
+
+
+
+
+ ); +} diff --git a/web/src/app/home/components/models-dialog/components/ExtraArgsEditor.tsx b/web/src/app/home/components/models-dialog/components/ExtraArgsEditor.tsx new file mode 100644 index 00000000..e17a229e --- /dev/null +++ b/web/src/app/home/components/models-dialog/components/ExtraArgsEditor.tsx @@ -0,0 +1,114 @@ +'use client'; + +import { Plus, X } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { useTranslation } from 'react-i18next'; +import { ExtraArg } from '../types'; + +interface ExtraArgsEditorProps { + args: ExtraArg[]; + onChange: (args: ExtraArg[]) => void; + disabled?: boolean; +} + +export default function ExtraArgsEditor({ + args, + onChange, + disabled = false, +}: ExtraArgsEditorProps) { + const { t } = useTranslation(); + + const handleAdd = () => { + onChange([...args, { key: '', type: 'string', value: '' }]); + }; + + const handleRemove = (index: number) => { + onChange(args.filter((_, i) => i !== index)); + }; + + const handleUpdate = ( + index: number, + field: keyof ExtraArg, + value: string, + ) => { + const newArgs = [...args]; + newArgs[index] = { ...newArgs[index], [field]: value }; + onChange(newArgs); + }; + + return ( +
+
+ + {!disabled && ( + + )} +
+ {args.length === 0 ? ( +

{t('common.none')}

+ ) : ( + args.map((arg, index) => ( +
+ handleUpdate(index, 'key', e.target.value)} + /> + + handleUpdate(index, 'value', e.target.value)} + /> + {!disabled && ( + + )} +
+ )) + )} +
+ ); +} diff --git a/web/src/app/home/components/models-dialog/components/ModelItem.tsx b/web/src/app/home/components/models-dialog/components/ModelItem.tsx new file mode 100644 index 00000000..113e5cff --- /dev/null +++ b/web/src/app/home/components/models-dialog/components/ModelItem.tsx @@ -0,0 +1,294 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Trash2, Eye, Wrench, Check } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { Checkbox } from '@/components/ui/checkbox'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { useTranslation } from 'react-i18next'; +import { LLMModel, EmbeddingModel } from '@/app/infra/entities/api'; +import { ExtraArg, ModelType, TestResult } from '../types'; +import ExtraArgsEditor from './ExtraArgsEditor'; + +interface ModelItemProps { + model: LLMModel | EmbeddingModel; + modelType: ModelType; + providerUuid: string; + isLangBotModels: boolean; + editModelPopoverOpen: string | null; + deleteConfirmOpen: string | null; + onOpenEditModel: (modelId: string) => void; + onCloseEditModel: () => void; + onOpenDeleteConfirm: (modelId: string) => void; + onCloseDeleteConfirm: () => void; + onDeleteModel: () => void; + onUpdateModel: ( + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + onTestModel: ( + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + isSubmitting: boolean; + isTesting: boolean; + testResult: TestResult | null; + onResetTestResult: () => void; +} + +function convertExtraArgsToArray(extraArgs?: object): ExtraArg[] { + if (!extraArgs) return []; + return Object.entries(extraArgs).map(([key, value]) => { + let type: 'string' | 'number' | 'boolean' = 'string'; + if (typeof value === 'number') type = 'number'; + else if (typeof value === 'boolean') type = 'boolean'; + return { key, type, value: String(value) }; + }); +} + +export default function ModelItem({ + model, + modelType, + providerUuid, + isLangBotModels, + editModelPopoverOpen, + deleteConfirmOpen, + onOpenEditModel, + onCloseEditModel, + onOpenDeleteConfirm, + onCloseDeleteConfirm, + onDeleteModel, + onUpdateModel, + onTestModel, + isSubmitting, + isTesting, + testResult, + onResetTestResult, +}: ModelItemProps) { + const { t } = useTranslation(); + + const [editName, setEditName] = useState(model.name); + const [editAbilities, setEditAbilities] = useState( + modelType === 'llm' ? (model as LLMModel).abilities || [] : [], + ); + const [editExtraArgs, setEditExtraArgs] = useState( + convertExtraArgsToArray(model.extra_args), + ); + + const isEditOpen = editModelPopoverOpen === model.uuid; + const isDeleteOpen = deleteConfirmOpen === model.uuid; + + // Reset form when popover opens + useEffect(() => { + if (isEditOpen) { + setEditName(model.name); + setEditAbilities( + modelType === 'llm' ? (model as LLMModel).abilities || [] : [], + ); + setEditExtraArgs(convertExtraArgsToArray(model.extra_args)); + onResetTestResult(); + } + }, [isEditOpen]); + + const handleSave = async () => { + await onUpdateModel(editName, editAbilities, editExtraArgs); + }; + + const handleTest = async () => { + await onTestModel(editName, editAbilities, editExtraArgs); + }; + + const toggleAbility = (ability: string, checked: boolean) => { + if (checked) { + setEditAbilities([...editAbilities, ability]); + } else { + setEditAbilities(editAbilities.filter((a) => a !== ability)); + } + }; + + return ( + { + if (open) { + onOpenEditModel(model.uuid); + } else { + onCloseEditModel(); + } + }} + > + +
+
+ {model.name} + + {modelType === 'llm' ? t('models.chat') : t('models.embedding')} + + {modelType === 'llm' && + (model as LLMModel).abilities?.includes('vision') && ( + + + + )} + {modelType === 'llm' && + (model as LLMModel).abilities?.includes('func_call') && ( + + + + )} +
+ {!isLangBotModels && ( + + open ? onOpenDeleteConfirm(model.uuid) : onCloseDeleteConfirm() + } + > + + + + e.stopPropagation()} + > +
+

{t('models.deleteConfirmation')}

+
+ + +
+
+
+
+ )} +
+
+ +
+
+ + setEditName(e.target.value)} + disabled={isLangBotModels} + /> +
+ + {modelType === 'llm' && ( +
+ +
+
+ + toggleAbility('vision', checked as boolean) + } + /> + +
+
+ + toggleAbility('func_call', checked as boolean) + } + /> + +
+
+
+ )} + + + +
+ {!isLangBotModels && ( + + )} + +
+
+
+
+ ); +} diff --git a/web/src/app/home/components/models-dialog/components/ProviderCard.tsx b/web/src/app/home/components/models-dialog/components/ProviderCard.tsx new file mode 100644 index 00000000..1076c032 --- /dev/null +++ b/web/src/app/home/components/models-dialog/components/ProviderCard.tsx @@ -0,0 +1,371 @@ +'use client'; + +import { + Plus, + ChevronDown, + ChevronRight, + Trash2, + Settings, + LogIn, +} from 'lucide-react'; +import { httpClient, systemInfo } from '@/app/infra/http/HttpClient'; +import { + ModelProvider, + LLMModel, + EmbeddingModel, +} from '@/app/infra/entities/api'; +import { Button } from '@/components/ui/button'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from '@/components/ui/collapsible'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { useTranslation } from 'react-i18next'; +import langbotIcon from '@/app/assets/langbot-logo.webp'; +import { ExtraArg, ModelType, TestResult, ProviderModels } from '../types'; +import ModelItem from './ModelItem'; +import AddModelPopover from './AddModelPopover'; + +interface ProviderCardProps { + provider: ModelProvider; + isLangBotModels?: boolean; + isExpanded: boolean; + isLoading: boolean; + models?: ProviderModels; + accountType: 'local' | 'space'; + spaceCredits: number | null; + requesterNameList: { label: string; value: string }[]; + // Popover states + addModelPopoverOpen: string | null; + editModelPopoverOpen: string | null; + deleteConfirmOpen: string | null; + // Handlers + onToggle: () => void; + onEditProvider: () => void; + onDeleteProvider: () => void; + onSpaceLogin: () => void; + onOpenAddModel: () => void; + onCloseAddModel: () => void; + onAddModel: ( + modelType: ModelType, + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + onOpenEditModel: (modelId: string) => void; + onCloseEditModel: () => void; + onUpdateModel: ( + modelId: string, + modelType: ModelType, + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + onOpenDeleteConfirm: (modelId: string) => void; + onCloseDeleteConfirm: () => void; + onDeleteModel: (modelId: string, modelType: ModelType) => Promise; + onTestModel: ( + name: string, + modelType: ModelType, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + isSubmitting: boolean; + isTesting: boolean; + testResult: TestResult | null; + onResetTestResult: () => void; +} + +function maskApiKey(key: string): string { + if (!key) return ''; + if (key.length <= 8) return '****'; + return `${key.slice(0, 4)}...${key.slice(-4)}`; +} + +export default function ProviderCard({ + provider, + isLangBotModels = false, + isExpanded, + isLoading, + models, + accountType, + spaceCredits, + requesterNameList, + addModelPopoverOpen, + editModelPopoverOpen, + deleteConfirmOpen, + onToggle, + onEditProvider, + onDeleteProvider, + onSpaceLogin, + onOpenAddModel, + onCloseAddModel, + onAddModel, + onOpenEditModel, + onCloseEditModel, + onUpdateModel, + onOpenDeleteConfirm, + onCloseDeleteConfirm, + onDeleteModel, + onTestModel, + isSubmitting, + isTesting, + testResult, + onResetTestResult, +}: ProviderCardProps) { + const { t } = useTranslation(); + + const canDelete = + !isLangBotModels && + (provider.llm_count || 0) === 0 && + (provider.embedding_count || 0) === 0; + const totalModels = + (provider.llm_count || 0) + (provider.embedding_count || 0); + + const getRequesterLabel = (requester: string) => { + return ( + requesterNameList.find((r) => r.value === requester)?.label || requester + ); + }; + + return ( + + + +
+
+ {isLangBotModels ? ( +
+ LangBot +
+ ) : ( + {provider.name} + )} +
+
+ + {isLangBotModels + ? provider.name + : getRequesterLabel(provider.requester)} + + + {t('models.modelsCount', { count: totalModels })} + +
+

+ {isLangBotModels ? ( + t('models.langbotModelsDescription') + ) : ( + <> + {provider.base_url} + {provider.base_url && + provider.api_keys?.length > 0 && + ' · '} + {provider.api_keys?.length > 0 && + maskApiKey(provider.api_keys[0])} + + )} +

+
+
+
+ {isLangBotModels && accountType !== 'space' && ( + + )} + {isLangBotModels && + accountType === 'space' && + spaceCredits !== null && ( +
+ + {(spaceCredits / 5000).toFixed(2)} {t('models.credits')} + + +
+ )} + {!isLangBotModels && ( + <> + + {canDelete && ( + + )} + + )} +
+
+
+ {totalModels > 0 ? ( + + {isExpanded ? ( + + ) : ( + + )} + + {isExpanded + ? t('models.collapseModels') + : t('models.expandModels')} + + + ) : ( +
+ )} + {!isLangBotModels && ( + + )} +
+ + + + {isLoading ? ( +

+ {t('common.loading')}... +

+ ) : models ? ( +
+ {models.llm.map((model) => ( + onDeleteModel(model.uuid, 'llm')} + onUpdateModel={(name, abilities, extraArgs) => + onUpdateModel( + model.uuid, + 'llm', + name, + abilities, + extraArgs, + ) + } + onTestModel={(name, abilities, extraArgs) => + onTestModel(name, 'llm', abilities, extraArgs) + } + isSubmitting={isSubmitting} + isTesting={isTesting} + testResult={testResult} + onResetTestResult={onResetTestResult} + /> + ))} + {models.embedding.map((model) => ( + onDeleteModel(model.uuid, 'embedding')} + onUpdateModel={(name, abilities, extraArgs) => + onUpdateModel( + model.uuid, + 'embedding', + name, + abilities, + extraArgs, + ) + } + onTestModel={(name, abilities, extraArgs) => + onTestModel(name, 'embedding', abilities, extraArgs) + } + isSubmitting={isSubmitting} + isTesting={isTesting} + testResult={testResult} + onResetTestResult={onResetTestResult} + /> + ))} + {models.llm.length === 0 && models.embedding.length === 0 && ( +

+ {t('models.noModels')} +

+ )} +
+ ) : ( +

+ {t('models.noModels')} +

+ )} +
+
+ + + ); +} diff --git a/web/src/app/home/components/models-dialog/components/index.ts b/web/src/app/home/components/models-dialog/components/index.ts new file mode 100644 index 00000000..4e0a41cf --- /dev/null +++ b/web/src/app/home/components/models-dialog/components/index.ts @@ -0,0 +1,4 @@ +export { default as ExtraArgsEditor } from './ExtraArgsEditor'; +export { default as ModelItem } from './ModelItem'; +export { default as AddModelPopover } from './AddModelPopover'; +export { default as ProviderCard } from './ProviderCard'; diff --git a/web/src/app/home/components/models-dialog/types.ts b/web/src/app/home/components/models-dialog/types.ts new file mode 100644 index 00000000..15217269 --- /dev/null +++ b/web/src/app/home/components/models-dialog/types.ts @@ -0,0 +1,102 @@ +import { + LLMModel, + EmbeddingModel, + ModelProvider, +} from '@/app/infra/entities/api'; + +export type ExtraArg = { + key: string; + type: 'string' | 'number' | 'boolean'; + value: string; +}; + +export type ModelType = 'llm' | 'embedding'; + +export interface ProviderModels { + llm: LLMModel[]; + embedding: EmbeddingModel[]; +} + +export interface TestResult { + success: boolean; + duration: number; +} + +export interface ModelItemProps { + model: LLMModel | EmbeddingModel; + modelType: ModelType; + providerUuid: string; + isLangBotModels: boolean; + isEditOpen: boolean; + isDeleteOpen: boolean; + onEditOpen: () => void; + onEditClose: () => void; + onDeleteOpen: () => void; + onDeleteClose: () => void; + onDelete: () => void; + onUpdate: ( + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + onTest: ( + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + isSubmitting: boolean; + isTesting: boolean; + testResult: TestResult | null; +} + +export interface ProviderCardProps { + provider: ModelProvider; + isLangBotModels?: boolean; + isExpanded: boolean; + isLoading: boolean; + models?: ProviderModels; + accountType: 'local' | 'space'; + spaceCredits: number | null; + requesterNameList: { label: string; value: string }[]; + // Popover states + addModelPopoverOpen: string | null; + editModelPopoverOpen: string | null; + deleteConfirmOpen: string | null; + // Handlers + onToggle: () => void; + onEditProvider: () => void; + onDeleteProvider: () => void; + onSpaceLogin: () => void; + onOpenAddModel: () => void; + onCloseAddModel: () => void; + onAddModel: ( + modelType: ModelType, + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + onOpenEditModel: (modelId: string) => void; + onCloseEditModel: () => void; + onUpdateModel: ( + modelId: string, + modelType: ModelType, + name: string, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + onOpenDeleteConfirm: (modelId: string) => void; + onCloseDeleteConfirm: () => void; + onDeleteModel: (modelId: string, modelType: ModelType) => Promise; + onTestModel: ( + name: string, + modelType: ModelType, + abilities: string[], + extraArgs: ExtraArg[], + ) => Promise; + isSubmitting: boolean; + isTesting: boolean; + testResult: TestResult | null; + onResetTestResult: () => void; +} + +export const LANGBOT_MODELS_PROVIDER_REQUESTER = 'space-chat-completions'; diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index ba134975..cc6847fa 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -122,6 +122,7 @@ const enUS = { 'Webhooks allow LangBot to push person and group message events to external systems', actions: 'Actions', apiKeyCreatedMessage: 'Please copy this API key.', + none: 'None', }, notFound: { title: 'Page not found', @@ -206,6 +207,9 @@ const enUS = { loginToUseModels: 'Login with Space to use cloud models', noModels: 'No models configured', editProvider: 'Edit Provider', + addProvider: 'Add Provider', + addProviderHint: 'Add providers to use models from other sources', + noProviders: 'No providers yet', providerName: 'Provider Name', providerNameRequired: 'Provider name is required', requesterRequired: 'Provider type is required', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index d9c18db7..ccf20861 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -124,6 +124,7 @@ const jaJP = { 'Webhook を使用すると、LangBot は個人メッセージとグループメッセージイベントを外部システムにプッシュできます', actions: 'アクション', apiKeyCreatedMessage: 'この API キーをコピーしてください。', + none: 'なし', }, notFound: { title: 'ページが見つかりません', @@ -211,6 +212,10 @@ const jaJP = { loginToUseModels: 'Space でログインしてクラウドモデルを使用', noModels: 'モデルがありません', editProvider: 'プロバイダーを編集', + addProvider: 'プロバイダーを追加', + addProviderHint: + '他のソースのモデルを使用するにはプロバイダーを追加してください', + noProviders: 'プロバイダーがありません', providerName: 'プロバイダー名', providerNameRequired: 'プロバイダー名は必須です', requesterRequired: 'プロバイダータイプは必須です', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index b1355b37..295a95b3 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -115,6 +115,7 @@ const zhHans = { webhookHint: 'Webhook 允许 LangBot 将个人消息和群消息事件推送到外部系统', actions: '操作', apiKeyCreatedMessage: '请复制此 API 密钥。', + none: '无', }, notFound: { title: '页面不存在', @@ -182,7 +183,7 @@ const zhHans = { spaceModelReadOnly: 'Space 模型为只读', noSpaceModels: '暂无 Space 模型。点击同步按钮从 Space 获取模型。', noLocalModels: '暂无本地模型。点击创建按钮添加模型。', - providerCount: '共 {{count}} 个供应商', + providerCount: '共 {{count}} 个自定义供应商', // 供应商结构新增键 addModel: '添加模型', addLLMModel: '添加对话模型', @@ -199,6 +200,9 @@ const zhHans = { loginToUseModels: '通过 Space 登录以使用云端模型', noModels: '暂无模型', editProvider: '编辑供应商', + addProvider: '添加供应商', + addProviderHint: '添加自定义供应商以使用其他来源的模型', + noProviders: '暂无自定义供应商', providerName: '供应商名称', providerNameRequired: '供应商名称不能为空', requesterRequired: '供应商类型不能为空', diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index e0bd4363..da6db88e 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -115,6 +115,7 @@ const zhHant = { webhookHint: 'Webhook 允許 LangBot 將個人訊息和群組訊息事件推送到外部系統', actions: '操作', apiKeyCreatedMessage: '請複製此 API 金鑰。', + none: '無', }, notFound: { title: '頁面不存在', @@ -198,6 +199,9 @@ const zhHant = { loginToUseModels: '使用 Space 登入以使用雲端模型', noModels: '暫無模型', editProvider: '編輯供應商', + addProvider: '新增供應商', + addProviderHint: '新增供應商以使用其他來源的模型', + noProviders: '暫無供應商', providerName: '供應商名稱', providerNameRequired: '供應商名稱不能為空', requesterRequired: '供應商類型不能為空', From 75c2a063cc9b705b161880ac16ec31f0432c2fcd Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 1 Jan 2026 15:07:37 +0800 Subject: [PATCH 22/30] refactor: remove providerUuid prop from model components and enhance provider deletion confirmation UI --- .../components/AddModelPopover.tsx | 2 - .../models-dialog/components/ModelItem.tsx | 2 - .../models-dialog/components/ProviderCard.tsx | 75 ++++++++++++++----- web/src/i18n/locales/en-US.ts | 2 + web/src/i18n/locales/ja-JP.ts | 1 + web/src/i18n/locales/zh-Hans.ts | 1 + web/src/i18n/locales/zh-Hant.ts | 1 + 7 files changed, 62 insertions(+), 22 deletions(-) diff --git a/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx b/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx index 0afb438c..ac64d37d 100644 --- a/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx +++ b/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx @@ -17,7 +17,6 @@ import { ExtraArg, ModelType, TestResult } from '../types'; import ExtraArgsEditor from './ExtraArgsEditor'; interface AddModelPopoverProps { - providerUuid: string; isOpen: boolean; onOpen: () => void; onClose: () => void; @@ -40,7 +39,6 @@ interface AddModelPopoverProps { } export default function AddModelPopover({ - providerUuid, isOpen, onOpen, onClose, diff --git a/web/src/app/home/components/models-dialog/components/ModelItem.tsx b/web/src/app/home/components/models-dialog/components/ModelItem.tsx index 113e5cff..81649195 100644 --- a/web/src/app/home/components/models-dialog/components/ModelItem.tsx +++ b/web/src/app/home/components/models-dialog/components/ModelItem.tsx @@ -20,7 +20,6 @@ import ExtraArgsEditor from './ExtraArgsEditor'; interface ModelItemProps { model: LLMModel | EmbeddingModel; modelType: ModelType; - providerUuid: string; isLangBotModels: boolean; editModelPopoverOpen: string | null; deleteConfirmOpen: string | null; @@ -58,7 +57,6 @@ function convertExtraArgsToArray(extraArgs?: object): ExtraArg[] { export default function ModelItem({ model, modelType, - providerUuid, isLangBotModels, editModelPopoverOpen, deleteConfirmOpen, diff --git a/web/src/app/home/components/models-dialog/components/ProviderCard.tsx b/web/src/app/home/components/models-dialog/components/ProviderCard.tsx index 1076c032..b9bd4f81 100644 --- a/web/src/app/home/components/models-dialog/components/ProviderCard.tsx +++ b/web/src/app/home/components/models-dialog/components/ProviderCard.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useState } from 'react'; import { Plus, ChevronDown, @@ -9,17 +10,18 @@ import { LogIn, } from 'lucide-react'; import { httpClient, systemInfo } from '@/app/infra/http/HttpClient'; -import { - ModelProvider, - LLMModel, - EmbeddingModel, -} from '@/app/infra/entities/api'; +import { ModelProvider } from '@/app/infra/entities/api'; import { Button } from '@/components/ui/button'; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { useTranslation } from 'react-i18next'; @@ -116,6 +118,8 @@ export default function ProviderCard({ onResetTestResult, }: ProviderCardProps) { const { t } = useTranslation(); + const [deleteProviderConfirmOpen, setDeleteProviderConfirmOpen] = + useState(false); const canDelete = !isLangBotModels && @@ -231,17 +235,55 @@ export default function ProviderCard({ {canDelete && ( - + + + + e.stopPropagation()} + > +
+

+ {t('models.deleteProviderConfirmation')} +

+
+ + +
+
+
+ )} )} @@ -266,7 +308,6 @@ export default function ProviderCard({ )} {!isLangBotModels && ( Date: Thu, 1 Jan 2026 15:40:39 +0800 Subject: [PATCH 23/30] feat: add disable_models_service configuration to manage model service availability and update related components --- .../pkg/api/http/controller/groups/system.py | 3 + src/langbot/pkg/provider/modelmgr/modelmgr.py | 6 + src/langbot/templates/config.yaml | 1 + .../dynamic-form/DynamicFormItemComponent.tsx | 11 +- .../components/models-dialog/ModelsDialog.tsx | 37 +-- .../component/provider-form/ProviderForm.tsx | 216 ++++++++++++------ .../models-dialog/components/ProviderCard.tsx | 14 +- .../knowledge/components/kb-form/KBForm.tsx | 11 +- web/src/app/infra/entities/api/index.ts | 1 + web/src/app/infra/http/BackendClient.ts | 2 +- web/src/app/infra/http/index.ts | 1 + web/src/i18n/locales/en-US.ts | 1 + web/src/i18n/locales/ja-JP.ts | 1 + web/src/i18n/locales/zh-Hans.ts | 1 + web/src/i18n/locales/zh-Hant.ts | 1 + 15 files changed, 194 insertions(+), 113 deletions(-) diff --git a/src/langbot/pkg/api/http/controller/groups/system.py b/src/langbot/pkg/api/http/controller/groups/system.py index e5d3bef0..e36707a5 100644 --- a/src/langbot/pkg/api/http/controller/groups/system.py +++ b/src/langbot/pkg/api/http/controller/groups/system.py @@ -22,6 +22,9 @@ class SystemRouterGroup(group.RouterGroup): 'allow_modify_login_info': self.ap.instance_config.data.get('system', {}).get( 'allow_modify_login_info', True ), + 'disable_models_service': self.ap.instance_config.data.get('space', {}).get( + 'disable_models_service', False + ), } ) diff --git a/src/langbot/pkg/provider/modelmgr/modelmgr.py b/src/langbot/pkg/provider/modelmgr/modelmgr.py index 1b0d4a38..b24bff77 100644 --- a/src/langbot/pkg/provider/modelmgr/modelmgr.py +++ b/src/langbot/pkg/provider/modelmgr/modelmgr.py @@ -45,6 +45,12 @@ class ModelManager: await self.load_models_from_db() + # Check if space models service is disabled + space_config = self.ap.instance_config.data.get('space', {}) + if space_config.get('disable_models_service', False): + self.ap.logger.info('LangBot Space Models service is disabled, skipping sync.') + return + try: await self.sync_new_models_from_space() except Exception as e: diff --git a/src/langbot/templates/config.yaml b/src/langbot/templates/config.yaml index 5925a95d..afc70354 100644 --- a/src/langbot/templates/config.yaml +++ b/src/langbot/templates/config.yaml @@ -78,3 +78,4 @@ space: models_gateway_api_url: 'https://api.langbot.cloud' # OAuth authorization page URL (user will be redirected here) oauth_authorize_url: 'https://space.langbot.app/auth/authorize' + disable_models_service: false diff --git a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx index 41586b43..16642e84 100644 --- a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx +++ b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx @@ -17,7 +17,7 @@ import { Switch } from '@/components/ui/switch'; import { ControllerRenderProps } from 'react-hook-form'; import { Button } from '@/components/ui/button'; import { useEffect, useState } from 'react'; -import { httpClient } from '@/app/infra/http/HttpClient'; +import { httpClient, systemInfo } from '@/app/infra/http/HttpClient'; import { LLMModel, Bot, @@ -98,7 +98,14 @@ export default function DynamicFormItemComponent({ httpClient .getProviderLLMModels() .then((resp) => { - setLlmModels(resp.models); + let models = resp.models; + // Filter out space-chat-completions models when models service is disabled + if (systemInfo.disable_models_service) { + models = models.filter( + (m) => m.provider?.requester !== 'space-chat-completions', + ); + } + setLlmModels(models); }) .catch((err) => { toast.error('Failed to get LLM model list: ' + err.message); diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index 2d67dafc..201e2570 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { Plus, Boxes } from 'lucide-react'; -import { httpClient } from '@/app/infra/http/HttpClient'; +import { httpClient, systemInfo } from '@/app/infra/http/HttpClient'; import { ModelProvider } from '@/app/infra/entities/api'; import { Dialog, @@ -13,7 +13,6 @@ import { import { Button } from '@/components/ui/button'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; -import { extractI18nObject } from '@/i18n/I18nProvider'; import ProviderForm from './component/provider-form/ProviderForm'; import { ProviderCard } from './components'; import { @@ -86,17 +85,13 @@ export default function ModelsDialog({ const [isTesting, setIsTesting] = useState(false); const [testResult, setTestResult] = useState(null); - const [requesterNameList, setRequesterNameList] = useState< - { label: string; value: string }[] - >([]); - // Track if providers have been loaded initially const [providersLoaded, setProvidersLoaded] = useState(false); - // Separate LangBot Models provider - const langbotProvider = providers.find( - (p) => p.requester === LANGBOT_MODELS_PROVIDER_REQUESTER, - ); + // Separate LangBot Models provider (hide when models service is disabled) + const langbotProvider = systemInfo.disable_models_service + ? undefined + : providers.find((p) => p.requester === LANGBOT_MODELS_PROVIDER_REQUESTER); const otherProviders = providers.filter( (p) => p.requester !== LANGBOT_MODELS_PROVIDER_REQUESTER, ); @@ -104,7 +99,6 @@ export default function ModelsDialog({ useEffect(() => { if (open) { loadUserInfo(); - loadRequesterLists(); loadProviders(); } }, [open]); @@ -134,20 +128,6 @@ export default function ModelsDialog({ } } - async function loadRequesterLists() { - try { - const llmRequesters = await httpClient.getProviderRequesters('llm'); - setRequesterNameList( - llmRequesters.requesters.map((item) => ({ - label: extractI18nObject(item.label), - value: item.name, - })), - ); - } catch (err) { - console.error('Failed to load requester lists', err); - } - } - async function loadProviders() { try { const resp = await httpClient.getModelProviders(); @@ -397,7 +377,6 @@ export default function ModelsDialog({ models={providerModels[provider.uuid]} accountType={accountType} spaceCredits={spaceCredits} - requesterNameList={requesterNameList} addModelPopoverOpen={addModelPopoverOpen} editModelPopoverOpen={editModelPopoverOpen} deleteConfirmOpen={deleteConfirmOpen} @@ -462,7 +441,11 @@ export default function ModelsDialog({
{otherProviders.length === 0 - ? t('models.addProviderHint') + ? t( + systemInfo.disable_models_service + ? 'models.addProviderHintSimple' + : 'models.addProviderHint', + ) : t('models.providerCount', { count: otherProviders.length })}
diff --git a/README_FR.md b/README_FR.md index 965029ef..5624c143 100644 --- a/README_FR.md +++ b/README_FR.md @@ -17,9 +17,11 @@ python Accueil | +FonctionnalitésDéploiement | -Plugin | -Soumettre un Plugin +Intégration API | +Marché des Plugins | +Feuille de Route
diff --git a/README_JP.md b/README_JP.md index 6bb4d04a..bf02627d 100644 --- a/README_JP.md +++ b/README_JP.md @@ -17,9 +17,11 @@ python ホーム | -デプロイ | -プラグイン | -プラグインの提出 +機能仕様 | +デプロイ | +API統合 | +プラグインマーケット | +ロードマップ
diff --git a/README_KO.md b/README_KO.md index c31f9405..541d0bf4 100644 --- a/README_KO.md +++ b/README_KO.md @@ -17,9 +17,11 @@ python | +기능 사양배포 | -플러그인 | -플러그인 제출 +API 통합 | +플러그인 마켓 | +로드맵
diff --git a/README_RU.md b/README_RU.md index 02b4f4d3..3bf68a44 100644 --- a/README_RU.md +++ b/README_RU.md @@ -17,9 +17,11 @@ python Главная | +ХарактеристикиРазвертывание | -Плагин | -Отправить плагин +Интеграция API | +Магазин плагинов | +Дорожная карта
diff --git a/README_TW.md b/README_TW.md index 9609d0de..f6b2c6bd 100644 --- a/README_TW.md +++ b/README_TW.md @@ -17,9 +17,11 @@ [![star](https://gitcode.com/RockChinQ/LangBot/star/badge.svg)](https://gitcode.com/RockChinQ/LangBot) 主頁 | +規格特性部署文件 | -外掛介紹 | -提交外掛 +API 整合 | +外掛市場 | +路線圖
diff --git a/README_VI.md b/README_VI.md index 6dcb80c1..1c26b393 100644 --- a/README_VI.md +++ b/README_VI.md @@ -17,9 +17,11 @@ python Trang chủ | +Tính năngTriển khai | -Plugin | -Gửi Plugin +Tích hợp API | +Chợ Plugin | +Lộ trình
From 914f77ff3714f28e49b5c6527c27037e1162d0e0 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 3 Jan 2026 00:56:25 +0800 Subject: [PATCH 27/30] refactor: standardize error handling across components by utilizing CustomApiError for improved error messaging --- .../home/bots/components/bot-form/BotForm.tsx | 11 +- web/src/app/home/bots/page.tsx | 6 +- .../dynamic-form/DynamicFormItemComponent.tsx | 6 +- .../components/models-dialog/ModelsDialog.tsx | 4 +- .../component/provider-form/ProviderForm.tsx | 3 +- .../external-kb-form/ExternalKBForm.tsx | 6 +- .../pipeline-form/PipelineFormComponent.tsx | 8 +- .../plugin-sort/PluginSortDialog.tsx | 214 ------------------ .../mcp-server/mcp-card/MCPCardComponent.tsx | 4 +- .../mcp-server/mcp-form/MCPFormDialog.tsx | 4 +- web/src/app/home/plugins/page.tsx | 4 +- web/src/app/infra/entities/common.ts | 4 + web/src/app/infra/http/BaseHttpClient.ts | 2 +- web/src/app/register/page.tsx | 3 +- 14 files changed, 38 insertions(+), 241 deletions(-) delete mode 100644 web/src/app/home/plugins/components/plugin-sort/PluginSortDialog.tsx diff --git a/web/src/app/home/bots/components/bot-form/BotForm.tsx b/web/src/app/home/bots/components/bot-form/BotForm.tsx index 5e030021..2c76c5fc 100644 --- a/web/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web/src/app/home/bots/components/bot-form/BotForm.tsx @@ -49,6 +49,7 @@ import { } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { extractI18nObject } from '@/i18n/I18nProvider'; +import { CustomApiError } from '@/app/infra/entities/common'; const getFormSchema = (t: (key: string) => string) => z.object({ @@ -241,7 +242,9 @@ export default function BotForm({ } }) .catch((err) => { - toast.error(t('bots.getBotConfigError') + err.message); + toast.error( + t('bots.getBotConfigError') + (err as CustomApiError).msg, + ); }); } else { form.reset(); @@ -384,7 +387,7 @@ export default function BotForm({ toast.success(t('bots.saveSuccess')); }) .catch((err) => { - toast.error(t('bots.saveError') + err.message); + toast.error(t('bots.saveError') + err.msg); }) .finally(() => { setIsLoading(false); @@ -410,7 +413,7 @@ export default function BotForm({ onNewBotCreated(res.uuid); }) .catch((err) => { - toast.error(t('bots.createError') + err.message); + toast.error(t('bots.createError') + err.msg); }) .finally(() => { setIsLoading(false); @@ -429,7 +432,7 @@ export default function BotForm({ toast.success(t('bots.deleteSuccess')); }) .catch((err) => { - toast.error(t('bots.deleteError') + err.message); + toast.error(t('bots.deleteError') + err.msg); }); } } diff --git a/web/src/app/home/bots/page.tsx b/web/src/app/home/bots/page.tsx index b3c0dd25..1550af53 100644 --- a/web/src/app/home/bots/page.tsx +++ b/web/src/app/home/bots/page.tsx @@ -11,6 +11,7 @@ import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; import { extractI18nObject } from '@/i18n/I18nProvider'; import BotDetailDialog from '@/app/home/bots/BotDetailDialog'; +import { CustomApiError } from '@/app/infra/entities/common'; export default function BotConfigPage() { const { t } = useTranslation(); @@ -54,10 +55,7 @@ export default function BotConfigPage() { }) .catch((err) => { console.error('get bot list error', err); - toast.error(t('bots.getBotListError') + err.message); - }) - .finally(() => { - // setIsLoading(false); + toast.error(t('bots.getBotListError') + (err as CustomApiError).msg); }); } diff --git a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx index 16642e84..a1285f12 100644 --- a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx +++ b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx @@ -108,7 +108,7 @@ export default function DynamicFormItemComponent({ setLlmModels(models); }) .catch((err) => { - toast.error('Failed to get LLM model list: ' + err.message); + toast.error('Failed to get LLM model list: ' + err.msg); }); } }, [config.type]); @@ -124,7 +124,7 @@ export default function DynamicFormItemComponent({ setKnowledgeBases(resp.bases); }) .catch((err) => { - toast.error('Failed to get knowledge base list: ' + err.message); + toast.error('Failed to get knowledge base list: ' + err.msg); }); // Fetch plugin system status @@ -165,7 +165,7 @@ export default function DynamicFormItemComponent({ setBots(resp.bots); }) .catch((err) => { - toast.error('Failed to get bot list: ' + err.message); + toast.error('Failed to get bot list: ' + err.msg); }); } }, [config.type]); diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index 201e2570..9f030cd2 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -22,6 +22,7 @@ import { ProviderModels, LANGBOT_MODELS_PROVIDER_REQUESTER, } from './types'; +import { CustomApiError } from '@/app/infra/entities/common'; interface ModelsDialogProps { open: boolean; @@ -349,7 +350,8 @@ export default function ModelsDialog({ const duration = Date.now() - startTime; setTestResult({ success: true, duration }); } catch (err) { - toast.error(t('models.testError') + ': ' + (err as Error).message); + console.error('Failed to test model', err); + toast.error(t('models.testError') + ': ' + (err as CustomApiError).msg); setTestResult(null); } finally { setIsTesting(false); diff --git a/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx b/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx index 209f23cc..94e4b0f9 100644 --- a/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx +++ b/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx @@ -28,6 +28,7 @@ import { import { DialogFooter } from '@/components/ui/dialog'; import { toast } from 'sonner'; import { extractI18nObject } from '@/i18n/I18nProvider'; +import { CustomApiError } from '@/app/infra/entities/common'; const getFormSchema = (t: (key: string) => string) => z.object({ @@ -124,7 +125,7 @@ export default function ProviderForm({ } onFormSubmit(); } catch (err) { - toast.error(t('models.providerSaveError') + (err as Error).message); + toast.error(t('models.providerSaveError') + (err as CustomApiError).msg); } } diff --git a/web/src/app/home/knowledge/components/external-kb-form/ExternalKBForm.tsx b/web/src/app/home/knowledge/components/external-kb-form/ExternalKBForm.tsx index f371f860..e5bf9d24 100644 --- a/web/src/app/home/knowledge/components/external-kb-form/ExternalKBForm.tsx +++ b/web/src/app/home/knowledge/components/external-kb-form/ExternalKBForm.tsx @@ -291,7 +291,7 @@ export default function ExternalKBForm({ toast.success(t('knowledge.updateExternalSuccess')); }) .catch((err) => { - toast.error('Failed to update KB: ' + err.message); + toast.error('Failed to update KB: ' + err.msg); }); } else { // Create new KB @@ -303,7 +303,7 @@ export default function ExternalKBForm({ form.reset(); }) .catch((err) => { - toast.error('Failed to create KB: ' + err.message); + toast.error('Failed to create KB: ' + err.msg); }); } } @@ -321,7 +321,7 @@ export default function ExternalKBForm({ toast.success(t('knowledge.deleteExternalSuccess')); }) .catch((err) => { - toast.error('Failed to delete KB: ' + err.message); + toast.error('Failed to delete KB: ' + err.msg); }); } diff --git a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx index 6f86d6ef..65bbc526 100644 --- a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx +++ b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx @@ -181,7 +181,7 @@ export default function PipelineFormComponent({ toast.success(t('pipelines.createSuccess')); }) .catch((err) => { - toast.error(t('pipelines.createError') + err.message); + toast.error(t('pipelines.createError') + err.msg); }); } @@ -211,7 +211,7 @@ export default function PipelineFormComponent({ toast.success(t('pipelines.saveSuccess')); }) .catch((err) => { - toast.error(t('pipelines.saveError') + err.message); + toast.error(t('pipelines.saveError') + err.msg); }); } @@ -340,7 +340,7 @@ export default function PipelineFormComponent({ toast.success(t('pipelines.deleteSuccess')); }) .catch((err) => { - toast.error(t('pipelines.deleteError') + err.message); + toast.error(t('pipelines.deleteError') + err.msg); }); } }; @@ -360,7 +360,7 @@ export default function PipelineFormComponent({ onCancel(); }) .catch((err) => { - toast.error(t('pipelines.createError') + err.message); + toast.error(t('pipelines.createError') + err.msg); }); } }; diff --git a/web/src/app/home/plugins/components/plugin-sort/PluginSortDialog.tsx b/web/src/app/home/plugins/components/plugin-sort/PluginSortDialog.tsx deleted file mode 100644 index 1c601e77..00000000 --- a/web/src/app/home/plugins/components/plugin-sort/PluginSortDialog.tsx +++ /dev/null @@ -1,214 +0,0 @@ -// 'use client'; - -// import * as React from 'react'; -// import { useState, useEffect } from 'react'; -// import { PluginCardVO } from '@/app/home/plugins/plugin-installed/PluginCardVO'; -// import { httpClient } from '@/app/infra/http/HttpClient'; -// import { PluginReorderElement } from '@/app/infra/entities/api'; -// import { toast } from 'sonner'; -// import { -// Dialog, -// DialogContent, -// DialogHeader, -// DialogTitle, -// DialogFooter, -// } from '@/components/ui/dialog'; -// import { Button } from '@/components/ui/button'; -// import { -// DndContext, -// closestCenter, -// KeyboardSensor, -// PointerSensor, -// useSensor, -// useSensors, -// DragEndEvent, -// } from '@dnd-kit/core'; -// import { -// arrayMove, -// SortableContext, -// sortableKeyboardCoordinates, -// useSortable, -// verticalListSortingStrategy, -// } from '@dnd-kit/sortable'; -// import { CSS } from '@dnd-kit/utilities'; -// import { useTranslation } from 'react-i18next'; -// import { extractI18nObject } from '@/i18n/I18nProvider'; - -// interface PluginSortDialogProps { -// open: boolean; -// onOpenChange: (open: boolean) => void; -// onSortComplete: () => void; -// } - -// function SortablePluginItem({ plugin }: { plugin: PluginCardVO }) { -// const { attributes, listeners, setNodeRef, transform, transition } = -// useSortable({ -// id: `${plugin.author}-${plugin.name}`, -// }); - -// const style = { -// transform: CSS.Transform.toString(transform), -// transition, -// }; - -// return ( -//
-//
-//
-// {plugin.author} -//
-//
{plugin.name}
-//
-// {plugin.description} -//
-//
-//
-// ); -// } - -// export default function PluginSortDialog({ -// open, -// onOpenChange, -// onSortComplete, -// }: PluginSortDialogProps) { -// const { t } = useTranslation(); -// const [sortedPlugins, setSortedPlugins] = useState([]); -// const [isLoading, setIsLoading] = useState(false); - -// function getPluginList() { -// httpClient.getPlugins().then((value) => { -// setSortedPlugins( -// value.plugins.map((plugin) => { -// return new PluginCardVO({ -// author: plugin.manifest.manifest.metadata.author ?? '', -// description: extractI18nObject( -// plugin.manifest.manifest.metadata.description ?? { -// en_US: '', -// zh_Hans: '', -// }, -// ), -// enabled: plugin.enabled, -// name: plugin.manifest.manifest.metadata.name, -// version: plugin.manifest.manifest.metadata.version ?? '', -// status: plugin.status, -// components: plugin.components, -// install_source: plugin.install_source, -// install_info: plugin.install_info, -// priority: plugin.priority, -// debug: plugin.debug, -// }); -// }), -// ); -// }); -// } - -// useEffect(() => { -// if (open) { -// getPluginList(); -// } -// }, [open]); - -// const sensors = useSensors( -// useSensor(PointerSensor), -// useSensor(KeyboardSensor, { -// coordinateGetter: sortableKeyboardCoordinates, -// }), -// ); - -// function handleDragEnd(event: DragEndEvent) { -// const { active, over } = event; - -// if (over && active.id !== over.id) { -// setSortedPlugins((items) => { -// const oldIndex = items.findIndex( -// (item) => `${item.author}-${item.name}` === active.id, -// ); -// const newIndex = items.findIndex( -// (item) => `${item.author}-${item.name}` === over.id, -// ); - -// const newItems = arrayMove(items, oldIndex, newIndex); - -// return newItems; -// }); -// } -// } - -// function handleSave() { -// setIsLoading(true); - -// const reorderElements: PluginReorderElement[] = sortedPlugins.map( -// (plugin, index) => ({ -// author: plugin.author, -// name: plugin.name, -// priority: index, -// }), -// ); - -// httpClient -// .reorderPlugins(reorderElements) -// .then(() => { -// toast.success(t('plugins.pluginSortSuccess')); -// onSortComplete(); -// onOpenChange(false); -// }) -// .catch((err) => { -// toast.error(t('plugins.pluginSortError') + err.message); -// }) -// .finally(() => { -// setIsLoading(false); -// }); -// } - -// return ( -// -// -// -// {t('plugins.pluginSort')} -// -//
-//

-// {t('plugins.pluginSortDescription')} -//

-// -// `${plugin.author}-${plugin.name}`, -// )} -// strategy={verticalListSortingStrategy} -// > -// {sortedPlugins.map((plugin) => ( -// -// ))} -// -// -//
-// -// -// -// -//
-//
-// ); -// } diff --git a/web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx b/web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx index 937da834..2ae43a9c 100644 --- a/web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx +++ b/web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx @@ -41,7 +41,7 @@ export default function MCPCardComponent({ setSwitchEnable(true); }) .catch((err) => { - toast.error(t('mcp.modifyFailed') + err.message); + toast.error(t('mcp.modifyFailed') + err.msg); setSwitchEnable(true); }); } @@ -76,7 +76,7 @@ export default function MCPCardComponent({ }, 1000); }) .catch((err) => { - toast.error(t('mcp.refreshFailed') + err.message); + toast.error(t('mcp.refreshFailed') + err.msg); setTesting(false); }); } diff --git a/web/src/app/home/plugins/mcp-server/mcp-form/MCPFormDialog.tsx b/web/src/app/home/plugins/mcp-server/mcp-form/MCPFormDialog.tsx index 2a0bf35a..72901d87 100644 --- a/web/src/app/home/plugins/mcp-server/mcp-form/MCPFormDialog.tsx +++ b/web/src/app/home/plugins/mcp-server/mcp-form/MCPFormDialog.tsx @@ -44,6 +44,7 @@ import { MCPServer, MCPSessionStatus, } from '@/app/infra/entities/api'; +import { CustomApiError } from '@/app/infra/entities/common'; // Status Display Component - 在测试中、连接中或连接失败时使用 function StatusDisplay({ @@ -409,7 +410,8 @@ export default function MCPFormDialog({ } catch (err) { clearInterval(interval); setMcpTesting(false); - const errorMsg = (err as Error).message || t('mcp.getTaskFailed'); + const errorMsg = + (err as CustomApiError).msg || t('mcp.getTaskFailed'); toast.error(`${t('mcp.testError')}: ${errorMsg}`); } }, 1000); diff --git a/web/src/app/home/plugins/page.tsx b/web/src/app/home/plugins/page.tsx index 822364d6..594f3829 100644 --- a/web/src/app/home/plugins/page.tsx +++ b/web/src/app/home/plugins/page.tsx @@ -282,7 +282,7 @@ export default function PluginConfigPage() { watchTask(taskId); }) .catch((err) => { - setInstallError(err.message); + setInstallError(err.msg); setPluginInstallStatus(PluginInstallStatus.ERROR); }); } else if (installSource === 'local') { @@ -293,7 +293,7 @@ export default function PluginConfigPage() { watchTask(taskId); }) .catch((err) => { - setInstallError(err.message); + setInstallError(err.msg); setPluginInstallStatus(PluginInstallStatus.ERROR); }); } else if (installSource === 'marketplace') { diff --git a/web/src/app/infra/entities/common.ts b/web/src/app/infra/entities/common.ts index 64331738..729aa77a 100644 --- a/web/src/app/infra/entities/common.ts +++ b/web/src/app/infra/entities/common.ts @@ -19,3 +19,7 @@ export interface ComponentManifest { }; spec: Record; // eslint-disable-line @typescript-eslint/no-explicit-any } + +export interface CustomApiError { + msg?: string; +} diff --git a/web/src/app/infra/http/BaseHttpClient.ts b/web/src/app/infra/http/BaseHttpClient.ts index 1198c647..c90e95d6 100644 --- a/web/src/app/infra/http/BaseHttpClient.ts +++ b/web/src/app/infra/http/BaseHttpClient.ts @@ -147,7 +147,7 @@ export abstract class BaseHttpClient { // 错误处理 protected handleError(error: object): never { if (axios.isCancel(error)) { - throw { code: -2, message: 'Request canceled', data: null }; + throw { code: -2, msg: 'Request canceled', data: null }; } throw error; } diff --git a/web/src/app/register/page.tsx b/web/src/app/register/page.tsx index 5ec3b249..3932e509 100644 --- a/web/src/app/register/page.tsx +++ b/web/src/app/register/page.tsx @@ -28,6 +28,7 @@ import langbotIcon from '@/app/assets/langbot-logo.webp'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; import { ThemeToggle } from '@/components/ui/theme-toggle'; +import { CustomApiError } from '@/app/infra/entities/common'; const formSchema = (t: (key: string) => string) => z.object({ @@ -75,7 +76,7 @@ export default function Register() { router.push('/login'); }) .catch((err: Error) => { - toast.error(t('register.initFailed') + err.message); + toast.error(t('register.initFailed') + (err as CustomApiError).msg); }); } From b295416e6c9dedb24f713e3b23c847b835cfc14d Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 3 Jan 2026 01:06:17 +0800 Subject: [PATCH 28/30] fix: adjust ModelsDialog component to set a maximum width for better layout consistency --- web/src/app/home/components/models-dialog/ModelsDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index 9f030cd2..c860c9a8 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -428,7 +428,7 @@ export default function ModelsDialog({ onOpenChange(newOpen); }} > - + {t('models.title')} From 840fa39979ba0d6e1f5ffaadd48813018852fae1 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 3 Jan 2026 15:26:24 +0800 Subject: [PATCH 29/30] feat: add informational popover to registration page with tips on using Space for account authentication --- web/src/app/register/page.tsx | 21 +++++++++++++++++++-- web/src/i18n/locales/en-US.ts | 8 +++++++- web/src/i18n/locales/ja-JP.ts | 8 +++++++- web/src/i18n/locales/zh-Hans.ts | 7 ++++++- web/src/i18n/locales/zh-Hant.ts | 7 ++++++- 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/web/src/app/register/page.tsx b/web/src/app/register/page.tsx index 3932e509..a19dfd78 100644 --- a/web/src/app/register/page.tsx +++ b/web/src/app/register/page.tsx @@ -23,7 +23,12 @@ import { import { useEffect, useState } from 'react'; import { httpClient } from '@/app/infra/http/HttpClient'; import { useRouter } from 'next/navigation'; -import { Mail, Lock, Loader2 } from 'lucide-react'; +import { Mail, Lock, Loader2, Info } from 'lucide-react'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; import langbotIcon from '@/app/assets/langbot-logo.webp'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; @@ -165,8 +170,20 @@ export default function Register() { )} {t('register.initWithSpace')} -

+

{t('register.spaceRecommended')} + + + + + +

    +
  • {t('register.spaceInfoTip1')}
  • +
  • {t('register.spaceInfoTip2')}
  • +
  • {t('register.spaceInfoTip3')}
  • +
+ +

diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index cd78389a..84bd0fca 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -713,11 +713,17 @@ const enUS = { title: 'Initialize LangBot 👋', description: 'This is your first time starting LangBot', adminAccountNote: - 'The email and password you fill in will be used as the initial administrator account', + 'The account you use here will be set as the administrator account', register: 'Register', initWithSpace: 'Initialize with Space', spaceRecommended: 'Recommended: Use official stable model APIs and cloud services', + spaceInfoTip1: + 'Space provides unified account authentication services without uploading any of your sensitive information.', + spaceInfoTip2: + 'Logging in with a Space account gives you access to LangBot Models and other cloud services, including free model call credits to help you get started quickly.', + spaceInfoTip3: + 'Your login method does not affect other features. You can configure and use models from other sources at any time.', registerLocal: 'Register local account', registerWithPassword: 'Register with email and password', initSuccess: 'Initialization successful, please login', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 3b40ba48..16f2f386 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -721,11 +721,17 @@ const jaJP = { title: 'LangBot を初期化 👋', description: 'これはLangBotの初回起動です', adminAccountNote: - '入力したメールアドレスとパスワードが初期管理者アカウントになります', + 'ここで初期化されたアカウントは管理者アカウントとして使用されます', register: '登録', initWithSpace: 'Space で初期化', spaceRecommended: 'おすすめ:公式の安定したモデル API とクラウドサービスを利用', + spaceInfoTip1: + 'Space は統一されたアカウント認証サービスを提供し、機密情報をアップロードすることはありません。', + spaceInfoTip2: + 'Space アカウントでログインすると、LangBot Models などのクラウドサービスを利用でき、無料のモデル呼び出しクレジットで迅速に開始できます。', + spaceInfoTip3: + 'ログイン方法は他の機能に影響しません。いつでも他のソースからモデルを設定して使用できます。', registerLocal: 'ローカルアカウントを登録', registerWithPassword: 'メールアドレスとパスワードで登録', initSuccess: '初期化に成功しました。ログインしてください', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index d6a6f029..445ae532 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -684,10 +684,15 @@ const zhHans = { register: { title: '初始化 LangBot 👋', description: '这是您首次启动 LangBot', - adminAccountNote: '您填写的邮箱和密码将作为初始管理员账号', + adminAccountNote: '您在此处初始化使用的账号将作为管理员账号', register: '注册', initWithSpace: '通过 Space 初始化', spaceRecommended: '推荐:使用官方提供的稳定模型 API 和云服务', + spaceInfoTip1: 'Space 提供统一的账户鉴权服务,不会上传您的任何敏感信息。', + spaceInfoTip2: + '使用 Space 账户登录可使用 LangBot Models 等云服务,您将会获得一定的免费模型调用额度帮助您快速起步。', + spaceInfoTip3: + '登录方式不会影响其他功能,您在任何情况下都可以配置使用其他来源的模型。', registerLocal: '注册本地账号', registerWithPassword: '通过邮箱密码组合注册', initSuccess: '初始化成功 请登录', diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index 3b99f99f..b8369fae 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -681,10 +681,15 @@ const zhHant = { register: { title: '初始化 LangBot 👋', description: '這是您首次啟動 LangBot', - adminAccountNote: '您填寫的電子郵件和密碼將作為初始管理員帳號', + adminAccountNote: '您在此處初始化使用的帳號將作為管理員帳號', register: '註冊', initWithSpace: '透過 Space 初始化', spaceRecommended: '推薦:使用官方提供的穩定模型 API 和雲服務', + spaceInfoTip1: 'Space 提供統一的帳戶鑑權服務,不會上傳您的任何敏感資訊。', + spaceInfoTip2: + '使用 Space 帳戶登入可使用 LangBot Models 等雲服務,您將會獲得一定的免費模型調用額度幫助您快速起步。', + spaceInfoTip3: + '登入方式不會影響其他功能,您在任何情況下都可以配置使用其他來源的模型。', registerLocal: '註冊本地帳號', registerWithPassword: '透過電子郵件密碼組合註冊', initSuccess: '初始化成功 請登入', From ba06555078c9797b0ddecaa125b72edcbc90f48f Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 3 Jan 2026 15:43:40 +0800 Subject: [PATCH 30/30] refactor: remove SQLite compatibility check for column cleanup in DB migration script --- .../persistence/migrations/dbm016_model_provider_refactor.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py b/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py index 223f2883..150e5209 100644 --- a/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py +++ b/src/langbot/pkg/persistence/migrations/dbm016_model_provider_refactor.py @@ -251,9 +251,6 @@ class DBMigrateModelProviderRefactor(migration.DBMigration): async def _cleanup_columns(self): """Remove deprecated columns from model tables""" - # SQLite doesn't support DROP COLUMN easily, so we skip for SQLite - if self.ap.persistence_mgr.db.name != 'postgresql': - return llm_columns = await self._get_columns('llm_models') deprecated_llm_cols = ['requester', 'requester_config', 'api_keys', 'description', 'source', 'space_model_id']