@nextclaw/ui 0.12.34 → 0.12.35-beta.1
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 +127 -0
- package/dist/assets/{api-rASx2-N4.js → api-De1PAch0.js} +3 -3
- package/dist/assets/{book-open-BcnAiKde.js → book-open-825Pcd0z.js} +1 -1
- package/dist/assets/channels-list-page-CGjXDSD-.js +8 -0
- package/dist/assets/chat-page-RsUTK_bY.js +105 -0
- package/dist/assets/{config-split-page-CcrEUtwu.js → config-split-page-BvfxFQSb.js} +1 -1
- package/dist/assets/{createLucideIcon-DzY6wN61.js → createLucideIcon-CeNHY6BW.js} +1 -1
- package/dist/assets/desktop-update-config-B_YIv5ZJ.js +1 -0
- package/dist/assets/{dist-NBjcwlsw.js → dist-uH7tiW6E.js} +1 -1
- package/dist/assets/{doc-browser-D2pq4IHR.js → doc-browser-CDe9yz9n.js} +1 -1
- package/dist/assets/doc-browser-DwT64GuF.js +1 -0
- package/dist/assets/{doc-browser-context-BPeeiNJ_.js → doc-browser-context-CQAx_92e.js} +1 -1
- package/dist/assets/{es2015-CVAuMqlM.js → es2015-c9XYpli5.js} +1 -1
- package/dist/assets/{external-link-CpEvG65F.js → external-link-BE1deZ_c.js} +1 -1
- package/dist/assets/i18n-DnubSHoK.js +1 -0
- package/dist/assets/index-6WZ6JjaU.css +1 -0
- package/dist/assets/index-BjH_p3R6.js +103 -0
- package/dist/assets/{key-round-DUq47t0P.js → key-round-CHo3gqpO.js} +1 -1
- package/dist/assets/mcp-marketplace-page-0C214hGi.js +40 -0
- package/dist/assets/mcp-marketplace-page-amWcMncu.js +1 -0
- package/dist/assets/model-config-B8zNrkag.js +1 -0
- package/dist/assets/notice-card-DO33_LzD.js +5 -0
- package/dist/assets/play-CqlWbVNo.js +1 -0
- package/dist/assets/plus-p61tQh2-.js +1 -0
- package/dist/assets/provider-scoped-model-input-B0_r_4EY.js +1 -0
- package/dist/assets/providers-list-B6v3foi7.js +1 -0
- package/dist/assets/remote-k671rLmY.js +1 -0
- package/dist/assets/{rotate-cw-BN9yjccP.js → rotate-cw-CYUByuqo.js} +1 -1
- package/dist/assets/runtime-config-page-DgH-XJwK.js +1 -0
- package/dist/assets/{save-CO_4qf6b.js → save-B6Ga8JIq.js} +1 -1
- package/dist/assets/{search-CRtQwr-h.js → search-5kKpgNoP.js} +1 -1
- package/dist/assets/search-config-DPZ6d9y6.js +1 -0
- package/dist/assets/{secrets-config-DFbHCb9n.js → secrets-config-CrXA11bo.js} +2 -2
- package/dist/assets/{select-HI5kiwX_.js → select-C0hGOCOK.js} +1 -1
- package/dist/assets/{setting-row-B4QnghjV.js → setting-row-xsM_0nTd.js} +1 -1
- package/dist/assets/{tabs-custom-D48zdZoc.js → tabs-custom-Bnqfhe4p.js} +1 -1
- package/dist/assets/{tag-chip-BPgMfp9l.js → tag-chip-cktJJMzh.js} +1 -1
- package/dist/assets/x-DDQw-C2x.js +1 -0
- package/dist/index.html +17 -29
- package/package.json +9 -9
- package/src/app/components/layout/app-layout.test.tsx +49 -4
- package/src/app/components/layout/app-layout.tsx +4 -7
- package/src/app/components/layout/settings-entry-page.test.tsx +9 -0
- package/src/app/components/layout/sidebar.layout.test.tsx +2 -2
- package/src/app/components/layout/sidebar.tsx +3 -3
- package/src/app/configs/app-navigation.config.ts +0 -12
- package/src/app/hooks/use-app-event-consumers.ts +1 -6
- package/src/app/hooks/use-viewport-layout.ts +0 -1
- package/src/app/index.tsx +2 -10
- package/src/features/channels/components/config/channel-form.tsx +85 -47
- package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +45 -1
- package/src/features/channels/components/config/weixin-channel-auth-section.tsx +119 -1
- package/src/features/channels/hooks/use-channel-auth.ts +8 -1
- package/src/features/channels/index.ts +1 -1
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +2 -2
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +2 -7
- package/src/features/chat/components/layout/chat-sidebar.test.tsx +27 -3
- package/src/features/chat/components/layout/chat-sidebar.tsx +2 -3
- package/src/features/chat/managers/chat-session-list.manager.test.ts +12 -11
- package/src/features/chat/managers/chat-session-list.manager.ts +2 -1
- package/src/features/chat/managers/chat-ui.manager.ts +2 -2
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +3 -3
- package/src/features/chat/pages/ncp-chat-page.test.ts +3 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +19 -19
- package/src/features/chat/utils/chat-session-route.utils.test.ts +14 -0
- package/src/features/chat/utils/chat-session-route.utils.ts +3 -1
- package/src/features/chat/utils/ncp-chat-runtime-availability.utils.test.ts +6 -6
- package/src/features/marketplace/components/marketplace-list-card.tsx +4 -36
- package/src/features/marketplace/components/marketplace-page-detail.test.tsx +0 -1
- package/src/features/marketplace/components/marketplace-page.test.tsx +9 -128
- package/src/features/marketplace/components/marketplace-page.tsx +16 -72
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +1 -1
- package/src/features/marketplace/hooks/use-marketplace.ts +1 -10
- package/src/features/marketplace/utils/marketplace-installed-cache.utils.test.ts +23 -21
- package/src/features/system-status/managers/system-status.manager.bootstrap-polling.test.ts +12 -12
- package/src/features/system-status/managers/system-status.manager.test.ts +6 -6
- package/src/index.css +1 -1
- package/src/platforms/desktop/components/desktop-app-shell.test.tsx +29 -13
- package/src/platforms/desktop/components/desktop-app-shell.tsx +14 -16
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +2 -3
- package/src/platforms/mobile/components/mobile-app-shell.tsx +3 -1
- package/src/platforms/mobile/components/mobile-topbar.tsx +6 -5
- package/src/platforms/mobile/index.ts +1 -0
- package/src/shared/components/config/provider-form-support.ts +51 -33
- package/src/shared/components/config/provider-form.tsx +68 -35
- package/src/shared/components/config/provider-models-section.tsx +19 -4
- package/src/shared/components/model-config.test.tsx +2 -0
- package/src/shared/components/ui/button.tsx +1 -1
- package/src/shared/lib/api/channel-auth.types.ts +8 -0
- package/src/shared/lib/api/types.ts +17 -20
- package/src/shared/lib/api/utils/channel-auth.utils.ts +10 -1
- package/src/shared/lib/api/utils/marketplace.utils.ts +0 -5
- package/src/shared/lib/i18n/channel-auth.constants.ts +24 -10
- package/src/shared/lib/i18n/chat-labels.utils.ts +2 -2
- package/src/shared/lib/i18n/index.ts +2 -0
- package/src/shared/lib/i18n/marketplace.ts +0 -16
- package/src/shared/lib/provider-models/index.ts +41 -13
- package/src/shared/lib/ui-document-title/index.ts +0 -1
- package/dist/assets/app-manager-provider-CUkcbdWk.js +0 -1
- package/dist/assets/channels-list-page-ZWYqfcCe.js +0 -8
- package/dist/assets/chat-page-ACm58eUO.js +0 -1
- package/dist/assets/cpu-DKc3bQic.js +0 -3
- package/dist/assets/desktop-C-cDRCBY.js +0 -3
- package/dist/assets/desktop-update-config-DAD9JN9Q.js +0 -1
- package/dist/assets/dialog-CR80q3Rm.js +0 -5
- package/dist/assets/doc-browser-DhIkcy0-.js +0 -1
- package/dist/assets/doc-browser-fLs0TcdS.js +0 -1
- package/dist/assets/download-CMM8po31.js +0 -1
- package/dist/assets/i18n-D1144VAA.js +0 -1
- package/dist/assets/index-DV57tBmg.css +0 -1
- package/dist/assets/index-Db9Cufsy.js +0 -100
- package/dist/assets/marketplace-page-CAp_gpNe.js +0 -105
- package/dist/assets/marketplace-page-PPY5uMHK.js +0 -1
- package/dist/assets/mcp-marketplace-page-87__92b1.js +0 -1
- package/dist/assets/mcp-marketplace-page-C6HENB6V.js +0 -40
- package/dist/assets/model-config-CK3a8DWH.js +0 -1
- package/dist/assets/notice-card-CF4AJPM3.js +0 -1
- package/dist/assets/play-AqrNslHI.js +0 -1
- package/dist/assets/plus-B-YHtTNC.js +0 -1
- package/dist/assets/popover-DeAliE_4.js +0 -1
- package/dist/assets/provider-scoped-model-input-B8LJ6RCt.js +0 -1
- package/dist/assets/providers-list-BTPjqNdK.js +0 -1
- package/dist/assets/refresh-cw-CrbD8EkT.js +0 -1
- package/dist/assets/remote-DiPWGRZc.js +0 -1
- package/dist/assets/runtime-config-page-Bb9he7t8.js +0 -1
- package/dist/assets/search-config-BkbSXa9d.js +0 -1
- package/dist/assets/settings-MWL2SMyk.js +0 -1
- package/dist/assets/sparkles-BmgOD4nY.js +0 -1
- package/dist/assets/status-dot-l3kPFdq_.js +0 -1
- package/dist/assets/use-config-BFqzVF8Y.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-DXhw9W4l.js +0 -1
- package/dist/assets/x-BeyYA_h6.js +0 -1
- /package/dist/assets/{config-hints-BNfpOL4J.js → config-hints-DGfwbumO.js} +0 -0
|
@@ -3,15 +3,11 @@ import type {
|
|
|
3
3
|
MarketplaceInstalledRecord,
|
|
4
4
|
MarketplaceItemSummary,
|
|
5
5
|
MarketplaceManageAction,
|
|
6
|
-
MarketplacePluginContentView,
|
|
7
6
|
MarketplaceSkillContentView,
|
|
8
7
|
MarketplaceSort,
|
|
9
8
|
MarketplaceItemType,
|
|
10
9
|
} from "@/shared/lib/api";
|
|
11
|
-
import {
|
|
12
|
-
fetchMarketplacePluginContent,
|
|
13
|
-
fetchMarketplaceSkillContent,
|
|
14
|
-
} from "@/shared/lib/api";
|
|
10
|
+
import { fetchMarketplaceSkillContent } from "@/shared/lib/api";
|
|
15
11
|
import { useDocBrowser } from "@/shared/components/doc-browser";
|
|
16
12
|
import { useI18n } from "@/app/components/i18n-provider";
|
|
17
13
|
import { useConfirmDialog } from "@/shared/hooks/use-confirm-dialog";
|
|
@@ -59,28 +55,11 @@ const SKELETON_CARD_COUNT = 36;
|
|
|
59
55
|
|
|
60
56
|
type ScopeType = "all" | "installed";
|
|
61
57
|
|
|
62
|
-
type MarketplaceRouteType = "plugins" | "skills";
|
|
63
58
|
type MarketplacePageProps = {
|
|
64
|
-
forcedType?:
|
|
59
|
+
forcedType?: "skills";
|
|
65
60
|
};
|
|
66
61
|
|
|
67
|
-
function getMarketplaceCopyKeys(
|
|
68
|
-
if (typeFilter === "plugin") {
|
|
69
|
-
return {
|
|
70
|
-
pageTitle: "marketplacePluginsPageTitle",
|
|
71
|
-
pageDescription: "marketplacePluginsPageDescription",
|
|
72
|
-
tabMarketplace: "marketplaceTabMarketplacePlugins",
|
|
73
|
-
tabInstalled: "marketplaceTabInstalledPlugins",
|
|
74
|
-
searchPlaceholder: "marketplaceSearchPlaceholderPlugins",
|
|
75
|
-
sectionCatalog: "marketplaceSectionPlugins",
|
|
76
|
-
sectionInstalled: "marketplaceSectionInstalledPlugins",
|
|
77
|
-
errorLoadData: "marketplaceErrorLoadingPluginsData",
|
|
78
|
-
errorLoadInstalled: "marketplaceErrorLoadingInstalledPlugins",
|
|
79
|
-
emptyData: "marketplaceNoPlugins",
|
|
80
|
-
emptyInstalled: "marketplaceNoInstalledPlugins",
|
|
81
|
-
installedCountSuffix: "marketplaceInstalledPluginsCountSuffix",
|
|
82
|
-
};
|
|
83
|
-
}
|
|
62
|
+
function getMarketplaceCopyKeys() {
|
|
84
63
|
return {
|
|
85
64
|
pageTitle: "marketplaceSkillsPageTitle",
|
|
86
65
|
pageDescription: "marketplaceSkillsPageDescription",
|
|
@@ -100,37 +79,17 @@ function getMarketplaceCopyKeys(typeFilter: MarketplaceItemType) {
|
|
|
100
79
|
export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
101
80
|
const { forcedType } = props;
|
|
102
81
|
const navigate = useNavigate();
|
|
103
|
-
const params = useParams<{
|
|
82
|
+
const params = useParams<{ scene?: string }>();
|
|
104
83
|
const { language } = useI18n();
|
|
105
84
|
const docBrowser = useDocBrowser();
|
|
106
85
|
|
|
107
|
-
const
|
|
108
|
-
if (forcedType === "plugins" || forcedType === "skills") {
|
|
109
|
-
return forcedType;
|
|
110
|
-
}
|
|
111
|
-
if (params.type === "plugins" || params.type === "skills") {
|
|
112
|
-
return params.type;
|
|
113
|
-
}
|
|
114
|
-
return null;
|
|
115
|
-
}, [forcedType, params.type]);
|
|
116
|
-
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
if (forcedType) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (!routeType) {
|
|
122
|
-
navigate("/marketplace/plugins", { replace: true });
|
|
123
|
-
}
|
|
124
|
-
}, [forcedType, routeType, navigate]);
|
|
125
|
-
|
|
126
|
-
const typeFilter: MarketplaceItemType =
|
|
127
|
-
routeType === "skills" ? "skill" : "plugin";
|
|
86
|
+
const typeFilter: MarketplaceItemType = "skill";
|
|
128
87
|
const localeFallbacks = useMemo(
|
|
129
88
|
() => buildLocaleFallbacks(language),
|
|
130
89
|
[language],
|
|
131
90
|
);
|
|
132
91
|
|
|
133
|
-
const copyKeys = getMarketplaceCopyKeys(
|
|
92
|
+
const copyKeys = getMarketplaceCopyKeys();
|
|
134
93
|
|
|
135
94
|
const [searchText, setSearchText] = useState("");
|
|
136
95
|
const [query, setQuery] = useState("");
|
|
@@ -302,12 +261,8 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
302
261
|
type: item.type,
|
|
303
262
|
spec: installSpec,
|
|
304
263
|
kind: item.install.kind,
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
skill: item.slug,
|
|
308
|
-
installPath: `skills/${item.slug}`,
|
|
309
|
-
}
|
|
310
|
-
: {}),
|
|
264
|
+
skill: item.slug,
|
|
265
|
+
installPath: `skills/${item.slug}`,
|
|
311
266
|
});
|
|
312
267
|
} catch {
|
|
313
268
|
// handled in mutation onError
|
|
@@ -391,10 +346,7 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
391
346
|
if (!item) {
|
|
392
347
|
const url = buildGenericDetailDataUrl({
|
|
393
348
|
title,
|
|
394
|
-
typeLabel:
|
|
395
|
-
record?.type === "plugin"
|
|
396
|
-
? t("marketplaceTypePlugin")
|
|
397
|
-
: t("marketplaceTypeSkill"),
|
|
349
|
+
typeLabel: t("marketplaceTypeSkill"),
|
|
398
350
|
spec: record?.spec ?? "-",
|
|
399
351
|
summary: t("marketplaceInstalledLocalSummary"),
|
|
400
352
|
metadataRaw: JSON.stringify(record ?? {}, null, 2),
|
|
@@ -413,19 +365,12 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
413
365
|
localeFallbacks,
|
|
414
366
|
);
|
|
415
367
|
const detailConfig =
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
: {
|
|
424
|
-
typeLabel: t("marketplaceTypePlugin"),
|
|
425
|
-
loadContent: () => fetchMarketplacePluginContent(item.slug),
|
|
426
|
-
fallbackContent: "-",
|
|
427
|
-
defaultContent: item.summary,
|
|
428
|
-
};
|
|
368
|
+
{
|
|
369
|
+
typeLabel: t("marketplaceTypeSkill"),
|
|
370
|
+
loadContent: () => fetchMarketplaceSkillContent(item.slug),
|
|
371
|
+
fallbackContent: t("marketplaceOperationFailed"),
|
|
372
|
+
defaultContent: undefined,
|
|
373
|
+
};
|
|
429
374
|
docBrowser.open(
|
|
430
375
|
buildGenericDetailDataUrl({
|
|
431
376
|
title,
|
|
@@ -436,8 +381,7 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
436
381
|
openOptions,
|
|
437
382
|
);
|
|
438
383
|
try {
|
|
439
|
-
const content: MarketplaceSkillContentView
|
|
440
|
-
await detailConfig.loadContent();
|
|
384
|
+
const content: MarketplaceSkillContentView = await detailConfig.loadContent();
|
|
441
385
|
if (detailRequestRef.current.byKey.get(dedupeKey) !== requestId) {
|
|
442
386
|
return;
|
|
443
387
|
}
|
|
@@ -14,7 +14,7 @@ type MarketplaceCuratedSceneRouteParams = {
|
|
|
14
14
|
items: MarketplaceItemSummary[];
|
|
15
15
|
installedRecordLookup: Map<string, MarketplaceInstalledRecord>;
|
|
16
16
|
scene?: string;
|
|
17
|
-
forcedType?: "
|
|
17
|
+
forcedType?: "skills";
|
|
18
18
|
typeFilter: MarketplaceItemType;
|
|
19
19
|
scope: "all" | "installed";
|
|
20
20
|
searchText: string;
|
|
@@ -148,13 +148,7 @@ export function useInstallMarketplaceItem() {
|
|
|
148
148
|
queryKey: ['marketplace-installed', result.type],
|
|
149
149
|
refetchType: 'inactive'
|
|
150
150
|
});
|
|
151
|
-
|
|
152
|
-
queryClient.invalidateQueries({ queryKey: ['ncp-session-types'] });
|
|
153
|
-
}
|
|
154
|
-
const fallback = result.type === 'plugin'
|
|
155
|
-
? t('marketplaceInstallSuccessPlugin')
|
|
156
|
-
: t('marketplaceInstallSuccessSkill');
|
|
157
|
-
toast.success(result.message || fallback);
|
|
151
|
+
toast.success(result.message || t('marketplaceInstallSuccessSkill'));
|
|
158
152
|
},
|
|
159
153
|
onError: (error: Error) => {
|
|
160
154
|
toast.error(error.message || t('marketplaceInstallFailed'));
|
|
@@ -180,9 +174,6 @@ export function useManageMarketplaceItem() {
|
|
|
180
174
|
queryKey: ['marketplace-installed', result.type],
|
|
181
175
|
refetchType: 'inactive'
|
|
182
176
|
});
|
|
183
|
-
if (result.type === 'plugin') {
|
|
184
|
-
queryClient.invalidateQueries({ queryKey: ['ncp-session-types'] });
|
|
185
|
-
}
|
|
186
177
|
const fallback = result.action === 'enable'
|
|
187
178
|
? t('marketplaceEnableSuccess')
|
|
188
179
|
: result.action === 'disable'
|
|
@@ -11,57 +11,59 @@ import {
|
|
|
11
11
|
} from './marketplace-installed-cache.utils';
|
|
12
12
|
|
|
13
13
|
describe('marketplace-installed-cache', () => {
|
|
14
|
-
it('adds
|
|
14
|
+
it('adds an MCP record immediately after install success', () => {
|
|
15
15
|
const request: MarketplaceInstallRequest = {
|
|
16
|
-
type: '
|
|
17
|
-
spec: '
|
|
18
|
-
kind: '
|
|
16
|
+
type: 'mcp',
|
|
17
|
+
spec: 'filesystem',
|
|
18
|
+
kind: 'template',
|
|
19
|
+
name: 'filesystem'
|
|
19
20
|
};
|
|
20
21
|
const result: MarketplaceInstallResult = {
|
|
21
|
-
type: '
|
|
22
|
-
spec: '
|
|
22
|
+
type: 'mcp',
|
|
23
|
+
spec: 'filesystem',
|
|
24
|
+
name: 'filesystem',
|
|
23
25
|
message: 'installed'
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
const next = applyInstallResultToInstalledView({ request, result });
|
|
27
29
|
|
|
28
30
|
expect(next.total).toBe(1);
|
|
29
|
-
expect(next.specs).toEqual(['
|
|
31
|
+
expect(next.specs).toEqual(['filesystem']);
|
|
30
32
|
expect(next.records[0]).toMatchObject({
|
|
31
|
-
type: '
|
|
32
|
-
spec: '
|
|
33
|
+
type: 'mcp',
|
|
34
|
+
spec: 'filesystem',
|
|
33
35
|
enabled: true,
|
|
34
36
|
origin: 'marketplace',
|
|
35
37
|
runtimeStatus: 'ready'
|
|
36
38
|
});
|
|
37
39
|
});
|
|
38
40
|
|
|
39
|
-
it('marks
|
|
41
|
+
it('marks an MCP record as disabled immediately after disable success', () => {
|
|
40
42
|
const view: MarketplaceInstalledView = {
|
|
41
|
-
type: '
|
|
43
|
+
type: 'mcp',
|
|
42
44
|
total: 1,
|
|
43
|
-
specs: ['
|
|
45
|
+
specs: ['filesystem'],
|
|
44
46
|
records: [
|
|
45
47
|
{
|
|
46
|
-
type: '
|
|
47
|
-
id: '
|
|
48
|
-
spec: '
|
|
49
|
-
label: '
|
|
48
|
+
type: 'mcp',
|
|
49
|
+
id: 'filesystem',
|
|
50
|
+
spec: 'filesystem',
|
|
51
|
+
label: 'Filesystem',
|
|
50
52
|
enabled: true,
|
|
51
53
|
origin: 'marketplace'
|
|
52
54
|
}
|
|
53
55
|
]
|
|
54
56
|
};
|
|
55
57
|
const request: MarketplaceManageRequest = {
|
|
56
|
-
type: '
|
|
58
|
+
type: 'mcp',
|
|
57
59
|
action: 'disable',
|
|
58
|
-
id: '
|
|
59
|
-
spec: '
|
|
60
|
+
id: 'filesystem',
|
|
61
|
+
spec: 'filesystem'
|
|
60
62
|
};
|
|
61
63
|
const result: MarketplaceManageResult = {
|
|
62
|
-
type: '
|
|
64
|
+
type: 'mcp',
|
|
63
65
|
action: 'disable',
|
|
64
|
-
id: '
|
|
66
|
+
id: 'filesystem',
|
|
65
67
|
message: 'disabled'
|
|
66
68
|
};
|
|
67
69
|
|
|
@@ -27,10 +27,10 @@ describe('getRuntimeBootstrapPollInterval', () => {
|
|
|
27
27
|
ncpAgent: {
|
|
28
28
|
state: 'running',
|
|
29
29
|
},
|
|
30
|
-
|
|
30
|
+
extensionLoading: {
|
|
31
31
|
state: 'pending',
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
loadedExtensionCount: 0,
|
|
33
|
+
totalExtensionCount: 0,
|
|
34
34
|
},
|
|
35
35
|
channels: {
|
|
36
36
|
state: 'pending',
|
|
@@ -51,10 +51,10 @@ describe('getRuntimeBootstrapPollInterval', () => {
|
|
|
51
51
|
state: 'error',
|
|
52
52
|
error: 'startup failed',
|
|
53
53
|
},
|
|
54
|
-
|
|
54
|
+
extensionLoading: {
|
|
55
55
|
state: 'pending',
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
loadedExtensionCount: 0,
|
|
57
|
+
totalExtensionCount: 0,
|
|
58
58
|
},
|
|
59
59
|
channels: {
|
|
60
60
|
state: 'pending',
|
|
@@ -75,10 +75,10 @@ describe('getRuntimeBootstrapPollInterval', () => {
|
|
|
75
75
|
ncpAgent: {
|
|
76
76
|
state: 'ready',
|
|
77
77
|
},
|
|
78
|
-
|
|
78
|
+
extensionLoading: {
|
|
79
79
|
state: 'ready',
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
loadedExtensionCount: 1,
|
|
81
|
+
totalExtensionCount: 1,
|
|
82
82
|
},
|
|
83
83
|
channels: {
|
|
84
84
|
state: 'ready',
|
|
@@ -108,10 +108,10 @@ describe('getRuntimeBootstrapPollInterval', () => {
|
|
|
108
108
|
ncpAgent: {
|
|
109
109
|
state: 'ready',
|
|
110
110
|
},
|
|
111
|
-
|
|
111
|
+
extensionLoading: {
|
|
112
112
|
state: 'ready',
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
loadedExtensionCount: 1,
|
|
114
|
+
totalExtensionCount: 1,
|
|
115
115
|
},
|
|
116
116
|
channels: {
|
|
117
117
|
state: 'ready',
|
|
@@ -10,10 +10,10 @@ const readyBootstrapStatus: BootstrapStatusView = {
|
|
|
10
10
|
ncpAgent: {
|
|
11
11
|
state: 'ready',
|
|
12
12
|
},
|
|
13
|
-
|
|
13
|
+
extensionLoading: {
|
|
14
14
|
state: 'ready',
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
loadedExtensionCount: 1,
|
|
16
|
+
totalExtensionCount: 1,
|
|
17
17
|
},
|
|
18
18
|
channels: {
|
|
19
19
|
state: 'ready',
|
|
@@ -53,10 +53,10 @@ describe('systemStatusManager', () => {
|
|
|
53
53
|
state: 'error',
|
|
54
54
|
error: 'startup failed',
|
|
55
55
|
},
|
|
56
|
-
|
|
56
|
+
extensionLoading: {
|
|
57
57
|
state: 'pending',
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
loadedExtensionCount: 0,
|
|
59
|
+
totalExtensionCount: 0,
|
|
60
60
|
},
|
|
61
61
|
channels: {
|
|
62
62
|
state: 'pending',
|
package/src/index.css
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { act, render, screen } from "@testing-library/react";
|
|
2
2
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
3
|
+
import { MemoryRouter } from "react-router-dom";
|
|
3
4
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
5
|
import { DesktopAppShell } from "@/platforms/desktop/components/desktop-app-shell";
|
|
5
6
|
|
|
@@ -7,6 +8,10 @@ vi.mock("@/app/components/layout/sidebar", () => ({
|
|
|
7
8
|
Sidebar: () => <aside data-testid="settings-sidebar">Settings Sidebar</aside>,
|
|
8
9
|
}));
|
|
9
10
|
|
|
11
|
+
vi.mock("@/platforms/mobile", () => ({
|
|
12
|
+
MobileBottomNav: () => <nav data-testid="mobile-bottom-nav" />,
|
|
13
|
+
}));
|
|
14
|
+
|
|
10
15
|
type WindowStateListener = (snapshot: { isMaximized: boolean }) => void;
|
|
11
16
|
|
|
12
17
|
let windowStateListener: WindowStateListener | null = null;
|
|
@@ -27,7 +32,7 @@ function setDesktopPlatform(platform: string | null, isMaximized = false): void
|
|
|
27
32
|
: undefined;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
function renderDesktopShell(platform: string | null) {
|
|
35
|
+
function renderDesktopShell(platform: string | null, isMobileLayout = false) {
|
|
31
36
|
setDesktopPlatform(platform);
|
|
32
37
|
const queryClient = new QueryClient({
|
|
33
38
|
defaultOptions: {
|
|
@@ -39,13 +44,16 @@ function renderDesktopShell(platform: string | null) {
|
|
|
39
44
|
|
|
40
45
|
return render(
|
|
41
46
|
<QueryClientProvider client={queryClient}>
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
<MemoryRouter initialEntries={["/chat"]}>
|
|
48
|
+
<DesktopAppShell
|
|
49
|
+
pathname="/chat"
|
|
50
|
+
isMobileLayout={isMobileLayout}
|
|
51
|
+
isDocBrowserOpen={false}
|
|
52
|
+
docBrowserMode="floating"
|
|
53
|
+
>
|
|
54
|
+
<div data-testid="app-content">App Content</div>
|
|
55
|
+
</DesktopAppShell>
|
|
56
|
+
</MemoryRouter>
|
|
49
57
|
</QueryClientProvider>,
|
|
50
58
|
);
|
|
51
59
|
}
|
|
@@ -60,19 +68,19 @@ describe("DesktopAppShell", () => {
|
|
|
60
68
|
|
|
61
69
|
const chrome = screen.getByTestId("desktop-window-chrome");
|
|
62
70
|
const sidebarChrome = screen.getByTestId("desktop-window-chrome-sidebar");
|
|
63
|
-
const mainDragRegion = screen.getByTestId("desktop-window-chrome-main-drag-region");
|
|
64
71
|
const resizeStrip = screen.getByTestId("desktop-window-chrome-resize-strip");
|
|
65
72
|
const controls = screen.getByTestId("desktop-window-controls");
|
|
66
73
|
|
|
67
74
|
expect(chrome).toBeTruthy();
|
|
68
75
|
expect(chrome.parentElement?.style.getPropertyValue("--desktop-titlebar-height")).toBe("40px");
|
|
69
76
|
expect(chrome.className).toContain("bg-secondary");
|
|
70
|
-
expect(chrome.className).toContain("border-b");
|
|
77
|
+
expect(chrome.className).not.toContain(" shrink-0 border-b ");
|
|
78
|
+
expect(chrome.className).toContain("after:left-[var(--desktop-sidebar-width)]");
|
|
79
|
+
expect(chrome.className).toContain("after:border-b");
|
|
71
80
|
expect(chrome.className).toContain("desktop-window-drag");
|
|
72
81
|
expect(sidebarChrome.className).toContain("w-[var(--desktop-sidebar-width)]");
|
|
73
|
-
expect(sidebarChrome.className).toContain("
|
|
74
|
-
expect(
|
|
75
|
-
expect(mainDragRegion.className).toContain("desktop-window-drag");
|
|
82
|
+
expect(sidebarChrome.className).not.toContain("border-b");
|
|
83
|
+
expect(sidebarChrome.className).toContain("desktop-window-drag");
|
|
76
84
|
expect(resizeStrip.className).toContain("desktop-window-no-drag");
|
|
77
85
|
expect(resizeStrip.className).toContain("top-0");
|
|
78
86
|
expect(resizeStrip.className).toContain("h-1");
|
|
@@ -102,4 +110,12 @@ describe("DesktopAppShell", () => {
|
|
|
102
110
|
expect(screen.queryByTestId("desktop-window-chrome")).toBeNull();
|
|
103
111
|
expect(screen.getByTestId("app-content")).toBeTruthy();
|
|
104
112
|
});
|
|
113
|
+
|
|
114
|
+
it("keeps mobile bottom navigation visible inside Windows desktop hosts at narrow widths", () => {
|
|
115
|
+
renderDesktopShell("win32", true);
|
|
116
|
+
|
|
117
|
+
expect(screen.getByTestId("desktop-window-chrome")).toBeTruthy();
|
|
118
|
+
expect(screen.getByTestId("mobile-bottom-nav")).toBeTruthy();
|
|
119
|
+
expect(screen.getByTestId("app-content")).toBeTruthy();
|
|
120
|
+
});
|
|
105
121
|
});
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { lazy, Suspense } from "react";
|
|
2
2
|
import type { CSSProperties } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
isChatSessionDetailRoute,
|
|
5
|
+
isMainWorkspaceRoute,
|
|
6
|
+
} from "@/app/configs/app-navigation.config";
|
|
4
7
|
import { Sidebar } from "@/app/components/layout/sidebar";
|
|
5
8
|
import { DesktopWindowChrome } from "@/platforms/desktop/components/desktop-window-chrome";
|
|
6
9
|
import { isWindowsDesktopHost } from "@/platforms/desktop/utils/desktop-host.utils";
|
|
10
|
+
import { MobileBottomNav } from "@/platforms/mobile";
|
|
7
11
|
import { cn } from "@/shared/lib/utils";
|
|
8
12
|
|
|
9
13
|
const DocBrowser = lazy(async () => ({
|
|
@@ -12,32 +16,21 @@ const DocBrowser = lazy(async () => ({
|
|
|
12
16
|
|
|
13
17
|
type DesktopAppShellProps = {
|
|
14
18
|
pathname: string;
|
|
19
|
+
isMobileLayout?: boolean;
|
|
15
20
|
isDocBrowserOpen: boolean;
|
|
16
21
|
docBrowserMode: "floating" | "docked";
|
|
17
22
|
children: React.ReactNode;
|
|
18
23
|
};
|
|
19
24
|
|
|
20
|
-
type DesktopShellStyle = CSSProperties & {
|
|
21
|
-
"--desktop-titlebar-height"?: string;
|
|
22
|
-
"--desktop-caption-safe-right"?: string;
|
|
23
|
-
"--desktop-sidebar-width"?: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function createWindowsDesktopShellStyle(isMainRoute: boolean): DesktopShellStyle {
|
|
27
|
-
return {
|
|
28
|
-
"--desktop-titlebar-height": "40px",
|
|
29
|
-
"--desktop-caption-safe-right": "140px",
|
|
30
|
-
"--desktop-sidebar-width": isMainRoute ? "280px" : "240px",
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
25
|
export function DesktopAppShell({
|
|
35
26
|
pathname,
|
|
27
|
+
isMobileLayout = false,
|
|
36
28
|
isDocBrowserOpen,
|
|
37
29
|
docBrowserMode,
|
|
38
30
|
children,
|
|
39
31
|
}: DesktopAppShellProps) {
|
|
40
32
|
const isMainRoute = isMainWorkspaceRoute(pathname);
|
|
33
|
+
const showMobileBottomNav = isMobileLayout && !isChatSessionDetailRoute(pathname);
|
|
41
34
|
const shouldUseWindowsChrome = isWindowsDesktopHost();
|
|
42
35
|
|
|
43
36
|
return (
|
|
@@ -46,7 +39,11 @@ export function DesktopAppShell({
|
|
|
46
39
|
"h-screen flex flex-col overflow-hidden bg-background font-sans text-foreground",
|
|
47
40
|
shouldUseWindowsChrome ? "rounded-[10px]" : null,
|
|
48
41
|
)}
|
|
49
|
-
style={shouldUseWindowsChrome ?
|
|
42
|
+
style={shouldUseWindowsChrome ? ({
|
|
43
|
+
"--desktop-titlebar-height": "40px",
|
|
44
|
+
"--desktop-caption-safe-right": "140px",
|
|
45
|
+
"--desktop-sidebar-width": isMainRoute ? "280px" : "240px",
|
|
46
|
+
} as CSSProperties) : undefined}
|
|
50
47
|
>
|
|
51
48
|
{shouldUseWindowsChrome ? <DesktopWindowChrome /> : null}
|
|
52
49
|
<div className="flex min-h-0 flex-1 overflow-hidden">
|
|
@@ -70,6 +67,7 @@ export function DesktopAppShell({
|
|
|
70
67
|
) : null}
|
|
71
68
|
</div>
|
|
72
69
|
</div>
|
|
70
|
+
{showMobileBottomNav ? <MobileBottomNav /> : null}
|
|
73
71
|
{isDocBrowserOpen && docBrowserMode === "floating" ? (
|
|
74
72
|
<Suspense fallback={null}>
|
|
75
73
|
<DocBrowser />
|
|
@@ -25,7 +25,7 @@ export function DesktopWindowChrome() {
|
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
27
|
<header
|
|
28
|
-
className="desktop-window-drag relative flex h-[var(--desktop-titlebar-height)] shrink-0 border-b border-[#ebe7dc]/80
|
|
28
|
+
className="desktop-window-drag relative flex h-[var(--desktop-titlebar-height)] shrink-0 bg-secondary after:absolute after:bottom-0 after:left-[var(--desktop-sidebar-width)] after:right-0 after:border-b after:border-[#ebe7dc]/80"
|
|
29
29
|
data-testid="desktop-window-chrome"
|
|
30
30
|
>
|
|
31
31
|
<div
|
|
@@ -33,7 +33,7 @@ export function DesktopWindowChrome() {
|
|
|
33
33
|
data-testid="desktop-window-chrome-resize-strip"
|
|
34
34
|
/>
|
|
35
35
|
<div
|
|
36
|
-
className="desktop-window-
|
|
36
|
+
className="desktop-window-drag relative z-10 flex h-full w-[var(--desktop-sidebar-width)] shrink-0 items-center bg-secondary pl-4 pr-3 text-secondary-foreground"
|
|
37
37
|
data-testid="desktop-window-chrome-sidebar"
|
|
38
38
|
>
|
|
39
39
|
<BrandHeader
|
|
@@ -41,7 +41,6 @@ export function DesktopWindowChrome() {
|
|
|
41
41
|
density="chrome"
|
|
42
42
|
/>
|
|
43
43
|
</div>
|
|
44
|
-
<div aria-hidden="true" className="desktop-window-drag h-full min-w-0 flex-1" data-testid="desktop-window-chrome-main-drag-region" />
|
|
45
44
|
<div
|
|
46
45
|
className="desktop-window-no-drag absolute right-0 top-0 z-20 flex h-full w-[var(--desktop-caption-safe-right)] items-start justify-end"
|
|
47
46
|
data-testid="desktop-window-controls"
|
|
@@ -13,12 +13,14 @@ const DocBrowser = lazy(async () => ({
|
|
|
13
13
|
type MobileAppShellProps = {
|
|
14
14
|
pathname: string;
|
|
15
15
|
isDocBrowserOpen: boolean;
|
|
16
|
+
topbarLeadingInset?: string;
|
|
16
17
|
children: React.ReactNode;
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
export function MobileAppShell({
|
|
20
21
|
pathname,
|
|
21
22
|
isDocBrowserOpen,
|
|
23
|
+
topbarLeadingInset,
|
|
22
24
|
children,
|
|
23
25
|
}: MobileAppShellProps) {
|
|
24
26
|
const isMainRoute = isMainWorkspaceRoute(pathname);
|
|
@@ -27,7 +29,7 @@ export function MobileAppShell({
|
|
|
27
29
|
|
|
28
30
|
return (
|
|
29
31
|
<div className="flex h-[100svh] flex-col bg-background font-sans text-foreground supports-[height:100dvh]:h-[100dvh]">
|
|
30
|
-
{showTopbar ? <MobileTopbar /> : null}
|
|
32
|
+
{showTopbar ? <MobileTopbar leadingInset={topbarLeadingInset} /> : null}
|
|
31
33
|
<div className="relative flex-1 min-h-0 overflow-hidden">
|
|
32
34
|
{isMainRoute ? (
|
|
33
35
|
<div className="h-full min-h-0 overflow-hidden">{children}</div>
|
|
@@ -3,7 +3,7 @@ import { useNavigate, useLocation } from "react-router-dom";
|
|
|
3
3
|
import { resolveMobileRouteMeta } from "@/app/configs/app-navigation.config";
|
|
4
4
|
import { t } from "@/shared/lib/i18n";
|
|
5
5
|
|
|
6
|
-
export function MobileTopbar() {
|
|
6
|
+
export function MobileTopbar({ leadingInset }: { leadingInset?: string }) {
|
|
7
7
|
const navigate = useNavigate();
|
|
8
8
|
const { pathname } = useLocation();
|
|
9
9
|
const routeMeta = resolveMobileRouteMeta(pathname, t);
|
|
@@ -16,7 +16,10 @@ export function MobileTopbar() {
|
|
|
16
16
|
paddingTop: "calc(env(safe-area-inset-top, 0px) + 0.25rem)",
|
|
17
17
|
}}
|
|
18
18
|
>
|
|
19
|
-
<div
|
|
19
|
+
<div
|
|
20
|
+
className="flex min-h-[2.75rem] items-center gap-2 py-1.5 pl-3 pr-3"
|
|
21
|
+
style={{ paddingLeft: leadingInset ? `calc(${leadingInset} + 0.75rem)` : undefined }}
|
|
22
|
+
>
|
|
20
23
|
{routeMeta.backTarget ? (
|
|
21
24
|
<button
|
|
22
25
|
type="button"
|
|
@@ -26,9 +29,7 @@ export function MobileTopbar() {
|
|
|
26
29
|
>
|
|
27
30
|
<ArrowLeft className="h-4 w-4" />
|
|
28
31
|
</button>
|
|
29
|
-
) :
|
|
30
|
-
null
|
|
31
|
-
)}
|
|
32
|
+
) : null}
|
|
32
33
|
<div className="min-w-0 flex-1">
|
|
33
34
|
<h1 className="truncate text-center text-[15px] font-semibold text-gray-900">
|
|
34
35
|
{routeMeta.title}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { MobileAppShell } from "./components/mobile-app-shell";
|
|
2
|
+
export { MobileBottomNav } from "./components/mobile-bottom-nav";
|
|
2
3
|
export { MobileSettingsShell } from "./components/mobile-settings-shell";
|
|
3
4
|
export { ChatMobileShell } from "./components/chat-mobile-shell";
|