@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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { NavLink } from 'react-router-dom';
|
|
2
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/shared/components/ui/popover';
|
|
3
|
+
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/shared/components/ui/select';
|
|
4
|
+
import { t, type I18nLanguage } from '@/shared/lib/i18n';
|
|
5
|
+
import type { UiTheme } from '@/shared/lib/theme';
|
|
6
|
+
import {
|
|
7
|
+
BookOpen,
|
|
8
|
+
ChevronRight,
|
|
9
|
+
Languages,
|
|
10
|
+
Palette,
|
|
11
|
+
Settings,
|
|
12
|
+
type LucideIcon,
|
|
13
|
+
} from 'lucide-react';
|
|
14
|
+
|
|
15
|
+
type ChatSidebarUtilityOption<Value extends string> = {
|
|
16
|
+
value: Value;
|
|
17
|
+
label: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ChatSidebarUtilityMenuProps = {
|
|
21
|
+
isOpen: boolean;
|
|
22
|
+
onOpenChange: (open: boolean) => void;
|
|
23
|
+
currentTheme: UiTheme;
|
|
24
|
+
currentThemeLabel: string;
|
|
25
|
+
themeOptions: ChatSidebarUtilityOption<UiTheme>[];
|
|
26
|
+
onSelectTheme: (theme: UiTheme) => void;
|
|
27
|
+
currentLanguage: I18nLanguage;
|
|
28
|
+
currentLanguageLabel: string;
|
|
29
|
+
languageOptions: ChatSidebarUtilityOption<I18nLanguage>[];
|
|
30
|
+
onSelectLanguage: (language: I18nLanguage) => void;
|
|
31
|
+
onOpenDocs: () => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function ChatSidebarUtilityMenu({
|
|
35
|
+
isOpen,
|
|
36
|
+
onOpenChange,
|
|
37
|
+
currentTheme,
|
|
38
|
+
currentThemeLabel,
|
|
39
|
+
themeOptions,
|
|
40
|
+
onSelectTheme,
|
|
41
|
+
currentLanguage,
|
|
42
|
+
currentLanguageLabel,
|
|
43
|
+
languageOptions,
|
|
44
|
+
onSelectLanguage,
|
|
45
|
+
onOpenDocs,
|
|
46
|
+
}: ChatSidebarUtilityMenuProps) {
|
|
47
|
+
const handleOpenDocs = () => {
|
|
48
|
+
onOpenDocs();
|
|
49
|
+
onOpenChange(false);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Popover open={isOpen} onOpenChange={onOpenChange}>
|
|
54
|
+
<PopoverTrigger asChild>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
aria-label={currentLanguage === 'zh' ? '设置菜单' : 'Settings menu'}
|
|
58
|
+
className="flex w-full items-center gap-2.5 rounded-xl px-3 py-2 text-[13px] font-medium text-gray-600 transition-all duration-base hover:bg-gray-200/60 hover:text-gray-800"
|
|
59
|
+
>
|
|
60
|
+
<Settings className="h-4 w-4 shrink-0 text-gray-400" />
|
|
61
|
+
<span className="min-w-0 flex-1 text-left">{t('settings')}</span>
|
|
62
|
+
<span className="max-w-[112px] truncate text-[13px] text-gray-500">
|
|
63
|
+
{currentThemeLabel} / {currentLanguageLabel}
|
|
64
|
+
</span>
|
|
65
|
+
</button>
|
|
66
|
+
</PopoverTrigger>
|
|
67
|
+
<PopoverContent side="top" align="start" className="w-64 p-2">
|
|
68
|
+
<div className="space-y-1">
|
|
69
|
+
<NavLink
|
|
70
|
+
to="/settings"
|
|
71
|
+
onClick={() => onOpenChange(false)}
|
|
72
|
+
className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] font-medium text-gray-700 transition-colors hover:bg-gray-100"
|
|
73
|
+
>
|
|
74
|
+
<Settings className="h-4 w-4 text-gray-400" />
|
|
75
|
+
<span className="flex-1 text-left">{t('settings')}</span>
|
|
76
|
+
</NavLink>
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
onClick={handleOpenDocs}
|
|
80
|
+
className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] font-medium text-gray-700 transition-colors hover:bg-gray-100"
|
|
81
|
+
>
|
|
82
|
+
<BookOpen className="h-4 w-4 text-gray-400" />
|
|
83
|
+
<span className="flex-1 text-left">{t('docBrowserHelp')}</span>
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div className="my-2 h-px bg-gray-200/70" />
|
|
88
|
+
|
|
89
|
+
<ChatSidebarUtilitySelect
|
|
90
|
+
icon={Palette}
|
|
91
|
+
label={t('theme')}
|
|
92
|
+
options={themeOptions}
|
|
93
|
+
value={currentTheme}
|
|
94
|
+
valueLabel={currentThemeLabel}
|
|
95
|
+
onSelect={onSelectTheme}
|
|
96
|
+
onCloseMenu={() => onOpenChange(false)}
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
<ChatSidebarUtilitySelect
|
|
100
|
+
icon={Languages}
|
|
101
|
+
label={t('language')}
|
|
102
|
+
options={languageOptions}
|
|
103
|
+
value={currentLanguage}
|
|
104
|
+
valueLabel={currentLanguageLabel}
|
|
105
|
+
onSelect={onSelectLanguage}
|
|
106
|
+
onCloseMenu={() => onOpenChange(false)}
|
|
107
|
+
/>
|
|
108
|
+
</PopoverContent>
|
|
109
|
+
</Popover>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ChatSidebarUtilitySelect<Value extends string>({
|
|
114
|
+
icon: Icon,
|
|
115
|
+
label,
|
|
116
|
+
options,
|
|
117
|
+
value,
|
|
118
|
+
valueLabel,
|
|
119
|
+
onSelect,
|
|
120
|
+
onCloseMenu,
|
|
121
|
+
}: {
|
|
122
|
+
icon: LucideIcon;
|
|
123
|
+
label: string;
|
|
124
|
+
options: ChatSidebarUtilityOption<Value>[];
|
|
125
|
+
value: Value;
|
|
126
|
+
valueLabel: string;
|
|
127
|
+
onSelect: (value: Value) => void;
|
|
128
|
+
onCloseMenu: () => void;
|
|
129
|
+
}) {
|
|
130
|
+
return (
|
|
131
|
+
<Select
|
|
132
|
+
value={value}
|
|
133
|
+
onOpenChange={(open) => {
|
|
134
|
+
if (!open) {
|
|
135
|
+
onCloseMenu();
|
|
136
|
+
}
|
|
137
|
+
}}
|
|
138
|
+
onValueChange={(nextValue) => onSelect(nextValue as Value)}
|
|
139
|
+
>
|
|
140
|
+
<SelectTrigger
|
|
141
|
+
aria-label={label}
|
|
142
|
+
className="h-auto w-full rounded-lg border-0 bg-transparent px-3 py-2 text-[13px] font-medium text-gray-700 shadow-none hover:bg-gray-100 focus:ring-0"
|
|
143
|
+
indicator={<ChevronRight className="h-4 w-4 text-gray-400" />}
|
|
144
|
+
>
|
|
145
|
+
<div className="flex min-w-0 items-center gap-2.5">
|
|
146
|
+
<Icon className="h-4 w-4 shrink-0 text-gray-400" />
|
|
147
|
+
<span className="text-left">{label}</span>
|
|
148
|
+
</div>
|
|
149
|
+
<span className="ml-auto max-w-[96px] truncate text-[13px] text-gray-500">
|
|
150
|
+
{valueLabel}
|
|
151
|
+
</span>
|
|
152
|
+
</SelectTrigger>
|
|
153
|
+
<SelectContent
|
|
154
|
+
side="right"
|
|
155
|
+
align="center"
|
|
156
|
+
sideOffset={6}
|
|
157
|
+
className="z-[var(--z-tooltip)] min-w-[7rem] rounded-lg"
|
|
158
|
+
viewportClassName="h-auto min-w-[7rem] w-auto"
|
|
159
|
+
>
|
|
160
|
+
{options.map((option) => {
|
|
161
|
+
return (
|
|
162
|
+
<SelectItem
|
|
163
|
+
key={option.value}
|
|
164
|
+
value={option.value}
|
|
165
|
+
className="text-[13px]"
|
|
166
|
+
>
|
|
167
|
+
{option.label}
|
|
168
|
+
</SelectItem>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</SelectContent>
|
|
172
|
+
</Select>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
@@ -9,11 +9,14 @@ import { useChatSessionListStore } from '@/features/chat/stores/chat-session-lis
|
|
|
9
9
|
const mocks = vi.hoisted(() => ({
|
|
10
10
|
createSession: vi.fn(() => 'draft-session-key'),
|
|
11
11
|
goToSession: vi.fn(),
|
|
12
|
+
goToChatRoot: vi.fn(),
|
|
12
13
|
setQuery: vi.fn(),
|
|
13
14
|
setListMode: vi.fn(),
|
|
14
15
|
selectSession: vi.fn(),
|
|
15
16
|
openChildSessionPanel: vi.fn(),
|
|
16
17
|
docOpen: vi.fn(),
|
|
18
|
+
setLanguage: vi.fn(),
|
|
19
|
+
setTheme: vi.fn(),
|
|
17
20
|
updateNcpSession: vi.fn(),
|
|
18
21
|
agents: [] as Array<{ id: string; displayName?: string; avatarUrl?: string | null }>,
|
|
19
22
|
sessionItems: [] as NcpSessionListItemView[],
|
|
@@ -31,6 +34,7 @@ vi.mock('@/features/chat/components/providers/chat-presenter.provider', () => ({
|
|
|
31
34
|
usePresenter: () => ({
|
|
32
35
|
chatUiManager: {
|
|
33
36
|
goToSession: mocks.goToSession,
|
|
37
|
+
goToChatRoot: mocks.goToChatRoot,
|
|
34
38
|
},
|
|
35
39
|
chatSessionListManager: {
|
|
36
40
|
createSession: mocks.createSession,
|
|
@@ -100,14 +104,14 @@ vi.mock('@/shared/hooks/use-agents', () => ({
|
|
|
100
104
|
vi.mock('@/app/components/i18n-provider', () => ({
|
|
101
105
|
useI18n: () => ({
|
|
102
106
|
language: 'en',
|
|
103
|
-
setLanguage:
|
|
107
|
+
setLanguage: mocks.setLanguage
|
|
104
108
|
})
|
|
105
109
|
}));
|
|
106
110
|
|
|
107
111
|
vi.mock('@/app/components/theme-provider', () => ({
|
|
108
112
|
useTheme: () => ({
|
|
109
113
|
theme: 'warm',
|
|
110
|
-
setTheme:
|
|
114
|
+
setTheme: mocks.setTheme
|
|
111
115
|
})
|
|
112
116
|
}));
|
|
113
117
|
|
|
@@ -121,11 +125,14 @@ function resetSidebarTestState() {
|
|
|
121
125
|
mocks.createSession.mockReset();
|
|
122
126
|
mocks.createSession.mockReturnValue('draft-session-key');
|
|
123
127
|
mocks.goToSession.mockReset();
|
|
128
|
+
mocks.goToChatRoot.mockReset();
|
|
124
129
|
mocks.setQuery.mockReset();
|
|
125
130
|
mocks.setListMode.mockReset();
|
|
126
131
|
mocks.selectSession.mockReset();
|
|
127
132
|
mocks.openChildSessionPanel.mockReset();
|
|
128
133
|
mocks.docOpen.mockReset();
|
|
134
|
+
mocks.setLanguage.mockReset();
|
|
135
|
+
mocks.setTheme.mockReset();
|
|
129
136
|
mocks.updateNcpSession.mockReset();
|
|
130
137
|
mocks.updateNcpSession.mockResolvedValue({});
|
|
131
138
|
mocks.agents = [];
|
|
@@ -166,7 +173,7 @@ describe('ChatSidebar create and list basics', () => {
|
|
|
166
173
|
fireEvent.click(screen.getByLabelText('Session Type'));
|
|
167
174
|
fireEvent.click(screen.getByText('Codex'));
|
|
168
175
|
|
|
169
|
-
expect(mocks.createSession).toHaveBeenCalledWith('codex');
|
|
176
|
+
expect(mocks.createSession).toHaveBeenCalledWith('codex', undefined);
|
|
170
177
|
await waitFor(() => {
|
|
171
178
|
expect(screen.queryByText('Codex')).toBeNull();
|
|
172
179
|
});
|
|
@@ -233,8 +240,38 @@ describe('ChatSidebar create and list basics', () => {
|
|
|
233
240
|
fireEvent.click(screen.getByText('Codex'));
|
|
234
241
|
|
|
235
242
|
expect(mocks.setQuery).toHaveBeenCalledWith('release notes');
|
|
236
|
-
expect(mocks.createSession).toHaveBeenCalledWith('codex');
|
|
237
|
-
expect(mocks.
|
|
243
|
+
expect(mocks.createSession).toHaveBeenCalledWith('codex', undefined);
|
|
244
|
+
expect(mocks.goToChatRoot).toHaveBeenCalledWith();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('keeps low-frequency utility choices behind nested selectors', () => {
|
|
248
|
+
render(
|
|
249
|
+
<MemoryRouter>
|
|
250
|
+
<ChatSidebar />
|
|
251
|
+
</MemoryRouter>
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
expect(screen.queryByRole('button', { name: 'Help Docs' })).toBeNull();
|
|
255
|
+
expect(screen.queryByText('Language')).toBeNull();
|
|
256
|
+
expect(screen.queryByRole('option', { name: 'Cool' })).toBeNull();
|
|
257
|
+
|
|
258
|
+
fireEvent.click(screen.getByRole('button', { name: 'Settings menu' }));
|
|
259
|
+
expect(screen.getByText('Theme')).not.toBeNull();
|
|
260
|
+
expect(screen.queryByRole('option', { name: 'Cool' })).toBeNull();
|
|
261
|
+
|
|
262
|
+
fireEvent.click(screen.getByRole('button', { name: 'Help Docs' }));
|
|
263
|
+
|
|
264
|
+
expect(mocks.docOpen).toHaveBeenCalledWith(undefined, {
|
|
265
|
+
kind: 'docs',
|
|
266
|
+
newTab: true,
|
|
267
|
+
title: 'Docs',
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
fireEvent.click(screen.getByRole('button', { name: 'Settings menu' }));
|
|
271
|
+
fireEvent.click(screen.getByRole('combobox', { name: 'Theme' }));
|
|
272
|
+
fireEvent.click(screen.getByRole('option', { name: 'Cool' }));
|
|
273
|
+
|
|
274
|
+
expect(mocks.setTheme).toHaveBeenCalledWith('cool');
|
|
238
275
|
});
|
|
239
276
|
|
|
240
277
|
it('creates the default session directly from the compact mobile add button when no menu is needed', () => {
|
|
@@ -254,8 +291,8 @@ describe('ChatSidebar create and list basics', () => {
|
|
|
254
291
|
|
|
255
292
|
fireEvent.click(screen.getByRole('button', { name: 'New Task' }));
|
|
256
293
|
|
|
257
|
-
expect(mocks.createSession).toHaveBeenCalledWith('native');
|
|
258
|
-
expect(mocks.
|
|
294
|
+
expect(mocks.createSession).toHaveBeenCalledWith('native', undefined);
|
|
295
|
+
expect(mocks.goToChatRoot).toHaveBeenCalledWith();
|
|
259
296
|
});
|
|
260
297
|
|
|
261
298
|
it('shows a session type badge for non-native sessions in the list', () => {
|
|
@@ -564,7 +601,7 @@ describe('ChatSidebar project-first mode', () => {
|
|
|
564
601
|
fireEvent.click(screen.getByText('Codex'));
|
|
565
602
|
|
|
566
603
|
expect(mocks.createSession).toHaveBeenCalledWith('codex', '/tmp/project-mobile');
|
|
567
|
-
expect(mocks.
|
|
604
|
+
expect(mocks.goToChatRoot).toHaveBeenCalledWith();
|
|
568
605
|
});
|
|
569
606
|
});
|
|
570
607
|
|
|
@@ -2,7 +2,6 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
2
2
|
import type { SessionEntryView } from '@/shared/lib/api';
|
|
3
3
|
import { BrandHeader } from '@/shared/components/common/brand-header';
|
|
4
4
|
import { StatusBadge } from '@/shared/components/common/status-badge';
|
|
5
|
-
import { SelectItem } from '@/shared/components/ui/select';
|
|
6
5
|
import { ChatSidebarListModeSwitch, ChatSidebarProjectGroups, type ChatSidebarProjectGroup } from '@/features/chat';
|
|
7
6
|
import { useChatSidebarSessionLabelEditor } from '@/features/chat/hooks/use-chat-sidebar-session-label-editor';
|
|
8
7
|
import { useNcpSessionListView, type NcpSessionListItemView } from '@/features/chat/hooks/use-ncp-session-list-view';
|
|
@@ -14,26 +13,24 @@ import { useAgents } from '@/shared/hooks/use-agents';
|
|
|
14
13
|
import { getSessionProjectName } from '@/shared/lib/session-project';
|
|
15
14
|
import { cn } from '@/shared/lib/utils';
|
|
16
15
|
import { LANGUAGE_OPTIONS, t, type I18nLanguage } from '@/shared/lib/i18n';
|
|
17
|
-
import { THEME_OPTIONS
|
|
16
|
+
import { THEME_OPTIONS } from '@/shared/lib/theme';
|
|
18
17
|
import { useI18n } from '@/app/components/i18n-provider';
|
|
19
18
|
import { useTheme } from '@/app/components/theme-provider';
|
|
20
19
|
import { useDocBrowser } from '@/shared/components/doc-browser';
|
|
21
|
-
import {
|
|
20
|
+
import { SidebarNavLinkItem } from '@/app/components/layout/sidebar-items';
|
|
22
21
|
import {
|
|
23
22
|
AlarmClock,
|
|
24
23
|
Bot,
|
|
25
|
-
BookOpen,
|
|
26
24
|
BrainCircuit,
|
|
27
|
-
Languages,
|
|
28
25
|
MessageSquareText,
|
|
29
|
-
Palette,
|
|
30
|
-
Settings
|
|
31
26
|
} from 'lucide-react';
|
|
32
27
|
import { ChatSidebarSessionEntry } from '@/features/chat/components/layout/chat-sidebar-session-entry';
|
|
33
28
|
import {
|
|
34
29
|
ChatSidebarDesktopToolbar,
|
|
35
30
|
ChatSidebarMobileToolbar,
|
|
36
31
|
} from '@/features/chat/components/layout/chat-sidebar-toolbar';
|
|
32
|
+
import { ChatSidebarUtilityMenu } from '@/features/chat/components/layout/chat-sidebar-utility-menu';
|
|
33
|
+
import { isMacDesktopHost, isWindowsDesktopHost } from '@/platforms/desktop';
|
|
37
34
|
|
|
38
35
|
type DateGroup = {
|
|
39
36
|
label: string;
|
|
@@ -186,6 +183,7 @@ export function ChatSidebar({
|
|
|
186
183
|
const presenter = usePresenter();
|
|
187
184
|
const docBrowser = useDocBrowser();
|
|
188
185
|
const [isCreateMenuOpen, setIsCreateMenuOpen] = useState(false);
|
|
186
|
+
const [isUtilityMenuOpen, setIsUtilityMenuOpen] = useState(false);
|
|
189
187
|
const inputSnapshot = useChatInputStore((state) => state.snapshot);
|
|
190
188
|
const listSnapshot = useChatSessionListStore((state) => state.snapshot);
|
|
191
189
|
const systemStatus = useSystemStatus();
|
|
@@ -195,6 +193,14 @@ export function ChatSidebar({
|
|
|
195
193
|
const { theme, setTheme } = useTheme();
|
|
196
194
|
const currentThemeLabel = t(THEME_OPTIONS.find((o) => o.value === theme)?.labelKey ?? 'themeWarm');
|
|
197
195
|
const currentLanguageLabel = LANGUAGE_OPTIONS.find((o) => o.value === language)?.label ?? language;
|
|
196
|
+
const utilityThemeOptions = useMemo(
|
|
197
|
+
() => THEME_OPTIONS.map((option) => ({ value: option.value, label: t(option.labelKey) })),
|
|
198
|
+
[],
|
|
199
|
+
);
|
|
200
|
+
const utilityLanguageOptions = useMemo(
|
|
201
|
+
() => LANGUAGE_OPTIONS.map((option) => ({ value: option.value, label: option.label })),
|
|
202
|
+
[],
|
|
203
|
+
);
|
|
198
204
|
const agentsById = useMemo(
|
|
199
205
|
() => new Map((agentsQuery.data?.agents ?? []).map((agent) => [agent.id, agent])),
|
|
200
206
|
[agentsQuery.data?.agents]
|
|
@@ -262,10 +268,10 @@ export function ChatSidebar({
|
|
|
262
268
|
: 'w-[280px] shrink-0 border-r border-gray-200/60',
|
|
263
269
|
)}
|
|
264
270
|
>
|
|
265
|
-
{!isMobileVariant ? (
|
|
266
|
-
<div className=
|
|
271
|
+
{!isMobileVariant && !isWindowsDesktopHost() ? (
|
|
272
|
+
<div className={cn('px-5 pb-3', isMacDesktopHost() ? 'pt-1.5' : 'pt-5')}>
|
|
267
273
|
<BrandHeader
|
|
268
|
-
className="flex items-center gap-2
|
|
274
|
+
className="flex min-w-0 items-center gap-2"
|
|
269
275
|
suffix={<StatusBadge status={systemStatus.connectionStatus} />}
|
|
270
276
|
/>
|
|
271
277
|
</div>
|
|
@@ -361,43 +367,20 @@ export function ChatSidebar({
|
|
|
361
367
|
</div>
|
|
362
368
|
|
|
363
369
|
{!isMobileVariant ? (
|
|
364
|
-
<div className="px-3 py-3 border-t border-gray-200/60
|
|
365
|
-
<
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
+
<div className="px-3 py-3 border-t border-gray-200/60">
|
|
371
|
+
<ChatSidebarUtilityMenu
|
|
372
|
+
isOpen={isUtilityMenuOpen}
|
|
373
|
+
onOpenChange={setIsUtilityMenuOpen}
|
|
374
|
+
currentTheme={theme}
|
|
375
|
+
currentThemeLabel={currentThemeLabel}
|
|
376
|
+
themeOptions={utilityThemeOptions}
|
|
377
|
+
onSelectTheme={setTheme}
|
|
378
|
+
currentLanguage={language}
|
|
379
|
+
currentLanguageLabel={currentLanguageLabel}
|
|
380
|
+
languageOptions={utilityLanguageOptions}
|
|
381
|
+
onSelectLanguage={handleLanguageSwitch}
|
|
382
|
+
onOpenDocs={() => docBrowser.open(undefined, { kind: 'docs', newTab: true, title: 'Docs' })}
|
|
370
383
|
/>
|
|
371
|
-
<SidebarActionItem
|
|
372
|
-
onClick={() => docBrowser.open(undefined, { kind: 'docs', newTab: true, title: 'Docs' })}
|
|
373
|
-
icon={BookOpen}
|
|
374
|
-
label={t('docBrowserHelp')}
|
|
375
|
-
density="compact"
|
|
376
|
-
/>
|
|
377
|
-
<SidebarSelectItem
|
|
378
|
-
value={theme}
|
|
379
|
-
onValueChange={(value) => setTheme(value as UiTheme)}
|
|
380
|
-
icon={Palette}
|
|
381
|
-
label={t('theme')}
|
|
382
|
-
valueLabel={currentThemeLabel}
|
|
383
|
-
density="compact"
|
|
384
|
-
>
|
|
385
|
-
{THEME_OPTIONS.map((option) => (
|
|
386
|
-
<SelectItem key={option.value} value={option.value} className="text-xs">{t(option.labelKey)}</SelectItem>
|
|
387
|
-
))}
|
|
388
|
-
</SidebarSelectItem>
|
|
389
|
-
<SidebarSelectItem
|
|
390
|
-
value={language}
|
|
391
|
-
onValueChange={(value) => handleLanguageSwitch(value as I18nLanguage)}
|
|
392
|
-
icon={Languages}
|
|
393
|
-
label={t('language')}
|
|
394
|
-
valueLabel={currentLanguageLabel}
|
|
395
|
-
density="compact"
|
|
396
|
-
>
|
|
397
|
-
{LANGUAGE_OPTIONS.map((option) => (
|
|
398
|
-
<SelectItem key={option.value} value={option.value} className="text-xs">{option.label}</SelectItem>
|
|
399
|
-
))}
|
|
400
|
-
</SidebarSelectItem>
|
|
401
384
|
</div>
|
|
402
385
|
) : null}
|
|
403
386
|
</aside>
|
|
@@ -10,6 +10,8 @@ import type { ChatThreadSnapshot } from '@/features/chat/stores/chat-thread.stor
|
|
|
10
10
|
export type ChatInputManagerLike = {
|
|
11
11
|
syncSnapshot: (patch: Record<string, unknown>) => void;
|
|
12
12
|
setDraft: (next: SetStateAction<string>) => void;
|
|
13
|
+
requestComposerFocusAtEnd: () => void;
|
|
14
|
+
consumeComposerFocusRequest: (requestId: number) => void;
|
|
13
15
|
setComposerNodes: (next: SetStateAction<ChatComposerNode[]>) => void;
|
|
14
16
|
addAttachments?: (attachments: NcpDraftAttachment[]) => NcpDraftAttachment[];
|
|
15
17
|
restoreComposerState?: (nodes: ChatComposerNode[], attachments: NcpDraftAttachment[]) => void;
|
|
@@ -33,6 +35,7 @@ export type ChatThreadManagerLike = {
|
|
|
33
35
|
createSession: () => void;
|
|
34
36
|
goToProviders: () => void;
|
|
35
37
|
openChildSessionPanel: (params: { parentSessionKey: string; activeChildSessionKey?: string | null }) => void;
|
|
38
|
+
openSessionCronPanel: (sessionKey: string) => void;
|
|
36
39
|
openFilePreview: (action: ChatFileOpenActionViewModel) => void;
|
|
37
40
|
openSessionFromToolAction: (action: ChatToolActionViewModel) => void;
|
|
38
41
|
selectChildSessionDetail: (sessionKey: string) => void;
|
|
@@ -48,6 +51,7 @@ export type ChatPresenterLike = {
|
|
|
48
51
|
chatInputManager: ChatInputManagerLike;
|
|
49
52
|
chatSessionListManager: ChatSessionListManager;
|
|
50
53
|
chatThreadManager: ChatThreadManagerLike;
|
|
54
|
+
startAgentCreationDraft: (prompt: string) => void;
|
|
51
55
|
};
|
|
52
56
|
|
|
53
57
|
const ChatPresenterContext = createContext<ChatPresenterLike | null>(null);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Trash2 } from "lucide-react";
|
|
2
|
+
import type { CronJobView } from "@/shared/lib/api";
|
|
3
|
+
import { Button } from "@/shared/components/ui/button";
|
|
4
|
+
import { useConfirmDialog } from "@/shared/hooks/use-confirm-dialog";
|
|
5
|
+
import { useDeleteCronJob } from "@/shared/hooks/use-config";
|
|
6
|
+
import {
|
|
7
|
+
describeCronDelivery,
|
|
8
|
+
describeCronSchedule,
|
|
9
|
+
formatCronDate,
|
|
10
|
+
} from "@/shared/lib/cron";
|
|
11
|
+
import { t } from "@/shared/lib/i18n";
|
|
12
|
+
|
|
13
|
+
export function SessionCronJobContent({ jobs }: { jobs: readonly CronJobView[] }) {
|
|
14
|
+
const deleteCronJob = useDeleteCronJob();
|
|
15
|
+
const { confirm, ConfirmDialog } = useConfirmDialog();
|
|
16
|
+
|
|
17
|
+
const handleDelete = async (job: CronJobView) => {
|
|
18
|
+
const confirmed = await confirm({
|
|
19
|
+
title: `${t("cronDeleteConfirm")}?`,
|
|
20
|
+
description: job.name ? `${job.name} (${job.id})` : job.id,
|
|
21
|
+
confirmLabel: t("delete"),
|
|
22
|
+
});
|
|
23
|
+
if (!confirmed) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
deleteCronJob.mutate({ id: job.id });
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="h-full overflow-y-auto custom-scrollbar px-4 py-4">
|
|
31
|
+
<div className="mb-4">
|
|
32
|
+
<div className="text-sm font-semibold text-gray-900">
|
|
33
|
+
{t("chatWorkspaceSessionCronJobs")}
|
|
34
|
+
</div>
|
|
35
|
+
<div className="mt-1 text-xs text-gray-500">
|
|
36
|
+
{t("cronTotalLabel")}: {jobs.length}
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
{jobs.length === 0 ? (
|
|
40
|
+
<div className="py-8 text-center text-sm text-gray-500">
|
|
41
|
+
{t("chatWorkspaceCronJobEmpty")}
|
|
42
|
+
</div>
|
|
43
|
+
) : (
|
|
44
|
+
<div className="space-y-3">
|
|
45
|
+
{jobs.map((job) => (
|
|
46
|
+
<div
|
|
47
|
+
key={job.id}
|
|
48
|
+
className="rounded-lg border border-gray-200 bg-white px-3 py-3"
|
|
49
|
+
>
|
|
50
|
+
<div className="flex items-start justify-between gap-3">
|
|
51
|
+
<div className="min-w-0">
|
|
52
|
+
<div className="truncate text-sm font-semibold text-gray-900">
|
|
53
|
+
{job.name || job.id}
|
|
54
|
+
</div>
|
|
55
|
+
<div className="mt-1 text-[11px] text-gray-400">
|
|
56
|
+
{job.id}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<Button
|
|
60
|
+
type="button"
|
|
61
|
+
variant="subtle"
|
|
62
|
+
size="sm"
|
|
63
|
+
className="h-7 gap-1 rounded-lg px-2"
|
|
64
|
+
onClick={() => void handleDelete(job)}
|
|
65
|
+
disabled={deleteCronJob.isPending}
|
|
66
|
+
>
|
|
67
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
68
|
+
{t("delete")}
|
|
69
|
+
</Button>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="mt-3 space-y-1.5 text-xs text-gray-500">
|
|
72
|
+
<div>
|
|
73
|
+
<span className="font-medium text-gray-700">{t("cronScheduleLabel")}:</span>{" "}
|
|
74
|
+
{describeCronSchedule(job)}
|
|
75
|
+
</div>
|
|
76
|
+
<div>
|
|
77
|
+
<span className="font-medium text-gray-700">{t("cronNextRun")}:</span>{" "}
|
|
78
|
+
{formatCronDate(job.state.nextRunAt)}
|
|
79
|
+
</div>
|
|
80
|
+
<div>
|
|
81
|
+
<span className="font-medium text-gray-700">{t("cronLastRun")}:</span>{" "}
|
|
82
|
+
{formatCronDate(job.state.lastRunAt)}
|
|
83
|
+
</div>
|
|
84
|
+
<div>
|
|
85
|
+
<span className="font-medium text-gray-700">{t("cronLastStatus")}:</span>{" "}
|
|
86
|
+
{job.state.lastStatus ?? "-"}
|
|
87
|
+
</div>
|
|
88
|
+
<div>
|
|
89
|
+
<span className="font-medium text-gray-700">{t("cronDeliverTo")}:</span>{" "}
|
|
90
|
+
{describeCronDelivery(job)}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="mt-3 whitespace-pre-wrap break-words text-sm text-gray-700">
|
|
94
|
+
{job.payload.message}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
<ConfirmDialog />
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|