From 475c1e38df4a43a81106253415775a7ae2894c6a Mon Sep 17 00:00:00 2001 From: lizhixuan Date: Sun, 18 May 2025 16:08:26 +0800 Subject: [PATCH] feat: refactor store to discover transition and enhance UI components - Updated package.json to include 'usehooks-ts' and upgraded 'lucide-react' to version 0.511.0. - Replaced 'store' with 'discover' in the routing and sidebar components for improved navigation. - Introduced new DiscoverPage and related components for better organization of content. - Enhanced localization support by adding Chinese translations for the discover feature. - Removed deprecated store components to streamline the codebase and improve maintainability. --- package.json | 3 +- src/renderer/src/App.tsx | 6 +- src/renderer/src/components/app/Sidebar.tsx | 4 +- src/renderer/src/i18n/locales/zh-cn.json | 4 +- src/renderer/src/pages/agents/AgentsPage.tsx | 8 +- src/renderer/src/pages/apps/AppsPage.tsx | 8 +- .../discover/components/DiscoverContent.tsx | 46 +++++ .../discover/components/DiscoverSidebar.tsx | 64 +++++++ .../dialog/DialogManagerContext.tsx | 0 .../components/dialog/InstallDialog.tsx | 0 .../components/dialog/index.tsx | 0 .../discover/hooks/useDiscoverCategories.ts | 166 ++++++++++++++++++ .../hooks/useFilteredStoreItems.ts | 0 src/renderer/src/pages/discover/index.tsx | 86 +++++++++ src/renderer/src/pages/discover/types.ts | 7 + .../components/Assistant/AssistantCard.tsx | 39 ---- .../src/pages/store/components/GridView.tsx | 46 ----- .../src/pages/store/components/ListView.tsx | 83 --------- .../store/components/MiniApp/MiniAppCard.tsx | 27 --- .../store/components/MiniApp/logoList.ts | 111 ------------ .../pages/store/components/StoreContent.tsx | 103 ----------- .../pages/store/components/StoreSidebar.tsx | 101 ----------- src/renderer/src/pages/store/data/index.ts | 125 ------------- src/renderer/src/pages/store/index.tsx | 121 ------------- src/renderer/src/store/migrate.ts | 2 +- src/renderer/src/ui/vercel-tabs.tsx | 113 ++++++++++++ yarn.lock | 29 ++- 27 files changed, 521 insertions(+), 781 deletions(-) create mode 100644 src/renderer/src/pages/discover/components/DiscoverContent.tsx create mode 100644 src/renderer/src/pages/discover/components/DiscoverSidebar.tsx rename src/renderer/src/pages/{store => discover}/components/dialog/DialogManagerContext.tsx (100%) rename src/renderer/src/pages/{store => discover}/components/dialog/InstallDialog.tsx (100%) rename src/renderer/src/pages/{store => discover}/components/dialog/index.tsx (100%) create mode 100644 src/renderer/src/pages/discover/hooks/useDiscoverCategories.ts rename src/renderer/src/pages/{store => discover}/hooks/useFilteredStoreItems.ts (100%) create mode 100644 src/renderer/src/pages/discover/index.tsx create mode 100644 src/renderer/src/pages/discover/types.ts delete mode 100644 src/renderer/src/pages/store/components/Assistant/AssistantCard.tsx delete mode 100644 src/renderer/src/pages/store/components/GridView.tsx delete mode 100644 src/renderer/src/pages/store/components/ListView.tsx delete mode 100644 src/renderer/src/pages/store/components/MiniApp/MiniAppCard.tsx delete mode 100644 src/renderer/src/pages/store/components/MiniApp/logoList.ts delete mode 100644 src/renderer/src/pages/store/components/StoreContent.tsx delete mode 100644 src/renderer/src/pages/store/components/StoreSidebar.tsx delete mode 100644 src/renderer/src/pages/store/data/index.ts delete mode 100644 src/renderer/src/pages/store/index.tsx create mode 100644 src/renderer/src/ui/vercel-tabs.tsx diff --git a/package.json b/package.json index c228edc65a..adf70c420f 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,7 @@ "lint-staged": "^15.5.0", "lodash": "^4.17.21", "lru-cache": "^11.1.0", - "lucide-react": "^0.509.0", + "lucide-react": "^0.511.0", "mime": "^4.0.4", "motion": "^12.12.1", "next-themes": "^0.4.6", @@ -224,6 +224,7 @@ "tokenx": "^0.4.1", "tw-animate-css": "^1.2.9", "typescript": "^5.6.2", + "usehooks-ts": "^3.1.1", "uuid": "^10.0.0", "vite": "6.2.6", "vitest": "^3.1.1" diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 258aacad46..171ecb286a 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -12,14 +12,13 @@ import StyleSheetManager from './context/StyleSheetManager' import { SyntaxHighlighterProvider } from './context/SyntaxHighlighterProvider' import { ThemeProvider } from './context/ThemeProvider' import NavigationHandler from './handler/NavigationHandler' -import AgentsPage from './pages/agents/AgentsPage' import AppsPage from './pages/apps/AppsPage' +import DiscoverPage from './pages/discover' import FilesPage from './pages/files/FilesPage' import HomePage from './pages/home/HomePage' import KnowledgePage from './pages/knowledge/KnowledgePage' import PaintingsRoutePage from './pages/paintings/PaintingsRoutePage' import SettingsPage from './pages/settings/SettingsPage' -import StorePage from './pages/store' import TranslatePage from './pages/translate/TranslatePage' function App(): React.ReactElement { @@ -36,14 +35,13 @@ function App(): React.ReactElement { } /> - } /> } /> } /> } /> } /> } /> } /> - } /> + } /> diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index 60fbe2d608..4d945be520 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -145,7 +145,7 @@ const MainMenus: FC = () => { minapp: , knowledge: , files: , - store: + discover: } const pathMap = { @@ -156,7 +156,7 @@ const MainMenus: FC = () => { minapp: '/apps', knowledge: '/knowledge', files: '/files', - store: '/store' + discover: '/discover' } return sidebarIcons.visible.map((icon) => { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 236f2d6b55..00a9c27298 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1546,8 +1546,8 @@ "enable_privacy_mode": "匿名发送错误报告和数据统计" } }, - "store": { - "title": "应用商店", + "discover": { + "title": "发现", "install": "安装", "uninstall": "卸载", "update": "更新", diff --git a/src/renderer/src/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx index b9873c310b..b9cad54ec4 100644 --- a/src/renderer/src/pages/agents/AgentsPage.tsx +++ b/src/renderer/src/pages/agents/AgentsPage.tsx @@ -1,5 +1,4 @@ import { ImportOutlined, PlusOutlined } from '@ant-design/icons' -import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import CustomTag from '@renderer/components/CustomTag' import ListItem from '@renderer/components/ListItem' import Scrollbar from '@renderer/components/Scrollbar' @@ -7,9 +6,8 @@ import { useAgents } from '@renderer/hooks/useAgents' import { createAssistantFromAgent } from '@renderer/services/AssistantService' import { Agent } from '@renderer/types' import { uuid } from '@renderer/utils' -import { Button, Empty, Flex, Input } from 'antd' +import { Button, Empty, Flex } from 'antd' import { omit } from 'lodash' -import { Search } from 'lucide-react' import { FC, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import ReactMarkdown from 'react-markdown' @@ -152,7 +150,7 @@ const AgentsPage: FC = () => { return ( - + {/* {t('agents.title')} { />
- + */}
diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx index e61def4972..3b15b73309 100644 --- a/src/renderer/src/pages/apps/AppsPage.tsx +++ b/src/renderer/src/pages/apps/AppsPage.tsx @@ -1,9 +1,7 @@ -import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Center } from '@renderer/components/Layout' import { useMinapps } from '@renderer/hooks/useMinapps' -import { Empty, Input } from 'antd' +import { Empty } from 'antd' import { isEmpty } from 'lodash' -import { Search } from 'lucide-react' import React, { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -34,7 +32,7 @@ const AppsPage: FC = () => { return ( - + {/* {t('minapp.title')} { />
- + */} {isEmpty(filteredApps) ? (
diff --git a/src/renderer/src/pages/discover/components/DiscoverContent.tsx b/src/renderer/src/pages/discover/components/DiscoverContent.tsx new file mode 100644 index 0000000000..b905e1bc59 --- /dev/null +++ b/src/renderer/src/pages/discover/components/DiscoverContent.tsx @@ -0,0 +1,46 @@ +import { Category } from '@renderer/types/cherryStore' +import React from 'react' +import { Navigate, Route, Routes, useLocation } from 'react-router-dom' + +// 实际的 AgentsPage 组件 - 请确保路径正确 +import AgentsPage from '../../agents/AgentsPage' +import AppsPage from '../../apps/AppsPage' +// import AssistantDetailsPage from '../../agents/AssistantDetailsPage'; // 示例详情页 + +// 其他分类的页面组件 (如果需要) +// const MiniAppPagePlaceholder = ({ categoryId, subcategoryId }: { categoryId?: string; subcategoryId?: string }) => ( +//
+// MiniApp Placeholder for Category: {categoryId || 'N/A'}, Subcategory: {subcategoryId || 'N/A'} +//
+// ) + +export interface DiscoverContentProps { + activeTabId: string // This should be one of the CherryStoreType values, e.g., "Assistant" + // selectedSubcategoryId: string + currentCategory: Category | undefined +} + +const DiscoverContent: React.FC = ({ activeTabId, currentCategory }) => { + const location = useLocation() // To see the current path for debugging or more complex logic + + if (!currentCategory || !activeTabId) { + return
Loading: Category or Tab ID missing...
+ } + + if (!activeTabId && !location.pathname.startsWith('/discover/')) { + return // Fallback redirect, adjust as needed + } + + return ( + + {/* Path for Assistant category */} + } /> + {/* Path for Mini-App category */} + } /> + + Discover Feature Not Found at {location.pathname}
} /> + + ) +} + +export default DiscoverContent diff --git a/src/renderer/src/pages/discover/components/DiscoverSidebar.tsx b/src/renderer/src/pages/discover/components/DiscoverSidebar.tsx new file mode 100644 index 0000000000..aa60d965ef --- /dev/null +++ b/src/renderer/src/pages/discover/components/DiscoverSidebar.tsx @@ -0,0 +1,64 @@ +import { SubCategoryItem } from '@renderer/types/cherryStore' +import { Badge } from '@renderer/ui/badge' +import { + Sidebar, + SidebarContent, + SidebarMenu, + SidebarMenuButton, + SidebarMenuSubItem, + SidebarProvider +} from '@renderer/ui/sidebar' + +import { InternalCategory } from '../hooks/useDiscoverCategories' + +interface DiscoverSidebarProps { + activeCategory: InternalCategory | undefined + selectedSubcategory: string + onSelectSubcategory: (subcategoryId: string, row?: SubCategoryItem) => void +} + +export default function DiscoverSidebar({ + activeCategory, + selectedSubcategory, + onSelectSubcategory +}: DiscoverSidebarProps) { + if (!activeCategory) { + return ( + + +

No active category selected.

+
+
+ ) + } + + return ( + + + + + {activeCategory.items && + activeCategory.items.length > 0 && + activeCategory.items.map((subItem) => ( + + { + onSelectSubcategory(subItem.id, subItem) + }} + size="sm"> + {subItem.name} + {typeof subItem.count === 'number' && ( + + {subItem.count} + + )} + + + ))} + + + + + ) +} diff --git a/src/renderer/src/pages/store/components/dialog/DialogManagerContext.tsx b/src/renderer/src/pages/discover/components/dialog/DialogManagerContext.tsx similarity index 100% rename from src/renderer/src/pages/store/components/dialog/DialogManagerContext.tsx rename to src/renderer/src/pages/discover/components/dialog/DialogManagerContext.tsx diff --git a/src/renderer/src/pages/store/components/dialog/InstallDialog.tsx b/src/renderer/src/pages/discover/components/dialog/InstallDialog.tsx similarity index 100% rename from src/renderer/src/pages/store/components/dialog/InstallDialog.tsx rename to src/renderer/src/pages/discover/components/dialog/InstallDialog.tsx diff --git a/src/renderer/src/pages/store/components/dialog/index.tsx b/src/renderer/src/pages/discover/components/dialog/index.tsx similarity index 100% rename from src/renderer/src/pages/store/components/dialog/index.tsx rename to src/renderer/src/pages/discover/components/dialog/index.tsx diff --git a/src/renderer/src/pages/discover/hooks/useDiscoverCategories.ts b/src/renderer/src/pages/discover/hooks/useDiscoverCategories.ts new file mode 100644 index 0000000000..c926f42fe5 --- /dev/null +++ b/src/renderer/src/pages/discover/hooks/useDiscoverCategories.ts @@ -0,0 +1,166 @@ +import { Category, CherryStoreType } from '@renderer/types/cherryStore' +import { useEffect, useMemo, useState } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' + +// Extended Category type for internal use in hook, including path and sidebar flag +// Export this interface so other files can import it +export interface InternalCategory extends Category { + path: string + hasSidebar?: boolean // Optional: defaults to true if not specified, or handle explicitly +} + +// Initial category data with path and hasSidebar +const initialCategories: InternalCategory[] = [ + { + id: CherryStoreType.ASSISTANT, + title: 'Assistants', + path: 'assistant', + hasSidebar: false, + items: [ + { id: 'all', name: 'All Assistants' }, + { id: 'gpt3', name: 'GPT-3' } + ] + }, + { + id: CherryStoreType.MINI_APP, + title: 'Mini Apps', + path: 'mini-app', + hasSidebar: false, + items: [ + { id: 'all', name: 'All Mini Apps' } + // Add other mini_app subcategories here if any + ] + } + // Add more categories as needed +] + +// Helper to find category by path +const findCategoryByPath = (path: string | undefined): InternalCategory | undefined => + initialCategories.find((cat) => cat.path === path) + +// Helper to find category by id (activeTab) +const findCategoryById = (id: string | undefined): InternalCategory | undefined => + initialCategories.find((cat) => cat.id === id) + +export function useDiscoverCategories() { + const [categories, setCategories] = useState(initialCategories) + const [activeTab, setActiveTab] = useState('') + const [selectedSubcategory, setSelectedSubcategory] = useState('all') + + const navigate = useNavigate() + const location = useLocation() + + // Effect to initialize activeTab from URL path segment or navigate to default + useEffect(() => { + const pathSegments = location.pathname.split('/').filter(Boolean) // e.g., ["discover", "assistant"] + // Expects URL like /discover/:categoryPathSegment/... + const currentCategoryPath = pathSegments.length >= 2 && pathSegments[0] === 'discover' ? pathSegments[1] : undefined + + const categoryFromPath = findCategoryByPath(currentCategoryPath) + + if (categoryFromPath) { + if (activeTab !== categoryFromPath.id) { + setActiveTab(categoryFromPath.id) + } + } else if (location.pathname === '/discover' || location.pathname === '/discover/') { + // If URL is exactly /discover or /discover/ and no specific category path is matched + if (categories.length > 0) { + const firstCategory = categories[0] + if (firstCategory?.path) { + navigate(`/discover/${firstCategory.path}?subcategory=all`, { replace: true }) + } + } + } else if (!currentCategoryPath && categories.length > 0 && !activeTab) { + // Fallback if no category path in URL and no active tab set yet (e.g. initial load to a bad /discover/xxx url) + const firstCategory = categories[0] + if (firstCategory?.path) { + navigate(`/discover/${firstCategory.path}?subcategory=all`, { replace: true }) + } + } + // If categoryFromPath is undefined, and it's not /discover, it means it's an invalid path like /discover/unknown + // In this case, we don't navigate from here; ideally App.tsx has a NotFound route, or DiscoverContent shows a message. + }, [location.pathname, categories, activeTab, navigate]) + + // Effect to initialize selectedSubcategory from URL query param or default to 'all' + useEffect(() => { + const searchParams = new URLSearchParams(location.search) + const subcategoryIdFromQuery = searchParams.get('subcategory') + const currentCatDetails = findCategoryById(activeTab) // Use the helper here + + if (subcategoryIdFromQuery && currentCatDetails) { + // Check if the subcategory from query is valid for the current active category + if (currentCatDetails.items.some((item) => item.id === subcategoryIdFromQuery)) { + if (selectedSubcategory !== subcategoryIdFromQuery) { + setSelectedSubcategory(subcategoryIdFromQuery) + } + return // Valid subcategory from URL is set, no further action needed in this effect iteration + } + } + + // If no valid subcategory in query, or if activeTab has changed and subcategory needs reset/defaulting + if (activeTab && currentCatDetails) { + const defaultSub = currentCatDetails.items.find((item) => item.id === 'all') || currentCatDetails.items[0] + if (defaultSub) { + // Ensure defaultSub exists + // Set selectedSubcategory state first + if (selectedSubcategory !== defaultSub.id) { + setSelectedSubcategory(defaultSub.id) + } + // Then, if URL doesn't match this default, update URL to reflect the default subcategory + // This ensures the URL is the source of truth / always consistent. + if (!subcategoryIdFromQuery || subcategoryIdFromQuery !== defaultSub.id) { + const newSearchParams = new URLSearchParams() // Start with clean params for this path + newSearchParams.set('subcategory', defaultSub.id) + // Ensure we use the current actual path from currentCatDetails if available for navigation + // This avoids issues if location.pathname is briefly out of sync during transitions. + const basePath = currentCatDetails.path + ? `/discover/${currentCatDetails.path}` + : location.pathname.split('?')[0] + navigate(`${basePath}?${newSearchParams.toString()}`, { replace: true }) + } + } + } + }, [activeTab, location.search, categories, navigate, selectedSubcategory]) // location.pathname removed as basePath logic handles path part + + const currentCategory = useMemo(() => { + return findCategoryById(activeTab) // Use the helper here + }, [activeTab]) // categories removed from deps as findCategoryById uses stable initialCategories + + const handleSelectTab = (tabId: string) => { + const categoryToSelect = findCategoryById(tabId) + if (categoryToSelect && categoryToSelect.path && activeTab !== tabId) { + navigate(`/discover/${categoryToSelect.path}?subcategory=all`) + } + } + + const handleSelectSubcategory = (subcategoryId: string) => { + const currentCatDetails = findCategoryById(activeTab) + if (selectedSubcategory !== subcategoryId && currentCatDetails?.path) { + const newSearchParams = new URLSearchParams() + newSearchParams.set('subcategory', subcategoryId) + navigate(`/discover/${currentCatDetails.path}?${newSearchParams.toString()}`, { replace: false }) + } + } + + // Ensure each category has an "All" subcategory (runs once on mount) + useEffect(() => { + setCategories((prev) => + prev.map((cat) => { + if (!cat.items.some((item) => item.id === 'all')) { + return { ...cat, items: [{ id: 'all', name: `All ${cat.title}` }, ...cat.items] } + } + return cat + }) + ) + }, []) + + return { + categories, + activeTab, + selectedSubcategory, + currentCategory, + handleSelectTab, + handleSelectSubcategory, + setActiveTab + } +} diff --git a/src/renderer/src/pages/store/hooks/useFilteredStoreItems.ts b/src/renderer/src/pages/discover/hooks/useFilteredStoreItems.ts similarity index 100% rename from src/renderer/src/pages/store/hooks/useFilteredStoreItems.ts rename to src/renderer/src/pages/discover/hooks/useFilteredStoreItems.ts diff --git a/src/renderer/src/pages/discover/index.tsx b/src/renderer/src/pages/discover/index.tsx new file mode 100644 index 0000000000..44eceeffcc --- /dev/null +++ b/src/renderer/src/pages/discover/index.tsx @@ -0,0 +1,86 @@ +import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' +// import { useRuntime } from '@renderer/hooks/useRuntime' // No longer needed if resourcesPath is not used +import { Tabs as VercelTabs } from '@renderer/ui/vercel-tabs' +import { useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { useParams } from 'react-router-dom' + +// Import Context and the main Dialog Manager component +import { DialogManagerProvider } from './components/dialog/DialogManagerContext' +import Dialogs from './components/dialog/index' +import DiscoverContent from './components/DiscoverContent' // Removed DiscoverContent import +import DiscoverSidebar from './components/DiscoverSidebar' +import { InternalCategory, useDiscoverCategories } from './hooks/useDiscoverCategories' + +// Function to adapt categories for VercelTabs +const adaptCategoriesForVercelTabs = (categories: InternalCategory[]) => { + return categories.map((category) => ({ + id: category.id, // VercelTabs expects `id` + label: category.title // VercelTabs expects `label` + })) +} + +export default function DiscoverPage() { + const { t } = useTranslation() + const { + categories, + activeTab, + selectedSubcategory, + currentCategory, + handleSelectTab, + handleSelectSubcategory, + setActiveTab + } = useDiscoverCategories() + + // Path like /discover/:categoryIdFromUrl. categoryIdFromUrl is lowercase from URL. + const { categoryIdFromUrl } = useParams<{ categoryIdFromUrl: string }>() + + useEffect(() => { + const matchedCategory = categories.find((cat) => cat.id.toLowerCase() === categoryIdFromUrl?.toLowerCase()) + if (matchedCategory && activeTab !== matchedCategory.id) { + setActiveTab(matchedCategory.id) + } + }, [categoryIdFromUrl, categories, activeTab, setActiveTab]) + + const vercelTabsData = adaptCategoriesForVercelTabs(categories) + + return ( + +
+ + {t('discover.title')} + + + {categories.length > 0 && ( +
+ +
+ )} + +
+ {currentCategory?.hasSidebar && ( +
+ +
+ )} + {/* {!currentCategory && categories.length > 0 && ( +
Select a category...
+ )} */} + +
+ +
+
+ +
+
+ ) +} diff --git a/src/renderer/src/pages/discover/types.ts b/src/renderer/src/pages/discover/types.ts new file mode 100644 index 0000000000..5a7b7557b9 --- /dev/null +++ b/src/renderer/src/pages/discover/types.ts @@ -0,0 +1,7 @@ +import { Category } from '@renderer/types/cherryStore' + +export interface DiscoverContextType { + selectedSubcategory: string + activeTabId: string + currentCategory?: Category // currentCategory might be undefined initially +} diff --git a/src/renderer/src/pages/store/components/Assistant/AssistantCard.tsx b/src/renderer/src/pages/store/components/Assistant/AssistantCard.tsx deleted file mode 100644 index e0e87fec47..0000000000 --- a/src/renderer/src/pages/store/components/Assistant/AssistantCard.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { AssistantItem } from '@renderer/types/cherryStore' -import { Badge } from '@renderer/ui/badge' -import { Card, CardContent, CardHeader, CardTitle } from '@renderer/ui/card' -import { BlurFade } from '@renderer/ui/third-party/BlurFade' -import { cn } from '@renderer/utils' - -import { useDialogManager } from '../dialog/DialogManagerContext' - -export default function AssistantCard({ item }: { item: AssistantItem }) { - const { openDialog } = useDialogManager() - - const handleCardClick = () => { - openDialog('install', item) - } - - return ( - - - -
- {item.icon} -
-
- - {item.title} -

{item.author}

-
- {item.tags.map((tag) => ( - - {tag} - - ))} -
-

{item.description}

-
-
-
- ) -} diff --git a/src/renderer/src/pages/store/components/GridView.tsx b/src/renderer/src/pages/store/components/GridView.tsx deleted file mode 100644 index 6cd07d042a..0000000000 --- a/src/renderer/src/pages/store/components/GridView.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { AssistantItem, CherryStoreItem, CherryStoreType, MiniAppItem } from '@renderer/types/cherryStore' -import { Fragment, useMemo } from 'react' - -import AssistantCard from './Assistant/AssistantCard' -import MiniAppCard from './MiniApp/MiniAppCard' - -interface GridViewProps { - items: CherryStoreItem[] - selectedCategory: string - className?: string -} - -const CardComponent = (selectedCategory: string, item: CherryStoreItem) => { - switch (selectedCategory) { - case CherryStoreType.ASSISTANT: - return - case CherryStoreType.MINI_APP: - return - default: - return null - } -} - -export function GridView({ items, selectedCategory }: GridViewProps) { - const effectiveGridClass = useMemo(() => { - let gridClass = 'columns-4 gap-4 ' - - switch (selectedCategory) { - case CherryStoreType.ASSISTANT: - gridClass += '2xl:columns-6' - break - case CherryStoreType.MINI_APP: - gridClass = 'grid grid-cols-8 gap-4 2xl:grid-cols-10' - break - } - return gridClass - }, [selectedCategory]) - - return ( -
- {items.map((item) => ( - {CardComponent(selectedCategory, item)} - ))} -
- ) -} diff --git a/src/renderer/src/pages/store/components/ListView.tsx b/src/renderer/src/pages/store/components/ListView.tsx deleted file mode 100644 index ccae16190c..0000000000 --- a/src/renderer/src/pages/store/components/ListView.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { CherryStoreItem } from '@renderer/types/cherryStore' -import { Badge } from '@renderer/ui/badge' -import { Card } from '@renderer/ui/card' -import { useState } from 'react' - -// import { ItemDetailDialog } from './ItemDetailDialog' - -export function ListView({ items }: { items: CherryStoreItem[] }) { - const [selectedItemForDetail, setSelectedItemForDetail] = useState(null) - const [isDetailDialogOpen, setIsDetailDialogOpen] = useState(false) - - const handleCardClick = (item: CherryStoreItem) => { - setSelectedItemForDetail(item) - setIsDetailDialogOpen(true) - } - - return ( - <> -
- {items.map((item) => ( - handleCardClick(item)}> -
-
- {item.icon ? ( -
- {item.icon} -
- ) : ( - {item.title} - )} -
-
-
-
-
-

{item.title}

-

{item.author}

-
- {item.type} -
-

{item.description}

-
- {item.tags.map((tag) => ( - - {tag} - - ))} -
-
-
- {/*
*/} - {/*
- - {item?.rating} - ({item?.downloads}) -
*/} - {/* -
*/} -
-
- ))} -
- {/* setIsDetailDialogOpen(false)} - /> */} - - ) -} diff --git a/src/renderer/src/pages/store/components/MiniApp/MiniAppCard.tsx b/src/renderer/src/pages/store/components/MiniApp/MiniAppCard.tsx deleted file mode 100644 index ade49d5c89..0000000000 --- a/src/renderer/src/pages/store/components/MiniApp/MiniAppCard.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useMinappPopup } from '@renderer/hooks/useMinappPopup' -import { MiniAppItem } from '@renderer/types/cherryStore' -import { BlurFade } from '@renderer/ui/third-party/BlurFade' - -import logoList from './logoList' -export default function MiniAppCard({ item }: { item: MiniAppItem }) { - const { openMinappKeepAlive } = useMinappPopup() - const handleClick = () => { - openMinappKeepAlive({ - id: item.id, - name: item.title, - url: item.url, - logo: item.image, - style: item.style - }) - } - return ( - -
- {item.title} -
-

{item.title}

-
-
-
- ) -} diff --git a/src/renderer/src/pages/store/components/MiniApp/logoList.ts b/src/renderer/src/pages/store/components/MiniApp/logoList.ts deleted file mode 100644 index 0142901bf6..0000000000 --- a/src/renderer/src/pages/store/components/MiniApp/logoList.ts +++ /dev/null @@ -1,111 +0,0 @@ -import ThreeMinTopAppLogo from '@renderer/assets/images/apps/3mintop.png?url' -import AbacusLogo from '@renderer/assets/images/apps/abacus.webp?url' -import AIStudioLogo from '@renderer/assets/images/apps/aistudio.svg?url' -import BaiduAiAppLogo from '@renderer/assets/images/apps/baidu-ai.png?url' -import BaiduAiSearchLogo from '@renderer/assets/images/apps/baidu-ai-search.webp?url' -import BaicuanAppLogo from '@renderer/assets/images/apps/baixiaoying.webp?url' -import BoltAppLogo from '@renderer/assets/images/apps/bolt.svg?url' -import CiciAppLogo from '@renderer/assets/images/apps/cici.webp?url' -import CozeAppLogo from '@renderer/assets/images/apps/coze.webp?url' -import DangbeiLogo from '@renderer/assets/images/apps/dangbei.jpg?url' -import DevvAppLogo from '@renderer/assets/images/apps/devv.png?url' -import DifyAppLogo from '@renderer/assets/images/apps/dify.svg?url' -import DoubaoAppLogo from '@renderer/assets/images/apps/doubao.png?url' -import DuckDuckGoAppLogo from '@renderer/assets/images/apps/duckduckgo.webp?url' -import FeloAppLogo from '@renderer/assets/images/apps/felo.png?url' -import FlowithAppLogo from '@renderer/assets/images/apps/flowith.svg?url' -import GeminiAppLogo from '@renderer/assets/images/apps/gemini.png?url' -import GensparkLogo from '@renderer/assets/images/apps/genspark.jpg?url' -import GithubCopilotLogo from '@renderer/assets/images/apps/github-copilot.webp?url' -import GrokAppLogo from '@renderer/assets/images/apps/grok.png?url' -import GrokXAppLogo from '@renderer/assets/images/apps/grok-x.png?url' -import HikaLogo from '@renderer/assets/images/apps/hika.webp?url' -import HuggingChatLogo from '@renderer/assets/images/apps/huggingchat.svg?url' -import KimiAppLogo from '@renderer/assets/images/apps/kimi.webp?url' -import LambdaChatLogo from '@renderer/assets/images/apps/lambdachat.webp?url' -import LeChatLogo from '@renderer/assets/images/apps/lechat.png?url' -import MetasoAppLogo from '@renderer/assets/images/apps/metaso.webp?url' -import MonicaLogo from '@renderer/assets/images/apps/monica.webp?url' -import NamiAiLogo from '@renderer/assets/images/apps/nm.png?url' -import NamiAiSearchLogo from '@renderer/assets/images/apps/nm-search.webp?url' -import NotebookLMAppLogo from '@renderer/assets/images/apps/notebooklm.svg?url' -import PerplexityAppLogo from '@renderer/assets/images/apps/perplexity.webp?url' -import PoeAppLogo from '@renderer/assets/images/apps/poe.webp?url' -import ZhipuProviderLogo from '@renderer/assets/images/apps/qingyan.png?url' -import QwenlmAppLogo from '@renderer/assets/images/apps/qwenlm.webp?url' -import SensetimeAppLogo from '@renderer/assets/images/apps/sensetime.png?url' -import SparkDeskAppLogo from '@renderer/assets/images/apps/sparkdesk.webp?url' -import ThinkAnyLogo from '@renderer/assets/images/apps/thinkany.webp?url' -import TiangongAiLogo from '@renderer/assets/images/apps/tiangong.png?url' -import WanZhiAppLogo from '@renderer/assets/images/apps/wanzhi.jpg?url' -import WPSLingXiLogo from '@renderer/assets/images/apps/wpslingxi.webp?url' -import XiaoYiAppLogo from '@renderer/assets/images/apps/xiaoyi.webp?url' -import YouLogo from '@renderer/assets/images/apps/you.jpg?url' -import TencentYuanbaoAppLogo from '@renderer/assets/images/apps/yuanbao.webp?url' -import YuewenAppLogo from '@renderer/assets/images/apps/yuewen.png?url' -import ZaiAppLogo from '@renderer/assets/images/apps/zai.png?url' -import ZhihuAppLogo from '@renderer/assets/images/apps/zhihu.png?url' -import ClaudeAppLogo from '@renderer/assets/images/models/claude.png?url' -import HailuoModelLogo from '@renderer/assets/images/models/hailuo.png?url' -import QwenModelLogo from '@renderer/assets/images/models/qwen.png?url' -import DeepSeekProviderLogo from '@renderer/assets/images/providers/deepseek.png?url' -import GroqProviderLogo from '@renderer/assets/images/providers/groq.png?url' -import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png?url' -import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png?url' - -export default { - ThreeMinTopAppLogo, - AbacusLogo, - AIStudioLogo, - BaiduAiAppLogo, - BaiduAiSearchLogo, - BaicuanAppLogo, - BoltAppLogo, - CiciAppLogo, - CozeAppLogo, - DangbeiLogo, - DevvAppLogo, - DifyAppLogo, - DoubaoAppLogo, - DuckDuckGoAppLogo, - FeloAppLogo, - FlowithAppLogo, - GeminiAppLogo, - GensparkLogo, - GithubCopilotLogo, - GrokAppLogo, - GrokXAppLogo, - HikaLogo, - HuggingChatLogo, - KimiAppLogo, - LambdaChatLogo, - LeChatLogo, - MetasoAppLogo, - MonicaLogo, - NamiAiLogo, - NamiAiSearchLogo, - NotebookLMAppLogo, - PerplexityAppLogo, - PoeAppLogo, - QwenlmAppLogo, - SensetimeAppLogo, - SparkDeskAppLogo, - ThinkAnyLogo, - TiangongAiLogo, - WanZhiAppLogo, - WPSLingXiLogo, - XiaoYiAppLogo, - YouLogo, - TencentYuanbaoAppLogo, - YuewenAppLogo, - ZaiAppLogo, - ZhihuAppLogo, - ClaudeAppLogo, - HailuoModelLogo, - QwenModelLogo, - DeepSeekProviderLogo, - GroqProviderLogo, - OpenAiProviderLogo, - SiliconFlowProviderLogo, - ZhipuProviderLogo -} diff --git a/src/renderer/src/pages/store/components/StoreContent.tsx b/src/renderer/src/pages/store/components/StoreContent.tsx deleted file mode 100644 index c3213b3104..0000000000 --- a/src/renderer/src/pages/store/components/StoreContent.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { CherryStoreItem } from '@renderer/types/cherryStore' -import { Button } from '@renderer/ui/button' -import { Input } from '@renderer/ui/input' -import { cn } from '@renderer/utils' -import { Grid3X3, List, Search } from 'lucide-react' -import React from 'react' // Import React for ComponentType - -// Import the card components -// Define the type for a store item based on store_list.json -import { GridView } from './GridView' -import { ListView } from './ListView' - -interface StoreContentProps { - viewMode: 'grid' | 'list' - searchQuery: string - selectedCategory: string - items: CherryStoreItem[] - onSearchQueryChange: (query: string) => void - onViewModeChange: (mode: 'grid' | 'list') => void -} - -export function StoreContent({ - viewMode, - searchQuery, - selectedCategory, // This prop will drive the component choice - items, - onSearchQueryChange, - onViewModeChange -}: StoreContentProps) { - return ( -
- {/* Sticky Header for Search, Filter, View Mode, and Category Tabs */} -
-
- {/* Top row: Search, Filter, View buttons */} -
-
- - onSearchQueryChange(e.target.value)} - /> -
- {/* - - - - - Most Popular - Newest - Highest Rated - - */} - - -
- - {/* Bottom row: Category Tabs */} - {/* - - All - MCP Services - Plugins - Applications - - */} -
-
- - {/* Main Content Area: Grid or List View */} -
- {items.length === 0 ? ( -
-

No items found matching your criteria.

-
- ) : viewMode === 'grid' ? ( - - ) : ( - - )} -
-
- ) -} - -// List View Component diff --git a/src/renderer/src/pages/store/components/StoreSidebar.tsx b/src/renderer/src/pages/store/components/StoreSidebar.tsx deleted file mode 100644 index 07dc4e58c6..0000000000 --- a/src/renderer/src/pages/store/components/StoreSidebar.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Category, SubCategoryItem } from '@renderer/types/cherryStore' -import { Badge } from '@renderer/ui/badge' -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@renderer/ui/collapsible' -import { - Sidebar, - SidebarContent, - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, - SidebarMenuSub, - SidebarMenuSubItem -} from '@renderer/ui/sidebar' - -interface StoreSidebarProps { - categories: Category[] - selectedCategory: string - selectedSubcategory: string - onSelectCategory: (categoryId: string, subcategoryId: string, row?: SubCategoryItem) => void -} - -export function StoreSidebar({ - categories, - selectedCategory, - selectedSubcategory, - onSelectCategory -}: StoreSidebarProps) { - if (!categories || categories.length === 0) { - return ( - - -

