@nextclaw/ui 0.12.24 → 0.12.25
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 +68 -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-FJDuPwU6.js +8 -0
- package/dist/assets/chat-page-D1fMNBrT.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-kk7qvZ-v.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-D-AAMKCt.js +103 -0
- package/dist/assets/index-DnBeV2Xm.css +1 -0
- package/dist/assets/{key-round-CJ5gDAAG.js → key-round-DUq47t0P.js} +1 -1
- package/dist/assets/marketplace-page-BrCLRIc4.js +105 -0
- package/dist/assets/marketplace-page-odDpPYEs.js +1 -0
- package/dist/assets/mcp-marketplace-page-CfbOBgKK.js +1 -0
- package/dist/assets/mcp-marketplace-page-DIq_SpMe.js +40 -0
- package/dist/assets/model-config-Bc6VVnxy.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-DN0tvISH.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-CRWOwBbl.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-C4c1yZSP.js +1 -0
- package/dist/assets/secrets-config-zAF30YfO.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-Cvz8ZteY.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 +34 -2
- package/src/features/chat/components/chat-sidebar-session-item.tsx +9 -3
- package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +71 -0
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +6 -0
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +181 -61
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +54 -23
- 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 +26 -5
- 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 +2 -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 +14 -0
- 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 +0 -2
- package/src/features/chat/managers/ncp-chat-presenter.manager.ts +6 -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 +5 -4
- 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-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 +67 -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
|
}
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
ChatFileOpenActionViewModel,
|
|
5
5
|
ChatToolActionViewModel,
|
|
6
6
|
} from "@nextclaw/agent-chat-ui";
|
|
7
|
+
import type { CronJobView } from "@/shared/lib/api";
|
|
7
8
|
import { useStickyBottomScroll } from "@nextclaw/agent-chat-ui";
|
|
8
9
|
import { ChatMessageListContainer } from "@/features/chat/components/conversation/chat-message-list.container";
|
|
9
10
|
import {
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
import { usePresenter } from "@/features/chat/components/providers/chat-presenter.provider";
|
|
29
30
|
import { ChatSessionWorkspaceFilePreview } from "./chat-session-workspace-file-preview";
|
|
30
31
|
import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
|
|
32
|
+
import { SessionCronJobContent } from "@/features/chat/components/workspace/session-cron-job-content";
|
|
31
33
|
import { t } from "@/shared/lib/i18n";
|
|
32
34
|
import { cn } from "@/shared/lib/utils";
|
|
33
35
|
|
|
@@ -36,11 +38,14 @@ type ChatSessionWorkspacePanelProps = {
|
|
|
36
38
|
activeChildSessionKey: string | null;
|
|
37
39
|
workspaceFileTabs: readonly ChatWorkspaceFileTab[];
|
|
38
40
|
activeWorkspaceFileKey: string | null;
|
|
41
|
+
activePanelKind?: "child-session" | "file" | "cron" | null;
|
|
42
|
+
sessionCronJobs?: readonly CronJobView[];
|
|
39
43
|
sessionProjectRoot: string | null;
|
|
40
44
|
displayMode?: "docked" | "overlay";
|
|
41
45
|
onSelectSession: (sessionKey: string) => void;
|
|
42
46
|
onSelectFile: (fileKey: string) => void;
|
|
43
47
|
onCloseFile: (fileKey: string) => void;
|
|
48
|
+
onSelectCronJobs?: () => void;
|
|
44
49
|
onClose: () => void;
|
|
45
50
|
onBackToParent: () => void;
|
|
46
51
|
onToolAction?: (action: ChatToolActionViewModel) => void;
|
|
@@ -167,20 +172,24 @@ function WorkspaceActiveChildHeader({
|
|
|
167
172
|
function buildWorkspaceTabsViewModel(params: {
|
|
168
173
|
resolvedChildTabs: ResolvedChildSessionTab[];
|
|
169
174
|
workspaceFileTabs: readonly ChatWorkspaceFileTab[];
|
|
175
|
+
sessionCronJobCount: number;
|
|
170
176
|
activeSelection: ReturnType<typeof resolveWorkspaceSelection>;
|
|
171
177
|
optimisticReadAtBySessionKey: Record<string, string>;
|
|
172
178
|
onSelectSession: (sessionKey: string) => void;
|
|
173
179
|
onSelectFile: (fileKey: string) => void;
|
|
174
180
|
onCloseFile: (fileKey: string) => void;
|
|
181
|
+
onSelectCronJobs: () => void;
|
|
175
182
|
}): WorkspaceTabViewModel[] {
|
|
176
183
|
const {
|
|
177
184
|
resolvedChildTabs,
|
|
178
185
|
workspaceFileTabs,
|
|
186
|
+
sessionCronJobCount,
|
|
179
187
|
activeSelection,
|
|
180
188
|
optimisticReadAtBySessionKey,
|
|
181
189
|
onSelectSession,
|
|
182
190
|
onSelectFile,
|
|
183
191
|
onCloseFile,
|
|
192
|
+
onSelectCronJobs,
|
|
184
193
|
} = params;
|
|
185
194
|
|
|
186
195
|
const childTabs = resolvedChildTabs.map((tab) => {
|
|
@@ -225,7 +234,19 @@ function buildWorkspaceTabsViewModel(params: {
|
|
|
225
234
|
onClose: () => onCloseFile(file.key),
|
|
226
235
|
}));
|
|
227
236
|
|
|
228
|
-
|
|
237
|
+
const cronTab =
|
|
238
|
+
sessionCronJobCount > 0
|
|
239
|
+
? [{
|
|
240
|
+
key: "cron:session",
|
|
241
|
+
kind: "cron" as const,
|
|
242
|
+
title: t("chatWorkspaceSessionCronJobs"),
|
|
243
|
+
tooltip: t("chatWorkspaceSessionCronJobs"),
|
|
244
|
+
active: activeSelection?.kind === "cron",
|
|
245
|
+
onSelect: onSelectCronJobs,
|
|
246
|
+
}]
|
|
247
|
+
: [];
|
|
248
|
+
|
|
249
|
+
return [...childTabs, ...fileTabs, ...cronTab];
|
|
229
250
|
}
|
|
230
251
|
|
|
231
252
|
export function ChatSessionWorkspacePanel({
|
|
@@ -233,11 +254,14 @@ export function ChatSessionWorkspacePanel({
|
|
|
233
254
|
activeChildSessionKey,
|
|
234
255
|
workspaceFileTabs,
|
|
235
256
|
activeWorkspaceFileKey,
|
|
257
|
+
activePanelKind,
|
|
258
|
+
sessionCronJobs = [],
|
|
236
259
|
sessionProjectRoot,
|
|
237
260
|
displayMode = "docked",
|
|
238
261
|
onSelectSession,
|
|
239
262
|
onSelectFile,
|
|
240
263
|
onCloseFile,
|
|
264
|
+
onSelectCronJobs = () => {},
|
|
241
265
|
onClose,
|
|
242
266
|
onBackToParent,
|
|
243
267
|
onToolAction,
|
|
@@ -251,8 +275,10 @@ export function ChatSessionWorkspacePanel({
|
|
|
251
275
|
const activeSelection = resolveWorkspaceSelection({
|
|
252
276
|
activeChildSessionKey,
|
|
253
277
|
activeWorkspaceFileKey,
|
|
278
|
+
activePanelKind,
|
|
254
279
|
childSessionTabs: resolvedChildTabs,
|
|
255
280
|
workspaceFileTabs,
|
|
281
|
+
sessionCronJobCount: sessionCronJobs.length,
|
|
256
282
|
});
|
|
257
283
|
const hasParentSession = resolvedChildTabs.some((tab) =>
|
|
258
284
|
Boolean(tab.parentSessionKey),
|
|
@@ -278,20 +304,24 @@ export function ChatSessionWorkspacePanel({
|
|
|
278
304
|
buildWorkspaceTabsViewModel({
|
|
279
305
|
resolvedChildTabs,
|
|
280
306
|
workspaceFileTabs,
|
|
307
|
+
sessionCronJobCount: sessionCronJobs.length,
|
|
281
308
|
activeSelection,
|
|
282
309
|
optimisticReadAtBySessionKey,
|
|
283
310
|
onSelectSession,
|
|
284
311
|
onSelectFile,
|
|
285
312
|
onCloseFile,
|
|
313
|
+
onSelectCronJobs,
|
|
286
314
|
}),
|
|
287
315
|
[
|
|
288
316
|
activeSelection,
|
|
289
317
|
onCloseFile,
|
|
318
|
+
onSelectCronJobs,
|
|
290
319
|
onSelectFile,
|
|
291
320
|
onSelectSession,
|
|
292
321
|
optimisticReadAtBySessionKey,
|
|
293
322
|
resolvedChildTabs,
|
|
294
323
|
workspaceFileTabs,
|
|
324
|
+
sessionCronJobs.length,
|
|
295
325
|
],
|
|
296
326
|
);
|
|
297
327
|
|
|
@@ -344,12 +374,14 @@ export function ChatSessionWorkspacePanel({
|
|
|
344
374
|
/>
|
|
345
375
|
</div>
|
|
346
376
|
</>
|
|
347
|
-
) : (
|
|
377
|
+
) : activeSelection.kind === "file" ? (
|
|
348
378
|
<ChatSessionWorkspaceFilePreview
|
|
349
379
|
file={activeSelection.file}
|
|
350
380
|
sessionProjectRoot={sessionProjectRoot}
|
|
351
381
|
onFileOpen={onFileOpen}
|
|
352
382
|
/>
|
|
383
|
+
) : (
|
|
384
|
+
<SessionCronJobContent jobs={sessionCronJobs} />
|
|
353
385
|
)}
|
|
354
386
|
</div>
|
|
355
387
|
</div>
|