@nextclaw/ui 0.12.24 → 0.12.26
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 +136 -29
- package/dist/assets/api-DGD9_Bg4.js +15 -0
- package/dist/assets/app-manager-provider-oYdeYPSv.js +1 -0
- package/dist/assets/{book-open-DDlN5MvX.js → book-open-BcnAiKde.js} +1 -1
- package/dist/assets/channels-list-page-HgLgrEg4.js +8 -0
- package/dist/assets/chat-page-DAKMFDrS.js +1 -0
- package/dist/assets/config-split-page-CcrEUtwu.js +1 -0
- package/dist/assets/cpu-DPPwMzoC.js +3 -0
- package/dist/assets/{createLucideIcon-BLMK3QUd.js → createLucideIcon-DzY6wN61.js} +1 -1
- package/dist/assets/desktop-DVUbOWbR.js +3 -0
- package/dist/assets/desktop-update-config-CP8dFYXK.js +1 -0
- package/dist/assets/{dialog-C3D7Be0p.js → dialog-BKo0RItd.js} +1 -1
- package/dist/assets/{dist-CPlbUgwU.js → dist-CFiwgaLs.js} +1 -1
- package/dist/assets/doc-browser-CAhfnm0D.js +1 -0
- package/dist/assets/{doc-browser-context-BJuMaI3o.js → doc-browser-context-FukQHvyo.js} +1 -1
- package/dist/assets/doc-browser-p9DDNPWB.js +1 -0
- package/dist/assets/doc-browser-rZIQIjuw.js +1 -0
- package/dist/assets/download-CMM8po31.js +1 -0
- package/dist/assets/{es2015-xqN1slyW.js → es2015-BhznEEyJ.js} +1 -1
- package/dist/assets/{external-link-DwfSfTLB.js → external-link-CpEvG65F.js} +1 -1
- package/dist/assets/i18n-D1144VAA.js +1 -0
- package/dist/assets/index-Cuwst6cc.js +100 -0
- package/dist/assets/index-dlcqieQ0.css +1 -0
- package/dist/assets/{key-round-CJ5gDAAG.js → key-round-DUq47t0P.js} +1 -1
- package/dist/assets/marketplace-page-BeFbwxR-.js +105 -0
- package/dist/assets/marketplace-page-CR4xq-TM.js +1 -0
- package/dist/assets/mcp-marketplace-page-DlRrSCj3.js +1 -0
- package/dist/assets/mcp-marketplace-page-DwnaLNTx.js +40 -0
- package/dist/assets/model-config-L2l6YAlQ.js +1 -0
- package/dist/assets/{notice-card-BFDbKQDA.js → notice-card-Dr6xCwva.js} +1 -1
- package/dist/assets/play-AqrNslHI.js +1 -0
- package/dist/assets/plus-B-YHtTNC.js +1 -0
- package/dist/assets/{popover-B86Dbfhf.js → popover-BDFNiLlg.js} +1 -1
- package/dist/assets/provider-scoped-model-input-BMTp4BEH.js +1 -0
- package/dist/assets/providers-list-DYAEunOp.js +1 -0
- package/dist/assets/refresh-cw-CrbD8EkT.js +1 -0
- package/dist/assets/remote-Dr3jcfWP.js +1 -0
- package/dist/assets/{rotate-cw-BZ2JObNs.js → rotate-cw-BN9yjccP.js} +1 -1
- package/dist/assets/runtime-config-page-BdeU8PEK.js +1 -0
- package/dist/assets/{save-euRxl8pI.js → save-CO_4qf6b.js} +1 -1
- package/dist/assets/{search-CLd7m0M7.js → search-CRtQwr-h.js} +1 -1
- package/dist/assets/search-config-CQUhd5RU.js +1 -0
- package/dist/assets/secrets-config-D-NWlW9q.js +3 -0
- package/dist/assets/{select-CJ0wbo3D.js → select-BUTwE_lC.js} +1 -1
- package/dist/assets/{setting-row-D1Yygqp7.js → setting-row-BavcnXw1.js} +1 -1
- package/dist/assets/settings-MWL2SMyk.js +1 -0
- package/dist/assets/{sparkles-DVfeSVJQ.js → sparkles-BmgOD4nY.js} +1 -1
- package/dist/assets/{status-dot-ChvPCib9.js → status-dot-l3kPFdq_.js} +1 -1
- package/dist/assets/{tabs-custom-Hia_ong0.js → tabs-custom-D48zdZoc.js} +1 -1
- package/dist/assets/{tag-chip-FrkmkT8r.js → tag-chip-Dm2Lqnpu.js} +1 -1
- package/dist/assets/use-config-Cyv5IuSt.js +1 -0
- package/dist/assets/use-infinite-scroll-loader-CFVdPpNv.js +1 -0
- package/dist/assets/x-BeyYA_h6.js +1 -0
- package/dist/index.html +29 -40
- package/package.json +9 -9
- package/src/app/components/layout/sidebar.layout.test.tsx +2 -4
- package/src/app/components/theme-provider.tsx +1 -0
- package/src/app/configs/app-navigation.config.ts +0 -6
- package/src/app/index.tsx +4 -7
- package/src/features/agents/components/agents-page.test.tsx +25 -15
- package/src/features/agents/components/agents-page.tsx +133 -172
- package/src/features/channels/components/config/channel-form.test.tsx +1 -0
- package/src/features/channels/components/config/channel-form.tsx +4 -3
- package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +38 -1
- package/src/features/channels/components/config/weixin-channel-auth-section.tsx +137 -40
- package/src/features/channels/index.ts +1 -1
- package/src/features/channels/utils/channel-form-fields.utils.test.ts +26 -0
- package/src/features/channels/utils/channel-form-fields.utils.ts +32 -18
- package/src/features/chat/components/chat-session-workspace-panel-nav.tsx +23 -4
- package/src/features/chat/components/chat-session-workspace-panel.tsx +53 -35
- package/src/features/chat/components/chat-sidebar-session-item.tsx +16 -12
- package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +74 -0
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +8 -2
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +262 -114
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +210 -174
- package/src/features/chat/components/conversation/chat-input-bar.container.tsx +11 -1
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.test.tsx +24 -0
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +27 -6
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +174 -0
- package/src/features/chat/components/layout/chat-sidebar.test.tsx +45 -8
- package/src/features/chat/components/layout/chat-sidebar.tsx +29 -46
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +4 -0
- package/src/features/chat/components/workspace/session-cron-job-content.tsx +103 -0
- package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +153 -80
- package/src/features/chat/hooks/use-ncp-chat-page-data.test.tsx +70 -0
- package/src/features/chat/hooks/use-ncp-chat-page-data.ts +1 -1
- package/src/features/chat/hooks/use-ncp-child-session-tabs-view.ts +2 -8
- package/src/features/chat/hooks/use-ncp-session-list-view.ts +1 -2
- package/src/features/chat/managers/chat-session-list.manager.test.ts +7 -9
- package/src/features/chat/managers/chat-session-list.manager.ts +5 -10
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +20 -2
- package/src/features/chat/managers/ncp-chat-input.manager.ts +18 -0
- package/src/features/chat/managers/ncp-chat-presenter.manager.ts +7 -0
- package/src/features/chat/managers/ncp-chat-thread.manager.test.ts +52 -1
- package/src/features/chat/managers/ncp-chat-thread.manager.ts +21 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +9 -5
- package/src/features/chat/stores/chat-input.store.ts +3 -1
- package/src/features/chat/stores/chat-session-list.store.ts +0 -2
- package/src/features/chat/stores/chat-thread.store.ts +4 -0
- package/src/features/chat/utils/chat-session-display.utils.test.ts +83 -1
- package/src/features/chat/utils/chat-session-display.utils.ts +73 -0
- package/src/features/chat/utils/ncp-chat-input-availability.utils.test.ts +1 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +22 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.ts +32 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +235 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.config.ts +162 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +355 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +118 -0
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +201 -0
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc.test.ts +40 -0
- package/src/features/marketplace/components/marketplace-catalog-grid.tsx +114 -0
- package/src/features/marketplace/components/marketplace-detail-doc.ts +73 -24
- package/src/features/marketplace/components/marketplace-item-icon.tsx +45 -0
- package/src/features/marketplace/components/marketplace-list-card.tsx +177 -93
- package/src/features/marketplace/components/marketplace-page-detail.test.tsx +9 -2
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/marketplace/components/marketplace-page.test.tsx +25 -6
- package/src/features/marketplace/components/marketplace-page.tsx +154 -132
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +97 -0
- package/src/features/marketplace/hooks/use-marketplace.ts +59 -3
- package/src/features/system-status/components/config/runtime-agent-list-card.tsx +4 -8
- package/src/features/system-status/components/config/runtime-binding-list-card.tsx +5 -7
- package/src/features/system-status/components/config/runtime-config-editor.tsx +1 -19
- package/src/features/system-status/components/config/runtime-entry-list-card.tsx +10 -11
- package/src/features/system-status/components/config/runtime-settings-card.tsx +15 -23
- package/src/features/system-status/components/runtime-control-card.test.tsx +8 -6
- package/src/features/system-status/components/runtime-control-card.tsx +7 -6
- package/src/features/system-status/pages/runtime-config-page.test.tsx +19 -9
- package/src/features/system-status/pages/runtime-config-page.tsx +2 -3
- package/src/features/system-status/utils/runtime-config-agent.utils.ts +4 -4
- package/src/features/system-status/utils/system-status.utils.ts +31 -6
- package/src/index.css +8 -0
- package/src/platforms/desktop/components/desktop-app-shell.test.tsx +68 -0
- package/src/platforms/desktop/components/desktop-app-shell.tsx +46 -18
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +30 -0
- package/src/platforms/desktop/index.ts +6 -0
- package/src/platforms/desktop/types/desktop-update.types.ts +3 -0
- package/src/platforms/desktop/utils/desktop-host.utils.ts +56 -0
- package/src/shared/components/common/brand-header.tsx +36 -16
- package/src/shared/components/config/provider-form-support.ts +2 -22
- package/src/shared/components/cron-config.tsx +12 -58
- package/src/shared/components/doc-browser/doc-browser.tsx +4 -4
- package/src/shared/components/ui/select.tsx +19 -7
- package/src/shared/lib/api/channel-auth.types.ts +1 -0
- package/src/shared/lib/api/ncp-session.types.ts +9 -0
- package/src/shared/lib/api/types.ts +12 -1
- package/src/shared/lib/api/utils/marketplace.utils.ts +7 -1
- package/src/shared/lib/cron/cron-job-view.utils.ts +59 -0
- package/src/shared/lib/cron/index.ts +1 -0
- package/src/shared/lib/i18n/{channel-auth.ts → channel-auth.constants.ts} +31 -0
- package/src/shared/lib/i18n/chat-labels.utils.ts +3 -2
- package/src/shared/lib/i18n/index.ts +20 -59
- package/src/shared/lib/i18n/{runtime-control.ts → runtime-control-labels.utils.ts} +30 -1
- package/src/shared/lib/provider-models/index.test.ts +39 -0
- package/src/shared/lib/provider-models/index.ts +1 -3
- package/src/shared/lib/ui-document-title/index.ts +0 -1
- package/tsconfig.json +1 -0
- package/vite.config.ts +1 -1
- package/vitest.config.ts +1 -1
- package/dist/assets/api-D2xRKmZd.js +0 -15
- package/dist/assets/app-manager-provider-CNaZboG4.js +0 -1
- package/dist/assets/app-navigation.config-Ihhrrt--.js +0 -1
- package/dist/assets/channels-list-page-p26lgxLk.js +0 -8
- package/dist/assets/chat-Dkh2qtuz.js +0 -61
- package/dist/assets/chat-page-DoTmE2wx.js +0 -1
- package/dist/assets/chunk-JZWAC4HX-Kydj4yEz.js +0 -3
- package/dist/assets/config-split-page-DIOCjj2Q.js +0 -1
- package/dist/assets/desktop-update-config-DlpzDfKM.js +0 -1
- package/dist/assets/doc-browser-C8FM5fC0.js +0 -1
- package/dist/assets/doc-browser-RJUOL_GO.js +0 -1
- package/dist/assets/doc-browser-p82AdNO-.js +0 -1
- package/dist/assets/folder-CeJKPx5P.js +0 -1
- package/dist/assets/hash-BqxRTZW5.js +0 -1
- package/dist/assets/i18n-DnTGDIRw.js +0 -1
- package/dist/assets/index-D8MKmXtO.css +0 -1
- package/dist/assets/index-pBvbJ5Mt.js +0 -2
- package/dist/assets/loader-circle-fd-vQKtW.js +0 -1
- package/dist/assets/logo-badge-KAe-7d8c.js +0 -1
- package/dist/assets/logos-C4sYP1Vl.js +0 -1
- package/dist/assets/marketplace-page-Cql0kDi-.js +0 -1
- package/dist/assets/marketplace-page-m4P5g_Ht.js +0 -49
- package/dist/assets/mcp-marketplace-page-9WVKl1m1.js +0 -1
- package/dist/assets/mcp-marketplace-page-ByzBQZcx.js +0 -40
- package/dist/assets/message-square-z_osm9c0.js +0 -1
- package/dist/assets/model-config-Dbr_0APb.js +0 -1
- package/dist/assets/play-Dv6Nr1Ew.js +0 -1
- package/dist/assets/plus-D8eKFY7h.js +0 -1
- package/dist/assets/provider-scoped-model-input-DFm6N2f7.js +0 -1
- package/dist/assets/providers-list-BJcLOjun.js +0 -1
- package/dist/assets/refresh-ccw-ByVwmnN_.js +0 -1
- package/dist/assets/refresh-cw-PcqoYB3K.js +0 -1
- package/dist/assets/remote-BOxo9iwd.js +0 -1
- package/dist/assets/runtime-config-page-CjLhnbSl.js +0 -1
- package/dist/assets/search-config-J4Htco-P.js +0 -1
- package/dist/assets/secrets-config-CUdERjco.js +0 -3
- package/dist/assets/sessions-config-page-DpK991fs.js +0 -2
- package/dist/assets/settings-drbWqzA4.js +0 -1
- package/dist/assets/skeleton-BK1SOSRA.js +0 -1
- package/dist/assets/theme-provider-0hxjiPc_.js +0 -2
- package/dist/assets/tooltip-Cj4yA0gH.js +0 -1
- package/dist/assets/trash-2-CBsHCfqq.js +0 -1
- package/dist/assets/use-config-38Ur-89i.js +0 -1
- package/dist/assets/use-confirm-dialog-DPQThaeU.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-5Gf1xQi7.js +0 -1
- package/dist/assets/use-viewport-layout-D1XzKeip.js +0 -1
- package/dist/assets/x-CM-XDMpk.js +0 -1
- package/src/features/chat/components/config/sessions-config-detail-pane.tsx +0 -244
- package/src/features/chat/pages/sessions-config-page.test.tsx +0 -152
- package/src/features/chat/pages/sessions-config-page.tsx +0 -192
- /package/dist/assets/{config-hints-MogHYQ8G.js → config-hints-BNfpOL4J.js} +0 -0
|
@@ -9,13 +9,64 @@ import { formatDateTime, t } from '@/shared/lib/i18n';
|
|
|
9
9
|
import { cn } from '@/shared/lib/utils';
|
|
10
10
|
import type { ChannelAuthPollResult, ChannelAuthStartResult } from '@/shared/lib/api';
|
|
11
11
|
|
|
12
|
-
type
|
|
12
|
+
type QrChannelAuthSectionProps = {
|
|
13
13
|
channelConfig: Record<string, unknown>;
|
|
14
14
|
formData: Record<string, unknown>;
|
|
15
|
+
channelName: 'weixin' | 'feishu';
|
|
15
16
|
channelEnabled: boolean;
|
|
16
17
|
disabled?: boolean;
|
|
17
18
|
};
|
|
18
19
|
|
|
20
|
+
type WeixinChannelAuthSectionProps = Omit<QrChannelAuthSectionProps, 'channelName'>;
|
|
21
|
+
|
|
22
|
+
type QrChannelAuthCopy = {
|
|
23
|
+
title: string;
|
|
24
|
+
description: string;
|
|
25
|
+
hint: string;
|
|
26
|
+
capabilityHint: string;
|
|
27
|
+
disabledHint: string;
|
|
28
|
+
connect: string;
|
|
29
|
+
qrAlt: string;
|
|
30
|
+
scanPrompt: string;
|
|
31
|
+
readyTitle: string;
|
|
32
|
+
readyDescription: string;
|
|
33
|
+
advancedTitle: string;
|
|
34
|
+
advancedDescription: string;
|
|
35
|
+
domainLabel?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const QR_AUTH_COPY: Record<QrChannelAuthSectionProps['channelName'], QrChannelAuthCopy> = {
|
|
39
|
+
weixin: {
|
|
40
|
+
title: 'weixinAuthTitle',
|
|
41
|
+
description: 'weixinAuthDescription',
|
|
42
|
+
hint: 'weixinAuthHint',
|
|
43
|
+
capabilityHint: 'weixinAuthCapabilityHint',
|
|
44
|
+
disabledHint: 'weixinAuthDisabledHint',
|
|
45
|
+
connect: 'weixinAuthConnect',
|
|
46
|
+
qrAlt: 'weixinAuthQrAlt',
|
|
47
|
+
scanPrompt: 'weixinAuthScanPrompt',
|
|
48
|
+
readyTitle: 'weixinAuthReadyTitle',
|
|
49
|
+
readyDescription: 'weixinAuthReadyDescription',
|
|
50
|
+
advancedTitle: 'weixinAuthAdvancedTitle',
|
|
51
|
+
advancedDescription: 'weixinAuthAdvancedDescription'
|
|
52
|
+
},
|
|
53
|
+
feishu: {
|
|
54
|
+
title: 'feishuAuthTitle',
|
|
55
|
+
description: 'feishuAuthDescription',
|
|
56
|
+
hint: 'feishuAuthHint',
|
|
57
|
+
capabilityHint: 'feishuAuthCapabilityHint',
|
|
58
|
+
disabledHint: 'feishuAuthDisabledHint',
|
|
59
|
+
connect: 'feishuAuthConnect',
|
|
60
|
+
qrAlt: 'feishuAuthQrAlt',
|
|
61
|
+
scanPrompt: 'feishuAuthScanPrompt',
|
|
62
|
+
readyTitle: 'feishuAuthReadyTitle',
|
|
63
|
+
readyDescription: 'feishuAuthReadyDescription',
|
|
64
|
+
advancedTitle: 'feishuAuthAdvancedTitle',
|
|
65
|
+
advancedDescription: 'feishuAuthAdvancedDescription',
|
|
66
|
+
domainLabel: 'feishuAuthDomain'
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
19
70
|
function resolveConnectedAccountIds(channelConfig: Record<string, unknown>): string[] {
|
|
20
71
|
const { accounts } = channelConfig;
|
|
21
72
|
const ids = new Set<string>();
|
|
@@ -43,21 +94,45 @@ function resolveBaseUrl(formData: Record<string, unknown>, channelConfig: Record
|
|
|
43
94
|
return undefined;
|
|
44
95
|
}
|
|
45
96
|
|
|
46
|
-
function
|
|
97
|
+
function resolveDomain(formData: Record<string, unknown>, channelConfig: Record<string, unknown>): string | undefined {
|
|
98
|
+
const formDomain = typeof formData.domain === 'string' ? formData.domain.trim() : '';
|
|
99
|
+
if (formDomain) {
|
|
100
|
+
return formDomain;
|
|
101
|
+
}
|
|
102
|
+
const configDomain = typeof channelConfig.domain === 'string' ? channelConfig.domain.trim() : '';
|
|
103
|
+
return configDomain || undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function useQrDataUrl(channelName: string, qrCodeUrl: string | undefined) {
|
|
47
107
|
return useQuery({
|
|
48
|
-
queryKey: ['
|
|
108
|
+
queryKey: ['channel-qr', channelName, qrCodeUrl],
|
|
49
109
|
enabled: Boolean(qrCodeUrl),
|
|
50
110
|
queryFn: () => toDataURL(qrCodeUrl!, { errorCorrectionLevel: 'M', margin: 1, width: 480 })
|
|
51
111
|
}).data ?? null;
|
|
52
112
|
}
|
|
53
113
|
|
|
54
|
-
function
|
|
114
|
+
function QrAuthSummary({
|
|
115
|
+
activeSession,
|
|
116
|
+
baseUrl,
|
|
117
|
+
channelEnabled,
|
|
118
|
+
copy,
|
|
119
|
+
connectButtonLabel,
|
|
120
|
+
connectedAccountIds,
|
|
121
|
+
disabled,
|
|
122
|
+
domain,
|
|
123
|
+
handleStartAuth,
|
|
124
|
+
hasConnectedAccount,
|
|
125
|
+
primaryAccountId,
|
|
126
|
+
statusLabel
|
|
127
|
+
}: {
|
|
55
128
|
activeSession: ChannelAuthStartResult | null;
|
|
56
129
|
baseUrl?: string;
|
|
57
130
|
channelEnabled: boolean;
|
|
131
|
+
copy: QrChannelAuthCopy;
|
|
58
132
|
connectButtonLabel: string;
|
|
59
133
|
connectedAccountIds: string[];
|
|
60
134
|
disabled: boolean;
|
|
135
|
+
domain?: string;
|
|
61
136
|
handleStartAuth: () => Promise<void>;
|
|
62
137
|
hasConnectedAccount: boolean;
|
|
63
138
|
primaryAccountId?: string;
|
|
@@ -67,46 +142,53 @@ function WeixinAuthSummary(props: {
|
|
|
67
142
|
<div className="space-y-3">
|
|
68
143
|
<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">
|
|
69
144
|
<QrCode className="h-3.5 w-3.5" />
|
|
70
|
-
{t(
|
|
145
|
+
{t(copy.title)}
|
|
71
146
|
</div>
|
|
72
147
|
<div>
|
|
73
|
-
<h4 className="text-base font-semibold text-gray-900">{t(
|
|
74
|
-
<p className="mt-1 text-sm text-gray-600">{t(
|
|
148
|
+
<h4 className="text-base font-semibold text-gray-900">{t(copy.description)}</h4>
|
|
149
|
+
<p className="mt-1 text-sm text-gray-600">{t(copy.hint)}</p>
|
|
75
150
|
</div>
|
|
76
151
|
<div
|
|
77
152
|
className={cn(
|
|
78
153
|
'inline-flex w-fit items-center gap-2 rounded-full px-3 py-1 text-xs font-medium',
|
|
79
|
-
|
|
154
|
+
activeSession ? 'bg-amber-50 text-amber-700' : hasConnectedAccount ? 'bg-emerald-50 text-emerald-700' : 'bg-gray-100 text-gray-600'
|
|
80
155
|
)}
|
|
81
156
|
>
|
|
82
|
-
{
|
|
83
|
-
{
|
|
157
|
+
{activeSession ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <MessageCircleMore className="h-3.5 w-3.5" />}
|
|
158
|
+
{statusLabel}
|
|
84
159
|
</div>
|
|
85
160
|
<div className="space-y-1 text-sm text-gray-600">
|
|
86
|
-
<p>{
|
|
87
|
-
{
|
|
88
|
-
{
|
|
89
|
-
{
|
|
161
|
+
<p>{channelEnabled || !hasConnectedAccount ? t(copy.capabilityHint) : t(copy.disabledHint)}</p>
|
|
162
|
+
{primaryAccountId ? <p>{t('weixinAuthPrimaryAccount')}: <span className="font-mono text-xs text-gray-900">{primaryAccountId}</span></p> : null}
|
|
163
|
+
{connectedAccountIds.length > 1 ? <p>{t('weixinAuthConnectedAccounts')}: <span className="font-mono text-xs text-gray-900">{connectedAccountIds.join(', ')}</span></p> : null}
|
|
164
|
+
{baseUrl ? <p>{t('weixinAuthBaseUrl')}: <span className="font-mono text-xs text-gray-900">{baseUrl}</span></p> : null}
|
|
165
|
+
{domain && copy.domainLabel ? <p>{t(copy.domainLabel)}: <span className="font-mono text-xs text-gray-900">{domain}</span></p> : null}
|
|
90
166
|
</div>
|
|
91
|
-
<Button type="button" onClick={() => void
|
|
92
|
-
{
|
|
167
|
+
<Button type="button" onClick={() => void handleStartAuth()} disabled={disabled} className="rounded-xl">
|
|
168
|
+
{connectButtonLabel}
|
|
93
169
|
</Button>
|
|
94
170
|
</div>
|
|
95
171
|
);
|
|
96
172
|
}
|
|
97
173
|
|
|
98
|
-
function
|
|
174
|
+
function QrAuthPanel({
|
|
175
|
+
activeSession,
|
|
176
|
+
authMessage,
|
|
177
|
+
copy,
|
|
178
|
+
qrDataUrl
|
|
179
|
+
}: {
|
|
99
180
|
activeSession: ChannelAuthStartResult | null;
|
|
100
181
|
authMessage?: string;
|
|
182
|
+
copy: QrChannelAuthCopy;
|
|
101
183
|
qrDataUrl: string | null;
|
|
102
184
|
}) {
|
|
103
185
|
return (
|
|
104
186
|
<div className="w-full max-w-sm rounded-2xl border border-dashed border-primary/25 bg-white/85 p-4 shadow-sm">
|
|
105
|
-
{
|
|
187
|
+
{activeSession ? (
|
|
106
188
|
<div className="space-y-3">
|
|
107
189
|
<div className="overflow-hidden rounded-2xl border border-gray-100 bg-white p-3">
|
|
108
|
-
{
|
|
109
|
-
<img src={
|
|
190
|
+
{qrDataUrl ? (
|
|
191
|
+
<img src={qrDataUrl} alt={t(copy.qrAlt)} className="mx-auto aspect-square w-full max-w-[240px] object-contain" />
|
|
110
192
|
) : (
|
|
111
193
|
<div className="flex aspect-square w-full items-center justify-center rounded-xl bg-gray-50 text-gray-500">
|
|
112
194
|
<div className="flex flex-col items-center gap-2 text-center">
|
|
@@ -117,10 +199,10 @@ function WeixinAuthQrPanel(props: {
|
|
|
117
199
|
)}
|
|
118
200
|
</div>
|
|
119
201
|
<div className="space-y-1 text-xs text-gray-500">
|
|
120
|
-
<p>{
|
|
121
|
-
<p>{t('weixinAuthExpiresAt')}: {formatDateTime(
|
|
202
|
+
<p>{authMessage || activeSession.note || t(copy.scanPrompt)}</p>
|
|
203
|
+
<p>{t('weixinAuthExpiresAt')}: {formatDateTime(activeSession.expiresAt)}</p>
|
|
122
204
|
</div>
|
|
123
|
-
<a href={
|
|
205
|
+
<a href={activeSession.qrCodeUrl} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover">
|
|
124
206
|
<ExternalLink className="h-3.5 w-3.5" />
|
|
125
207
|
{t('weixinAuthOpenQr')}
|
|
126
208
|
</a>
|
|
@@ -128,27 +210,35 @@ function WeixinAuthQrPanel(props: {
|
|
|
128
210
|
) : (
|
|
129
211
|
<div className="flex min-h-[280px] flex-col items-center justify-center rounded-2xl bg-gray-50/80 px-6 text-center">
|
|
130
212
|
<QrCode className="h-9 w-9 text-gray-300" />
|
|
131
|
-
<p className="mt-3 text-sm font-medium text-gray-700">{t(
|
|
132
|
-
<p className="mt-1 text-xs leading-5 text-gray-500">{t(
|
|
213
|
+
<p className="mt-3 text-sm font-medium text-gray-700">{t(copy.readyTitle)}</p>
|
|
214
|
+
<p className="mt-1 text-xs leading-5 text-gray-500">{t(copy.readyDescription)}</p>
|
|
133
215
|
</div>
|
|
134
216
|
)}
|
|
135
217
|
</div>
|
|
136
218
|
);
|
|
137
219
|
}
|
|
138
220
|
|
|
139
|
-
export function
|
|
221
|
+
export function QrChannelAuthSection({
|
|
222
|
+
channelConfig,
|
|
223
|
+
channelEnabled,
|
|
224
|
+
channelName,
|
|
225
|
+
disabled,
|
|
226
|
+
formData
|
|
227
|
+
}: QrChannelAuthSectionProps) {
|
|
140
228
|
const queryClient = useQueryClient();
|
|
141
229
|
const startChannelAuth = useStartChannelAuth();
|
|
142
230
|
const pollChannelAuth = usePollChannelAuth();
|
|
143
231
|
const [activeSession, setActiveSession] = useState<ChannelAuthStartResult | null>(null);
|
|
144
232
|
const [authState, setAuthState] = useState<ChannelAuthPollResult | null>(null);
|
|
145
233
|
const [sessionStartedWhileConnected, setSessionStartedWhileConnected] = useState(false);
|
|
146
|
-
const connectedAccountIds = useMemo(() => resolveConnectedAccountIds(
|
|
234
|
+
const connectedAccountIds = useMemo(() => resolveConnectedAccountIds(channelConfig), [channelConfig]);
|
|
147
235
|
const primaryAccountId = connectedAccountIds[0];
|
|
148
|
-
const baseUrl = resolveBaseUrl(
|
|
236
|
+
const baseUrl = resolveBaseUrl(formData, channelConfig);
|
|
237
|
+
const domain = channelName === 'feishu' ? resolveDomain(formData, channelConfig) : undefined;
|
|
149
238
|
const hasConnectedAccount = connectedAccountIds.length > 0;
|
|
150
239
|
const effectiveActiveSession = hasConnectedAccount && !sessionStartedWhileConnected ? null : activeSession;
|
|
151
|
-
const qrDataUrl =
|
|
240
|
+
const qrDataUrl = useQrDataUrl(channelName, effectiveActiveSession?.qrCodeUrl);
|
|
241
|
+
const copy = QR_AUTH_COPY[channelName];
|
|
152
242
|
|
|
153
243
|
useEffect(() => {
|
|
154
244
|
if (!effectiveActiveSession) {
|
|
@@ -158,7 +248,7 @@ export function WeixinChannelAuthSection(props: WeixinChannelAuthSectionProps) {
|
|
|
158
248
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
159
249
|
const runPoll = async () => {
|
|
160
250
|
try {
|
|
161
|
-
const result = await pollChannelAuth.mutateAsync({ channel:
|
|
251
|
+
const result = await pollChannelAuth.mutateAsync({ channel: channelName, data: { sessionId: effectiveActiveSession.sessionId } });
|
|
162
252
|
if (cancelled) {
|
|
163
253
|
return;
|
|
164
254
|
}
|
|
@@ -190,20 +280,21 @@ export function WeixinChannelAuthSection(props: WeixinChannelAuthSectionProps) {
|
|
|
190
280
|
clearTimeout(timer);
|
|
191
281
|
}
|
|
192
282
|
};
|
|
193
|
-
}, [effectiveActiveSession, pollChannelAuth, queryClient]);
|
|
283
|
+
}, [channelName, effectiveActiveSession, pollChannelAuth, queryClient]);
|
|
194
284
|
|
|
195
285
|
const handleStartAuth = async () => {
|
|
196
286
|
try {
|
|
197
287
|
const result = await startChannelAuth.mutateAsync({
|
|
198
|
-
channel:
|
|
288
|
+
channel: channelName,
|
|
199
289
|
data: {
|
|
200
290
|
baseUrl,
|
|
201
|
-
|
|
291
|
+
domain,
|
|
292
|
+
accountId: typeof formData.defaultAccountId === 'string' && formData.defaultAccountId.trim() ? formData.defaultAccountId.trim() : undefined
|
|
202
293
|
}
|
|
203
294
|
});
|
|
204
295
|
setSessionStartedWhileConnected(hasConnectedAccount);
|
|
205
296
|
setActiveSession(result);
|
|
206
|
-
setAuthState({ channel:
|
|
297
|
+
setAuthState({ channel: channelName, status: 'pending', message: result.note, nextPollMs: result.intervalMs });
|
|
207
298
|
} catch (error) {
|
|
208
299
|
toast.error(`${t('error')}: ${error instanceof Error ? error.message : String(error)}`);
|
|
209
300
|
}
|
|
@@ -212,7 +303,7 @@ export function WeixinChannelAuthSection(props: WeixinChannelAuthSectionProps) {
|
|
|
212
303
|
const statusLabel = effectiveActiveSession
|
|
213
304
|
? authState?.status === 'scanned' ? t('weixinAuthScanned') : t('weixinAuthWaiting')
|
|
214
305
|
: hasConnectedAccount
|
|
215
|
-
?
|
|
306
|
+
? channelEnabled ? t('weixinAuthAuthorized') : t('weixinAuthConnectedDisabled')
|
|
216
307
|
: t('weixinAuthNotConnected');
|
|
217
308
|
const connectButtonLabel = startChannelAuth.isPending
|
|
218
309
|
? t('weixinAuthStarting')
|
|
@@ -220,26 +311,32 @@ export function WeixinChannelAuthSection(props: WeixinChannelAuthSectionProps) {
|
|
|
220
311
|
? t('weixinAuthWaiting')
|
|
221
312
|
: hasConnectedAccount
|
|
222
313
|
? t('weixinAuthReconnect')
|
|
223
|
-
: t(
|
|
314
|
+
: t(copy.connect);
|
|
224
315
|
const authMessage = hasConnectedAccount ? t('weixinAuthAuthorized') : authState?.message;
|
|
225
316
|
|
|
226
317
|
return (
|
|
227
318
|
<section className="rounded-2xl border border-primary/20 bg-gradient-to-br from-primary-50/70 via-white to-emerald-50/60 p-5">
|
|
228
319
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
229
|
-
<
|
|
320
|
+
<QrAuthSummary
|
|
230
321
|
activeSession={effectiveActiveSession}
|
|
231
322
|
baseUrl={baseUrl}
|
|
232
|
-
channelEnabled={
|
|
323
|
+
channelEnabled={channelEnabled}
|
|
324
|
+
copy={copy}
|
|
233
325
|
connectButtonLabel={connectButtonLabel}
|
|
234
326
|
connectedAccountIds={connectedAccountIds}
|
|
235
|
-
disabled={
|
|
327
|
+
disabled={disabled || startChannelAuth.isPending || Boolean(effectiveActiveSession)}
|
|
328
|
+
domain={domain}
|
|
236
329
|
handleStartAuth={handleStartAuth}
|
|
237
330
|
hasConnectedAccount={hasConnectedAccount}
|
|
238
331
|
primaryAccountId={primaryAccountId}
|
|
239
332
|
statusLabel={statusLabel}
|
|
240
333
|
/>
|
|
241
|
-
<
|
|
334
|
+
<QrAuthPanel activeSession={effectiveActiveSession} authMessage={authMessage} copy={copy} qrDataUrl={qrDataUrl} />
|
|
242
335
|
</div>
|
|
243
336
|
</section>
|
|
244
337
|
);
|
|
245
338
|
}
|
|
339
|
+
|
|
340
|
+
export function WeixinChannelAuthSection(props: WeixinChannelAuthSectionProps) {
|
|
341
|
+
return <QrChannelAuthSection {...props} channelName="weixin" />;
|
|
342
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ChannelForm } from './components/config/channel-form';
|
|
2
2
|
export { ChannelFormFieldsSection } from './components/channel-form-fields-section';
|
|
3
|
-
export { WeixinChannelAuthSection } from './components/config/weixin-channel-auth-section';
|
|
3
|
+
export { QrChannelAuthSection, WeixinChannelAuthSection } from './components/config/weixin-channel-auth-section';
|
|
4
4
|
export { ChannelsList } from './pages/channels-list-page';
|
|
5
5
|
export { buildChannelFields, buildChannelFormDefinitions, type ChannelField, type ChannelFieldType, type ChannelFormBlock, type ChannelFormDefinition, type ChannelFormFieldSection, type ChannelOption } from './utils/channel-form-fields.utils';
|
|
@@ -25,4 +25,30 @@ describe('buildChannelFormDefinitions', () => {
|
|
|
25
25
|
}
|
|
26
26
|
]);
|
|
27
27
|
});
|
|
28
|
+
|
|
29
|
+
it('declares feishu as a QR-first extension channel layout', () => {
|
|
30
|
+
const definitions = buildChannelFormDefinitions();
|
|
31
|
+
|
|
32
|
+
expect(definitions.feishu?.fields.map((field) => field.name)).toEqual([
|
|
33
|
+
'enabled',
|
|
34
|
+
'defaultAccountId',
|
|
35
|
+
'domain',
|
|
36
|
+
'allowFrom',
|
|
37
|
+
'groupPolicy',
|
|
38
|
+
'requireMention',
|
|
39
|
+
'accounts'
|
|
40
|
+
]);
|
|
41
|
+
expect(definitions.feishu?.layout).toEqual([
|
|
42
|
+
{ type: 'fields', section: 'primary' },
|
|
43
|
+
{ type: 'custom', sectionId: 'feishu-auth' },
|
|
44
|
+
{
|
|
45
|
+
type: 'fields',
|
|
46
|
+
section: 'advanced',
|
|
47
|
+
collapsible: {
|
|
48
|
+
title: 'Advanced settings',
|
|
49
|
+
description: 'Expand these fields only when you need to switch Feishu/Lark domains, choose a default account, or adjust allowlist and group policies.'
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
28
54
|
});
|
|
@@ -41,6 +41,11 @@ const GROUP_POLICY_OPTIONS: ChannelOption[] = [
|
|
|
41
41
|
{ value: 'disabled', label: 'disabled' }
|
|
42
42
|
];
|
|
43
43
|
|
|
44
|
+
const FEISHU_DOMAIN_OPTIONS: ChannelOption[] = [
|
|
45
|
+
{ value: 'feishu', label: 'feishu' },
|
|
46
|
+
{ value: 'lark', label: 'lark' }
|
|
47
|
+
];
|
|
48
|
+
|
|
44
49
|
const STREAMING_MODE_OPTIONS: ChannelOption[] = [
|
|
45
50
|
{ value: 'off', label: 'off' },
|
|
46
51
|
{ value: 'partial', label: 'partial' },
|
|
@@ -48,6 +53,32 @@ const STREAMING_MODE_OPTIONS: ChannelOption[] = [
|
|
|
48
53
|
{ value: 'progress', label: 'progress' }
|
|
49
54
|
];
|
|
50
55
|
|
|
56
|
+
function buildFeishuFormDefinition(): ChannelFormDefinition {
|
|
57
|
+
return {
|
|
58
|
+
fields: [
|
|
59
|
+
{ name: 'enabled', type: 'boolean', label: t('enabled'), section: 'primary' },
|
|
60
|
+
{ name: 'defaultAccountId', type: 'text', label: t('defaultAccountId') },
|
|
61
|
+
{ name: 'domain', type: 'select', label: t('feishuAuthDomain'), options: FEISHU_DOMAIN_OPTIONS },
|
|
62
|
+
{ name: 'allowFrom', type: 'tags', label: t('allowFrom') },
|
|
63
|
+
{ name: 'groupPolicy', type: 'select', label: t('groupPolicy'), options: GROUP_POLICY_OPTIONS },
|
|
64
|
+
{ name: 'requireMention', type: 'boolean', label: t('requireMention') },
|
|
65
|
+
{ name: 'accounts', type: 'json', label: t('accountsJson') }
|
|
66
|
+
],
|
|
67
|
+
layout: [
|
|
68
|
+
{ type: 'fields', section: 'primary' },
|
|
69
|
+
{ type: 'custom', sectionId: 'feishu-auth' },
|
|
70
|
+
{
|
|
71
|
+
type: 'fields',
|
|
72
|
+
section: 'advanced',
|
|
73
|
+
collapsible: {
|
|
74
|
+
title: t('feishuAuthAdvancedTitle'),
|
|
75
|
+
description: t('feishuAuthAdvancedDescription')
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
51
82
|
export function buildChannelFormDefinitions(): Record<string, ChannelFormDefinition> {
|
|
52
83
|
return {
|
|
53
84
|
telegram: {
|
|
@@ -94,24 +125,7 @@ export function buildChannelFormDefinitions(): Record<string, ChannelFormDefinit
|
|
|
94
125
|
{ name: 'allowFrom', type: 'tags', label: t('allowFrom') }
|
|
95
126
|
]
|
|
96
127
|
},
|
|
97
|
-
feishu:
|
|
98
|
-
fields: [
|
|
99
|
-
{ name: 'enabled', type: 'boolean', label: t('enabled') },
|
|
100
|
-
{ name: 'appId', type: 'text', label: t('appId') },
|
|
101
|
-
{ name: 'appSecret', type: 'password', label: t('appSecret') },
|
|
102
|
-
{ name: 'encryptKey', type: 'password', label: t('encryptKey') },
|
|
103
|
-
{ name: 'verificationToken', type: 'password', label: t('verificationToken') },
|
|
104
|
-
{ name: 'domain', type: 'text', label: 'Domain' },
|
|
105
|
-
{ name: 'allowFrom', type: 'tags', label: t('allowFrom') },
|
|
106
|
-
{ name: 'dmPolicy', type: 'select', label: t('dmPolicy'), options: DM_POLICY_OPTIONS },
|
|
107
|
-
{ name: 'groupPolicy', type: 'select', label: t('groupPolicy'), options: GROUP_POLICY_OPTIONS },
|
|
108
|
-
{ name: 'groupAllowFrom', type: 'tags', label: t('groupAllowFrom') },
|
|
109
|
-
{ name: 'requireMention', type: 'boolean', label: t('requireMention') },
|
|
110
|
-
{ name: 'mentionPatterns', type: 'tags', label: t('mentionPatterns') },
|
|
111
|
-
{ name: 'groups', type: 'json', label: t('groupRulesJson') },
|
|
112
|
-
{ name: 'accounts', type: 'json', label: t('accountsJson') }
|
|
113
|
-
]
|
|
114
|
-
},
|
|
128
|
+
feishu: buildFeishuFormDefinition(),
|
|
115
129
|
dingtalk: {
|
|
116
130
|
fields: [
|
|
117
131
|
{ name: 'enabled', type: 'boolean', label: t('enabled') },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FileCode2, MessageSquareText, X } from "lucide-react";
|
|
1
|
+
import { AlarmClock, FileCode2, MessageSquareText, X } from "lucide-react";
|
|
2
2
|
import type { ResolvedChildSessionTab } from "@/features/chat/hooks/use-ncp-child-session-tabs-view";
|
|
3
3
|
import type { ChatWorkspaceFileTab } from "@/features/chat/stores/chat-thread.store";
|
|
4
4
|
import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
|
|
@@ -14,11 +14,14 @@ export type WorkspaceSelection =
|
|
|
14
14
|
| {
|
|
15
15
|
kind: "file";
|
|
16
16
|
file: ChatWorkspaceFileTab;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
kind: "cron";
|
|
17
20
|
};
|
|
18
21
|
|
|
19
22
|
export type WorkspaceTabViewModel = {
|
|
20
23
|
key: string;
|
|
21
|
-
kind: "child-session" | "file";
|
|
24
|
+
kind: "child-session" | "file" | "cron";
|
|
22
25
|
title: string;
|
|
23
26
|
tooltip: string;
|
|
24
27
|
active: boolean;
|
|
@@ -38,19 +41,27 @@ export function readWorkspaceFileTitle(file: ChatWorkspaceFileTab): string {
|
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
export function resolveWorkspaceSelection(params: {
|
|
44
|
+
activePanelKind?: "child-session" | "file" | "cron" | null;
|
|
41
45
|
activeChildSessionKey: string | null;
|
|
42
46
|
activeWorkspaceFileKey: string | null;
|
|
43
47
|
childSessionTabs: ResolvedChildSessionTab[];
|
|
44
48
|
workspaceFileTabs: readonly ChatWorkspaceFileTab[];
|
|
49
|
+
sessionCronJobCount: number;
|
|
45
50
|
}): WorkspaceSelection | null {
|
|
46
51
|
const {
|
|
52
|
+
activePanelKind,
|
|
47
53
|
activeChildSessionKey,
|
|
48
54
|
activeWorkspaceFileKey,
|
|
49
55
|
childSessionTabs,
|
|
50
56
|
workspaceFileTabs,
|
|
57
|
+
sessionCronJobCount,
|
|
51
58
|
} = params;
|
|
52
59
|
|
|
53
|
-
if (
|
|
60
|
+
if (activePanelKind === "cron" && sessionCronJobCount > 0) {
|
|
61
|
+
return { kind: "cron" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (activePanelKind !== "child-session" && activeWorkspaceFileKey) {
|
|
54
65
|
const activeFile = workspaceFileTabs.find(
|
|
55
66
|
(file) => file.key === activeWorkspaceFileKey,
|
|
56
67
|
);
|
|
@@ -62,7 +73,7 @@ export function resolveWorkspaceSelection(params: {
|
|
|
62
73
|
}
|
|
63
74
|
}
|
|
64
75
|
|
|
65
|
-
if (activeChildSessionKey) {
|
|
76
|
+
if (activePanelKind !== "file" && activeChildSessionKey) {
|
|
66
77
|
const activeChild = childSessionTabs.find(
|
|
67
78
|
(tab) => tab.sessionKey === activeChildSessionKey,
|
|
68
79
|
);
|
|
@@ -88,10 +99,18 @@ export function resolveWorkspaceSelection(params: {
|
|
|
88
99
|
};
|
|
89
100
|
}
|
|
90
101
|
|
|
102
|
+
if (sessionCronJobCount > 0) {
|
|
103
|
+
return { kind: "cron" };
|
|
104
|
+
}
|
|
105
|
+
|
|
91
106
|
return null;
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
function WorkspaceTabIcon({ agentId, kind }: Pick<WorkspaceTabViewModel, "agentId" | "kind">) {
|
|
110
|
+
if (kind === "cron") {
|
|
111
|
+
return <AlarmClock className="h-3.5 w-3.5 shrink-0 text-gray-400" />;
|
|
112
|
+
}
|
|
113
|
+
|
|
95
114
|
if (kind === "file") {
|
|
96
115
|
return <FileCode2 className="h-3.5 w-3.5 shrink-0 text-gray-400" />;
|
|
97
116
|
}
|