No categories loaded.

-
-
- ) - } - - return ( - - - - {categories.map((category, index) => - category.items?.length ? ( - - - - - {category.title} - - - - - {category.items.map((subItem) => ( - - { - onSelectCategory(category.id, subItem.id, subItem) - }} - size="sm"> - {subItem.name} - {typeof subItem.count === 'number' && ( - - {subItem.count} - - )} - - - ))} - {/* {category.items.length === 0 && ( - - No items - - )} */} - - - - - ) : ( - - { - onSelectCategory(category.id, '') - }}> - {/* */} - {category.title} - - - ) - )} - - - - ) -} diff --git a/src/renderer/src/pages/store/data/index.ts b/src/renderer/src/pages/store/data/index.ts deleted file mode 100644 index 7e990d84a1..0000000000 --- a/src/renderer/src/pages/store/data/index.ts +++ /dev/null @@ -1,125 +0,0 @@ -import store from '@renderer/store' -import { Category, CherryStoreItem } from '@renderer/types/cherryStore' - -// 移除 LoadedStoreData 和 LoadedStoreDataByType,因为我们将按需加载 -// export interface LoadedStoreData { -// categories: Category[] -// allItems: CherryStoreItem[] -// } - -// export interface LoadedStoreDataByType { -// categories: Category[] -// assistantItems?: CherryStoreItem[] -// knowledgeItems?: CherryStoreItem[] // Example for another type -// mcpServerItems?: CherryStoreItem[] // Example for another type -// // Add other item types here as you create their list_*.json files -// } - -const getResourcesPath = (path: string) => { - const { resourcesPath } = store.getState().runtime - return resourcesPath + path -} - -// 缓存变量 -let cachedCategories: Category[] | null = null -const cachedItemsByFile: Record = {} - -// Helper function to read and parse JSON files safely -async function readFileSafe(filePath: string): Promise { - try { - if (!window.api?.fs?.read) { - console.error('window.api.fs.read is not available. Ensure preload script is set up correctly.') - return undefined - } - const fileContent = await window.api.fs.read(filePath) - if (typeof fileContent === 'string') { - return JSON.parse(fileContent) as T - } - console.warn( - `Content read from ${filePath} was not a string or file might be empty/missing. Received:`, - fileContent - ) - return undefined - } catch (error) { - console.error(`Error reading or parsing file ${filePath}:`, error) - return undefined - } -} - -export async function loadCategories(resourcesPath: string): Promise { - if (cachedCategories) { - console.log('Returning cached categories:', cachedCategories.length) - return cachedCategories - } - - const categoriesFilePath = resourcesPath + '/data/store_categories.json' - console.log('categoriesFilePath', categoriesFilePath) - const categories = (await readFileSafe(categoriesFilePath)) || [] - - if (categories.length > 0) { - cachedCategories = categories - console.log('Categories loaded and cached:', categories.length) - } else { - console.log('No categories found or error loading categories.') - } - return categories -} - -// 新函数:根据分类、子分类和搜索查询加载和筛选商品 -export async function loadAndFilterItems( - categoryId: string, - subcategoryId: string, - searchQuery: string -): Promise { - let itemsFilePath = '' - if (categoryId === 'all' || !categoryId) { - console.warn("loadAndFilterItems called with 'all' or invalid categoryId. Returning empty for now.") - return [] - } else { - itemsFilePath = getResourcesPath(`/data/store_list_${categoryId}.json`) - } - - if (!itemsFilePath) { - console.error(`No item file path determined for categoryId: ${categoryId}`) - return [] - } - - let items: CherryStoreItem[] = [] - - if (cachedItemsByFile[itemsFilePath]) { - items = cachedItemsByFile[itemsFilePath] - console.log(`Returning cached items for ${itemsFilePath}:`, items.length) - } else { - const loadedItems = await readFileSafe(itemsFilePath) - if (loadedItems) { - items = loadedItems - cachedItemsByFile[itemsFilePath] = loadedItems - console.log(`Items loaded and cached for ${itemsFilePath}:`, items.length) - } else { - console.log(`No items found or error loading items for: ${itemsFilePath}`) - // 确保在文件读取失败或为空时,items 仍然是空数组 - items = [] - // 也可以选择缓存一个空数组,以避免重复尝试读取不存在或错误的文件 - // cachedItemsByFile[itemsFilePath] = []; - } - } - - if (!items.length) { - // 如果缓存中是空数组或者新加载的是空数组,直接返回,避免不必要的筛选 - return [] - } - - let filteredItems = items - - if (searchQuery || subcategoryId) { - const query = searchQuery.toLowerCase() - filteredItems = filteredItems.filter((item) => { - const searchableText = `${item.subcategoryId} ${item.title.toLowerCase()} ${item.author?.toLowerCase() || ''} ${item.tags?.join(' ')?.toLowerCase() || ''}` - return searchableText.includes(query) - }) - } - console.log( - `Filtered items for ${categoryId} - ${subcategoryId} - "${searchQuery}". Found: ${filteredItems.length} (from ${items.length} initial)` - ) - return filteredItems -} diff --git a/src/renderer/src/pages/store/index.tsx b/src/renderer/src/pages/store/index.tsx deleted file mode 100644 index 7e4d6f05d5..0000000000 --- a/src/renderer/src/pages/store/index.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' -import { useRuntime } from '@renderer/hooks/useRuntime' -import { Category, CherryStoreItem, SubCategoryItem } from '@renderer/types/cherryStore' -import { SidebarProvider } from '@renderer/ui/sidebar' -import { useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' - -// Import Context and the main Dialog Manager component -import { DialogManagerProvider } from './components/dialog/DialogManagerContext' -import Dialogs from './components/dialog/index' -import { StoreContent } from './components/StoreContent' -import { StoreSidebar } from './components/StoreSidebar' -import { loadAndFilterItems, loadCategories } from './data' - -export default function StoreLayout() { - const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid') - const [searchQuery, setSearchQuery] = useState('') - const [selectedCategory, setSelectedCategory] = useState('') - const [selectedSubcategory, setSelectedSubcategory] = useState('all') - const { t } = useTranslation() - const { resourcesPath } = useRuntime() - - const [categories, setCategories] = useState([]) - const [items, setItems] = useState([]) - const [isLoading, setIsLoading] = useState(true) - const [isLoadingItems, setIsLoadingItems] = useState(false) - const [error, setError] = useState(null) - - useEffect(() => { - const fetchCategories = async () => { - try { - setIsLoading(true) - setError(null) - const loadedCategories = await loadCategories(resourcesPath) - setCategories(loadedCategories) - if (loadedCategories.length > 0 && !selectedCategory) { - setSelectedCategory(loadedCategories[0].id) - setSelectedSubcategory(loadedCategories[0].items[0].id) - } - } catch (err) { - console.error('Error in StoreLayout fetchCategories:', err) - setError('Failed to load store categories. Check console for details.') - } finally { - setIsLoading(false) - } - } - resourcesPath && fetchCategories() - }, [resourcesPath]) - - useEffect(() => { - if (!selectedCategory) { - setItems([]) - return - } - - const fetchItems = async () => { - try { - setIsLoadingItems(true) - setError(null) - const filteredItems = await loadAndFilterItems(selectedCategory, selectedSubcategory, searchQuery) - setItems(filteredItems) - } catch (err) { - console.error('Error in StoreLayout fetchItems:', err) - setError('Failed to load store items. Check console for details.') - setItems([]) - } finally { - setIsLoadingItems(false) - } - } - - fetchItems() - }, [selectedCategory, selectedSubcategory, searchQuery]) - - const handleSelectCategory = (categoryId: string, subcategoryId: string, row?: SubCategoryItem) => { - setSelectedCategory(categoryId) - setSelectedSubcategory(subcategoryId) - setSearchQuery(row?.name || '') - } - - if (isLoading) { - return
Loading store categories...
- } - - if (error) { - return
Error: {error}
- } - console.log('categories', categories) - return ( - -
- - {t('store.title')} - -
- - - {isLoadingItems ? ( - // TODO: 添加 loading 动画 -
Loading items...
- ) : ( - - )} -
-
- -
-
- ) -} diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 921de83efb..fd2cce5383 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1261,7 +1261,7 @@ const migrateConfig = { ...state.settings, sidebarIcons: { ...state.settings.sidebarIcons, - visible: [...state.settings.sidebarIcons.visible, 'store'] + visible: [...state.settings.sidebarIcons.visible, 'discover'] } } } diff --git a/src/renderer/src/ui/vercel-tabs.tsx b/src/renderer/src/ui/vercel-tabs.tsx new file mode 100644 index 0000000000..1162662733 --- /dev/null +++ b/src/renderer/src/ui/vercel-tabs.tsx @@ -0,0 +1,113 @@ +import { cn } from '@renderer/utils' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' + +interface Tab { + id: string + label: string +} + +interface TabsProps extends React.HTMLAttributes { + tabs: Tab[] + activeTab?: string + onTabChange?: (tabId: string) => void +} + +const Tabs = ({ + ref, + className, + tabs, + activeTab: _, + onTabChange, + ...props +}: TabsProps & { ref?: React.RefObject }) => { + const [hoveredIndex, setHoveredIndex] = useState(null) + const [activeIndex, setActiveIndex] = useState(0) + const [hoverStyle, setHoverStyle] = useState({}) + const [activeStyle, setActiveStyle] = useState({ left: '0px', width: '0px' }) + const tabRefs = useRef<(HTMLDivElement | null)[]>([]) + + useEffect(() => { + if (hoveredIndex !== null) { + const hoveredElement = tabRefs.current[hoveredIndex] + if (hoveredElement) { + const { offsetLeft, offsetWidth } = hoveredElement + setHoverStyle({ + left: `${offsetLeft}px`, + width: `${offsetWidth}px` + }) + } + } + }, [hoveredIndex]) + + useEffect(() => { + const activeElement = tabRefs.current[activeIndex] + if (activeElement) { + const { offsetLeft, offsetWidth } = activeElement + setActiveStyle({ + left: `${offsetLeft}px`, + width: `${offsetWidth}px` + }) + } + }, [activeIndex]) + + useEffect(() => { + requestAnimationFrame(() => { + const firstElement = tabRefs.current[0] + if (firstElement) { + const { offsetLeft, offsetWidth } = firstElement + setActiveStyle({ + left: `${offsetLeft}px`, + width: `${offsetWidth}px` + }) + } + }) + }, []) + + return ( +
+
+ {/* Hover Highlight */} +
+ + {/* Active Indicator */} +
+ + {/* Tabs */} +
+ {tabs.map((tab, index) => ( +
(tabRefs.current[index] = el)} + className={cn( + 'h-[30px] cursor-pointer px-3 py-2 transition-colors duration-300', + index === activeIndex ? 'text-[#0e0e10] dark:text-white' : 'text-[#0e0f1199] dark:text-[#ffffff99]' + )} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + onClick={() => { + setActiveIndex(index) + onTabChange?.(tab.id) + }}> +
+ {tab.label} +
+
+ ))} +
+
+
+ ) +} +Tabs.displayName = 'Tabs' + +export { Tabs } diff --git a/yarn.lock b/yarn.lock index 02945b8596..769e959cf0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5335,7 +5335,7 @@ __metadata: lint-staged: "npm:^15.5.0" lodash: "npm:^4.17.21" lru-cache: "npm:^11.1.0" - lucide-react: "npm:^0.509.0" + lucide-react: "npm:^0.511.0" markdown-it: "npm:^14.1.0" mime: "npm:^4.0.4" motion: "npm:^12.12.1" @@ -5386,6 +5386,7 @@ __metadata: tw-animate-css: "npm:^1.2.9" typescript: "npm:^5.6.2" undici: "npm:^7.4.0" + usehooks-ts: "npm:^3.1.1" uuid: "npm:^10.0.0" vite: "npm:6.2.6" vitest: "npm:^3.1.1" @@ -11949,6 +11950,13 @@ __metadata: languageName: node linkType: hard +"lodash.debounce@npm:^4.0.8": + version: 4.0.8 + resolution: "lodash.debounce@npm:4.0.8" + checksum: 10c0/762998a63e095412b6099b8290903e0a8ddcb353ac6e2e0f2d7e7d03abd4275fe3c689d88960eb90b0dde4f177554d51a690f22a343932ecbc50a5d111849987 + languageName: node + linkType: hard + "lodash.escaperegexp@npm:^4.1.2": version: 4.1.2 resolution: "lodash.escaperegexp@npm:4.1.2" @@ -12085,12 +12093,12 @@ __metadata: languageName: node linkType: hard -"lucide-react@npm:^0.509.0": - version: 0.509.0 - resolution: "lucide-react@npm:0.509.0" +"lucide-react@npm:^0.511.0": + version: 0.511.0 + resolution: "lucide-react@npm:0.511.0" peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/eb7434a99738d2a3b3a193c4e0cf51691a06223ec8eda514ea7fd96cdbd311069d25353cd9e720810e5c7ba3891186de6a0463c3ac5382cc1d981938847b9b95 + checksum: 10c0/bf09dd73cf2233abea90506ad31a91739555d761062722acbe045cb73e274f035b196472de0971a8a8f0645b2b54e3f21b8c1980fe87c909ca93171a9c28428a languageName: node linkType: hard @@ -18503,6 +18511,17 @@ __metadata: languageName: node linkType: hard +"usehooks-ts@npm:^3.1.1": + version: 3.1.1 + resolution: "usehooks-ts@npm:3.1.1" + dependencies: + lodash.debounce: "npm:^4.0.8" + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc + checksum: 10c0/8bbebf52b063f2e705eb27364f08ec2eff987b9e8d28d82a28248652dd89ef53cb514e1f2ee1cbc6ac6e083a8dce86310301c3e92a99902b98c32a26381202d7 + languageName: node + linkType: hard + "utf8-byte-length@npm:^1.0.1": version: 1.0.5 resolution: "utf8-byte-length@npm:1.0.5"