@nextclaw/ui 0.9.16 → 0.9.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -1
- package/dist/assets/ChannelsList-D75wfbDS.js +8 -0
- package/dist/assets/{ChatPage-4niJBFCu.js → ChatPage-gWZ3rDTy.js} +1 -1
- package/dist/assets/{DocBrowser-DpXDQNhb.js → DocBrowser-CebTdor0.js} +1 -1
- package/dist/assets/{LogoBadge-nqabOtgk.js → LogoBadge-gdbraoaZ.js} +1 -1
- package/dist/assets/{MarketplacePage-CrkTftqZ.js → MarketplacePage-C-zz0lBT.js} +1 -1
- package/dist/assets/{McpMarketplacePage-DH1qKJqo.js → McpMarketplacePage-tfpLh6Zz.js} +1 -1
- package/dist/assets/{ModelConfig-CrrxPK_y.js → ModelConfig-j74dn-5k.js} +1 -1
- package/dist/assets/{ProvidersList-BG36JlSJ.js → ProvidersList-BGI9EgVV.js} +1 -1
- package/dist/assets/{RemoteAccessPage-Dcj2Pzpt.js → RemoteAccessPage-CusGQmZE.js} +1 -1
- package/dist/assets/{RuntimeConfig-BrxgUzjJ.js → RuntimeConfig-pmhW8ifz.js} +1 -1
- package/dist/assets/{SearchConfig-D-NLwowp.js → SearchConfig-rrD2_F5u.js} +1 -1
- package/dist/assets/{SecretsConfig-DjNqBB05.js → SecretsConfig-D7onb-hv.js} +1 -1
- package/dist/assets/{SessionsConfig-DdlsWXQc.js → SessionsConfig-m-6RSeja.js} +1 -1
- package/dist/assets/{chat-message-B7THd1Mh.js → chat-message-BO-s2mvl.js} +1 -1
- package/dist/assets/{index-CgqD0Jfg.js → index-BsL1YIJ1.js} +5 -5
- package/dist/assets/{index-UC08nscf.css → index-C63mHRbE.css} +1 -1
- package/dist/assets/{label-B-TkPZRF.js → label-CDSYExvV.js} +1 -1
- package/dist/assets/{page-layout-BTVBRo6H.js → page-layout-BMCVAnQM.js} +1 -1
- package/dist/assets/{popover-DBZvpGcL.js → popover-DfywyUDH.js} +1 -1
- package/dist/assets/{security-config-DotxwVFR.js → security-config-BU-K2EOM.js} +1 -1
- package/dist/assets/{skeleton-DGtduHZV.js → skeleton-Cg9CRkOt.js} +1 -1
- package/dist/assets/{status-dot-BCUTVN2R.js → status-dot-2vau2Xtc.js} +1 -1
- package/dist/assets/{switch-Bp2mda29.js → switch-CJRPF2V6.js} +1 -1
- package/dist/assets/{tabs-custom-BE8yZ2kE.js → tabs-custom-B-2uSCfW.js} +1 -1
- package/dist/assets/{useConfirmDialog-DCy-eYnV.js → useConfirmDialog-CfOpdypA.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +7 -6
- package/src/components/chat/ChatSidebar.test.tsx +1 -1
- package/src/components/chat/chat-sidebar-session-item.tsx +0 -1
- package/src/components/config/ChannelsList.test.tsx +8 -0
- package/src/components/config/weixin-channel-auth-section.test.tsx +90 -0
- package/src/components/config/weixin-channel-auth-section.tsx +60 -1
- package/src/components/layout/Sidebar.tsx +128 -120
- package/src/components/layout/sidebar.layout.test.tsx +99 -0
- package/src/qrcode.d.ts +10 -0
- package/dist/assets/ChannelsList-DhM0gvDV.js +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useQueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { toDataURL } from 'qrcode';
|
|
3
4
|
import { Button } from '@/components/ui/button';
|
|
4
5
|
import { formatDateTime, t } from '@/lib/i18n';
|
|
5
6
|
import { cn } from '@/lib/utils';
|
|
@@ -51,12 +52,61 @@ export function WeixinChannelAuthSection({
|
|
|
51
52
|
const pollChannelAuth = usePollChannelAuth();
|
|
52
53
|
const [activeSession, setActiveSession] = useState<ChannelAuthStartResult | null>(null);
|
|
53
54
|
const [authState, setAuthState] = useState<ChannelAuthPollResult | null>(null);
|
|
55
|
+
const [qrDataUrl, setQrDataUrl] = useState<string | null>(null);
|
|
54
56
|
|
|
55
57
|
const connectedAccountIds = useMemo(() => resolveConnectedAccountIds(channelConfig), [channelConfig]);
|
|
56
58
|
const primaryAccountId = connectedAccountIds[0];
|
|
57
59
|
const baseUrl = resolveBaseUrl(formData, channelConfig);
|
|
58
60
|
const hasConnectedAccount = connectedAccountIds.length > 0;
|
|
59
61
|
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!hasConnectedAccount) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setActiveSession(null);
|
|
68
|
+
setAuthState((prev) => {
|
|
69
|
+
if (prev?.status === 'authorized') {
|
|
70
|
+
return prev;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
channel: 'weixin',
|
|
74
|
+
status: 'authorized',
|
|
75
|
+
message: t('weixinAuthAuthorized'),
|
|
76
|
+
accountId: primaryAccountId ?? null
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}, [hasConnectedAccount, primaryAccountId]);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!activeSession) {
|
|
83
|
+
setQrDataUrl(null);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let cancelled = false;
|
|
88
|
+
|
|
89
|
+
void toDataURL(activeSession.qrCodeUrl, {
|
|
90
|
+
errorCorrectionLevel: 'M',
|
|
91
|
+
margin: 1,
|
|
92
|
+
width: 480
|
|
93
|
+
})
|
|
94
|
+
.then((dataUrl: string) => {
|
|
95
|
+
if (!cancelled) {
|
|
96
|
+
setQrDataUrl(dataUrl);
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.catch(() => {
|
|
100
|
+
if (!cancelled) {
|
|
101
|
+
setQrDataUrl(null);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return () => {
|
|
106
|
+
cancelled = true;
|
|
107
|
+
};
|
|
108
|
+
}, [activeSession]);
|
|
109
|
+
|
|
60
110
|
useEffect(() => {
|
|
61
111
|
if (!activeSession) {
|
|
62
112
|
return;
|
|
@@ -210,7 +260,16 @@ export function WeixinChannelAuthSection({
|
|
|
210
260
|
{activeSession ? (
|
|
211
261
|
<div className="space-y-3">
|
|
212
262
|
<div className="overflow-hidden rounded-2xl border border-gray-100 bg-white p-3">
|
|
213
|
-
|
|
263
|
+
{qrDataUrl ? (
|
|
264
|
+
<img src={qrDataUrl} alt={t('weixinAuthQrAlt')} className="mx-auto aspect-square w-full max-w-[240px] object-contain" />
|
|
265
|
+
) : (
|
|
266
|
+
<div className="flex aspect-square w-full items-center justify-center rounded-xl bg-gray-50 text-gray-500">
|
|
267
|
+
<div className="flex flex-col items-center gap-2 text-center">
|
|
268
|
+
<Loader2 className="h-5 w-5 animate-spin" />
|
|
269
|
+
<p className="text-xs">{t('weixinAuthStarting')}</p>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
214
273
|
</div>
|
|
215
274
|
<div className="space-y-1 text-xs text-gray-500">
|
|
216
275
|
<p>{authState?.message || activeSession.note || t('weixinAuthScanPrompt')}</p>
|
|
@@ -122,147 +122,155 @@ export function Sidebar({ mode }: SidebarProps) {
|
|
|
122
122
|
const navItems = mode === 'main' ? mainNavItems : settingsNavItems;
|
|
123
123
|
|
|
124
124
|
return (
|
|
125
|
-
<aside className="w-[240px] shrink-0 flex flex-col
|
|
125
|
+
<aside className="w-[240px] shrink-0 flex h-full min-h-0 flex-col overflow-hidden bg-secondary px-4 py-6">
|
|
126
126
|
{mode === 'settings' ? (
|
|
127
|
-
<div className="px-2
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
<div className="shrink-0 px-2 pb-3">
|
|
128
|
+
<div
|
|
129
|
+
className="flex items-center gap-2 px-1 py-1"
|
|
130
|
+
data-testid="settings-sidebar-header"
|
|
131
131
|
>
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
132
|
+
<NavLink
|
|
133
|
+
to="/chat"
|
|
134
|
+
className="group inline-flex min-w-0 items-center gap-1.5 rounded-lg px-1 py-1 text-[12px] font-medium text-gray-500 transition-colors hover:text-gray-900"
|
|
135
|
+
>
|
|
136
|
+
<ArrowLeft className="h-3.5 w-3.5 shrink-0 text-gray-400 group-hover:text-gray-700" />
|
|
137
|
+
<span className="truncate">{t('backToMain')}</span>
|
|
138
|
+
</NavLink>
|
|
139
|
+
<span className="h-4 w-px shrink-0 bg-[#dddfe6]" aria-hidden="true" />
|
|
140
|
+
<h1 className="truncate text-[15px] font-semibold tracking-[-0.01em] text-gray-800">{t('settings')}</h1>
|
|
140
141
|
</div>
|
|
141
142
|
</div>
|
|
142
143
|
) : (
|
|
143
|
-
<div className="px-2
|
|
144
|
+
<div className="shrink-0 px-2 pb-8">
|
|
144
145
|
<BrandHeader className="flex items-center gap-2.5 cursor-pointer" />
|
|
145
146
|
</div>
|
|
146
147
|
)}
|
|
147
148
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
150
|
+
{/* Navigation */}
|
|
151
|
+
<nav className="custom-scrollbar min-h-0 flex-1 overflow-y-auto pr-1">
|
|
152
|
+
<ul className="space-y-1 pb-4">
|
|
153
|
+
{navItems.map((item) => {
|
|
154
|
+
const Icon = item.icon;
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
156
|
+
return (
|
|
157
|
+
<li key={item.target}>
|
|
158
|
+
<NavLink
|
|
159
|
+
to={item.target}
|
|
160
|
+
className={({ isActive }) =>
|
|
161
|
+
cn(
|
|
162
|
+
'group w-full flex items-center gap-3 rounded-xl px-3 py-2.5 text-[14px] font-medium transition-all duration-base',
|
|
163
|
+
isActive
|
|
164
|
+
? 'bg-gray-200 text-gray-900 font-semibold shadow-sm'
|
|
165
|
+
: 'text-gray-600 hover:bg-gray-200/60 hover:text-gray-900'
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
>
|
|
169
|
+
{({ isActive }) => (
|
|
170
|
+
<>
|
|
171
|
+
<Icon
|
|
172
|
+
className={cn(
|
|
173
|
+
'h-[17px] w-[17px] transition-colors',
|
|
174
|
+
isActive ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-800'
|
|
175
|
+
)}
|
|
176
|
+
/>
|
|
177
|
+
<span className="flex-1 text-left">{item.label}</span>
|
|
178
|
+
</>
|
|
179
|
+
)}
|
|
180
|
+
</NavLink>
|
|
181
|
+
</li>
|
|
176
182
|
);
|
|
177
183
|
})}
|
|
178
184
|
</ul>
|
|
179
|
-
|
|
185
|
+
</nav>
|
|
180
186
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
<
|
|
190
|
-
|
|
191
|
-
<div className="
|
|
192
|
-
<p className="truncate text-[14px] font-medium text-gray-
|
|
193
|
-
{
|
|
187
|
+
{/* Footer actions stay reachable while the nav scrolls independently. */}
|
|
188
|
+
<div className="mt-3 shrink-0 border-t border-[#dde0ea] bg-secondary pt-3">
|
|
189
|
+
{mode === 'settings' ? (
|
|
190
|
+
<button
|
|
191
|
+
onClick={() => presenter.accountManager.openAccountPanel()}
|
|
192
|
+
className="mb-2 w-full rounded-xl px-3 py-2.5 text-left text-gray-600 transition-all duration-base hover:bg-[#e4e7ef] hover:text-gray-900"
|
|
193
|
+
data-testid="settings-sidebar-account-entry"
|
|
194
|
+
>
|
|
195
|
+
<div className="flex items-start gap-3">
|
|
196
|
+
<KeyRound className="mt-0.5 h-[17px] w-[17px] shrink-0 text-gray-400" />
|
|
197
|
+
<div className="min-w-0 flex-1">
|
|
198
|
+
<p className="truncate text-[14px] font-medium text-gray-600">
|
|
199
|
+
{t('remoteAccountEntryManage')}
|
|
200
|
+
</p>
|
|
201
|
+
<p className="mt-1 truncate text-xs text-gray-500">
|
|
202
|
+
{accountConnected ? accountEmail || t('remoteAccountEntryConnected') : t('remoteAccountEntryDisconnected')}
|
|
194
203
|
</p>
|
|
195
204
|
</div>
|
|
196
|
-
<p className="mt-1 truncate text-xs text-gray-500">
|
|
197
|
-
{accountConnected ? t('remoteAccountEntryConnected') : t('remoteAccountEntryDisconnected')}
|
|
198
|
-
</p>
|
|
199
205
|
</div>
|
|
206
|
+
</button>
|
|
207
|
+
) : null}
|
|
208
|
+
{mode === 'main' && (
|
|
209
|
+
<div className="mb-2">
|
|
210
|
+
<NavLink
|
|
211
|
+
to="/settings"
|
|
212
|
+
className={({ isActive }) =>
|
|
213
|
+
cn(
|
|
214
|
+
'group w-full flex items-center gap-3 rounded-xl px-3 py-2.5 text-[14px] font-medium transition-all duration-base',
|
|
215
|
+
isActive
|
|
216
|
+
? 'bg-gray-200 text-gray-900 font-semibold shadow-sm'
|
|
217
|
+
: 'text-gray-600 hover:bg-[#e4e7ef] hover:text-gray-900'
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
>
|
|
221
|
+
{({ isActive }) => (
|
|
222
|
+
<>
|
|
223
|
+
<Settings className={cn('h-[17px] w-[17px] transition-colors', isActive ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-800')} />
|
|
224
|
+
<span className="flex-1 text-left">{t('settings')}</span>
|
|
225
|
+
</>
|
|
226
|
+
)}
|
|
227
|
+
</NavLink>
|
|
200
228
|
</div>
|
|
201
|
-
|
|
202
|
-
) : null}
|
|
203
|
-
{mode === 'main' && (
|
|
229
|
+
)}
|
|
204
230
|
<div className="mb-2">
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
</
|
|
231
|
+
<Select value={theme} onValueChange={(value) => handleThemeSwitch(value as UiTheme)}>
|
|
232
|
+
<SelectTrigger className="w-full h-auto rounded-xl border-0 bg-transparent px-3 py-2.5 text-[14px] font-medium text-gray-600 shadow-none hover:bg-[#e4e7ef] focus:ring-0">
|
|
233
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
234
|
+
<Palette className="h-[17px] w-[17px] text-gray-400" />
|
|
235
|
+
<span className="text-left">{t('theme')}</span>
|
|
236
|
+
</div>
|
|
237
|
+
<span className="ml-auto text-xs text-gray-500">{currentThemeLabel}</span>
|
|
238
|
+
</SelectTrigger>
|
|
239
|
+
<SelectContent>
|
|
240
|
+
{THEME_OPTIONS.map((option) => (
|
|
241
|
+
<SelectItem key={option.value} value={option.value} className="text-xs">
|
|
242
|
+
{t(option.labelKey)}
|
|
243
|
+
</SelectItem>
|
|
244
|
+
))}
|
|
245
|
+
</SelectContent>
|
|
246
|
+
</Select>
|
|
221
247
|
</div>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
</
|
|
239
|
-
</
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
</div>
|
|
248
|
-
<span className="ml-auto text-xs text-gray-500">{currentLanguageLabel}</span>
|
|
249
|
-
</SelectTrigger>
|
|
250
|
-
<SelectContent>
|
|
251
|
-
{LANGUAGE_OPTIONS.map((option) => (
|
|
252
|
-
<SelectItem key={option.value} value={option.value} className="text-xs">
|
|
253
|
-
{option.label}
|
|
254
|
-
</SelectItem>
|
|
255
|
-
))}
|
|
256
|
-
</SelectContent>
|
|
257
|
-
</Select>
|
|
248
|
+
<div className="mb-2">
|
|
249
|
+
<Select value={language} onValueChange={(value) => handleLanguageSwitch(value as I18nLanguage)}>
|
|
250
|
+
<SelectTrigger className="w-full h-auto rounded-xl border-0 bg-transparent px-3 py-2.5 text-[14px] font-medium text-gray-600 shadow-none hover:bg-[#e4e7ef] focus:ring-0">
|
|
251
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
252
|
+
<Languages className="h-[17px] w-[17px] text-gray-400" />
|
|
253
|
+
<span className="text-left">{t('language')}</span>
|
|
254
|
+
</div>
|
|
255
|
+
<span className="ml-auto text-xs text-gray-500">{currentLanguageLabel}</span>
|
|
256
|
+
</SelectTrigger>
|
|
257
|
+
<SelectContent>
|
|
258
|
+
{LANGUAGE_OPTIONS.map((option) => (
|
|
259
|
+
<SelectItem key={option.value} value={option.value} className="text-xs">
|
|
260
|
+
{option.label}
|
|
261
|
+
</SelectItem>
|
|
262
|
+
))}
|
|
263
|
+
</SelectContent>
|
|
264
|
+
</Select>
|
|
265
|
+
</div>
|
|
266
|
+
<button
|
|
267
|
+
onClick={() => docBrowser.open(undefined, { kind: 'docs', newTab: true, title: 'Docs' })}
|
|
268
|
+
className="flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-[14px] font-medium text-gray-600 transition-all duration-base hover:bg-[#e4e7ef] hover:text-gray-800"
|
|
269
|
+
>
|
|
270
|
+
<BookOpen className="h-[17px] w-[17px] text-gray-400" />
|
|
271
|
+
<span className="flex-1 text-left">{t('docBrowserHelp')}</span>
|
|
272
|
+
</button>
|
|
258
273
|
</div>
|
|
259
|
-
<button
|
|
260
|
-
onClick={() => docBrowser.open(undefined, { kind: 'docs', newTab: true, title: 'Docs' })}
|
|
261
|
-
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-[14px] font-medium transition-all duration-base text-gray-600 hover:bg-[#e4e7ef] hover:text-gray-800"
|
|
262
|
-
>
|
|
263
|
-
<BookOpen className="h-[17px] w-[17px] text-gray-400" />
|
|
264
|
-
<span className="flex-1 text-left">{t('docBrowserHelp')}</span>
|
|
265
|
-
</button>
|
|
266
274
|
</div>
|
|
267
275
|
</aside>
|
|
268
276
|
);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { Sidebar } from '@/components/layout/Sidebar';
|
|
5
|
+
|
|
6
|
+
const mocks = vi.hoisted(() => ({
|
|
7
|
+
openAccountPanel: vi.fn(),
|
|
8
|
+
docOpen: vi.fn(),
|
|
9
|
+
remoteStatus: {
|
|
10
|
+
data: {
|
|
11
|
+
account: {
|
|
12
|
+
loggedIn: true,
|
|
13
|
+
email: 'user@example.com'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock('@/components/doc-browser', () => ({
|
|
20
|
+
useDocBrowser: () => ({
|
|
21
|
+
open: mocks.docOpen
|
|
22
|
+
})
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('@/presenter/app-presenter-context', () => ({
|
|
26
|
+
useAppPresenter: () => ({
|
|
27
|
+
accountManager: {
|
|
28
|
+
openAccountPanel: mocks.openAccountPanel
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
vi.mock('@/hooks/useRemoteAccess', () => ({
|
|
34
|
+
useRemoteStatus: () => mocks.remoteStatus
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
vi.mock('@/components/providers/I18nProvider', () => ({
|
|
38
|
+
useI18n: () => ({
|
|
39
|
+
language: 'en',
|
|
40
|
+
setLanguage: vi.fn()
|
|
41
|
+
})
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
vi.mock('@/components/providers/ThemeProvider', () => ({
|
|
45
|
+
useTheme: () => ({
|
|
46
|
+
theme: 'warm',
|
|
47
|
+
setTheme: vi.fn()
|
|
48
|
+
})
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
describe('Sidebar', () => {
|
|
52
|
+
it('keeps the settings sidebar bounded and lets the navigation scroll independently', () => {
|
|
53
|
+
const { container } = render(
|
|
54
|
+
<MemoryRouter initialEntries={['/model']}>
|
|
55
|
+
<Sidebar mode="settings" />
|
|
56
|
+
</MemoryRouter>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const aside = container.querySelector('aside');
|
|
60
|
+
const nav = container.querySelector('nav');
|
|
61
|
+
|
|
62
|
+
expect(aside?.className).toContain('min-h-0');
|
|
63
|
+
expect(aside?.className).toContain('overflow-hidden');
|
|
64
|
+
expect(nav?.className).toContain('flex-1');
|
|
65
|
+
expect(nav?.className).toContain('min-h-0');
|
|
66
|
+
expect(nav?.className).toContain('overflow-y-auto');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('uses a compact single-row header for settings mode', () => {
|
|
70
|
+
render(
|
|
71
|
+
<MemoryRouter initialEntries={['/model']}>
|
|
72
|
+
<Sidebar mode="settings" />
|
|
73
|
+
</MemoryRouter>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const header = screen.getByTestId('settings-sidebar-header');
|
|
77
|
+
|
|
78
|
+
expect(header).toBeTruthy();
|
|
79
|
+
expect(screen.getByRole('heading', { name: 'Settings' })).toBeTruthy();
|
|
80
|
+
expect(screen.getByRole('link', { name: 'Back to Main' })).toBeTruthy();
|
|
81
|
+
expect(header.className).not.toContain('bg-white');
|
|
82
|
+
expect(header.className).not.toContain('rounded-2xl');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('renders the account entry with the same neutral visual tone as other footer items', () => {
|
|
86
|
+
render(
|
|
87
|
+
<MemoryRouter initialEntries={['/model']}>
|
|
88
|
+
<Sidebar mode="settings" />
|
|
89
|
+
</MemoryRouter>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const accountEntry = screen.getByTestId('settings-sidebar-account-entry');
|
|
93
|
+
|
|
94
|
+
expect(accountEntry).toBeTruthy();
|
|
95
|
+
expect(screen.getByText('Account and Device Entry')).toBeTruthy();
|
|
96
|
+
expect(screen.getByText('user@example.com')).toBeTruthy();
|
|
97
|
+
expect(accountEntry.className).toContain('text-gray-600');
|
|
98
|
+
});
|
|
99
|
+
});
|
package/src/qrcode.d.ts
ADDED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as A,j as t,X as se,K as ne,aq as le,b1 as re,b2 as oe,b3 as ie,a0 as ce,g as G,f as me,b4 as _,ae as de,b5 as ue,ai as H,e as k,a3 as pe,i as xe,G as be,H as ye}from"./vendor-DJt0Azq5.js";import{Z as he,t as e,c as T,I as F,S as ge,e as we,f as fe,g as je,h as ve,$ as K,B as U,n as Ae,u as z,a as J,b as Q,a0 as Ne,a1 as Ce}from"./index-CgqD0Jfg.js";import{S as W}from"./status-dot-BCUTVN2R.js";import{L as Y}from"./LogoBadge-nqabOtgk.js";import{h as D}from"./config-hints-CApS3K_7.js";import{c as Se,b as ke,a as Ie,C as Pe}from"./config-layout-BHnOoweL.js";import{L as Te}from"./label-B-TkPZRF.js";import{S as Fe}from"./switch-Bp2mda29.js";import{T as Me}from"./tabs-custom-BE8yZ2kE.js";import{P as Le,a as Ue}from"./page-layout-BTVBRo6H.js";function V(a){var o,r;const n=he();return((o=a.tutorialUrls)==null?void 0:o[n])||((r=a.tutorialUrls)==null?void 0:r.default)||a.tutorialUrl}const De={telegram:"telegram.svg",slack:"slack.svg",discord:"discord.svg",whatsapp:"whatsapp.svg",qq:"qq.svg",feishu:"feishu.svg",dingtalk:"dingtalk.svg",wecom:"wecom.svg",weixin:"weixin.svg",mochat:"mochat.svg",email:"email.svg"};function Ee(a,n){const l=n.toLowerCase(),o=a[l];return o?`/logos/${o}`:null}function X(a){return Ee(De,a)}function Oe({value:a,onChange:n,className:l,placeholder:o=""}){const[r,u]=A.useState(""),i=m=>{m.key==="Enter"&&r.trim()?(m.preventDefault(),n([...a,r.trim()]),u("")):m.key==="Backspace"&&!r&&a.length>0&&n(a.slice(0,-1))},s=m=>{n(a.filter((w,y)=>y!==m))};return t.jsxs("div",{className:T("flex flex-wrap gap-2 p-2 border rounded-md min-h-[42px]",l),children:[a.map((m,w)=>t.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-primary text-primary-foreground rounded text-sm",children:[m,t.jsx("button",{type:"button",onClick:()=>s(w),className:"hover:text-red-300 transition-colors",children:t.jsx(se,{className:"h-3 w-3"})})]},w)),t.jsx("input",{type:"text",value:r,onChange:m=>u(m.target.value),onKeyDown:i,className:"flex-1 outline-none min-w-[100px] bg-transparent text-sm",placeholder:o||e("enterTag")})]})}function _e(a){return a.includes("token")||a.includes("secret")||a.includes("password")?t.jsx(ne,{className:"h-3.5 w-3.5 text-gray-500"}):a.includes("url")||a.includes("host")?t.jsx(le,{className:"h-3.5 w-3.5 text-gray-500"}):a.includes("email")||a.includes("mail")?t.jsx(re,{className:"h-3.5 w-3.5 text-gray-500"}):a.includes("id")||a.includes("from")?t.jsx(oe,{className:"h-3.5 w-3.5 text-gray-500"}):a==="enabled"||a==="consentGranted"?t.jsx(ie,{className:"h-3.5 w-3.5 text-gray-500"}):t.jsx(ce,{className:"h-3.5 w-3.5 text-gray-500"})}function q({channelName:a,fields:n,formData:l,jsonDrafts:o,setJsonDrafts:r,updateField:u,uiHints:i}){return t.jsx(t.Fragment,{children:n.map(s=>{const m=D(`channels.${a}.${s.name}`,i),w=(m==null?void 0:m.label)??s.label,y=m==null?void 0:m.placeholder;return t.jsxs("div",{className:"space-y-2.5",children:[t.jsxs(Te,{htmlFor:s.name,className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[_e(s.name),w]}),s.type==="boolean"&&t.jsxs("div",{className:"flex items-center justify-between rounded-xl bg-gray-50 p-3",children:[t.jsx("span",{className:"text-sm text-gray-500",children:l[s.name]?e("enabled"):e("disabled")}),t.jsx(Fe,{id:s.name,checked:l[s.name]||!1,onCheckedChange:c=>u(s.name,c),className:"data-[state=checked]:bg-emerald-500"})]}),(s.type==="text"||s.type==="email")&&t.jsx(F,{id:s.name,type:s.type,value:l[s.name]||"",onChange:c=>u(s.name,c.target.value),placeholder:y,className:"rounded-xl"}),s.type==="password"&&t.jsx(F,{id:s.name,type:"password",value:l[s.name]||"",onChange:c=>u(s.name,c.target.value),placeholder:y??e("leaveBlankToKeepUnchanged"),className:"rounded-xl"}),s.type==="number"&&t.jsx(F,{id:s.name,type:"number",value:l[s.name]||0,onChange:c=>u(s.name,parseInt(c.target.value,10)||0),placeholder:y,className:"rounded-xl"}),s.type==="tags"&&t.jsx(Oe,{value:l[s.name]||[],onChange:c=>u(s.name,c)}),s.type==="select"&&t.jsxs(ge,{value:l[s.name]||"",onValueChange:c=>u(s.name,c),children:[t.jsx(we,{className:"rounded-xl",children:t.jsx(fe,{})}),t.jsx(je,{children:(s.options??[]).map(c=>t.jsx(ve,{value:c.value,children:c.label},c.value))})]}),s.type==="json"&&t.jsx("textarea",{id:s.name,value:o[s.name]??"{}",onChange:c=>r(f=>({...f,[s.name]:c.target.value})),className:"min-h-[120px] w-full resize-none rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"})]},s.name)})})}const B=[{value:"pairing",label:"pairing"},{value:"allowlist",label:"allowlist"},{value:"open",label:"open"},{value:"disabled",label:"disabled"}],R=[{value:"open",label:"open"},{value:"allowlist",label:"allowlist"},{value:"disabled",label:"disabled"}],qe=[{value:"off",label:"off"},{value:"partial",label:"partial"},{value:"block",label:"block"},{value:"progress",label:"progress"}];function $(){return{telegram:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"token",type:"password",label:e("botToken")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"proxy",type:"text",label:e("proxy")},{name:"accountId",type:"text",label:e("accountId")},{name:"dmPolicy",type:"select",label:e("dmPolicy"),options:B},{name:"groupPolicy",type:"select",label:e("groupPolicy"),options:R},{name:"groupAllowFrom",type:"tags",label:e("groupAllowFrom")},{name:"requireMention",type:"boolean",label:e("requireMention")},{name:"mentionPatterns",type:"tags",label:e("mentionPatterns")},{name:"groups",type:"json",label:e("groupRulesJson")}],discord:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"token",type:"password",label:e("botToken")},{name:"allowBots",type:"boolean",label:e("allowBotMessages")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"gatewayUrl",type:"text",label:e("gatewayUrl")},{name:"intents",type:"number",label:e("intents")},{name:"proxy",type:"text",label:e("proxy")},{name:"mediaMaxMb",type:"number",label:e("attachmentMaxSizeMb")},{name:"streaming",type:"select",label:e("streamingMode"),options:qe},{name:"draftChunk",type:"json",label:e("draftChunkingJson")},{name:"textChunkLimit",type:"number",label:e("textChunkLimit")},{name:"accountId",type:"text",label:e("accountId")},{name:"dmPolicy",type:"select",label:e("dmPolicy"),options:B},{name:"groupPolicy",type:"select",label:e("groupPolicy"),options:R},{name:"groupAllowFrom",type:"tags",label:e("groupAllowFrom")},{name:"requireMention",type:"boolean",label:e("requireMention")},{name:"mentionPatterns",type:"tags",label:e("mentionPatterns")},{name:"groups",type:"json",label:e("groupRulesJson")}],whatsapp:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"bridgeUrl",type:"text",label:e("bridgeUrl")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],feishu:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"appId",type:"text",label:e("appId")},{name:"appSecret",type:"password",label:e("appSecret")},{name:"encryptKey",type:"password",label:e("encryptKey")},{name:"verificationToken",type:"password",label:e("verificationToken")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],dingtalk:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"clientId",type:"text",label:e("clientId")},{name:"clientSecret",type:"password",label:e("clientSecret")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],wecom:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"corpId",type:"text",label:e("corpId")},{name:"agentId",type:"text",label:e("agentId")},{name:"secret",type:"password",label:e("secret")},{name:"token",type:"password",label:e("token")},{name:"callbackPort",type:"number",label:e("callbackPort")},{name:"callbackPath",type:"text",label:e("callbackPath")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],weixin:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"defaultAccountId",type:"text",label:e("defaultAccountId")},{name:"baseUrl",type:"text",label:e("baseUrl")},{name:"pollTimeoutMs",type:"number",label:e("pollTimeoutMs")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"accounts",type:"json",label:e("accountsJson")}],slack:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"mode",type:"text",label:e("mode")},{name:"webhookPath",type:"text",label:e("webhookPath")},{name:"allowBots",type:"boolean",label:e("allowBotMessages")},{name:"botToken",type:"password",label:e("botToken")},{name:"appToken",type:"password",label:e("appToken")}],email:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"consentGranted",type:"boolean",label:e("consentGranted")},{name:"imapHost",type:"text",label:e("imapHost")},{name:"imapPort",type:"number",label:e("imapPort")},{name:"imapUsername",type:"text",label:e("imapUsername")},{name:"imapPassword",type:"password",label:e("imapPassword")},{name:"fromAddress",type:"email",label:e("fromAddress")}],mochat:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"baseUrl",type:"text",label:e("baseUrl")},{name:"clawToken",type:"password",label:e("clawToken")},{name:"agentUserId",type:"text",label:e("agentUserId")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],qq:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"appId",type:"text",label:e("appId")},{name:"secret",type:"password",label:e("appSecret")},{name:"markdownSupport",type:"boolean",label:e("markdownSupport")},{name:"allowFrom",type:"tags",label:e("allowFrom")}]}}async function Be(a,n={}){const l=await K.post(`/api/config/channels/${a}/auth/start`,n);if(!l.ok)throw new Error(l.error.message);return l.data}async function Re(a,n){const l=await K.post(`/api/config/channels/${a}/auth/poll`,n);if(!l.ok)throw new Error(l.error.message);return l.data}function $e(){return G({mutationFn:({channel:a,data:n})=>Be(a,n)})}function Ge(){return G({mutationFn:({channel:a,data:n})=>Re(a,n)})}function He(a){const n=a.accounts,l=new Set;if(typeof a.defaultAccountId=="string"&&a.defaultAccountId.trim()&&l.add(a.defaultAccountId.trim()),n&&typeof n=="object"&&!Array.isArray(n))for(const o of Object.keys(n)){const r=o.trim();r&&l.add(r)}return[...l]}function Ke(a,n){if(typeof a.baseUrl=="string"&&a.baseUrl.trim())return a.baseUrl.trim();if(typeof n.baseUrl=="string"&&n.baseUrl.trim())return n.baseUrl.trim()}function ze({channelConfig:a,formData:n,disabled:l=!1}){const o=me(),r=$e(),u=Ge(),[i,s]=A.useState(null),[m,w]=A.useState(null),y=A.useMemo(()=>He(a),[a]),c=y[0],f=Ke(n,a),j=y.length>0;A.useEffect(()=>{if(!i)return;let h=!1,N=null;const I=async()=>{try{const v=await u.mutateAsync({channel:"weixin",data:{sessionId:i.sessionId}});if(h)return;if(w(v),v.status==="authorized"){await o.invalidateQueries({queryKey:["config"]}),await o.invalidateQueries({queryKey:["config-meta"]}),k.success(v.message||e("weixinAuthAuthorized")),s(null);return}if(v.status==="expired"||v.status==="error"){k.error(v.message||e("weixinAuthRetryRequired")),s(null);return}N=setTimeout(I,v.nextPollMs??i.intervalMs)}catch(v){if(h)return;const P=v instanceof Error?v.message:String(v);k.error(`${e("error")}: ${P}`),s(null)}};return N=setTimeout(I,i.intervalMs),()=>{h=!0,N&&clearTimeout(N)}},[i,u,o]);const d=async()=>{try{const h=await r.mutateAsync({channel:"weixin",data:{baseUrl:f,accountId:typeof n.defaultAccountId=="string"&&n.defaultAccountId.trim()?n.defaultAccountId.trim():void 0}});s(h),w({channel:"weixin",status:"pending",message:h.note,nextPollMs:h.intervalMs})}catch(h){const N=h instanceof Error?h.message:String(h);k.error(`${e("error")}: ${N}`)}},b=i?(m==null?void 0:m.status)==="scanned"?e("weixinAuthScanned"):e("weixinAuthWaiting"):j?e("weixinAuthAuthorized"):e("weixinAuthNotConnected"),C=r.isPending?e("weixinAuthStarting"):i?e("weixinAuthWaiting"):j?e("weixinAuthReconnect"):e("weixinAuthConnect");return t.jsx("section",{className:"rounded-2xl border border-primary/20 bg-gradient-to-br from-primary-50/70 via-white to-emerald-50/60 p-5",children:t.jsxs("div",{className:"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between",children:[t.jsxs("div",{className:"space-y-3",children:[t.jsxs("div",{className:"inline-flex items-center gap-2 rounded-full bg-white/90 px-3 py-1 text-xs font-medium text-primary shadow-sm",children:[t.jsx(_,{className:"h-3.5 w-3.5"}),e("weixinAuthTitle")]}),t.jsxs("div",{children:[t.jsx("h4",{className:"text-base font-semibold text-gray-900",children:e("weixinAuthDescription")}),t.jsx("p",{className:"mt-1 text-sm text-gray-600",children:e("weixinAuthHint")})]}),t.jsxs("div",{className:T("inline-flex w-fit items-center gap-2 rounded-full px-3 py-1 text-xs font-medium",i?"bg-amber-50 text-amber-700":j?"bg-emerald-50 text-emerald-700":"bg-gray-100 text-gray-600"),children:[i?t.jsx(de,{className:"h-3.5 w-3.5 animate-spin"}):t.jsx(ue,{className:"h-3.5 w-3.5"}),b]}),t.jsxs("div",{className:"space-y-1 text-sm text-gray-600",children:[t.jsx("p",{children:e("weixinAuthCapabilityHint")}),c?t.jsxs("p",{children:[e("weixinAuthPrimaryAccount"),": ",t.jsx("span",{className:"font-mono text-xs text-gray-900",children:c})]}):null,y.length>1?t.jsxs("p",{children:[e("weixinAuthConnectedAccounts"),": ",t.jsx("span",{className:"font-mono text-xs text-gray-900",children:y.join(", ")})]}):null,f?t.jsxs("p",{children:[e("weixinAuthBaseUrl"),": ",t.jsx("span",{className:"font-mono text-xs text-gray-900",children:f})]}):null]}),t.jsx(U,{type:"button",onClick:d,disabled:l||r.isPending||!!i,className:"rounded-xl",children:C})]}),t.jsx("div",{className:"w-full max-w-sm rounded-2xl border border-dashed border-primary/25 bg-white/85 p-4 shadow-sm",children:i?t.jsxs("div",{className:"space-y-3",children:[t.jsx("div",{className:"overflow-hidden rounded-2xl border border-gray-100 bg-white p-3",children:t.jsx("img",{src:i.qrCodeUrl,alt:e("weixinAuthQrAlt"),className:"mx-auto aspect-square w-full max-w-[240px] object-contain"})}),t.jsxs("div",{className:"space-y-1 text-xs text-gray-500",children:[t.jsx("p",{children:(m==null?void 0:m.message)||i.note||e("weixinAuthScanPrompt")}),t.jsxs("p",{children:[e("weixinAuthExpiresAt"),": ",Ae(i.expiresAt)]})]}),t.jsxs("a",{href:i.qrCodeUrl,target:"_blank",rel:"noreferrer",className:"inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover",children:[t.jsx(H,{className:"h-3.5 w-3.5"}),e("weixinAuthOpenQr")]})]}):t.jsxs("div",{className:"flex min-h-[280px] flex-col items-center justify-center rounded-2xl bg-gray-50/80 px-6 text-center",children:[t.jsx(_,{className:"h-9 w-9 text-gray-300"}),t.jsx("p",{className:"mt-3 text-sm font-medium text-gray-700",children:e("weixinAuthReadyTitle")}),t.jsx("p",{className:"mt-1 text-xs leading-5 text-gray-500",children:e("weixinAuthReadyDescription")})]})})]})})}function M(a){return typeof a=="object"&&a!==null&&!Array.isArray(a)}function Z(a,n){const l={...a};for(const[o,r]of Object.entries(n)){const u=l[o];if(M(u)&&M(r)){l[o]=Z(u,r);continue}l[o]=r}return l}function Je(a,n){const l=a.split("."),o={};let r=o;for(let u=0;u<l.length-1;u+=1){const i=l[u];r[i]={},r=r[i]}return r[l[l.length-1]]=n,o}function Qe({channelName:a}){var E,O;const{data:n}=z(),{data:l}=J(),{data:o}=Q(),r=Ne(),u=Ce(),[i,s]=A.useState({}),[m,w]=A.useState({}),[y,c]=A.useState(null),f=a?n==null?void 0:n.channels[a]:null,j=a?$()[a]??[]:[],d=o==null?void 0:o.uiHints,b=a?`channels.${a}`:null,C=((E=o==null?void 0:o.actions)==null?void 0:E.filter(p=>p.scope===b))??[],h=a&&(((O=D(`channels.${a}`,d))==null?void 0:O.label)??a),N=l==null?void 0:l.channels.find(p=>p.name===a),I=N?V(N):void 0,v=a==="weixin";A.useEffect(()=>{if(f){s({...f});const p={};(a?$()[a]??[]:[]).filter(x=>x.type==="json").forEach(x=>{const S=f[x.name];p[x.name]=JSON.stringify(S??{},null,2)}),w(p)}else s({}),w({})},[f,a]);const P=(p,g)=>{s(x=>({...x,[p]:g}))},ee=p=>{if(p.preventDefault(),!a)return;const g={...i};for(const x of j){if(x.type!=="password")continue;const S=g[x.name];(typeof S!="string"||S.length===0)&&delete g[x.name]}for(const x of j){if(x.type!=="json")continue;const S=m[x.name]??"";try{g[x.name]=S.trim()?JSON.parse(S):{}}catch{k.error(`${e("invalidJson")}: ${x.name}`);return}}r.mutate({channel:a,data:g})},te=p=>{if(!p||!a)return;const g=p.channels;if(!M(g))return;const x=g[a];M(x)&&s(S=>Z(S,x))},ae=async p=>{if(!(!a||!b)){c(p.id);try{let g={...i};p.saveBeforeRun&&(g={...g,...p.savePatch??{}},s(g),await r.mutateAsync({channel:a,data:g}));const x=await u.mutateAsync({actionId:p.id,data:{scope:b,draftConfig:Je(b,g)}});te(x.patch),x.ok?k.success(x.message||e("success")):k.error(x.message||e("error"))}catch(g){const x=g instanceof Error?g.message:String(g);k.error(`${e("error")}: ${x}`)}finally{c(null)}}};if(!a||!N||!f)return t.jsx("div",{className:Se,children:t.jsxs("div",{children:[t.jsx("h3",{className:"text-base font-semibold text-gray-900",children:e("channelsSelectTitle")}),t.jsx("p",{className:"mt-2 text-sm text-gray-500",children:e("channelsSelectDescription")})]})});const L=!!f.enabled;return t.jsxs("div",{className:ke,children:[t.jsx("div",{className:"border-b border-gray-100 px-6 py-5",children:t.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[t.jsxs("div",{className:"min-w-0",children:[t.jsxs("div",{className:"flex items-center gap-3",children:[t.jsx(Y,{name:a,src:X(a),className:T("h-9 w-9 rounded-lg border",L?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:t.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:a[0]})}),t.jsx("h3",{className:"truncate text-lg font-semibold text-gray-900 capitalize",children:h})]}),t.jsx("p",{className:"mt-2 text-sm text-gray-500",children:e("channelsFormDescription")}),I&&t.jsxs("a",{href:I,className:"mt-2 inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover",children:[t.jsx(pe,{className:"h-3.5 w-3.5"}),e("channelsGuideTitle")]})]}),t.jsx(W,{status:L?"active":"inactive",label:L?e("statusActive"):e("statusInactive")})]})}),t.jsxs("form",{onSubmit:ee,className:"flex min-h-0 flex-1 flex-col",children:[t.jsx("div",{className:"min-h-0 flex-1 space-y-6 overflow-y-auto overscroll-contain px-6 py-5",children:v?t.jsxs(t.Fragment,{children:[t.jsx(ze,{channelConfig:f,formData:i,disabled:r.isPending||!!y}),t.jsxs("details",{className:"group rounded-2xl border border-gray-200/80 bg-white",children:[t.jsxs("summary",{className:"flex cursor-pointer list-none items-center justify-between gap-3 px-5 py-4 text-sm font-medium text-gray-900",children:[t.jsxs("div",{children:[t.jsx("p",{children:e("weixinAuthAdvancedTitle")}),t.jsx("p",{className:"mt-1 text-xs font-normal text-gray-500",children:e("weixinAuthAdvancedDescription")})]}),t.jsx(xe,{className:"h-4 w-4 text-gray-400 transition-transform group-open:rotate-180"})]}),t.jsx("div",{className:"space-y-6 border-t border-gray-100 px-5 py-5",children:t.jsx(q,{channelName:a,fields:j,formData:i,jsonDrafts:m,setJsonDrafts:w,updateField:P,uiHints:d})})]})]}):t.jsx(q,{channelName:a,fields:j,formData:i,jsonDrafts:m,setJsonDrafts:w,updateField:P,uiHints:d})}),t.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3 border-t border-gray-100 px-6 py-4",children:[t.jsx("div",{className:"flex flex-wrap items-center gap-2",children:C.filter(p=>p.trigger==="manual").map(p=>t.jsx(U,{type:"button",onClick:()=>ae(p),disabled:r.isPending||!!y,variant:"secondary",children:y===p.id?e("connecting"):p.title},p.id))}),t.jsx(U,{type:"submit",disabled:r.isPending||!!y,children:r.isPending?e("saving"):e("save")})]})]})]})}const We={telegram:"channelDescTelegram",slack:"channelDescSlack",email:"channelDescEmail",webhook:"channelDescWebhook",discord:"channelDescDiscord",feishu:"channelDescFeishu",weixin:"channelDescWeixin"};function rt(){const{data:a}=z(),{data:n}=J(),{data:l}=Q(),[o,r]=A.useState("enabled"),[u,i]=A.useState(),[s,m]=A.useState(""),w=l==null?void 0:l.uiHints,y=n==null?void 0:n.channels,c=a==null?void 0:a.channels,f=[{id:"enabled",label:e("channelsTabEnabled"),count:(y??[]).filter(d=>{var b;return(b=c==null?void 0:c[d.name])==null?void 0:b.enabled}).length},{id:"all",label:e("channelsTabAll"),count:(y??[]).length}],j=A.useMemo(()=>{const d=s.trim().toLowerCase();return(y??[]).filter(b=>{var h;const C=((h=c==null?void 0:c[b.name])==null?void 0:h.enabled)||!1;return o==="enabled"?C:!0}).filter(b=>d?(b.displayName||b.name).toLowerCase().includes(d)||b.name.toLowerCase().includes(d):!0)},[o,c,y,s]);return A.useEffect(()=>{if(j.length===0){i(void 0);return}j.some(b=>b.name===u)||i(j[0].name)},[j,u]),!a||!n?t.jsx("div",{className:"p-8 text-gray-400",children:e("channelsLoading")}):t.jsxs(Le,{className:"xl:flex xl:h-full xl:min-h-0 xl:flex-col xl:pb-0",children:[t.jsx(Ue,{title:e("channelsPageTitle"),description:e("channelsPageDescription")}),t.jsxs("div",{className:T(Pe,"xl:min-h-0 xl:flex-1"),children:[t.jsxs("section",{className:Ie,children:[t.jsx("div",{className:"border-b border-gray-100 px-4 pt-4",children:t.jsx(Me,{tabs:f,activeTab:o,onChange:r,className:"mb-0"})}),t.jsx("div",{className:"border-b border-gray-100 px-4 py-3",children:t.jsxs("div",{className:"relative",children:[t.jsx(be,{className:"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),t.jsx(F,{value:s,onChange:d=>m(d.target.value),placeholder:e("channelsFilterPlaceholder"),className:"h-10 rounded-xl pl-9"})]})}),t.jsxs("div",{className:"min-h-0 flex-1 space-y-2 overflow-y-auto overscroll-contain p-3",children:[j.map(d=>{const b=a.channels[d.name],C=(b==null?void 0:b.enabled)||!1,h=D(`channels.${d.name}`,w),N=V(d),I=(h==null?void 0:h.help)||e(We[d.name]||"channelDescriptionDefault"),v=u===d.name;return t.jsx("button",{type:"button",onClick:()=>i(d.name),className:T("w-full rounded-xl border p-2.5 text-left transition-all",v?"border-primary/30 bg-primary-50/40 shadow-sm":"border-gray-200/70 bg-white hover:border-gray-300 hover:bg-gray-50/70"),children:t.jsxs("div",{className:"flex items-start justify-between gap-3",children:[t.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[t.jsx(Y,{name:d.name,src:X(d.name),className:T("h-10 w-10 rounded-lg border",C?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:t.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:d.name[0]})}),t.jsxs("div",{className:"min-w-0",children:[t.jsx("p",{className:"truncate text-sm font-semibold text-gray-900",children:d.displayName||d.name}),t.jsx("p",{className:"line-clamp-1 text-[11px] text-gray-500",children:I})]})]}),t.jsxs("div",{className:"flex items-center gap-2",children:[N&&t.jsx("a",{href:N,onClick:P=>P.stopPropagation(),className:"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-300 transition-colors hover:bg-gray-100/70 hover:text-gray-500",title:e("channelsGuideTitle"),children:t.jsx(H,{className:"h-3.5 w-3.5"})}),t.jsx(W,{status:C?"active":"inactive",label:C?e("statusActive"):e("statusInactive"),className:"min-w-[56px] justify-center"})]})]})},d.name)}),j.length===0&&t.jsxs("div",{className:"flex h-full min-h-[220px] flex-col items-center justify-center rounded-xl border border-dashed border-gray-200 bg-gray-50/70 py-10 text-center",children:[t.jsx("div",{className:"mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-white",children:t.jsx(ye,{className:"h-5 w-5 text-gray-300"})}),t.jsx("p",{className:"text-sm font-medium text-gray-700",children:e("channelsNoMatch")})]})]})]}),t.jsx(Qe,{channelName:u})]})]})}export{rt as ChannelsList};
|