@nextclaw/ui 0.15.5 → 0.15.7
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 +41 -0
- package/dist/assets/api-V8m62-13.js +13 -0
- package/dist/assets/app-presenter-provider-Bzj9h_ZS.js +106 -0
- package/dist/assets/{appearance-settings-page-80fLa4-9.js → appearance-settings-page-DwBwu1AU.js} +1 -1
- package/dist/assets/{book-open-CN7Shpij.js → book-open-DtVSr5O9.js} +1 -1
- package/dist/assets/{channels-list-page-R7M6pzp6.js → channels-list-page-CYIqMliJ.js} +2 -2
- package/dist/assets/{chat-page-152YuwfU.js → chat-page-BC0icTtI.js} +1 -1
- package/dist/assets/{config-split-page-BlfvfmTm.js → config-split-page-B88prd8s.js} +1 -1
- package/dist/assets/{confirm-dialog-Dt8Kdbgp.js → confirm-dialog-BB09IC2u.js} +1 -1
- package/dist/assets/desktop-update-config-B-q0OhxO.js +1 -0
- package/dist/assets/{dist-D5ycbwCo.js → dist-Bytser-a.js} +1 -1
- package/dist/assets/{doc-browser-C9u7N9vH.js → doc-browser-B1FXvZm6.js} +1 -1
- package/dist/assets/doc-browser-Bodhf0fj2.js +1 -0
- package/dist/assets/{doc-browser-context-CCdW9TJf.js → doc-browser-context-D5VLNoj3.js} +1 -1
- package/dist/assets/{download-CyPgglKr.js → download-CdO6NoHj.js} +1 -1
- package/dist/assets/{external-link-BahW1sl3.js → external-link-C5NFoh5I.js} +1 -1
- package/dist/assets/{eye-D8gGXMkD.js → eye-B51oNRRc.js} +1 -1
- package/dist/assets/{folder-DGWqFDvE.js → folder-DxH7Nef-.js} +1 -1
- package/dist/assets/{form-actions-D2imZLnU.js → form-actions-C9pUwtYV.js} +1 -1
- package/dist/assets/index-DtWf7kk9.css +1 -0
- package/dist/assets/index-UnY4HtVa.js +2 -0
- package/dist/assets/loader-circle-BRSfaztA.js +1 -0
- package/dist/assets/{logo-badge-CuPetu-v.js → logo-badge-D2vORX3a.js} +1 -1
- package/dist/assets/mcp-marketplace-page-CGVu7mfL.js +9 -0
- package/dist/assets/mcp-marketplace-page-DnV0e1JS.js +1 -0
- package/dist/assets/{model-config-page-DiJcvBML.js → model-config-page-DYpchGVE.js} +1 -1
- package/dist/assets/plus-BgMBV9HJ.js +1 -0
- package/dist/assets/{popover-Btp7KVc3.js → popover-CONujDHf.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-DZMtoAJG.js → provider-scoped-model-input-DA3Ua9v8.js} +1 -1
- package/dist/assets/providers-config-page-DXsoyMrh.js +1 -0
- package/dist/assets/{react-BY-U3h3t.js → react-Io6Pfq8D.js} +1 -1
- package/dist/assets/{refresh-cw-Cme5qFej.js → refresh-cw-C6j2tjul.js} +1 -1
- package/dist/assets/remote-C1y3ARHu.js +1 -0
- package/dist/assets/rotate-cw-Bv2B6fAV.js +1 -0
- package/dist/assets/runtime-config-page-eYrrKBrA.js +1 -0
- package/dist/assets/{save-DNg2yJTS.js → save-CBBZVvZ-.js} +1 -1
- package/dist/assets/{search-3ykfrAFt.js → search-DFF842z2.js} +1 -1
- package/dist/assets/{search-config-page-DrVKa_Ls.js → search-config-page-DUyuwD7d.js} +1 -1
- package/dist/assets/{secrets-config-page-B39uQ5kF.js → secrets-config-page-DwzKQJ76.js} +2 -2
- package/dist/assets/security-config-D8me3pXl.js +1 -0
- package/dist/assets/{select-B5EecXJt.js → select-BiOpo2kV.js} +1 -1
- package/dist/assets/{setting-row-Qv1Ps79v.js → setting-row-C3WQXLtS.js} +1 -1
- package/dist/assets/{settings-2-D-3um9Az.js → settings-2-DyOvA_6K.js} +1 -1
- package/dist/assets/{skeleton-BgkzQd4U.js → skeleton-CQIAEbA4.js} +1 -1
- package/dist/assets/{tabs-BVD8gp3y.js → tabs-BTSENlI2.js} +1 -1
- package/dist/assets/{tag-chip-0qn-f3Bo.js → tag-chip-BG5JIGwY.js} +1 -1
- package/dist/assets/{trash-2-cnsMppRC.js → trash-2-CTIfwpt1.js} +1 -1
- package/dist/assets/use-config-CY4uF2Ou.js +1 -0
- package/dist/assets/{use-confirm-dialog-C_o0eQeH.js → use-confirm-dialog-Bj4u78F9.js} +1 -1
- package/dist/assets/{use-viewport-layout-BWav4KCL.js → use-viewport-layout-ChJUcSQ6.js} +1 -1
- package/dist/assets/x-C6cM8wkT.js +1 -0
- package/dist/index.html +30 -29
- package/index.html +1 -0
- package/package.json +8 -8
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +97 -3
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +13 -2
- package/src/features/chat/components/layout/__tests__/chat-sidebar-test.utils.ts +22 -0
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +13 -24
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +16 -4
- package/src/features/chat/components/layout/chat-sidebar.tsx +38 -2
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -0
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +70 -2
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +22 -0
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +8 -9
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +55 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +12 -11
- package/src/features/chat/features/conversation/hooks/use-session-conversation-preference-actions.ts +94 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +82 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +187 -63
- package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +225 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +23 -0
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +6 -1
- package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +357 -0
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +4 -1
- package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +26 -0
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +67 -5
- package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +4 -10
- package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +29 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +83 -8
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +1 -0
- package/src/features/chat/features/message/components/__tests__/chat-inline-file-preview.test.tsx +159 -0
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +92 -26
- package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +90 -21
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +24 -3
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +1 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +96 -3
- package/src/features/chat/features/project/components/__tests__/chat-project-create-dialog.test.tsx +67 -0
- package/src/features/chat/features/project/components/chat-project-create-dialog.tsx +203 -0
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +7 -7
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-dialog.test.tsx +47 -0
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +6 -3
- package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -0
- package/src/features/chat/features/session/components/session-header/chat-session-project-dialog.tsx +3 -0
- package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +245 -0
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +82 -128
- package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts +22 -0
- package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +6 -6
- package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +12 -1
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +3 -0
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +1 -0
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +20 -0
- package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +15 -2
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +5 -6
- package/src/features/chat/features/workspace/components/__tests__/workspace-file-content-preview.test.tsx +67 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +9 -7
- package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +95 -4
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +23 -0
- package/src/features/marketplace/components/__tests__/marketplace-page-detail.test.tsx +1 -0
- package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +62 -0
- package/src/features/marketplace/components/curated-shelves/__tests__/marketplace-curated-scene-route.test.tsx +1 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +2 -15
- package/src/features/marketplace/components/marketplace-page.tsx +10 -2
- package/src/features/marketplace/hooks/__tests__/use-marketplace.test.tsx +26 -1
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +11 -0
- package/src/features/marketplace/hooks/use-marketplace-list-model.ts +1 -1
- package/src/features/marketplace/hooks/use-marketplace.ts +28 -1
- package/src/features/settings/components/config/provider-form.tsx +0 -4
- package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +7 -9
- package/src/features/settings/utils/provider-form-model.utils.ts +0 -4
- package/src/features/system-status/hooks/use-current-version-release-notes-link.ts +33 -0
- package/src/features/system-status/index.ts +9 -0
- package/src/features/system-status/utils/update-release-notes.utils.ts +44 -3
- package/src/index.css +10 -16
- package/src/shared/components/common/__tests__/brand-header.test.tsx +110 -3
- package/src/shared/components/common/brand-header.tsx +121 -35
- package/src/shared/components/path-picker/__tests__/server-path-picker-dialog.test.tsx +288 -38
- package/src/shared/components/path-picker/server-path-picker-dialog.tsx +290 -199
- package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +103 -0
- package/src/shared/components/path-picker/server-path-picker-footer.tsx +58 -0
- package/src/shared/components/path-picker/server-path-picker-locations.tsx +148 -0
- package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +198 -0
- package/src/shared/components/path-picker/server-path-picker-new-folder.tsx +59 -0
- package/src/shared/components/path-picker/server-path-picker-toolbar.tsx +25 -0
- package/src/shared/hooks/use-projects.ts +28 -0
- package/src/shared/hooks/use-server-path-create-directory.ts +6 -0
- package/src/shared/hooks/use-server-path-search.ts +40 -0
- package/src/shared/lib/api/index.ts +1 -0
- package/src/shared/lib/api/projects/project.utils.ts +14 -0
- package/src/shared/lib/api/types.ts +27 -0
- package/src/shared/lib/api/utils/server-path.utils.ts +21 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +40 -2
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -3
- package/src/shared/lib/i18n/locales/en-US/desktop-update.json +5 -1
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +1 -0
- package/src/shared/lib/i18n/locales/en-US/path-picker.json +25 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +40 -2
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -3
- package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +5 -1
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +1 -0
- package/src/shared/lib/i18n/locales/zh-CN/path-picker.json +25 -0
- package/dist/assets/api-IlX_CbwU.js +0 -13
- package/dist/assets/app-presenter-provider-Bbl_MVbg.js +0 -106
- package/dist/assets/desktop-update-config-DreMJu9z.js +0 -1
- package/dist/assets/doc-browser-DJQ5XPsi2.js +0 -1
- package/dist/assets/index-0av6WO9-.css +0 -1
- package/dist/assets/index-CT0iYlxV.js +0 -2
- package/dist/assets/loader-circle-DtKek-qG.js +0 -1
- package/dist/assets/mcp-marketplace-page-4qMwFDpz.js +0 -1
- package/dist/assets/mcp-marketplace-page-BbxN5aZF.js +0 -9
- package/dist/assets/plus-BwgE-Zwf.js +0 -1
- package/dist/assets/providers-config-page-DfiWveEF.js +0 -1
- package/dist/assets/remote-BaVECEZw.js +0 -1
- package/dist/assets/rotate-cw-CMRZxIkH.js +0 -1
- package/dist/assets/runtime-config-page-wygwvMB4.js +0 -1
- package/dist/assets/security-config-BPzet76z.js +0 -1
- package/dist/assets/use-config-D6yx0lb0.js +0 -1
- package/dist/assets/x-B3o1dWR5.js +0 -1
- package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +0 -77
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +0 -144
|
@@ -4,11 +4,20 @@ export type ReleaseNotesLocale = 'zh-CN' | 'en-US';
|
|
|
4
4
|
|
|
5
5
|
export type ReleaseNotesText = Partial<Record<ReleaseNotesLocale, string>>;
|
|
6
6
|
|
|
7
|
+
export type ReleaseNotesLink = {
|
|
8
|
+
url: string;
|
|
9
|
+
versionLabel: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
7
12
|
export type ReleaseNotesItem = {
|
|
8
13
|
title: ReleaseNotesText;
|
|
9
14
|
body?: ReleaseNotesText;
|
|
10
15
|
};
|
|
11
16
|
|
|
17
|
+
export type ReleaseNotesLinks = {
|
|
18
|
+
html?: ReleaseNotesText;
|
|
19
|
+
};
|
|
20
|
+
|
|
12
21
|
export type ReleaseNotesSection = {
|
|
13
22
|
kind: string;
|
|
14
23
|
title?: ReleaseNotesText;
|
|
@@ -18,9 +27,12 @@ export type ReleaseNotesSection = {
|
|
|
18
27
|
export type ReleaseNotesPayload = {
|
|
19
28
|
title?: ReleaseNotesText;
|
|
20
29
|
summary?: ReleaseNotesText;
|
|
30
|
+
links?: ReleaseNotesLinks;
|
|
21
31
|
sections: ReleaseNotesSection[];
|
|
22
32
|
};
|
|
23
33
|
|
|
34
|
+
const DEFAULT_RELEASE_NOTES_BASE_URL = 'https://docs.nextclaw.io/';
|
|
35
|
+
|
|
24
36
|
export function readReleaseNotesText(
|
|
25
37
|
value: ReleaseNotesText | undefined,
|
|
26
38
|
locale: ReleaseNotesLocale
|
|
@@ -33,17 +45,40 @@ export function readReleaseNotesText(
|
|
|
33
45
|
export function resolveReleaseNotesDataUrl(snapshot: UpdateSnapshot): string | null {
|
|
34
46
|
const releaseNotesUrl = snapshot.releaseNotesUrl?.trim();
|
|
35
47
|
const version = (snapshot.availableVersion ?? snapshot.downloadedVersion)?.trim();
|
|
36
|
-
if (!releaseNotesUrl
|
|
48
|
+
if (!releaseNotesUrl) {
|
|
37
49
|
return null;
|
|
38
50
|
}
|
|
51
|
+
return resolveVersionReleaseNotesDataUrl(version, releaseNotesUrl);
|
|
52
|
+
}
|
|
39
53
|
|
|
54
|
+
export function resolveVersionReleaseNotesDataUrl(version: string | null | undefined, baseUrl = DEFAULT_RELEASE_NOTES_BASE_URL): string | null {
|
|
55
|
+
const normalizedVersion = version?.trim();
|
|
56
|
+
if (!normalizedVersion || !/^[0-9A-Za-z.-]+$/.test(normalizedVersion)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
40
59
|
try {
|
|
41
|
-
return new URL(`/release-notes/nextclaw-v${
|
|
60
|
+
return new URL(`/release-notes/nextclaw-v${normalizedVersion}.json`, baseUrl).toString();
|
|
42
61
|
} catch {
|
|
43
62
|
return null;
|
|
44
63
|
}
|
|
45
64
|
}
|
|
46
65
|
|
|
66
|
+
export function resolveReleaseNotesHtmlUrl(payload: ReleaseNotesPayload | undefined, locale: ReleaseNotesLocale): string | null {
|
|
67
|
+
return readReleaseNotesText(payload?.links?.html, locale);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolveUpdateReleaseNotesLink(snapshot: UpdateSnapshot): ReleaseNotesLink | null {
|
|
71
|
+
const url = snapshot.releaseNotesUrl?.trim();
|
|
72
|
+
const version = (snapshot.downloadedVersion ?? snapshot.availableVersion)?.trim();
|
|
73
|
+
if (!url || !version) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
url,
|
|
78
|
+
versionLabel: `v${version}`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
47
82
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
48
83
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
49
84
|
}
|
|
@@ -70,12 +105,18 @@ function isReleaseNotesSection(value: unknown): value is ReleaseNotesSection {
|
|
|
70
105
|
&& (value.title === undefined || isReleaseNotesText(value.title));
|
|
71
106
|
}
|
|
72
107
|
|
|
108
|
+
function isReleaseNotesLinks(value: unknown): value is ReleaseNotesLinks {
|
|
109
|
+
return isRecord(value)
|
|
110
|
+
&& (value.html === undefined || isReleaseNotesText(value.html));
|
|
111
|
+
}
|
|
112
|
+
|
|
73
113
|
function isReleaseNotesPayload(value: unknown): value is ReleaseNotesPayload {
|
|
74
114
|
return isRecord(value)
|
|
75
115
|
&& Array.isArray(value.sections)
|
|
76
116
|
&& value.sections.every(isReleaseNotesSection)
|
|
77
117
|
&& (value.title === undefined || isReleaseNotesText(value.title))
|
|
78
|
-
&& (value.summary === undefined || isReleaseNotesText(value.summary))
|
|
118
|
+
&& (value.summary === undefined || isReleaseNotesText(value.summary))
|
|
119
|
+
&& (value.links === undefined || isReleaseNotesLinks(value.links));
|
|
79
120
|
}
|
|
80
121
|
|
|
81
122
|
export async function fetchReleaseNotesData(url: string): Promise<ReleaseNotesPayload> {
|
package/src/index.css
CHANGED
|
@@ -719,8 +719,9 @@
|
|
|
719
719
|
--md-table-head-bg: hsl(var(--muted) / 0.84);
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
+
/* The root flow owns external spacing; individual blocks only own their surface. */
|
|
722
723
|
.chat-markdown > * + * {
|
|
723
|
-
margin-
|
|
724
|
+
margin-block-start: 0.75rem;
|
|
724
725
|
}
|
|
725
726
|
|
|
726
727
|
.chat-markdown p,
|
|
@@ -729,21 +730,19 @@
|
|
|
729
730
|
color: var(--md-text);
|
|
730
731
|
}
|
|
731
732
|
|
|
732
|
-
.chat-markdown h1,
|
|
733
|
-
|
|
734
|
-
.chat-markdown h3,
|
|
735
|
-
.chat-markdown h4,
|
|
736
|
-
.chat-markdown h5,
|
|
737
|
-
.chat-markdown h6 {
|
|
738
|
-
margin: 0.2rem 0 0;
|
|
739
|
-
padding-bottom: 0.5em;
|
|
733
|
+
.chat-markdown :is(h1, h2, h3, h4, h5, h6) {
|
|
734
|
+
margin: 0;
|
|
740
735
|
font-weight: 700;
|
|
741
736
|
line-height: 1.35;
|
|
742
737
|
letter-spacing: -0.01em;
|
|
743
738
|
}
|
|
744
739
|
|
|
745
|
-
.chat-markdown :is(h1, h2, h3, h4, h5, h6)
|
|
746
|
-
margin-
|
|
740
|
+
.chat-markdown > :is(h1, h2, h3, h4, h5, h6):not(:first-child) {
|
|
741
|
+
margin-block-start: 1.25rem;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
.chat-markdown > :is(h1, h2, h3, h4, h5, h6) + * {
|
|
745
|
+
margin-block-start: 0.5rem;
|
|
747
746
|
}
|
|
748
747
|
|
|
749
748
|
.chat-markdown h1 {
|
|
@@ -766,7 +765,6 @@
|
|
|
766
765
|
|
|
767
766
|
.chat-markdown ul,
|
|
768
767
|
.chat-markdown ol {
|
|
769
|
-
margin: 0.35rem 0;
|
|
770
768
|
padding-left: 1.5rem;
|
|
771
769
|
list-style-position: outside;
|
|
772
770
|
}
|
|
@@ -839,7 +837,6 @@
|
|
|
839
837
|
.chat-markdown hr {
|
|
840
838
|
border: 0;
|
|
841
839
|
border-top: 1px solid var(--md-border);
|
|
842
|
-
margin: 0.92rem 0;
|
|
843
840
|
}
|
|
844
841
|
|
|
845
842
|
.chat-markdown a {
|
|
@@ -874,7 +871,6 @@
|
|
|
874
871
|
}
|
|
875
872
|
|
|
876
873
|
.chat-codeblock {
|
|
877
|
-
margin: 0.2rem 0 0.1rem;
|
|
878
874
|
border: 1px solid var(--md-code-toolbar-border);
|
|
879
875
|
border-radius: 0.5rem;
|
|
880
876
|
overflow: hidden;
|
|
@@ -1299,7 +1295,6 @@
|
|
|
1299
1295
|
}
|
|
1300
1296
|
|
|
1301
1297
|
.chat-table-wrap {
|
|
1302
|
-
margin: 0.15rem 0;
|
|
1303
1298
|
border: 1px solid var(--md-border);
|
|
1304
1299
|
border-radius: 0.72rem;
|
|
1305
1300
|
overflow-x: auto;
|
|
@@ -1378,7 +1373,6 @@
|
|
|
1378
1373
|
}
|
|
1379
1374
|
|
|
1380
1375
|
.chat-markdown blockquote {
|
|
1381
|
-
margin: 0.2rem 0;
|
|
1382
1376
|
border-left: 3px solid var(--md-blockquote-border);
|
|
1383
1377
|
border-radius: 0 0.55rem 0.55rem 0;
|
|
1384
1378
|
padding: 0.45rem 0.72rem;
|
|
@@ -10,7 +10,9 @@ import { setLanguage } from '@/shared/lib/i18n';
|
|
|
10
10
|
|
|
11
11
|
const mocks = vi.hoisted(() => ({
|
|
12
12
|
applyDownloadedUpdate: vi.fn(),
|
|
13
|
-
downloadUpdate: vi.fn()
|
|
13
|
+
downloadUpdate: vi.fn(),
|
|
14
|
+
fetchReleaseNotes: vi.fn(),
|
|
15
|
+
openExternalUrl: vi.fn()
|
|
14
16
|
}));
|
|
15
17
|
|
|
16
18
|
vi.mock('@/features/system-status', async () => {
|
|
@@ -25,7 +27,30 @@ vi.mock('@/features/system-status', async () => {
|
|
|
25
27
|
};
|
|
26
28
|
});
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
vi.mock('@/shared/lib/host-capabilities', () => ({
|
|
31
|
+
hostCapabilityManager: {
|
|
32
|
+
openExternalUrl: mocks.openExternalUrl,
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
function createReleaseNotesResponse(urls?: { zh?: string; en?: string }): Response {
|
|
37
|
+
return new Response(JSON.stringify({
|
|
38
|
+
links: urls
|
|
39
|
+
? {
|
|
40
|
+
html: {
|
|
41
|
+
...(urls.zh ? { 'zh-CN': urls.zh } : {}),
|
|
42
|
+
...(urls.en ? { 'en-US': urls.en } : {})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
: undefined,
|
|
46
|
+
sections: []
|
|
47
|
+
}), {
|
|
48
|
+
headers: { 'content-type': 'application/json' },
|
|
49
|
+
status: 200
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function renderBrandHeader(options: { productVersion?: string } = {}) {
|
|
29
54
|
const queryClient = new QueryClient({
|
|
30
55
|
defaultOptions: {
|
|
31
56
|
queries: {
|
|
@@ -35,7 +60,7 @@ function renderBrandHeader() {
|
|
|
35
60
|
});
|
|
36
61
|
queryClient.setQueryData(['app-meta'], {
|
|
37
62
|
name: 'NextClaw',
|
|
38
|
-
productVersion: '0.18.11'
|
|
63
|
+
productVersion: options.productVersion ?? '0.18.11'
|
|
39
64
|
});
|
|
40
65
|
return render(
|
|
41
66
|
<QueryClientProvider client={queryClient}>
|
|
@@ -57,6 +82,10 @@ describe('BrandHeader', () => {
|
|
|
57
82
|
});
|
|
58
83
|
mocks.applyDownloadedUpdate.mockReset();
|
|
59
84
|
mocks.downloadUpdate.mockReset();
|
|
85
|
+
mocks.fetchReleaseNotes.mockReset();
|
|
86
|
+
mocks.fetchReleaseNotes.mockResolvedValue(createReleaseNotesResponse());
|
|
87
|
+
mocks.openExternalUrl.mockReset();
|
|
88
|
+
vi.stubGlobal('fetch', mocks.fetchReleaseNotes);
|
|
60
89
|
});
|
|
61
90
|
|
|
62
91
|
it('shows update progress next to the product version', async () => {
|
|
@@ -105,6 +134,84 @@ describe('BrandHeader', () => {
|
|
|
105
134
|
expect(screen.queryByRole('button', { name: '更新' })).toBeNull();
|
|
106
135
|
});
|
|
107
136
|
|
|
137
|
+
it('opens release notes from the current version tooltip when that version has docs notes', async () => {
|
|
138
|
+
const user = userEvent.setup();
|
|
139
|
+
mocks.fetchReleaseNotes.mockResolvedValueOnce(createReleaseNotesResponse({
|
|
140
|
+
zh: 'https://docs.nextclaw.io/zh/notes/2026-07-15-nextclaw-v0-23-0',
|
|
141
|
+
en: 'https://docs.nextclaw.io/en/notes/2026-07-15-nextclaw-v0-23-0'
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
renderBrandHeader({ productVersion: '0.23.0' });
|
|
145
|
+
|
|
146
|
+
const version = await screen.findByRole('button', {
|
|
147
|
+
name: '当前版本 v0.23.0,查看 v0.23.0 更新说明'
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await user.hover(version);
|
|
151
|
+
expect((await screen.findByRole('tooltip')).textContent).toContain('点击查看 v0.23.0 更新说明');
|
|
152
|
+
|
|
153
|
+
await user.click(version);
|
|
154
|
+
|
|
155
|
+
expect(mocks.fetchReleaseNotes).toHaveBeenCalledWith(
|
|
156
|
+
'https://docs.nextclaw.io/release-notes/nextclaw-v0.23.0.json',
|
|
157
|
+
expect.objectContaining({ cache: 'no-store' })
|
|
158
|
+
);
|
|
159
|
+
expect(mocks.openExternalUrl).toHaveBeenCalledWith('https://docs.nextclaw.io/zh/notes/2026-07-15-nextclaw-v0-23-0');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('keeps update target release notes available without replacing the current version link', async () => {
|
|
163
|
+
const user = userEvent.setup();
|
|
164
|
+
mocks.fetchReleaseNotes.mockResolvedValueOnce(createReleaseNotesResponse({
|
|
165
|
+
zh: 'https://docs.nextclaw.io/zh/notes/2026-07-14-nextclaw-v0-18-11'
|
|
166
|
+
}));
|
|
167
|
+
useRuntimeUpdateStore.setState({
|
|
168
|
+
supported: true,
|
|
169
|
+
initialized: true,
|
|
170
|
+
busyAction: null,
|
|
171
|
+
snapshot: {
|
|
172
|
+
status: 'update-available',
|
|
173
|
+
installationKind: 'npm-runtime-bundle',
|
|
174
|
+
channel: 'stable',
|
|
175
|
+
hostVersion: '0.18.11',
|
|
176
|
+
currentVersion: '0.18.11',
|
|
177
|
+
availableVersion: '0.18.12',
|
|
178
|
+
downloadedVersion: null,
|
|
179
|
+
minimumHostVersion: null,
|
|
180
|
+
releaseNotesUrl: 'https://docs.nextclaw.io/zh/notes/2026-07-15-nextclaw-v0-18-12',
|
|
181
|
+
lastCheckedAt: null,
|
|
182
|
+
progress: null,
|
|
183
|
+
canAutoDownload: true,
|
|
184
|
+
canApplyInApp: false,
|
|
185
|
+
requiresRestart: false,
|
|
186
|
+
blockReason: null,
|
|
187
|
+
recoveryCommand: null,
|
|
188
|
+
errorMessage: null,
|
|
189
|
+
preferences: {
|
|
190
|
+
automaticChecks: true,
|
|
191
|
+
autoDownload: true
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
renderBrandHeader();
|
|
197
|
+
|
|
198
|
+
const currentVersion = await screen.findByRole('button', {
|
|
199
|
+
name: '当前版本 v0.18.11,查看 v0.18.11 更新说明'
|
|
200
|
+
});
|
|
201
|
+
await user.click(currentVersion);
|
|
202
|
+
expect(mocks.openExternalUrl).toHaveBeenCalledWith('https://docs.nextclaw.io/zh/notes/2026-07-14-nextclaw-v0-18-11');
|
|
203
|
+
|
|
204
|
+
await user.click(screen.getByRole('button', { name: '下载' }));
|
|
205
|
+
expect(mocks.downloadUpdate).toHaveBeenCalledTimes(1);
|
|
206
|
+
|
|
207
|
+
const updateReleaseNotes = screen.getByRole('button', { name: '查看 v0.18.12 更新说明' });
|
|
208
|
+
await user.hover(updateReleaseNotes);
|
|
209
|
+
expect((await screen.findByRole('tooltip')).textContent).toBe('查看 v0.18.12 更新说明');
|
|
210
|
+
|
|
211
|
+
await user.click(updateReleaseNotes);
|
|
212
|
+
expect(mocks.openExternalUrl).toHaveBeenCalledWith('https://docs.nextclaw.io/zh/notes/2026-07-15-nextclaw-v0-18-12');
|
|
213
|
+
});
|
|
214
|
+
|
|
108
215
|
it('applies the downloaded update from the version-adjacent update button', async () => {
|
|
109
216
|
const user = userEvent.setup();
|
|
110
217
|
useRuntimeUpdateStore.setState({
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { UpdateSnapshot } from '@nextclaw/shared';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
runtimeUpdateManager,
|
|
4
|
+
resolveUpdateReleaseNotesLink,
|
|
5
|
+
useCurrentVersionReleaseNotesLink,
|
|
6
|
+
useRuntimeUpdateStore,
|
|
7
|
+
type ReleaseNotesLink,
|
|
8
|
+
type RuntimeUpdateBusyAction
|
|
9
|
+
} from '@/features/system-status';
|
|
3
10
|
import { useAppMeta } from '@/shared/hooks/use-app-meta';
|
|
4
11
|
import type { ReactNode } from 'react';
|
|
5
12
|
import { RuntimeStatusEntry } from '@/app/components/layout/runtime-status-entry';
|
|
6
13
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/shared/components/ui/tooltip';
|
|
14
|
+
import { hostCapabilityManager } from '@/shared/lib/host-capabilities';
|
|
7
15
|
import { t } from '@/shared/lib/i18n';
|
|
8
16
|
import { cn } from '@/shared/lib/utils';
|
|
17
|
+
import { ExternalLink } from 'lucide-react';
|
|
9
18
|
|
|
10
19
|
type BrandHeaderProps = {
|
|
11
20
|
className?: string;
|
|
@@ -15,9 +24,11 @@ type BrandHeaderProps = {
|
|
|
15
24
|
|
|
16
25
|
export function BrandHeader({ className, density = 'sidebar', suffix }: BrandHeaderProps) {
|
|
17
26
|
const { data } = useAppMeta();
|
|
27
|
+
const { supported, busyAction, snapshot } = useRuntimeUpdateStore();
|
|
18
28
|
const productName = data?.name ?? 'NextClaw';
|
|
19
29
|
const productVersion = data?.productVersion?.trim();
|
|
20
30
|
const versionLabel = productVersion ? `v${productVersion}` : null;
|
|
31
|
+
const releaseNotesLink = useCurrentVersionReleaseNotesLink(productVersion);
|
|
21
32
|
const resolvedSuffix = suffix ?? <RuntimeStatusEntry />;
|
|
22
33
|
const shouldReserveMacWindowControls = typeof window !== 'undefined' && window.nextclawDesktop?.platform === 'darwin';
|
|
23
34
|
const isChromeDensity = density === 'chrome';
|
|
@@ -42,9 +53,9 @@ export function BrandHeader({ className, density = 'sidebar', suffix }: BrandHea
|
|
|
42
53
|
>
|
|
43
54
|
{productName}
|
|
44
55
|
</span>
|
|
45
|
-
{versionLabel ? <BrandVersionLabel versionLabel={versionLabel} density={density} /> : null}
|
|
56
|
+
{versionLabel ? <BrandVersionLabel versionLabel={versionLabel} density={density} releaseNotesLink={releaseNotesLink} /> : null}
|
|
46
57
|
</div>
|
|
47
|
-
<RuntimeUpdateInlineStatus />
|
|
58
|
+
<RuntimeUpdateInlineStatus supported={supported} busyAction={busyAction} snapshot={snapshot} />
|
|
48
59
|
{resolvedSuffix ? <span className="inline-flex items-center shrink-0">{resolvedSuffix}</span> : null}
|
|
49
60
|
</div>
|
|
50
61
|
</div>
|
|
@@ -54,68 +65,143 @@ export function BrandHeader({ className, density = 'sidebar', suffix }: BrandHea
|
|
|
54
65
|
function BrandVersionLabel({
|
|
55
66
|
versionLabel,
|
|
56
67
|
density,
|
|
68
|
+
releaseNotesLink,
|
|
57
69
|
}: {
|
|
58
70
|
versionLabel: string;
|
|
59
71
|
density: BrandHeaderProps['density'];
|
|
72
|
+
releaseNotesLink: ReleaseNotesLink | null;
|
|
60
73
|
}) {
|
|
61
74
|
const isChromeDensity = density === 'chrome';
|
|
75
|
+
const triggerClassName = cn(
|
|
76
|
+
'block min-w-0 flex-1 truncate font-medium text-gray-500 outline-none',
|
|
77
|
+
isChromeDensity ? 'text-[12px]' : 'text-[12px]',
|
|
78
|
+
releaseNotesLink && 'cursor-pointer rounded-sm transition-colors hover:text-gray-700 hover:underline focus-visible:ring-2 focus-visible:ring-gray-300'
|
|
79
|
+
);
|
|
80
|
+
const tooltip = releaseNotesLink
|
|
81
|
+
? [
|
|
82
|
+
t('desktopUpdatesVersionTooltipCurrent').replace('{version}', versionLabel),
|
|
83
|
+
t('desktopUpdatesVersionTooltipReleaseNotes').replace('{version}', releaseNotesLink.versionLabel)
|
|
84
|
+
].join('\n')
|
|
85
|
+
: versionLabel;
|
|
86
|
+
const trigger = releaseNotesLink ? (
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
aria-label={t('desktopUpdatesVersionReleaseNotesLabel').replace('{currentVersion}', versionLabel).replace('{releaseVersion}', releaseNotesLink.versionLabel)}
|
|
90
|
+
className={cn(triggerClassName, 'border-0 bg-transparent p-0 text-left')}
|
|
91
|
+
onClick={() => void hostCapabilityManager.openExternalUrl(releaseNotesLink.url)}
|
|
92
|
+
>
|
|
93
|
+
{versionLabel}
|
|
94
|
+
</button>
|
|
95
|
+
) : (
|
|
96
|
+
<span tabIndex={0} aria-label={versionLabel} className={triggerClassName}>
|
|
97
|
+
{versionLabel}
|
|
98
|
+
</span>
|
|
99
|
+
);
|
|
62
100
|
|
|
63
101
|
return (
|
|
64
102
|
<TooltipProvider delayDuration={250}>
|
|
65
103
|
<Tooltip>
|
|
66
|
-
<TooltipTrigger asChild>
|
|
67
|
-
|
|
68
|
-
tabIndex={0}
|
|
69
|
-
aria-label={versionLabel}
|
|
70
|
-
className={cn(
|
|
71
|
-
'block min-w-0 flex-1 truncate font-medium text-gray-500 outline-none',
|
|
72
|
-
isChromeDensity ? 'text-[12px]' : 'text-[12px]',
|
|
73
|
-
)}
|
|
74
|
-
>
|
|
75
|
-
{versionLabel}
|
|
76
|
-
</span>
|
|
77
|
-
</TooltipTrigger>
|
|
78
|
-
<TooltipContent side="bottom" className="text-xs">{versionLabel}</TooltipContent>
|
|
104
|
+
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
|
|
105
|
+
<TooltipContent side="bottom" className="max-w-72 whitespace-pre-line text-xs leading-relaxed">{tooltip}</TooltipContent>
|
|
79
106
|
</Tooltip>
|
|
80
107
|
</TooltipProvider>
|
|
81
108
|
);
|
|
82
109
|
}
|
|
83
110
|
|
|
84
|
-
function RuntimeUpdateInlineStatus(
|
|
85
|
-
|
|
111
|
+
function RuntimeUpdateInlineStatus({
|
|
112
|
+
supported,
|
|
113
|
+
busyAction,
|
|
114
|
+
snapshot,
|
|
115
|
+
}: {
|
|
116
|
+
supported: boolean;
|
|
117
|
+
busyAction: RuntimeUpdateBusyAction;
|
|
118
|
+
snapshot: UpdateSnapshot | null;
|
|
119
|
+
}) {
|
|
86
120
|
if (!supported || !snapshot) {
|
|
87
121
|
return null;
|
|
88
122
|
}
|
|
89
|
-
|
|
123
|
+
const releaseNotesLink = resolveUpdateReleaseNotesLink(snapshot);
|
|
124
|
+
if (snapshot.status === 'downloading') {
|
|
125
|
+
return (
|
|
126
|
+
<RuntimeUpdateInlineGroup>
|
|
127
|
+
<RuntimeUpdateInlineBadge snapshot={snapshot} />
|
|
128
|
+
{releaseNotesLink ? <RuntimeUpdateReleaseNotesButton link={releaseNotesLink} tone="amber" /> : null}
|
|
129
|
+
</RuntimeUpdateInlineGroup>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (snapshot.status === 'blocked' || snapshot.status === 'failed') {
|
|
90
133
|
return <RuntimeUpdateInlineBadge snapshot={snapshot} />;
|
|
91
134
|
}
|
|
92
135
|
if (snapshot.status === 'downloaded') {
|
|
93
136
|
return (
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
137
|
+
<RuntimeUpdateInlineGroup>
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
className="inline-flex h-5 shrink-0 items-center rounded-full bg-emerald-50 px-2 text-[11px] font-semibold leading-none text-emerald-700 ring-1 ring-emerald-100 transition-colors hover:bg-emerald-100 disabled:opacity-70"
|
|
141
|
+
disabled={busyAction === 'applying'}
|
|
142
|
+
onClick={() => void runtimeUpdateManager.applyDownloadedUpdate()}
|
|
143
|
+
>
|
|
144
|
+
{busyAction === 'applying' ? t('desktopUpdatesInlineApplying') : t('desktopUpdatesInlineReady')}
|
|
145
|
+
</button>
|
|
146
|
+
{releaseNotesLink ? <RuntimeUpdateReleaseNotesButton link={releaseNotesLink} tone="emerald" /> : null}
|
|
147
|
+
</RuntimeUpdateInlineGroup>
|
|
102
148
|
);
|
|
103
149
|
}
|
|
104
150
|
if (snapshot.status === 'update-available') {
|
|
105
151
|
return (
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
152
|
+
<RuntimeUpdateInlineGroup>
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
className="inline-flex h-5 shrink-0 items-center rounded-full bg-amber-50 px-2 text-[11px] font-semibold leading-none text-amber-700 ring-1 ring-amber-100 transition-colors hover:bg-amber-100 disabled:opacity-70"
|
|
156
|
+
disabled={busyAction === 'downloading'}
|
|
157
|
+
onClick={() => void runtimeUpdateManager.downloadUpdate()}
|
|
158
|
+
>
|
|
159
|
+
{busyAction === 'downloading' ? t('desktopUpdatesInlineDownloading') : t('desktopUpdatesInlineDownload')}
|
|
160
|
+
</button>
|
|
161
|
+
{releaseNotesLink ? <RuntimeUpdateReleaseNotesButton link={releaseNotesLink} tone="amber" /> : null}
|
|
162
|
+
</RuntimeUpdateInlineGroup>
|
|
114
163
|
);
|
|
115
164
|
}
|
|
116
165
|
return null;
|
|
117
166
|
}
|
|
118
167
|
|
|
168
|
+
function RuntimeUpdateInlineGroup({ children }: { children: ReactNode }) {
|
|
169
|
+
return <span className="inline-flex shrink-0 items-center gap-1">{children}</span>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function RuntimeUpdateReleaseNotesButton({
|
|
173
|
+
link,
|
|
174
|
+
tone,
|
|
175
|
+
}: {
|
|
176
|
+
link: ReleaseNotesLink;
|
|
177
|
+
tone: 'amber' | 'emerald';
|
|
178
|
+
}) {
|
|
179
|
+
const label = t('desktopUpdatesUpdateReleaseNotesLabel').replace('{version}', link.versionLabel);
|
|
180
|
+
const className = cn(
|
|
181
|
+
'inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[11px] ring-1 transition-colors focus-visible:outline-none focus-visible:ring-2',
|
|
182
|
+
tone === 'emerald'
|
|
183
|
+
? 'bg-emerald-50 text-emerald-700 ring-emerald-100 hover:bg-emerald-100 focus-visible:ring-emerald-300'
|
|
184
|
+
: 'bg-amber-50 text-amber-700 ring-amber-100 hover:bg-amber-100 focus-visible:ring-amber-300'
|
|
185
|
+
);
|
|
186
|
+
return (
|
|
187
|
+
<TooltipProvider delayDuration={250}>
|
|
188
|
+
<Tooltip>
|
|
189
|
+
<TooltipTrigger asChild>
|
|
190
|
+
<button
|
|
191
|
+
type="button"
|
|
192
|
+
aria-label={label}
|
|
193
|
+
className={className}
|
|
194
|
+
onClick={() => void hostCapabilityManager.openExternalUrl(link.url)}
|
|
195
|
+
>
|
|
196
|
+
<ExternalLink className="h-3 w-3" aria-hidden="true" />
|
|
197
|
+
</button>
|
|
198
|
+
</TooltipTrigger>
|
|
199
|
+
<TooltipContent side="bottom" className="text-xs">{label}</TooltipContent>
|
|
200
|
+
</Tooltip>
|
|
201
|
+
</TooltipProvider>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
119
205
|
function RuntimeUpdateInlineBadge({ snapshot }: { snapshot: UpdateSnapshot }) {
|
|
120
206
|
if (snapshot.status === 'blocked' || snapshot.status === 'failed') {
|
|
121
207
|
return <RuntimeUpdateIssueIcon snapshot={snapshot} />;
|