feat: update store components and add dialog management functionality

- Updated package.json to use the latest version of the 'motion' library.
- Refactored store components to improve organization and user experience, including the addition of AssistantCard and MiniAppCard components.
- Introduced a DialogManager for handling dialog states and interactions.
- Enhanced StoreContent and StoreSidebar components to support new item types and improved layout.
- Added new JSON data for mini-apps and updated store categories for better accessibility.
This commit is contained in:
MyPrototypeWhat 2025-05-16 19:11:46 +08:00
parent ef16558947
commit 80289f1dc3
16 changed files with 1130 additions and 168 deletions

View File

@ -185,7 +185,7 @@
"lru-cache": "^11.1.0",
"lucide-react": "^0.509.0",
"mime": "^4.0.4",
"motion": "^12.11.0",
"motion": "^12.12.1",
"next-themes": "^0.4.6",
"npx-scope-finder": "^1.2.0",
"openai": "patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch",

View File

@ -21,7 +21,7 @@
]
},
{
"id": "assistant",
"id": "Assistant",
"title": "助手",
"items": [
{
@ -192,12 +192,12 @@
]
},
{
"id": "mini-app",
"id": "Mini-App",
"title": "小程序",
"items": []
},
{
"id": "knowledge",
"id": "Knowledge",
"title": "知识库",
"items": [
{
@ -243,7 +243,7 @@
]
},
{
"id": "mcp-server",
"id": "MCP-Server",
"title": "MCP 服务器",
"items": [
{
@ -333,12 +333,12 @@
]
},
{
"id": "model-provider",
"id": "Model-Provider",
"title": "模型服务",
"items": []
},
{
"id": "agent",
"id": "Agent",
"title": "智能体",
"items": []
}

View File

@ -0,0 +1,691 @@
[
{
"id": "mini-app-openai",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "ChatGPT",
"description": "",
"author": "openai",
"image": "OpenAiProviderLogo",
"tags": [],
"url": "https://chatgpt.com/",
"bodered": true
},
{
"id": "mini-app-gemini",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Gemini",
"description": "",
"author": "gemini",
"image": "GeminiAppLogo",
"tags": [],
"url": "https://gemini.google.com/"
},
{
"id": "mini-app-silicon",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "SiliconFlow",
"description": "",
"author": "silicon",
"image": "SiliconFlowProviderLogo",
"tags": [],
"url": "https://cloud.siliconflow.cn/playground/chat"
},
{
"id": "mini-app-deepseek",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "DeepSeek",
"description": "",
"author": "deepseek",
"image": "DeepSeekProviderLogo",
"tags": [],
"url": "https://chat.deepseek.com/"
},
{
"id": "mini-app-yi",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "万知",
"description": "",
"author": "yi",
"image": "WanZhiAppLogo",
"tags": [],
"url": "https://www.wanzhi.com/",
"bodered": true
},
{
"id": "mini-app-zhipu",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "智谱清言",
"description": "",
"author": "zhipu",
"image": "ZhipuProviderLogo",
"tags": [],
"url": "https://chatglm.cn/main/alltoolsdetail"
},
{
"id": "mini-app-moonshot",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Kimi",
"description": "",
"author": "moonshot",
"image": "KimiAppLogo",
"tags": [],
"url": "https://kimi.moonshot.cn/"
},
{
"id": "mini-app-baichuan",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "百小应",
"description": "",
"author": "baichuan",
"image": "BaicuanAppLogo",
"tags": [],
"url": "https://ying.baichuan-ai.com/chat"
},
{
"id": "mini-app-dashscope",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "通义千问",
"description": "",
"author": "dashscope",
"image": "QwenModelLogo",
"tags": [],
"url": "https://tongyi.aliyun.com/qianwen/"
},
{
"id": "mini-app-stepfun",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "跃问",
"description": "",
"author": "stepfun",
"image": "YuewenAppLogo",
"tags": [],
"url": "https://yuewen.cn/chats/new",
"bodered": true
},
{
"id": "mini-app-doubao",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "豆包",
"description": "",
"author": "doubao",
"image": "DoubaoAppLogo",
"tags": [],
"url": "https://www.doubao.com/chat/"
},
{
"id": "mini-app-cici",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Cici",
"description": "",
"author": "cici",
"image": "CiciAppLogo",
"tags": [],
"url": "https://www.cici.com/chat/"
},
{
"id": "mini-app-minimax",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "海螺",
"description": "",
"author": "minimax",
"image": "HailuoModelLogo",
"tags": [],
"url": "https://hailuoai.com/"
},
{
"id": "mini-app-groq",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Groq",
"description": "",
"author": "groq",
"image": "GroqProviderLogo",
"tags": [],
"url": "https://chat.groq.com/"
},
{
"id": "mini-app-anthropic",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Claude",
"description": "",
"author": "anthropic",
"image": "ClaudeAppLogo",
"tags": [],
"url": "https://claude.ai/"
},
{
"id": "mini-app-baidu-ai-chat",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "文心一言",
"description": "",
"author": "baidu-ai-chat",
"image": "BaiduAiAppLogo",
"tags": [],
"url": "https://yiyan.baidu.com/"
},
{
"id": "mini-app-baidu-ai-search",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "百度AI搜索",
"description": "",
"author": "baidu-ai-search",
"image": "BaiduAiSearchLogo",
"tags": [],
"url": "https://chat.baidu.com/",
"bodered": true,
"style": {
"padding": 5
}
},
{
"id": "mini-app-tencent-yuanbao",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "腾讯元宝",
"description": "",
"author": "tencent-yuanbao",
"image": "TencentYuanbaoAppLogo",
"tags": [],
"url": "https://yuanbao.tencent.com/chat",
"bodered": true
},
{
"id": "mini-app-sensetime-chat",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "商量",
"description": "",
"author": "sensetime-chat",
"image": "SensetimeAppLogo",
"tags": [],
"url": "https://chat.sensetime.com/wb/chat",
"bodered": true
},
{
"id": "mini-app-spark-desk",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "SparkDesk",
"description": "",
"author": "spark-desk",
"image": "SparkDeskAppLogo",
"tags": [],
"url": "https://xinghuo.xfyun.cn/desk"
},
{
"id": "mini-app-metaso",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "秘塔AI搜索",
"description": "",
"author": "metaso",
"image": "MetasoAppLogo",
"tags": [],
"url": "https://metaso.cn/"
},
{
"id": "mini-app-poe",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Poe",
"description": "",
"author": "poe",
"image": "PoeAppLogo",
"tags": [],
"url": "https://poe.com"
},
{
"id": "mini-app-perplexity",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Perplexity",
"description": "",
"author": "perplexity",
"image": "PerplexityAppLogo",
"tags": [],
"url": "https://www.perplexity.ai/"
},
{
"id": "mini-app-devv",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "DEVV_",
"description": "",
"author": "devv",
"image": "DevvAppLogo",
"tags": [],
"url": "https://devv.ai/"
},
{
"id": "mini-app-tiangong-ai",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "天工AI",
"description": "",
"author": "tiangong-ai",
"image": "TiangongAiLogo",
"tags": [],
"url": "https://www.tiangong.cn/",
"bodered": true
},
{
"id": "mini-app-hugging-chat",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "HuggingChat",
"description": "",
"author": "hugging-chat",
"image": "HuggingChatLogo",
"tags": [],
"url": "https://huggingface.co/chat/",
"bodered": true
},
{
"id": "mini-app-Felo",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Felo",
"description": "",
"author": "Felo",
"image": "FeloAppLogo",
"tags": [],
"url": "https://felo.ai/",
"bodered": true
},
{
"id": "mini-app-duckduckgo",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "DuckDuckGo",
"description": "",
"author": "duckduckgo",
"image": "DuckDuckGoAppLogo",
"tags": [],
"url": "https://duck.ai"
},
{
"id": "mini-app-bolt",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "bolt",
"description": "",
"author": "bolt",
"image": "BoltAppLogo",
"tags": [],
"url": "https://bolt.new/",
"bodered": true
},
{
"id": "mini-app-nm",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "纳米AI",
"description": "",
"author": "nm",
"image": "NamiAiLogo",
"tags": [],
"url": "https://bot.n.cn/",
"bodered": true
},
{
"id": "mini-app-nm-search",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "纳米AI搜索",
"description": "",
"author": "nm-search",
"image": "NamiAiSearchLogo",
"tags": [],
"url": "https://www.n.cn/",
"bodered": true
},
{
"id": "mini-app-thinkany",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "ThinkAny",
"description": "",
"author": "thinkany",
"image": "ThinkAnyLogo",
"tags": [],
"url": "https://thinkany.ai/",
"bodered": true,
"style": {
"padding": 5
}
},
{
"id": "mini-app-hika",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Hika",
"description": "",
"author": "hika",
"image": "HikaLogo",
"tags": [],
"url": "https://hika.fyi/",
"bodered": true
},
{
"id": "mini-app-github-copilot",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "GitHub Copilot",
"description": "",
"author": "github-copilot",
"image": "GithubCopilotLogo",
"tags": [],
"url": "https://github.com/copilot"
},
{
"id": "mini-app-genspark",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Genspark",
"description": "",
"author": "genspark",
"image": "GensparkLogo",
"tags": [],
"url": "https://www.genspark.ai/"
},
{
"id": "mini-app-grok",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Grok",
"description": "",
"author": "grok",
"image": "GrokAppLogo",
"tags": [],
"url": "https://grok.com",
"bodered": true
},
{
"id": "mini-app-grok-x",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Grok / X",
"description": "",
"author": "grok-x",
"image": "GrokXAppLogo",
"tags": [],
"url": "https://x.com/i/grok",
"bodered": true
},
{
"id": "mini-app-qwenlm",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "QwenLM",
"description": "",
"author": "qwenlm",
"image": "QwenlmAppLogo",
"tags": [],
"url": "https://qwenlm.ai/"
},
{
"id": "mini-app-flowith",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Flowith",
"description": "",
"author": "flowith",
"image": "FlowithAppLogo",
"tags": [],
"url": "https://www.flowith.io/",
"bodered": true
},
{
"id": "mini-app-3mintop",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "3MinTop",
"description": "",
"author": "3mintop",
"image": "ThreeMinTopAppLogo",
"tags": [],
"url": "https://3min.top",
"bodered": false
},
{
"id": "mini-app-aistudio",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "AI Studio",
"description": "",
"author": "aistudio",
"image": "AIStudioLogo",
"tags": [],
"url": "https://aistudio.google.com/"
},
{
"id": "mini-app-xiaoyi",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "小艺",
"description": "",
"author": "xiaoyi",
"image": "XiaoYiAppLogo",
"tags": [],
"url": "https://xiaoyi.huawei.com/chat/",
"bodered": true
},
{
"id": "mini-app-notebooklm",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "NotebookLM",
"description": "",
"author": "notebooklm",
"image": "NotebookLMAppLogo",
"tags": [],
"url": "https://notebooklm.google.com/"
},
{
"id": "mini-app-coze",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Coze",
"description": "",
"author": "coze",
"image": "CozeAppLogo",
"tags": [],
"url": "https://www.coze.com/space",
"bodered": true
},
{
"id": "mini-app-dify",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Dify",
"description": "",
"author": "dify",
"image": "DifyAppLogo",
"tags": [],
"url": "https://cloud.dify.ai/apps",
"bodered": true,
"style": {
"padding": 5
}
},
{
"id": "mini-app-wpslingxi",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "WPS灵犀",
"description": "",
"author": "wpslingxi",
"image": "WPSLingXiLogo",
"tags": [],
"url": "https://copilot.wps.cn/",
"bodered": true
},
{
"id": "mini-app-lechat",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "LeChat",
"description": "",
"author": "lechat",
"image": "LeChatLogo",
"tags": [],
"url": "https://chat.mistral.ai/chat",
"bodered": true
},
{
"id": "mini-app-abacus",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Abacus",
"description": "",
"author": "abacus",
"image": "AbacusLogo",
"tags": [],
"url": "https://apps.abacus.ai/chatllm",
"bodered": true
},
{
"id": "mini-app-lambdachat",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Lambda Chat",
"description": "",
"author": "lambdachat",
"image": "LambdaChatLogo",
"tags": [],
"url": "https://lambda.chat/",
"bodered": true
},
{
"id": "mini-app-monica",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Monica",
"description": "",
"author": "monica",
"image": "MonicaLogo",
"tags": [],
"url": "https://monica.im/home/",
"bodered": true
},
{
"id": "mini-app-you",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "You",
"description": "",
"author": "you",
"image": "YouLogo",
"tags": [],
"url": "https://you.com/"
},
{
"id": "mini-app-zhihu",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "知乎直答",
"description": "",
"author": "zhihu",
"image": "ZhihuAppLogo",
"tags": [],
"url": "https://zhida.zhihu.com/",
"bodered": true
},
{
"id": "mini-app-dangbei",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "当贝AI",
"description": "",
"author": "dangbei",
"image": "DangbeiLogo",
"tags": [],
"url": "https://ai.dangbei.com/",
"bodered": true
},
{
"id": "mini-app-zai",
"type": "Mini-App",
"categoryId": "mini-app",
"subcategoryId": "",
"title": "Z.ai",
"description": "",
"author": "zai",
"image": "ZaiAppLogo",
"tags": [],
"url": "https://chat.z.ai/",
"bodered": true,
"style": {
"padding": 10
}
}
]

View File

@ -0,0 +1,39 @@
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 (
<BlurFade key={item.id} delay={0.2} inView className="mb-4 cursor-pointer">
<Card className="overflow-hidden transition-transform hover:scale-105" onClick={handleCardClick}>
<CardHeader className="p-0">
<div className="flex h-full w-full items-center justify-center text-4xl" role="img" aria-label={item.title}>
{item.icon}
</div>
</CardHeader>
<CardContent className={cn('px-4', 'min-h-[120px]', 'space-y-1')}>
<CardTitle className="line-clamp-2 text-base">{item.title}</CardTitle>
<p className="text-sm text-muted-foreground">{item.author}</p>
<div className="space-x-2">
{item.tags.map((tag) => (
<Badge key={tag} variant="outline">
{tag}
</Badge>
))}
</div>
<p className={cn('mt-2 text-sm text-muted-foreground', 'line-clamp-4 xl:line-clamp-7')}>{item.description}</p>
</CardContent>
</Card>
</BlurFade>
)
}

View File

@ -1,70 +1,46 @@
import { CherryStoreItem } 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 { useState } from 'react'
import { AssistantItem, CherryStoreItem, CherryStoreType, MiniAppItem } from '@renderer/types/cherryStore'
import { Fragment, useMemo } from 'react'
import { ItemDetailDialog } from './ItemDetailDialog'
import AssistantCard from './Assistant/AssistantCard'
import MiniAppCard from './MiniApp/MiniAppCard'
export function GridView({ items }: { items: CherryStoreItem[] }) {
const [selectedItemForDetail, setSelectedItemForDetail] = useState<CherryStoreItem | null>(null)
const [isDetailDialogOpen, setIsDetailDialogOpen] = useState(false)
interface GridViewProps {
items: CherryStoreItem[]
selectedCategory: string
className?: string
}
const handleCardClick = (item: CherryStoreItem) => {
setSelectedItemForDetail(item)
setIsDetailDialogOpen(true)
const CardComponent = (selectedCategory: string, item: CherryStoreItem) => {
switch (selectedCategory) {
case CherryStoreType.ASSISTANT:
return <AssistantCard item={item as AssistantItem} />
case CherryStoreType.MINI_APP:
return <MiniAppCard item={item as MiniAppItem} />
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 (
<>
<div className="columns-4 gap-4">
{items.map((item) => (
<BlurFade key={item.id} delay={0.2} inView className="mb-4 cursor-pointer">
<Card
className="overflow-hidden transition-transform hover:scale-105"
onClick={() => handleCardClick(item)}>
<CardHeader className="p-0">
{item.icon ? (
<div
className="flex h-full w-full items-center justify-center text-4xl"
role="img"
aria-label={item.title}>
{item.icon}
</div>
) : (
<div className={cn('w-full overflow-hidden bg-muted', 'aspect-square')}>
<img
src={item.image || '/placeholder.svg'}
alt={item.title}
className="h-full w-full object-cover transition-transform"
/>
</div>
)}
</CardHeader>
<CardContent className={cn('px-4', 'min-h-[120px]', 'space-y-1')}>
<CardTitle className="line-clamp-2 text-base">{item.title}</CardTitle>
<p className="text-sm text-muted-foreground">{item.author}</p>
<div className="space-x-2">
{item.tags.map((tag) => (
<Badge key={tag} variant="outline">
{tag}
</Badge>
))}
</div>
<p className={cn('mt-2 text-sm text-muted-foreground', 'line-clamp-4 xl:line-clamp-7')}>
{item.description}
</p>
</CardContent>
</Card>
</BlurFade>
))}
</div>
<ItemDetailDialog
item={selectedItemForDetail}
isOpen={isDetailDialogOpen}
onClose={() => setIsDetailDialogOpen(false)}
/>
</>
<div className={effectiveGridClass}>
{items.map((item) => (
<Fragment key={item.id}>{CardComponent(selectedCategory, item)}</Fragment>
))}
</div>
)
}

View File

@ -3,7 +3,7 @@ import { Badge } from '@renderer/ui/badge'
import { Card } from '@renderer/ui/card'
import { useState } from 'react'
import { ItemDetailDialog } from './ItemDetailDialog'
// import { ItemDetailDialog } from './ItemDetailDialog'
export function ListView({ items }: { items: CherryStoreItem[] }) {
const [selectedItemForDetail, setSelectedItemForDetail] = useState<CherryStoreItem | null>(null)
@ -73,11 +73,11 @@ export function ListView({ items }: { items: CherryStoreItem[] }) {
</Card>
))}
</div>
<ItemDetailDialog
{/* <ItemDetailDialog
item={selectedItemForDetail}
isOpen={isDetailDialogOpen}
onClose={() => setIsDetailDialogOpen(false)}
/>
/> */}
</>
)
}

View File

@ -0,0 +1,27 @@
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 (
<BlurFade key={item.id} delay={0.2} inView className="mb-4 cursor-pointer">
<div className="flex h-full w-full flex-col items-center justify-between" onClick={handleClick}>
<img src={logoList[item.image]} alt={item.title} className="w-full rounded-2xl" style={item.style} />
<div className="mt-2 flex flex-col items-center justify-center">
<p className="text-base text-[clamp(12px,1.1vw,16px)] font-medium text-muted-foreground">{item.title}</p>
</div>
</div>
</BlurFade>
)
}

View File

@ -0,0 +1,111 @@
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
}

