@nextclaw/ui 0.18.4 → 0.19.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 +25 -0
- package/dist/assets/api-Cyxp93Fn.js +13 -0
- package/dist/assets/appearance-settings-page-NlQIHbXh.js +1 -0
- package/dist/assets/{book-open-BOJLGVsx.js → book-open-Cl5CzL0g.js} +1 -1
- package/dist/assets/{channels-list-page-DKrUSeNz.js → channels-list-page-DhXfsjxF.js} +2 -2
- package/dist/assets/chat-page-C4DHLN6T.js +1 -0
- package/dist/assets/config-split-page-C-YVl2mf.js +1 -0
- package/dist/assets/{confirm-dialog-Bsg9A62_.js → confirm-dialog-Bz3ieDQ5.js} +2 -2
- package/dist/assets/desktop-update-config-CpsXBUJK.js +1 -0
- package/dist/assets/{dist-IBUY9_9h.js → dist-CDPl8mm7.js} +1 -1
- package/dist/assets/{dist-DyUY9uQ0.js → dist-HFDWY6-K.js} +1 -1
- package/dist/assets/doc-browser-CWhs0JQN.js +1 -0
- package/dist/assets/doc-browser-D6H_nPzO.js +1 -0
- package/dist/assets/{doc-browser-context-BM6Af8gN.js → doc-browser-context-BKwz_j44.js} +1 -1
- package/dist/assets/form-actions-BUZOCfPR.js +1 -0
- package/dist/assets/index-Bfr0I0Ga.js +109 -0
- package/dist/assets/index-DcqrfCMy.css +1 -0
- package/dist/assets/loader-circle-s65a4ZzL.js +1 -0
- package/dist/assets/mcp-marketplace-page-BJvzGt7h.js +9 -0
- package/dist/assets/mcp-marketplace-page-EpFHeJzw.js +1 -0
- package/dist/assets/model-config-page-BRScCkS-.js +1 -0
- package/dist/assets/{navigation-link-DtPZZ_Q_.js → navigation-link-CDMYpkVv.js} +2 -2
- package/dist/assets/plus-XDIkqpkt.js +1 -0
- package/dist/assets/{provider-scoped-model-input-BFzyywsF.js → provider-scoped-model-input-CD93ZOgZ.js} +1 -1
- package/dist/assets/providers-config-page-CFdgth2A.js +1 -0
- package/dist/assets/react-B7Paq2b3.js +8 -0
- package/dist/assets/{refresh-cw-BQHORcAs.js → refresh-cw-XrnNVwuQ.js} +1 -1
- package/dist/assets/remote-DDN70hyU.js +1 -0
- package/dist/assets/rotate-cw-BhIWpLaU.js +1 -0
- package/dist/assets/runtime-config-page-BxJlm6qT.js +1 -0
- package/dist/assets/{save-BgiS2u-T.js → save-_nSNAYQu.js} +1 -1
- package/dist/assets/{search-B6mO7mmo.js → search-0OTHFRMx.js} +1 -1
- package/dist/assets/{search-config-page-ydeDD1OQ.js → search-config-page-2VAarRcG.js} +1 -1
- package/dist/assets/secrets-config-page-KoFrETds.js +3 -0
- package/dist/assets/security-config-BgIIrg1s.js +1 -0
- package/dist/assets/{select-DToos7Rq.js → select-BZHvnwQB.js} +2 -2
- package/dist/assets/{settings-2-ZAWXrq81.js → settings-2-CM_PvDNk.js} +1 -1
- package/dist/assets/skeleton-Bv4MMbH7.js +1 -0
- package/dist/assets/{tag-chip-urB3N-kk.js → tag-chip-Ce53PMTL.js} +1 -1
- package/dist/assets/x-CCjWN5ys.js +1 -0
- package/dist/index.html +20 -20
- package/package.json +5 -5
- package/src/app/components/layout/sidebar-items.tsx +49 -15
- package/src/app/configs/__tests__/app-navigation.config.test.ts +21 -0
- package/src/app/configs/app-navigation.config.ts +10 -1
- package/src/app/index.tsx +4 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +7 -0
- package/src/features/chat/components/layout/chat-page-shell.tsx +4 -1
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +26 -22
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +2 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +1 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +1 -0
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +1 -3
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +15 -1
- package/src/features/chat/features/message/components/chat-tool-call-stress-harness.tsx +178 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +21 -0
- package/src/features/chat/features/message/utils/__tests__/chat-tool-call-stress-fixture.utils.test.ts +56 -0
- package/src/features/chat/features/message/utils/chat-message-core.utils.ts +1 -1
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +3 -10
- package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +29 -1
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +3 -0
- package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +3 -28
- package/src/features/chat/features/message/utils/chat-tool-call-stress-fixture.utils.ts +122 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-session-conversation.test.tsx +69 -0
- package/src/features/chat/features/ncp/hooks/use-ncp-session-conversation.ts +29 -0
- package/src/features/chat/features/ncp/hooks/use-ncp-session-message-history.ts +106 -1
- package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +2 -12
- package/src/features/chat/managers/__tests__/chat-ui.manager.test.ts +4 -2
- package/src/features/panel-apps/components/__tests__/panel-app-list-item.test.tsx +57 -2
- package/src/features/panel-apps/components/__tests__/panel-app-main-sidebar-nav.test.tsx +133 -0
- package/src/features/panel-apps/components/__tests__/panel-app-toolbar.test.tsx +68 -7
- package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +7 -1
- package/src/features/panel-apps/components/panel-app-icon.tsx +48 -0
- package/src/features/panel-apps/components/panel-app-list-item.tsx +7 -29
- package/src/features/panel-apps/components/panel-app-main-sidebar-menu-item.tsx +37 -0
- package/src/features/panel-apps/components/panel-app-main-sidebar-nav.tsx +123 -0
- package/src/features/panel-apps/components/panel-app-toolbar.tsx +61 -20
- package/src/features/panel-apps/components/panel-apps-list.tsx +5 -27
- package/src/features/panel-apps/hooks/__tests__/use-panel-apps.test.tsx +105 -0
- package/src/features/panel-apps/hooks/use-panel-app-client-grant.ts +50 -0
- package/src/features/panel-apps/hooks/use-panel-app-main-runtime.ts +66 -0
- package/src/features/panel-apps/hooks/use-panel-apps.ts +102 -13
- package/src/features/panel-apps/index.ts +2 -0
- package/src/features/panel-apps/managers/__tests__/panel-app-bridge.manager.test.ts +1 -14
- package/src/features/panel-apps/managers/panel-app-bridge.manager.ts +9 -5
- package/src/features/panel-apps/pages/__tests__/panel-app-main-page.test.tsx +183 -0
- package/src/features/panel-apps/pages/panel-app-main-page.tsx +125 -0
- package/src/features/panel-apps/utils/__tests__/panel-app-view.utils.test.ts +2 -1
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +5 -4
- package/src/features/right-panel-resources/configs/right-panel-resource-routes.config.ts +12 -25
- package/src/features/right-panel-resources/index.ts +2 -0
- package/src/features/right-panel-resources/utils/__tests__/right-panel-resource-uri.utils.test.ts +21 -0
- package/src/features/right-panel-resources/utils/right-panel-resource-uri.utils.ts +37 -0
- package/src/main.tsx +24 -11
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +102 -0
- package/src/shared/lib/api/ncp-session.types.ts +1 -0
- package/src/shared/lib/api/types.ts +3 -0
- package/src/shared/lib/api/utils/ncp-session.utils.ts +24 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +4 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +10 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +4 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +10 -0
- package/dist/assets/api-BSsQ0I4l.js +0 -13
- package/dist/assets/appearance-settings-page-CEPxG1w0.js +0 -1
- package/dist/assets/chat-page-sGhIjLSr.js +0 -1
- package/dist/assets/config-split-page-aUTKtWJu.js +0 -1
- package/dist/assets/desktop-update-config-VgvIATTT.js +0 -1
- package/dist/assets/doc-browser-D0arfzHY.js +0 -1
- package/dist/assets/doc-browser-D73yN8ts.js +0 -1
- package/dist/assets/form-actions-Cpd7glh8.js +0 -1
- package/dist/assets/index-Bsoscjk7.css +0 -1
- package/dist/assets/index-CtPLj_hk.js +0 -109
- package/dist/assets/loader-circle-C1fL3kBZ.js +0 -1
- package/dist/assets/mcp-marketplace-page-D8V0GU4Y.js +0 -9
- package/dist/assets/mcp-marketplace-page-q6T-vKoH.js +0 -1
- package/dist/assets/model-config-page-DQ-iQFSO.js +0 -1
- package/dist/assets/plus-B9V7Z_Eu.js +0 -1
- package/dist/assets/providers-config-page-DUYwVuYe.js +0 -1
- package/dist/assets/react-D-jOo51s.js +0 -8
- package/dist/assets/remote-CNFQ6bFY.js +0 -1
- package/dist/assets/rotate-cw-DuCF-3PX.js +0 -1
- package/dist/assets/runtime-config-page-CaWoWoQk.js +0 -1
- package/dist/assets/secrets-config-page-BZYqw8BF.js +0 -3
- package/dist/assets/security-config-Ds_2SXBh.js +0 -1
- package/dist/assets/skeleton-Bs2eO2CA.js +0 -1
- package/dist/assets/x-BkjTUiww.js +0 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { Boxes, RotateCcw } from "lucide-react";
|
|
3
|
+
import { useParams } from "react-router-dom";
|
|
4
|
+
import { useAppPresenter } from "@/app/components/app-presenter-provider";
|
|
5
|
+
import { usePanelAppMainRuntime } from "@/features/panel-apps/hooks/use-panel-app-main-runtime";
|
|
6
|
+
import {
|
|
7
|
+
usePanelApps,
|
|
8
|
+
} from "@/features/panel-apps/hooks/use-panel-apps";
|
|
9
|
+
import {
|
|
10
|
+
PANEL_APP_IFRAME_SANDBOX,
|
|
11
|
+
focusPanelAppIframe,
|
|
12
|
+
} from "@/features/panel-apps/utils/panel-app-iframe.utils";
|
|
13
|
+
import { openApps } from "@/features/panel-apps/utils/panel-app-doc-browser.utils";
|
|
14
|
+
import { useDocBrowser } from "@/shared/components/doc-browser";
|
|
15
|
+
import { t } from "@/shared/lib/i18n";
|
|
16
|
+
|
|
17
|
+
export function PanelAppMainPage() {
|
|
18
|
+
const { appId = "" } = useParams<{ appId: string }>();
|
|
19
|
+
const presenter = useAppPresenter();
|
|
20
|
+
const docBrowser = useDocBrowser();
|
|
21
|
+
const panelApps = usePanelApps();
|
|
22
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
23
|
+
const entry = useMemo(
|
|
24
|
+
() => panelApps.data?.entries.find((candidate) => candidate.appId === appId),
|
|
25
|
+
[appId, panelApps.data?.entries],
|
|
26
|
+
);
|
|
27
|
+
const runtime = usePanelAppMainRuntime(entry);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const handleMessage = (event: MessageEvent) => {
|
|
31
|
+
presenter.panelAppBridgeManager.handleIframeMessage({
|
|
32
|
+
event,
|
|
33
|
+
iframe: iframeRef.current,
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
window.addEventListener("message", handleMessage);
|
|
37
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
38
|
+
}, [presenter]);
|
|
39
|
+
|
|
40
|
+
if (panelApps.isLoading) {
|
|
41
|
+
return <PanelAppMainStatus message={t("panelAppsLoading")} />;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (panelApps.isError) {
|
|
45
|
+
return (
|
|
46
|
+
<PanelAppMainStatus
|
|
47
|
+
message={panelApps.error instanceof Error
|
|
48
|
+
? panelApps.error.message
|
|
49
|
+
: t("panelAppsLoadFailed")}
|
|
50
|
+
actionLabel={t("panelAppsTryAgain")}
|
|
51
|
+
onAction={() => void panelApps.refetch()}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!entry) {
|
|
57
|
+
return (
|
|
58
|
+
<PanelAppMainStatus
|
|
59
|
+
message={t("panelAppsMainUnavailable")}
|
|
60
|
+
actionLabel={t("panelAppsOpenApps")}
|
|
61
|
+
onAction={() => openApps(docBrowser)}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (runtime.state === "checking" || runtime.state === "idle") {
|
|
67
|
+
return <PanelAppMainStatus message={t("panelAppsCheckingPermission")} />;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (runtime.state === "denied" || runtime.state === "error") {
|
|
71
|
+
return (
|
|
72
|
+
<PanelAppMainStatus
|
|
73
|
+
message={runtime.state === "denied"
|
|
74
|
+
? t("panelAppsPermissionDenied")
|
|
75
|
+
: t("panelAppsPermissionFailed")}
|
|
76
|
+
actionLabel={t("panelAppsTryAgain")}
|
|
77
|
+
onAction={() => void runtime.retryClientGrant()}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className="h-full min-h-0 bg-background">
|
|
84
|
+
<iframe
|
|
85
|
+
key={entry.appId}
|
|
86
|
+
ref={iframeRef}
|
|
87
|
+
src={entry.contentPath}
|
|
88
|
+
title={entry.title}
|
|
89
|
+
sandbox={PANEL_APP_IFRAME_SANDBOX}
|
|
90
|
+
className="block h-full w-full border-0 bg-background"
|
|
91
|
+
onPointerOver={(event) => focusPanelAppIframe(event.currentTarget)}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function PanelAppMainStatus({
|
|
98
|
+
actionLabel,
|
|
99
|
+
message,
|
|
100
|
+
onAction,
|
|
101
|
+
}: {
|
|
102
|
+
actionLabel?: string;
|
|
103
|
+
message: string;
|
|
104
|
+
onAction?: () => void;
|
|
105
|
+
}) {
|
|
106
|
+
return (
|
|
107
|
+
<div className="flex h-full min-h-0 items-center justify-center bg-background px-6 text-center">
|
|
108
|
+
<div className="max-w-sm">
|
|
109
|
+
<div className="mx-auto flex h-10 w-10 items-center justify-center rounded-lg bg-muted text-muted-foreground">
|
|
110
|
+
{onAction ? <RotateCcw className="h-5 w-5" /> : <Boxes className="h-5 w-5" />}
|
|
111
|
+
</div>
|
|
112
|
+
<p className="mt-3 text-sm leading-6 text-muted-foreground">{message}</p>
|
|
113
|
+
{actionLabel && onAction ? (
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
className="mt-4 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-[var(--interaction-hover)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
117
|
+
onClick={onAction}
|
|
118
|
+
>
|
|
119
|
+
{actionLabel}
|
|
120
|
+
</button>
|
|
121
|
+
) : null}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import type { PanelAppEntryView } from '@/shared/lib/api';
|
|
3
|
-
import { getPanelAppViewEntries } from '
|
|
3
|
+
import { getPanelAppViewEntries } from '@/features/panel-apps/utils/panel-app-view.utils';
|
|
4
4
|
|
|
5
5
|
function createPanelAppEntry(overrides: Partial<PanelAppEntryView> = {}): PanelAppEntryView {
|
|
6
6
|
const createdAt = overrides.createdAt ?? '2026-05-01T00:00:00.000Z';
|
|
@@ -16,6 +16,7 @@ function createPanelAppEntry(overrides: Partial<PanelAppEntryView> = {}): PanelA
|
|
|
16
16
|
updatedAt,
|
|
17
17
|
sizeBytes: overrides.sizeBytes ?? 12,
|
|
18
18
|
favorite: overrides.favorite ?? false,
|
|
19
|
+
mainSidebar: overrides.mainSidebar ?? false,
|
|
19
20
|
clientDeclared: overrides.clientDeclared ?? false,
|
|
20
21
|
clientGranted: overrides.clientGranted ?? false,
|
|
21
22
|
openCount: overrides.openCount ?? 0,
|
|
@@ -3,7 +3,7 @@ import type { DocBrowserContextValue } from '@/shared/components/doc-browser/doc
|
|
|
3
3
|
import type { DocBrowserCustomTabRenderers } from '@/shared/components/doc-browser/doc-browser-renderer.types';
|
|
4
4
|
import { getPresenter } from '@/app/presenters/app.presenter';
|
|
5
5
|
import { AppsPanel, type AppsPanelTab } from '@/features/apps';
|
|
6
|
-
import {
|
|
6
|
+
import { PanelAppDocBrowserToolbar } from '@/features/panel-apps/components/panel-app-toolbar';
|
|
7
7
|
import {
|
|
8
8
|
createPanelAppRightPanelResourceTarget,
|
|
9
9
|
RIGHT_PANEL_APPS_TAB_KIND,
|
|
@@ -75,11 +75,12 @@ export const PANEL_APPS_DOC_BROWSER_RENDERERS: DocBrowserCustomTabRenderers = {
|
|
|
75
75
|
onIframeMessage: (params) => getPresenter().panelAppBridgeManager.handleIframeMessage(params),
|
|
76
76
|
onIframePointerOver: (event) => focusPanelAppIframe(event.currentTarget),
|
|
77
77
|
renderIcon: () => <AppWindow className="w-4 h-4 text-primary shrink-0" />,
|
|
78
|
-
renderToolbar: ({
|
|
79
|
-
<
|
|
78
|
+
renderToolbar: ({ refreshIframe, tab }) => (
|
|
79
|
+
<PanelAppDocBrowserToolbar
|
|
80
80
|
appTitle={tab.title || t('panelAppsTitle')}
|
|
81
|
-
|
|
81
|
+
currentUrl={tab.currentUrl}
|
|
82
82
|
onRefresh={refreshIframe}
|
|
83
|
+
resourceUri={tab.resourceUri}
|
|
83
84
|
/>
|
|
84
85
|
),
|
|
85
86
|
supportsScrollRestoration: true,
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
} from '@/features/right-panel-resources/types/right-panel-resource.types';
|
|
15
15
|
import {
|
|
16
16
|
createPanelAppContentPath,
|
|
17
|
+
readPanelAppIdFromParsedResourceUri,
|
|
17
18
|
RIGHT_PANEL_APPS_URL,
|
|
18
19
|
RIGHT_PANEL_APPS_TAB_KIND,
|
|
19
20
|
RIGHT_PANEL_HOME_TAB_KIND,
|
|
@@ -58,39 +59,25 @@ function createDocsResourceUriFromUrl(url: string): string {
|
|
|
58
59
|
return createDocsResourceUriFromSegments(pathname.split('/'));
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
function getPanelAppId(uri: ParsedResourceUri): string {
|
|
62
|
-
if (uri.scheme === 'nextclaw' && uri.authority === 'panel-app') {
|
|
63
|
-
return uri.pathSegments[0] ?? '';
|
|
64
|
-
}
|
|
65
|
-
const [apiSegment, collectionSegment, appId, contentSegment] = uri.pathSegments;
|
|
66
|
-
return apiSegment === 'api'
|
|
67
|
-
&& collectionSegment === 'panel-apps'
|
|
68
|
-
&& appId !== undefined
|
|
69
|
-
&& appId.length > 0
|
|
70
|
-
&& contentSegment === 'content'
|
|
71
|
-
? appId
|
|
72
|
-
: '';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
62
|
function isPanelAppContentUri(uri: ParsedResourceUri): boolean {
|
|
76
|
-
return
|
|
63
|
+
return readPanelAppIdFromParsedResourceUri(uri) !== null;
|
|
77
64
|
}
|
|
78
65
|
|
|
79
66
|
function resolvePanelAppPlaceholderUrl(uri: ParsedResourceUri): string {
|
|
80
|
-
const appId =
|
|
67
|
+
const appId = readPanelAppIdFromParsedResourceUri(uri);
|
|
81
68
|
if (!appId) {
|
|
82
69
|
return 'nextclaw://panel-app';
|
|
83
70
|
}
|
|
84
71
|
const path = uri.searchParams.get('path')?.trim();
|
|
85
|
-
return createPanelAppContentPath(
|
|
72
|
+
return createPanelAppContentPath(appId, path);
|
|
86
73
|
}
|
|
87
74
|
|
|
88
75
|
function createPanelAppResourceUri(uri: ParsedResourceUri): string {
|
|
89
|
-
const appId =
|
|
76
|
+
const appId = readPanelAppIdFromParsedResourceUri(uri);
|
|
90
77
|
if (!appId) {
|
|
91
78
|
return 'nextclaw://panel-app';
|
|
92
79
|
}
|
|
93
|
-
const resourceUri = `nextclaw://panel-app/${encodeURIComponent(
|
|
80
|
+
const resourceUri = `nextclaw://panel-app/${encodeURIComponent(appId)}`;
|
|
94
81
|
const path = uri.searchParams.get('path')?.trim();
|
|
95
82
|
return path ? `${resourceUri}?${new URLSearchParams({ path }).toString()}` : resourceUri;
|
|
96
83
|
}
|
|
@@ -98,12 +85,12 @@ function createPanelAppResourceUri(uri: ParsedResourceUri): string {
|
|
|
98
85
|
function arePanelAppUrlsEquivalent(left: string, right: string): boolean {
|
|
99
86
|
const leftUri = parseResourceUri(left);
|
|
100
87
|
const rightUri = parseResourceUri(right);
|
|
101
|
-
const leftAppId =
|
|
102
|
-
const rightAppId =
|
|
88
|
+
const leftAppId = readPanelAppIdFromParsedResourceUri(leftUri);
|
|
89
|
+
const rightAppId = readPanelAppIdFromParsedResourceUri(rightUri);
|
|
103
90
|
const leftPath = leftUri.searchParams.get('path')?.trim() ?? '';
|
|
104
91
|
const rightPath = rightUri.searchParams.get('path')?.trim() ?? '';
|
|
105
|
-
return leftAppId
|
|
106
|
-
?
|
|
92
|
+
return leftAppId !== null && rightAppId !== null
|
|
93
|
+
? leftAppId === rightAppId && leftPath === rightPath
|
|
107
94
|
: left === right;
|
|
108
95
|
}
|
|
109
96
|
|
|
@@ -181,10 +168,10 @@ export const RIGHT_PANEL_RESOURCE_ROUTE_DEFINITIONS: RightPanelResourceRouteDefi
|
|
|
181
168
|
match: (uri) => (uri.scheme === 'nextclaw' && uri.authority === 'panel-app') || isPanelAppContentUri(uri),
|
|
182
169
|
resolve: (uri) => {
|
|
183
170
|
const url = resolvePanelAppPlaceholderUrl(uri);
|
|
184
|
-
const appId =
|
|
171
|
+
const appId = readPanelAppIdFromParsedResourceUri(uri);
|
|
185
172
|
return {
|
|
186
173
|
dedupeKey: appId
|
|
187
|
-
? `panel-app:${uri.searchParams.get('path')?.trim() ||
|
|
174
|
+
? `panel-app:${uri.searchParams.get('path')?.trim() || appId}`
|
|
188
175
|
: undefined,
|
|
189
176
|
historyPolicy: 'managed',
|
|
190
177
|
kind: RIGHT_PANEL_PANEL_APP_TAB_KIND,
|
package/src/features/right-panel-resources/utils/__tests__/right-panel-resource-uri.utils.test.ts
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
createPanelAppContentPath,
|
|
6
6
|
createPanelAppResourceUri,
|
|
7
7
|
createPanelAppRightPanelResourceTarget,
|
|
8
|
+
readPanelAppIdFromResourceUri,
|
|
9
|
+
readPanelAppIdFromTab,
|
|
8
10
|
} from '@/features/right-panel-resources/utils/right-panel-resource-uri.utils';
|
|
9
11
|
|
|
10
12
|
function createPanelAppEntry(overrides: Partial<PanelAppEntryView> = {}): PanelAppEntryView {
|
|
@@ -15,6 +17,7 @@ function createPanelAppEntry(overrides: Partial<PanelAppEntryView> = {}): PanelA
|
|
|
15
17
|
contentPath: '/api/panel-apps/demo/content',
|
|
16
18
|
createdAt: '2026-06-02T00:00:00.000Z',
|
|
17
19
|
favorite: false,
|
|
20
|
+
mainSidebar: false,
|
|
18
21
|
fileName: 'demo.html',
|
|
19
22
|
id: 'demo',
|
|
20
23
|
kind: 'single-file',
|
|
@@ -69,6 +72,24 @@ describe('right-panel-resource uri utils', () => {
|
|
|
69
72
|
);
|
|
70
73
|
});
|
|
71
74
|
|
|
75
|
+
it('reads stable panel app ids from resource and content URLs', () => {
|
|
76
|
+
expect(readPanelAppIdFromResourceUri('nextclaw://panel-app/publisher.todo')).toBe('publisher.todo');
|
|
77
|
+
expect(readPanelAppIdFromResourceUri('/api/panel-apps/publisher.todo/content')).toBe('publisher.todo');
|
|
78
|
+
expect(readPanelAppIdFromResourceUri('nextclaw://panel-app/publisher.todo?path=%2Ftmp%2Ftodo.panel')).toBeNull();
|
|
79
|
+
expect(readPanelAppIdFromResourceUri('nextclaw://apps')).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('falls back to the stable current URL for restored panel app shortcuts', () => {
|
|
83
|
+
expect(readPanelAppIdFromTab({
|
|
84
|
+
currentUrl: '/api/panel-apps/publisher.todo/content',
|
|
85
|
+
resourceUri: 'nextclaw://panel-app',
|
|
86
|
+
})).toBe('publisher.todo');
|
|
87
|
+
expect(readPanelAppIdFromTab({
|
|
88
|
+
currentUrl: '/api/panel-apps/publisher.todo/content?path=%2Ftmp%2Ftodo.panel',
|
|
89
|
+
resourceUri: 'nextclaw://panel-app/publisher.todo?path=%2Ftmp%2Ftodo.panel',
|
|
90
|
+
})).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
|
|
72
93
|
it('keeps panel app image icons as dock image icons', () => {
|
|
73
94
|
const target = createPanelAppRightPanelResourceTarget(createPanelAppEntry({
|
|
74
95
|
icon: '/api/panel-apps/demo/assets/icon.png',
|
|
@@ -6,6 +6,7 @@ import type { PanelAppEntryView } from '@/shared/lib/api';
|
|
|
6
6
|
import type { DocBrowserDockIcon } from '@/shared/components/doc-browser';
|
|
7
7
|
import type { DocBrowserTab } from '@/shared/components/doc-browser/doc-browser-context';
|
|
8
8
|
import type { RightPanelResourceTarget } from '@/features/right-panel-resources/types/right-panel-resource.types';
|
|
9
|
+
import { parseResourceUri, type ParsedResourceUri } from '@/shared/lib/resource-uri';
|
|
9
10
|
|
|
10
11
|
export const RIGHT_PANEL_HOME_TAB_KIND = 'home';
|
|
11
12
|
export const RIGHT_PANEL_HOME_URL = 'nextclaw://new-tab';
|
|
@@ -78,6 +79,42 @@ export function createPanelAppContentPath(appId: string, sourcePath?: string): s
|
|
|
78
79
|
return path ? `${url}?${new URLSearchParams({ path }).toString()}` : url;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
export function readPanelAppIdFromParsedResourceUri(uri: ParsedResourceUri): string | null {
|
|
83
|
+
const encodedAppId = uri.scheme === 'nextclaw' && uri.authority === 'panel-app'
|
|
84
|
+
? uri.pathSegments[0]
|
|
85
|
+
: (() => {
|
|
86
|
+
const [apiSegment, collectionSegment, appId, contentSegment] = uri.pathSegments;
|
|
87
|
+
return apiSegment === 'api'
|
|
88
|
+
&& collectionSegment === 'panel-apps'
|
|
89
|
+
&& contentSegment === 'content'
|
|
90
|
+
? appId
|
|
91
|
+
: undefined;
|
|
92
|
+
})();
|
|
93
|
+
if (!encodedAppId) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
return decodeURIComponent(encodedAppId);
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function readPanelAppIdFromResourceUri(value: string): string | null {
|
|
104
|
+
const uri = parseResourceUri(value);
|
|
105
|
+
return uri.searchParams.has('path')
|
|
106
|
+
? null
|
|
107
|
+
: readPanelAppIdFromParsedResourceUri(uri);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function readPanelAppIdFromTab(
|
|
111
|
+
tab: Pick<DocBrowserTab, 'currentUrl' | 'resourceUri'>,
|
|
112
|
+
): string | null {
|
|
113
|
+
return (tab.resourceUri
|
|
114
|
+
? readPanelAppIdFromResourceUri(tab.resourceUri)
|
|
115
|
+
: null) ?? readPanelAppIdFromResourceUri(tab.currentUrl);
|
|
116
|
+
}
|
|
117
|
+
|
|
81
118
|
function isPanelAppImageIcon(icon: string): boolean {
|
|
82
119
|
return (
|
|
83
120
|
icon.startsWith('data:image/') ||
|
package/src/main.tsx
CHANGED
|
@@ -7,15 +7,28 @@ import { ThemeProvider } from '@/components/providers/ThemeProvider';
|
|
|
7
7
|
import './index.css';
|
|
8
8
|
|
|
9
9
|
const AppRouter = window.nextclawDesktop?.platform === 'win32' ? MemoryRouter : BrowserRouter;
|
|
10
|
+
const root = createRoot(document.getElementById('root')!);
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
</
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
if (import.meta.env.DEV && window.location.pathname === '/__debug/chat-tool-call-stress') {
|
|
13
|
+
void import('@/features/chat/features/message/components/chat-tool-call-stress-harness').then(
|
|
14
|
+
({ ChatToolCallStressHarness }) => {
|
|
15
|
+
root.render(
|
|
16
|
+
<StrictMode>
|
|
17
|
+
<ChatToolCallStressHarness />
|
|
18
|
+
</StrictMode>,
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
} else {
|
|
23
|
+
root.render(
|
|
24
|
+
<StrictMode>
|
|
25
|
+
<ThemeProvider>
|
|
26
|
+
<I18nProvider>
|
|
27
|
+
<AppRouter>
|
|
28
|
+
<App />
|
|
29
|
+
</AppRouter>
|
|
30
|
+
</I18nProvider>
|
|
31
|
+
</ThemeProvider>
|
|
32
|
+
</StrictMode>,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -14,11 +14,43 @@ const { navigateMock } = vi.hoisted(() => ({
|
|
|
14
14
|
navigateMock: vi.fn(),
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
|
+
const panelAppHooks = vi.hoisted(() => ({
|
|
18
|
+
mutate: vi.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
17
21
|
vi.mock("react-router-dom", async () => ({
|
|
18
22
|
...((await vi.importActual("react-router-dom")) as object),
|
|
19
23
|
useNavigate: () => navigateMock,
|
|
20
24
|
}));
|
|
21
25
|
|
|
26
|
+
vi.mock("@/features/panel-apps/hooks/use-panel-apps", async (importOriginal) => ({
|
|
27
|
+
...(await importOriginal<object>()),
|
|
28
|
+
usePanelApps: () => ({
|
|
29
|
+
data: {
|
|
30
|
+
entries: [{
|
|
31
|
+
appId: "piano",
|
|
32
|
+
clientDeclared: false,
|
|
33
|
+
clientGranted: false,
|
|
34
|
+
contentPath: "/api/panel-apps/piano/content",
|
|
35
|
+
createdAt: "2026-08-19T00:00:00.000Z",
|
|
36
|
+
favorite: false,
|
|
37
|
+
fileName: "piano.panel.html",
|
|
38
|
+
id: "piano",
|
|
39
|
+
kind: "single-file",
|
|
40
|
+
mainSidebar: false,
|
|
41
|
+
openCount: 0,
|
|
42
|
+
sizeBytes: 10,
|
|
43
|
+
title: "Piano",
|
|
44
|
+
updatedAt: "2026-08-19T00:00:00.000Z",
|
|
45
|
+
}],
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
useUpdatePanelAppPreferences: () => ({
|
|
49
|
+
isPending: false,
|
|
50
|
+
mutate: panelAppHooks.mutate,
|
|
51
|
+
}),
|
|
52
|
+
}));
|
|
53
|
+
|
|
22
54
|
const { docBrowserState } = vi.hoisted<{
|
|
23
55
|
docBrowserState: DocBrowserContextValue;
|
|
24
56
|
}>(() => ({
|
|
@@ -463,6 +495,76 @@ describe("DocBrowser", () => {
|
|
|
463
495
|
});
|
|
464
496
|
});
|
|
465
497
|
|
|
498
|
+
describe("DocBrowser panel app navigation", () => {
|
|
499
|
+
beforeEach(() => {
|
|
500
|
+
resetDocBrowserTestState();
|
|
501
|
+
installDocBrowserPointerTestEnvironment();
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("uses browser history as the only back contract for panel apps", async () => {
|
|
505
|
+
const user = userEvent.setup();
|
|
506
|
+
const panelAppTab: DocBrowserTab = {
|
|
507
|
+
id: "piano",
|
|
508
|
+
kind: "panel-app" as const,
|
|
509
|
+
title: "Piano",
|
|
510
|
+
currentUrl: "/api/panel-apps/piano/content",
|
|
511
|
+
resourceUri: "nextclaw://panel-app/piano",
|
|
512
|
+
history: ["/api/panel-apps/piano/content"],
|
|
513
|
+
historyIndex: 0,
|
|
514
|
+
navVersion: 0,
|
|
515
|
+
};
|
|
516
|
+
docBrowserState.tabs = [panelAppTab];
|
|
517
|
+
docBrowserState.activeTabId = panelAppTab.id;
|
|
518
|
+
docBrowserState.activeHistory = [
|
|
519
|
+
{ kind: "apps", tabId: "apps", url: "nextclaw://apps?tab=panel-apps" },
|
|
520
|
+
{ kind: "panel-app", tabId: "piano", resourceUri: "nextclaw://panel-app/piano", url: panelAppTab.currentUrl },
|
|
521
|
+
];
|
|
522
|
+
docBrowserState.activeHistoryIndex = 1;
|
|
523
|
+
docBrowserState.currentTab = panelAppTab;
|
|
524
|
+
|
|
525
|
+
render(<DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />);
|
|
526
|
+
|
|
527
|
+
expect(screen.getAllByRole("button", { name: "Back" })).toHaveLength(1);
|
|
528
|
+
expect(screen.queryByRole("button", { name: "Apps" })).toBeNull();
|
|
529
|
+
await user.click(screen.getByRole("button", { name: "Back" }));
|
|
530
|
+
expect(docBrowserState.goBack).toHaveBeenCalledTimes(1);
|
|
531
|
+
|
|
532
|
+
expect(screen.queryByRole("button", { name: "Add to main sidebar" })).toBeNull();
|
|
533
|
+
await user.click(screen.getByRole("button", { name: "More panel app actions" }));
|
|
534
|
+
await user.click(screen.getByRole("button", { name: "Add to main sidebar" }));
|
|
535
|
+
expect(panelAppHooks.mutate).toHaveBeenCalledWith({
|
|
536
|
+
id: "piano",
|
|
537
|
+
preferences: { mainSidebar: true },
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("keeps placement actions when a restored shortcut only has a stable current URL", async () => {
|
|
542
|
+
const user = userEvent.setup();
|
|
543
|
+
const panelAppTab: DocBrowserTab = {
|
|
544
|
+
id: "piano",
|
|
545
|
+
kind: "panel-app",
|
|
546
|
+
title: "Piano",
|
|
547
|
+
currentUrl: "/api/panel-apps/piano/content",
|
|
548
|
+
resourceUri: "nextclaw://panel-app",
|
|
549
|
+
history: ["/api/panel-apps/piano/content"],
|
|
550
|
+
historyIndex: 0,
|
|
551
|
+
navVersion: 0,
|
|
552
|
+
};
|
|
553
|
+
docBrowserState.tabs = [panelAppTab];
|
|
554
|
+
docBrowserState.activeTabId = panelAppTab.id;
|
|
555
|
+
docBrowserState.currentTab = panelAppTab;
|
|
556
|
+
|
|
557
|
+
render(<DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />);
|
|
558
|
+
|
|
559
|
+
await user.click(screen.getByRole("button", { name: "More panel app actions" }));
|
|
560
|
+
await user.click(screen.getByRole("button", { name: "Add to main sidebar" }));
|
|
561
|
+
expect(panelAppHooks.mutate).toHaveBeenCalledWith({
|
|
562
|
+
id: "piano",
|
|
563
|
+
preferences: { mainSidebar: true },
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
466
568
|
describe("DocBrowser scroll restoration", () => {
|
|
467
569
|
beforeEach(() => {
|
|
468
570
|
resetDocBrowserTestState();
|
|
@@ -111,6 +111,7 @@ export type NcpSessionMessagesView = {
|
|
|
111
111
|
sessionId: string;
|
|
112
112
|
status: NcpSessionStatus;
|
|
113
113
|
messages: NcpMessageView[];
|
|
114
|
+
deferredToolPayloads?: Record<string, { cursor: string }>;
|
|
114
115
|
contextWindow?: SessionContextWindowView | null;
|
|
115
116
|
total: number;
|
|
116
117
|
pageInfo: NcpSessionMessagePageInfo;
|
|
@@ -467,6 +467,8 @@ export type PanelAppEntryView = {
|
|
|
467
467
|
updatedAt: string;
|
|
468
468
|
sizeBytes: number;
|
|
469
469
|
favorite: boolean;
|
|
470
|
+
mainSidebar: boolean;
|
|
471
|
+
mainSidebarOrder?: number;
|
|
470
472
|
clientDeclared: boolean;
|
|
471
473
|
clientGranted: boolean;
|
|
472
474
|
lastOpenedAt?: string;
|
|
@@ -484,6 +486,7 @@ export type PanelAppListView = {
|
|
|
484
486
|
|
|
485
487
|
export type PanelAppPreferencesUpdateView = {
|
|
486
488
|
favorite?: boolean;
|
|
489
|
+
mainSidebar?: boolean;
|
|
487
490
|
};
|
|
488
491
|
|
|
489
492
|
export type {
|
|
@@ -19,7 +19,12 @@ export async function fetchNcpSessions(params?: {
|
|
|
19
19
|
// GET /api/ncp/sessions/:sessionId/messages
|
|
20
20
|
export async function fetchNcpSessionMessages(
|
|
21
21
|
sessionId: string,
|
|
22
|
-
options: {
|
|
22
|
+
options: {
|
|
23
|
+
limit?: number;
|
|
24
|
+
cursor?: string;
|
|
25
|
+
toolPayload?: "summary";
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
} = {},
|
|
23
28
|
): Promise<NcpSessionMessagesView> {
|
|
24
29
|
return (await nextclawClient.sessions.listMessages(
|
|
25
30
|
sessionId,
|
|
@@ -27,6 +32,24 @@ export async function fetchNcpSessionMessages(
|
|
|
27
32
|
)) as NcpSessionMessagesView;
|
|
28
33
|
}
|
|
29
34
|
|
|
35
|
+
export async function fetchNcpSessionMessageDetail(
|
|
36
|
+
sessionId: string,
|
|
37
|
+
messageId: string,
|
|
38
|
+
cursor: string,
|
|
39
|
+
signal?: AbortSignal,
|
|
40
|
+
): Promise<NcpSessionMessagesView["messages"][number]> {
|
|
41
|
+
const response = await fetchNcpSessionMessages(sessionId, {
|
|
42
|
+
cursor,
|
|
43
|
+
limit: 1,
|
|
44
|
+
...(signal ? { signal } : {}),
|
|
45
|
+
});
|
|
46
|
+
const message = response.messages[0];
|
|
47
|
+
if (!message || message.id !== messageId) {
|
|
48
|
+
throw new Error(`Session message detail no longer matches ${messageId}.`);
|
|
49
|
+
}
|
|
50
|
+
return message;
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
// GET /api/ncp/sessions/:sessionId/usage
|
|
31
54
|
export async function fetchNcpSessionTokenUsage(
|
|
32
55
|
sessionId: string,
|
|
@@ -219,6 +219,10 @@
|
|
|
219
219
|
"chatHistoryLoadFailed": "Couldn’t load earlier messages",
|
|
220
220
|
"chatHistoryRetry": "Retry",
|
|
221
221
|
"chatProcessSummaryProcessed": "Processed",
|
|
222
|
+
"chatProcessSummaryToolCalls": "{count} tool calls",
|
|
223
|
+
"chatToolPayloadLoading": "Loading details",
|
|
224
|
+
"chatToolPayloadLoadFailed": "Couldn’t load details. Try again",
|
|
225
|
+
"chatToolActivityShowMore": "Show next {count} ({remaining} remaining)",
|
|
222
226
|
"chatAiExecutionInput": "input",
|
|
223
227
|
"chatAiExecutionOutput": "output",
|
|
224
228
|
"chatAiExecutionTokens": "tokens",
|
|
@@ -165,6 +165,16 @@
|
|
|
165
165
|
"panelAppsFavorite": "Favorite panel app",
|
|
166
166
|
"panelAppsUnfavorite": "Unfavorite panel app",
|
|
167
167
|
"panelAppsMoreActions": "More panel app actions",
|
|
168
|
+
"panelAppsAddToMainSidebar": "Add to main sidebar",
|
|
169
|
+
"panelAppsRemoveFromMainSidebar": "Remove from main sidebar",
|
|
170
|
+
"panelAppsPreferenceUpdateFailed": "Could not update panel app preferences",
|
|
171
|
+
"panelAppsOpenInRightPanel": "Open in right panel",
|
|
172
|
+
"panelAppsOpenApps": "Open app management",
|
|
173
|
+
"panelAppsMainUnavailable": "This panel app is unavailable. It may be disabled, uninstalled, or removed from the workspace.",
|
|
174
|
+
"panelAppsCheckingPermission": "Checking this panel app's permissions…",
|
|
175
|
+
"panelAppsPermissionDenied": "The required NextClaw Client permission was not granted to this panel app.",
|
|
176
|
+
"panelAppsPermissionFailed": "NextClaw could not confirm this panel app's permissions.",
|
|
177
|
+
"panelAppsTryAgain": "Try again",
|
|
168
178
|
"panelAppsDelete": "Delete panel app",
|
|
169
179
|
"panelAppsDeleteConfirmTitle": "Delete panel app?",
|
|
170
180
|
"panelAppsDeleteConfirmDescription": "This will delete the panel app file and cannot be undone.",
|
|
@@ -219,6 +219,10 @@
|
|
|
219
219
|
"chatHistoryLoadFailed": "较早消息加载失败",
|
|
220
220
|
"chatHistoryRetry": "重试",
|
|
221
221
|
"chatProcessSummaryProcessed": "已处理",
|
|
222
|
+
"chatProcessSummaryToolCalls": "{count} 次工具调用",
|
|
223
|
+
"chatToolPayloadLoading": "正在加载详情",
|
|
224
|
+
"chatToolPayloadLoadFailed": "详情加载失败,点击重试",
|
|
225
|
+
"chatToolActivityShowMore": "继续显示 {count} 项(剩余 {remaining} 项)",
|
|
222
226
|
"chatAiExecutionInput": "输入",
|
|
223
227
|
"chatAiExecutionOutput": "输出",
|
|
224
228
|
"chatAiExecutionTokens": "Token",
|
|
@@ -165,6 +165,16 @@
|
|
|
165
165
|
"panelAppsFavorite": "收藏面板应用",
|
|
166
166
|
"panelAppsUnfavorite": "取消收藏面板应用",
|
|
167
167
|
"panelAppsMoreActions": "更多面板应用操作",
|
|
168
|
+
"panelAppsAddToMainSidebar": "添加到主侧栏",
|
|
169
|
+
"panelAppsRemoveFromMainSidebar": "从主侧栏移除",
|
|
170
|
+
"panelAppsPreferenceUpdateFailed": "无法更新面板应用设置",
|
|
171
|
+
"panelAppsOpenInRightPanel": "在右侧面板打开",
|
|
172
|
+
"panelAppsOpenApps": "打开应用管理",
|
|
173
|
+
"panelAppsMainUnavailable": "此面板应用当前不可用。它可能已被禁用、卸载或从工作区删除。",
|
|
174
|
+
"panelAppsCheckingPermission": "正在确认此面板应用的权限…",
|
|
175
|
+
"panelAppsPermissionDenied": "未授予此面板应用所需的 NextClaw Client 权限。",
|
|
176
|
+
"panelAppsPermissionFailed": "无法确认此面板应用的权限。",
|
|
177
|
+
"panelAppsTryAgain": "重试",
|
|
168
178
|
"panelAppsDelete": "删除面板应用",
|
|
169
179
|
"panelAppsDeleteConfirmTitle": "删除面板应用?",
|
|
170
180
|
"panelAppsDeleteConfirmDescription": "将删除这个面板应用文件,删除后不可恢复。",
|