返回 AiToEarn
PluginEntry.tsx
1 /**
2 * PluginEntry - 插件入口组件
3 * 根据插件状态显示不同颜色
4 */
5
6 'use client'
7
8 import type { SidebarCommonProps } from '../../types'
9 import { Puzzle } from 'lucide-react'
10 import { useShallow } from 'zustand/react/shallow'
11 import { useTransClient } from '@/app/i18n/client'
12 import { PluginModal, PluginUpdatePopover } from '@/components/Plugin'
13 import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
14 import { usePluginStore } from '@/store/plugin'
15 import { PluginStatus } from '@/store/plugin/types/baseTypes'
16 import { cn } from '@/utils/className'
17
18 export function PluginEntry({ collapsed }: SidebarCommonProps) {
19 const { t } = useTransClient('common')
20
21 const { hostAccessGranted, pluginNeedsUpdate, pluginStatus, pluginVersion, pluginModalVisible, openPluginModal, closePluginModal } = usePluginStore(
22 useShallow(state => ({
23 hostAccessGranted: state.hostAccessGranted,
24 pluginNeedsUpdate: state.pluginNeedsUpdate,
25 pluginStatus: state.status,
26 pluginVersion: state.pluginVersion,
27 pluginModalVisible: state.pluginModalVisible,
28 openPluginModal: state.openPluginModal,
29 closePluginModal: state.closePluginModal,
30 })),
31 )
32
33 // 根据插件状态返回对应的颜色和状态文本
34 const getStatusInfo = () => {
35 if (pluginStatus === PluginStatus.READY && pluginNeedsUpdate) {
36 return {
37 iconColor: 'text-warning',
38 dotColor: 'bg-warning',
39 statusText: t('pluginStatus.updateAvailable'),
40 statusColor: 'text-warning',
41 }
42 }
43
44 switch (pluginStatus) {
45 case PluginStatus.READY:
46 return {
47 iconColor: 'text-success',
48 dotColor: 'bg-success',
49 statusText: t('pluginStatus.ready'),
50 statusColor: 'text-success',
51 }
52 case PluginStatus.INSTALLED_NO_PERMISSION:
53 return {
54 iconColor: 'text-warning',
55 dotColor: 'bg-warning',
56 statusText: hostAccessGranted === false
57 ? t('pluginStatus.siteAccessRequired')
58 : t('pluginStatus.noPermission'),
59 statusColor: 'text-warning',
60 }
61 case PluginStatus.CHECKING:
62 return {
63 iconColor: 'text-info',
64 dotColor: 'bg-info animate-pulse',
65 statusText: t('pluginStatus.checking'),
66 statusColor: 'text-info',
67 }
68 case PluginStatus.NOT_INSTALLED:
69 case PluginStatus.UNKNOWN:
70 default:
71 return {
72 iconColor: 'text-muted-foreground/70',
73 dotColor: 'bg-muted-foreground/70',
74 statusText: t('pluginStatus.notInstalled'),
75 statusColor: 'text-muted-foreground/70',
76 }
77 }
78 }
79
80 const { iconColor, dotColor, statusText, statusColor } = getStatusInfo()
81 const statusNode = (() => {
82 if (collapsed)
83 return null
84
85 if (pluginNeedsUpdate && pluginVersion) {
86 return (
87 <PluginUpdatePopover currentVersion={pluginVersion} side="right" align="end">
88 <span
89 className={cn('text-xs', statusColor)}
90 data-testid="sidebar-plugin-status"
91 >
92 {statusText}
93 </span>
94 </PluginUpdatePopover>
95 )
96 }
97
98 return (
99 <span className={cn('text-xs', statusColor)} data-testid="sidebar-plugin-status">
100 {statusText}
101 </span>
102 )
103 })()
104
105 const content = (
106 <button
107 onClick={openPluginModal}
108 data-testid="sidebar-plugin"
109 className={cn(
110 'flex w-full cursor-pointer items-center rounded-lg border-none bg-transparent text-muted-foreground transition-colors hover:bg-brand-cyan/10 hover:text-brand-cyan',
111 collapsed ? 'h-9 w-9 justify-center' : 'justify-between px-3 py-2',
112 )}
113 >
114 <div className="flex items-center gap-2">
115 <div className="relative">
116 <Puzzle size={18} className={iconColor} />
117 {/* 状态指示点 */}
118 <span
119 className={cn(
120 'absolute -right-0.5 -top-0.5 h-2 w-2 rounded-full border border-background',
121 dotColor,
122 )}
123 />
124 </div>
125 {!collapsed && <span className="text-sm">{t('plugin')}</span>}
126 </div>
127 {statusNode}
128 </button>
129 )
130
131 return (
132 <>
133 {collapsed ? (
134 <TooltipProvider>
135 <Tooltip>
136 <TooltipTrigger asChild>{content}</TooltipTrigger>
137 <TooltipContent side="right">
138 <p>
139 {t('plugin')}
140 {' '}
141 -
142 {statusText}
143 </p>
144 </TooltipContent>
145 </Tooltip>
146 </TooltipProvider>
147 ) : (
148 content
149 )}
150
151 {/* 插件状态弹框 */}
152 <PluginModal visible={pluginModalVisible} onClose={closePluginModal} />
153 </>
154 )
155 }
156
156 lines Plain Text