View File

@ -1,10 +1,11 @@
import { CherryStoreItem } from '@renderer/types/cherryStore'
import { Button } from '@renderer/ui/button'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@renderer/ui/dropdown-menu'
import { Input } from '@renderer/ui/input'
import { cn } from '@renderer/utils'
import { Filter, Grid3X3, List, Search } from 'lucide-react'
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'
@ -21,6 +22,7 @@ interface StoreContentProps {
export function StoreContent({
viewMode,
searchQuery,
selectedCategory, // This prop will drive the component choice
items,
onSearchQueryChange,
onViewModeChange
@ -42,19 +44,18 @@ export function StoreContent({
onChange={(e) => onSearchQueryChange(e.target.value)}
/>
</div>
<DropdownMenu>
{/* <DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Filter className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{/* Add actual filtering logic later */}
<DropdownMenuItem>Most Popular</DropdownMenuItem>
<DropdownMenuItem>Newest</DropdownMenuItem>
<DropdownMenuItem>Highest Rated</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</DropdownMenu> */}
<Button
variant="outline"
size="icon"
@ -90,7 +91,7 @@ export function StoreContent({
<p className="text-center text-muted-foreground">No items found matching your criteria.</p>
</div>
) : viewMode === 'grid' ? (
<GridView items={items} />
<GridView items={items} selectedCategory={selectedCategory} />
) : (
<ListView items={items} />
)}

View File

@ -1,13 +1,21 @@
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 } from '@renderer/ui/sidebar'
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
onSelectCategory: (categoryId: string, subcategoryId: string, row?: SubCategoryItem) => void
}
export function StoreSidebar({
@ -30,44 +38,62 @@ export function StoreSidebar({
<Sidebar className="absolute top-0 left-0 h-full border-r">
<SidebarContent>
<SidebarMenu className="gap-0">
{categories.map((category, index) => (
<Collapsible key={category.id} defaultOpen={index === 0} className="group/collapsible w-full">
<SidebarMenuItem className="w-full px-0 py-0">
<CollapsibleTrigger asChild>
<SidebarMenuButton
variant="outline"
className="rounded-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none">
<span className="truncate">{category.title}</span>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent className="overflow-hidden text-sm transition-all data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
<SidebarMenu className="py-1 pr-1 pl-4">
{category.items.map((subItem) => (
<SidebarMenuItem key={subItem.id}>
<SidebarMenuButton
isActive={category.id === selectedCategory && subItem.id === selectedSubcategory}
className="w-full justify-between"
onClick={() => {
onSelectCategory(category.id, subItem.id, subItem)
}}
size="sm">
<span className="truncate">{subItem.name}</span>
{typeof subItem.count === 'number' && (
<Badge variant="secondary" className="ml-auto shrink-0">
{subItem.count}
</Badge>
)}
</SidebarMenuButton>
</SidebarMenuItem>
))}
{category.items.length === 0 && (
<SidebarMenuItem className="px-3 py-1.5 text-xs text-muted-foreground">No items</SidebarMenuItem>
)}
</SidebarMenu>
</CollapsibleContent>
{categories.map((category, index) =>
category.items?.length ? (
<Collapsible key={category.id} defaultOpen={index === 0} className="group/collapsible w-full">
<SidebarMenuItem className="w-full px-0 py-0">
<CollapsibleTrigger asChild>
<SidebarMenuButton
variant="outline"
className="rounded-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none">
<span className="truncate">{category.title}</span>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent className="overflow-hidden text-sm transition-all data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
<SidebarMenuSub className="py-1 pr-1 pl-4">
{category.items.map((subItem) => (
<SidebarMenuSubItem key={subItem.id}>
<SidebarMenuButton
isActive={category.id === selectedCategory && subItem.id === selectedSubcategory}
className="w-full justify-between"
onClick={() => {
onSelectCategory(category.id, subItem.id, subItem)
}}
size="sm">
<span className="truncate">{subItem.name}</span>
{typeof subItem.count === 'number' && (
<Badge variant="secondary" className="ml-auto shrink-0">
{subItem.count}
</Badge>
)}
</SidebarMenuButton>
</SidebarMenuSubItem>
))}
{/* {category.items.length === 0 && (
<SidebarMenuSubItem className="px-3 py-1.5 text-xs text-muted-foreground">
No items
</SidebarMenuSubItem>
)} */}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
) : (
<SidebarMenuItem key={category.id}>
<SidebarMenuButton
asChild
isActive={category.id === selectedCategory}
variant="outline"
className="rounded-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none"
onClick={() => {
onSelectCategory(category.id, '')
}}>
{/* <item.icon /> */}
<span>{category.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</Collapsible>
))}
)
)}
</SidebarMenu>
</SidebarContent>
</Sidebar>

View File

@ -0,0 +1,38 @@
import { CherryStoreItem } from '@renderer/types/cherryStore'
import { createContext, ReactNode, use, useState } from 'react'
interface ActiveDialog {
type: string
item: CherryStoreItem
}
interface DialogManagerContextType {
activeDialog: ActiveDialog | null
openDialog: (type: string, item: CherryStoreItem) => void
closeDialog: () => void
}
const DialogManagerContext = createContext<DialogManagerContextType | undefined>(undefined)
export const DialogManagerProvider = ({ children }: { children: ReactNode }) => {
const [activeDialog, setActiveDialog] = useState<ActiveDialog | null>(null)
const openDialog = (type: string, item: CherryStoreItem) => {
setActiveDialog({ type, item })
}
const closeDialog = () => {
setActiveDialog(null)
}
return <DialogManagerContext value={{ activeDialog, openDialog, closeDialog }}>{children}</DialogManagerContext>
}
export const useDialogManager = (): DialogManagerContextType => {
const context = use(DialogManagerContext)
if (!context) {
// More robust check
throw new Error('useDialogManager must be used within a DialogManagerProvider')
}
return context
}

View File

@ -8,7 +8,7 @@ import { Download } from 'lucide-react'
import ReactMarkdown from 'react-markdown'
import { v4 as uuid } from 'uuid'
export function ItemDetailDialog({
export default function InstallDialog({
item,
isOpen,
onClose

View File

@ -0,0 +1,36 @@
import { useDialogManager } from './DialogManagerContext'
import InstallDialog from './InstallDialog'
// Import other dialog components here as they are created
// e.g., import ItemDetailsDialog from './ItemDetailsDialog';
export default function DialogManager() {
// Renamed component for clarity
const { activeDialog, closeDialog } = useDialogManager()
if (!activeDialog) {
return null // No dialog is active
}
switch (activeDialog.type) {
case 'install':
return (
<InstallDialog
item={activeDialog.item} // item is guaranteed by activeDialog structure
isOpen={true} // If we are rendering, it means this dialog should be open
onClose={closeDialog}
/>
)
// case 'viewDetails':
// return (
// <ItemDetailsDialog
// item={activeDialog.item}
// isOpen={true}
// onClose={closeDialog}
// />
// );
// Add more cases for other dialog types here
default:
console.warn('Unknown dialog type:', activeDialog.type)
return null
}
}

View File

@ -5,9 +5,13 @@ 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('')
@ -67,10 +71,10 @@ export default function StoreLayout() {
fetchItems()
}, [selectedCategory, selectedSubcategory, searchQuery])
const handleSelectCategory = (categoryId: string, subcategoryId: string, row: SubCategoryItem) => {
const handleSelectCategory = (categoryId: string, subcategoryId: string, row?: SubCategoryItem) => {
setSelectedCategory(categoryId)
setSelectedSubcategory(subcategoryId)
setSearchQuery(row.name)
setSearchQuery(row?.name || '')
}
if (isLoading) {
@ -80,34 +84,38 @@ export default function StoreLayout() {
if (error) {
return <div className="p-4 text-center text-red-500">Error: {error}</div>
}
console.log('categories', categories)
return (
<div className="h-[calc(100vh_-_var(--navbar-height))] w-full">
<Navbar className="h-full">
<NavbarCenter>{t('store.title')}</NavbarCenter>
</Navbar>
<div id="content-container" className="h-full w-full">
<SidebarProvider className="relative h-full min-h-full w-full">
<StoreSidebar
categories={categories}
selectedCategory={selectedCategory}
selectedSubcategory={selectedSubcategory}
onSelectCategory={handleSelectCategory}
/>
{isLoadingItems ? (
<div className="p-4 text-center">Loading items...</div>
) : (
<StoreContent
viewMode={viewMode}
searchQuery={searchQuery}
<DialogManagerProvider>
<div className="h-[calc(100vh_-_var(--navbar-height))] w-full">
<Navbar className="h-full">
<NavbarCenter>{t('store.title')}</NavbarCenter>
</Navbar>
<div id="content-container" className="h-full w-full">
<SidebarProvider className="relative h-full min-h-full w-full">
<StoreSidebar
categories={categories}
selectedCategory={selectedCategory}
items={items}
onSearchQueryChange={setSearchQuery}
onViewModeChange={setViewMode}
selectedSubcategory={selectedSubcategory}
onSelectCategory={handleSelectCategory}
/>
)}
</SidebarProvider>
{isLoadingItems ? (
// TODO: 添加 loading 动画
<div className="p-4 text-center">Loading items...</div>
) : (
<StoreContent
viewMode={viewMode}
searchQuery={searchQuery}
selectedCategory={selectedCategory}
items={items}
onSearchQueryChange={setSearchQuery}
onViewModeChange={setViewMode}
/>
)}
</SidebarProvider>
</div>
<Dialogs />
</div>
</div>
</DialogManagerProvider>
)
}

View File

@ -29,7 +29,7 @@ export interface SubCategoryItem {
}
export interface Category {
id: string
id: CherryStoreType
title: string
items: SubCategoryItem[]
}
@ -40,4 +40,13 @@ export interface AssistantItem extends CherryStoreBaseItem {
prompt?: string
}
export type CherryStoreItem = AssistantItem
export interface MiniAppItem extends CherryStoreBaseItem {
type: CherryStoreType.MINI_APP
url: string
bodered?: boolean
style?: {
padding?: number
}
}
export type CherryStoreItem = AssistantItem | MiniAppItem

View File

@ -5338,7 +5338,7 @@ __metadata:
lucide-react: "npm:^0.509.0"
markdown-it: "npm:^14.1.0"
mime: "npm:^4.0.4"
motion: "npm:^12.11.0"
motion: "npm:^12.12.1"
next-themes: "npm:^0.4.6"
node-stream-zip: "npm:^1.15.0"
npx-scope-finder: "npm:^1.2.0"
@ -9464,12 +9464,12 @@ __metadata:
languageName: node
linkType: hard
"framer-motion@npm:^12.11.0":
version: 12.11.0
resolution: "framer-motion@npm:12.11.0"
"framer-motion@npm:^12.12.1":
version: 12.12.1
resolution: "framer-motion@npm:12.12.1"
dependencies:
motion-dom: "npm:^12.11.0"
motion-utils: "npm:^12.9.4"
motion-dom: "npm:^12.12.1"
motion-utils: "npm:^12.12.1"
tslib: "npm:^2.4.0"
peerDependencies:
"@emotion/is-prop-valid": "*"
@ -9482,7 +9482,7 @@ __metadata:
optional: true
react-dom:
optional: true
checksum: 10c0/369026997d12e51ba5ca12ecf507ccf2108941c9e634d0d57ff93524d56d72dc38cd909ce1272a155d15b92faf0ddf9268942908be29032e36cc535a353fd497
checksum: 10c0/a782ffa3613f35d23b550123e5409504dadd1f708af40d6014969a8c78dae31046ab9c35a44cbb768e20cec837675d1e240e06e01818b250386af6d0ff570593
languageName: node
linkType: hard
@ -13460,27 +13460,27 @@ __metadata:
languageName: node
linkType: hard
"motion-dom@npm:^12.11.0":
version: 12.11.0
resolution: "motion-dom@npm:12.11.0"
"motion-dom@npm:^12.12.1":
version: 12.12.1
resolution: "motion-dom@npm:12.12.1"
dependencies:
motion-utils: "npm:^12.9.4"
checksum: 10c0/9fd7441c38b28560ea2db0f4dbd6f412873e777f5d32e623792cc8ff32c0bbff761f68102115060af81325227cc639e548f6123bdced50722f55bc2abda76b55
motion-utils: "npm:^12.12.1"
checksum: 10c0/d80cdde894d07042e792ae69f47d7b58aa20691d4e4da7ce803af246295855fd59fdbd7a9e0def8554e22fd105c407cc672bd85e0a79a660d523adee7989d61d
languageName: node
linkType: hard
"motion-utils@npm:^12.9.4":
version: 12.9.4
resolution: "motion-utils@npm:12.9.4"
checksum: 10c0/b6783babfd1282ad320585f7cdac9fe7a1f97b39e07d12a500d3709534441bd9d49b556fa1cd838d1bde188570d4ab6b4c5aa9d297f7f5aa9dc16d600c17afdc
"motion-utils@npm:^12.12.1":
version: 12.12.1
resolution: "motion-utils@npm:12.12.1"
checksum: 10c0/880a174769d1be42b46cfb34af81b4a629c068d30d5cf7e07d249fbf2f5121d577482d3ea5bdc1db549c0288733e1e987efecb195fae350995270651559c6697
languageName: node
linkType: hard
"motion@npm:^12.11.0":
version: 12.11.0
resolution: "motion@npm:12.11.0"
"motion@npm:^12.12.1":
version: 12.12.1
resolution: "motion@npm:12.12.1"
dependencies:
framer-motion: "npm:^12.11.0"
framer-motion: "npm:^12.12.1"
tslib: "npm:^2.4.0"
peerDependencies:
"@emotion/is-prop-valid": "*"
@ -13493,7 +13493,7 @@ __metadata:
optional: true
react-dom:
optional: true
checksum: 10c0/1066de76a5d8a7a6898d8b47afe4363098466c67ed79cb989c0f05c11aa62a2cc83f61cc283ec07b452ae0622f6cd1c4584f1a0a68174798d67ba8811c52ac11
checksum: 10c0/1083c6138486c71d946379bb0d7559e11ab27f6c4ed2aed1d874c0b00fc762d5064bcf98079a313fcb8fc923c2457213d35fa7ca7e2a2bd53e71bef57cb94948
languageName: node
linkType: hard