@nextclaw/ui 0.14.1 → 0.14.3
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-DPcvCLfa.js → api-BP_8IaxB.js} +7 -7
- package/dist/assets/channels-list-page-DEh5Jjqx.js +8 -0
- package/dist/assets/chat-page-BRB0N5WW.js +105 -0
- package/dist/assets/config-split-page-CRhlZ_pH.js +1 -0
- package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-DsivFJ4r.js} +2 -2
- package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-B0w9p2ep.js} +1 -1
- package/dist/assets/{dist-DPrgcEC0.js → dist-CikkhOlq.js} +1 -1
- package/dist/assets/{dist-DsE4OsSS.js → dist-PVA_xL1D.js} +1 -1
- package/dist/assets/doc-browser-BoNJ7-tE.js +1 -0
- package/dist/assets/doc-browser-CdJ_d4ik.js +1 -0
- package/dist/assets/doc-browser-context-MTw4EvKd.js +1 -0
- package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-y6vDtq4M.js} +1 -1
- package/dist/assets/{external-link-C_dmG-WJ.js → external-link-DVJIQf_G.js} +1 -1
- package/dist/assets/index-BiE-K8-q.css +1 -0
- package/dist/assets/index-D9BAE_L_.js +105 -0
- package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +40 -0
- package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +1 -0
- package/dist/assets/middleware-CkFT3R4v.js +1 -0
- package/dist/assets/model-config-page-DotaTUHb.js +1 -0
- package/dist/assets/plus-BbZqquJe.js +1 -0
- package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +1 -0
- package/dist/assets/providers-config-page-DW4tQSLN.js +1 -0
- package/dist/assets/{react-sZz5HSCm.js → react-VNqT5Zp7.js} +1 -1
- package/dist/assets/remote-B8TiUfwW.js +1 -0
- package/dist/assets/rotate-cw-DjHDTkCv.js +1 -0
- package/dist/assets/runtime-config-page-S6ErRHR8.js +1 -0
- package/dist/assets/{save-MzjoiHri.js → save-BOJ477H-.js} +1 -1
- package/dist/assets/{search-B29dE4GN.js → search-BEti5beA.js} +1 -1
- package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-DGj5am03.js} +1 -1
- package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page--IzrIOid.js} +2 -2
- package/dist/assets/{select-BKmh05hd.js → select-hoj8dK4K.js} +2 -2
- package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-T8-h-die.js} +1 -1
- package/dist/assets/use-config-CjRV2hwL.js +1 -0
- package/dist/assets/x-D9PXs_xP.js +1 -0
- package/dist/index.html +70 -19
- package/index.html +55 -5
- package/package.json +9 -9
- package/src/app/components/app-presenter-provider.tsx +11 -2
- package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
- package/src/app/components/layout/app-layout.tsx +18 -4
- package/src/app/components/layout/header.tsx +3 -3
- package/src/app/components/layout/page-layout.tsx +2 -2
- package/src/app/components/layout/runtime-status-entry.tsx +7 -7
- package/src/app/components/layout/sidebar-items.tsx +194 -71
- package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
- package/src/app/components/layout/sidebar.tsx +340 -83
- package/src/app/managers/viewport-layout.manager.ts +10 -0
- package/src/app/stores/viewport-layout.store.ts +39 -3
- package/src/app/styles/design-system.css +388 -26
- package/src/app/test/vitest-setup.ts +31 -0
- package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
- package/src/features/agents/components/agents-page.tsx +10 -6
- package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +3 -3
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +4 -2
- package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
- package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +15 -16
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -6
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +19 -19
- package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +13 -13
- package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +18 -16
- package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +93 -20
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +93 -36
- package/src/features/chat/components/layout/chat-sidebar.tsx +60 -93
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +225 -0
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +199 -0
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +308 -0
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +439 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +194 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
- package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +51 -20
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
- package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
- package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
- package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
- package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
- package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +7 -7
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +99 -18
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
- package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +7 -7
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +6 -6
- package/src/features/chat/features/session/components/session-context-icon.tsx +2 -2
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +2 -2
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
- package/src/features/chat/features/session/components/session-run-badge.tsx +1 -1
- package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
- package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
- package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
- package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
- package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
- package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +10 -2
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -9
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
- package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +3 -3
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +4 -4
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +14 -19
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +5 -5
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +6 -6
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +6 -8
- package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +1 -16
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
- package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
- package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
- package/src/features/chat/index.ts +0 -1
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
- package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
- package/src/features/chat/managers/chat-thread.manager.ts +108 -34
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
- package/src/features/chat/presenters/chat.presenter.ts +2 -19
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
- package/src/features/chat/stores/chat-thread.store.ts +42 -26
- package/src/features/panel-apps/components/panel-app-list-item.tsx +8 -8
- package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
- package/src/features/panel-apps/components/panel-apps-list.tsx +20 -20
- package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +70 -6
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +10 -5
- package/src/features/service-apps/components/service-action-authorization-dialog.tsx +8 -8
- package/src/features/service-apps/components/service-apps-panel.tsx +24 -24
- package/src/features/settings/components/config/provider-advanced-settings-section.tsx +5 -5
- package/src/features/settings/components/config/provider-auth-section.tsx +7 -7
- package/src/features/settings/components/config/provider-form.tsx +10 -10
- package/src/features/settings/components/config/provider-models-section.tsx +16 -16
- package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
- package/src/features/settings/pages/providers-config-page.tsx +17 -17
- package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
- package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
- package/src/shared/components/config-split-page.tsx +11 -11
- package/src/shared/components/doc-browser/doc-browser-home-page.tsx +2 -2
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +4 -4
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +4 -4
- package/src/shared/components/doc-browser/doc-browser.tsx +3 -3
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +1 -1
- package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
- package/src/shared/components/ui/button.tsx +4 -4
- package/src/shared/components/ui/card.tsx +4 -4
- package/src/shared/components/ui/dialog.tsx +5 -5
- package/src/shared/components/ui/input.tsx +1 -1
- package/src/shared/components/ui/label.tsx +1 -1
- package/src/shared/components/ui/popover.tsx +1 -1
- package/src/shared/components/ui/select.tsx +3 -3
- package/src/shared/components/ui/switch.tsx +3 -3
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
- package/src/shared/components/ui/tabs.tsx +4 -4
- package/src/shared/components/ui/textarea.tsx +1 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
- package/src/shared/lib/i18n/locales/en-US/core.json +9 -2
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
- package/src/shared/lib/i18n/locales/zh-CN/core.json +9 -2
- package/src/shared/lib/theme/index.ts +94 -52
- package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
- package/src/shared/lib/ui-document-title/index.ts +50 -21
- package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
- package/dist/assets/chat-page-BBnNJoKG.js +0 -105
- package/dist/assets/config-split-page-BCJhqdK9.js +0 -1
- package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
- package/dist/assets/doc-browser-DgQedeAn.js +0 -1
- package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
- package/dist/assets/index-7o1WdUbU.js +0 -103
- package/dist/assets/index-DQWmEWzk.css +0 -1
- package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
- package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
- package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
- package/dist/assets/plus-Bnt56oqG.js +0 -1
- package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
- package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
- package/dist/assets/remote-B0LMs8u7.js +0 -1
- package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
- package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
- package/dist/assets/use-config-DZkBWIdL.js +0 -1
- package/dist/assets/x-CW2KO-Hz.js +0 -1
- package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
- package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
- package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
- package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
- package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
- package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
- package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
- package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
- package/src/features/chat/managers/chat-input.manager.ts +0 -430
- package/src/features/chat/managers/chat-run.manager.ts +0 -110
- package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
- package/src/features/chat/stores/chat-input.store.ts +0 -104
- /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createChatComposerTextNode, createChatComposerTokenNode } from '@nextclaw/agent-chat-ui';
|
|
2
|
-
import { deriveNcpMessagePartsFromComposer } from '
|
|
2
|
+
import { deriveNcpMessagePartsFromComposer } from '@/features/chat/features/input/utils/chat-composer-state.utils';
|
|
3
3
|
|
|
4
4
|
describe('deriveNcpMessagePartsFromComposer', () => {
|
|
5
5
|
it('preserves interleaved text and image token order while serializing skill tokens inline', () => {
|
|
@@ -68,6 +68,27 @@ describe('deriveNcpMessagePartsFromComposer', () => {
|
|
|
68
68
|
]);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
it('serializes panel app tokens as inline panel app references', () => {
|
|
72
|
+
expect(
|
|
73
|
+
deriveNcpMessagePartsFromComposer(
|
|
74
|
+
[
|
|
75
|
+
createChatComposerTextNode('review '),
|
|
76
|
+
createChatComposerTokenNode({
|
|
77
|
+
tokenKind: 'panel_app',
|
|
78
|
+
tokenKey: 'task-board',
|
|
79
|
+
label: 'Task Board'
|
|
80
|
+
})
|
|
81
|
+
],
|
|
82
|
+
[]
|
|
83
|
+
)
|
|
84
|
+
).toEqual([
|
|
85
|
+
{
|
|
86
|
+
type: 'text',
|
|
87
|
+
text: 'review @panel-app:task-board'
|
|
88
|
+
}
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
|
|
71
92
|
it('preserves uploaded attachment references when the attachment has a server uri', () => {
|
|
72
93
|
const parts = deriveNcpMessagePartsFromComposer(
|
|
73
94
|
[
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createChatComposerTextNode, createChatComposerTokenNode } from '@nextclaw/agent-chat-ui';
|
|
2
2
|
import {
|
|
3
|
+
buildInlineTokensFromTextProtocol,
|
|
3
4
|
buildInlineSkillTokensFromComposer,
|
|
4
5
|
CHAT_UI_INLINE_TOKENS_METADATA_KEY,
|
|
5
6
|
readInlineTokensFromMetadata,
|
|
6
|
-
|
|
7
|
-
} from '
|
|
7
|
+
resolveInlineTokensForText
|
|
8
|
+
} from '@/features/chat/features/input/utils/chat-inline-token.utils';
|
|
8
9
|
|
|
9
10
|
describe('chat-inline-token utils', () => {
|
|
10
11
|
it('builds ordered inline skill tokens from composer nodes', () => {
|
|
@@ -60,9 +61,9 @@ describe('chat-inline-token utils', () => {
|
|
|
60
61
|
]);
|
|
61
62
|
});
|
|
62
63
|
|
|
63
|
-
it('
|
|
64
|
+
it('merges metadata tokens with pure text protocol tokens', () => {
|
|
64
65
|
expect(
|
|
65
|
-
|
|
66
|
+
resolveInlineTokensForText('please use $weather and @panel-app:task-board', [
|
|
66
67
|
{
|
|
67
68
|
kind: 'skill',
|
|
68
69
|
key: 'weather',
|
|
@@ -71,17 +72,29 @@ describe('chat-inline-token utils', () => {
|
|
|
71
72
|
}
|
|
72
73
|
])
|
|
73
74
|
).toEqual([
|
|
74
|
-
{ type: 'text', text: 'please use ' },
|
|
75
75
|
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
label: 'Weather',
|
|
81
|
-
rawText: '$weather'
|
|
82
|
-
}
|
|
76
|
+
kind: 'skill',
|
|
77
|
+
key: 'weather',
|
|
78
|
+
label: 'Weather',
|
|
79
|
+
rawText: '$weather'
|
|
83
80
|
},
|
|
84
|
-
{
|
|
81
|
+
{
|
|
82
|
+
kind: 'panel_app',
|
|
83
|
+
key: 'task-board',
|
|
84
|
+
label: 'task-board',
|
|
85
|
+
rawText: '@panel-app:task-board'
|
|
86
|
+
}
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('builds inline panel app tokens from pure text protocol', () => {
|
|
91
|
+
expect(buildInlineTokensFromTextProtocol('review @panel-app:task-board now')).toEqual([
|
|
92
|
+
{
|
|
93
|
+
kind: 'panel_app',
|
|
94
|
+
key: 'task-board',
|
|
95
|
+
label: 'task-board',
|
|
96
|
+
rawText: '@panel-app:task-board'
|
|
97
|
+
}
|
|
85
98
|
]);
|
|
86
99
|
});
|
|
87
100
|
});
|
package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts
CHANGED
|
@@ -6,39 +6,18 @@ import {
|
|
|
6
6
|
isNcpChatModelOptionsLoading,
|
|
7
7
|
isNcpChatSendDisabled,
|
|
8
8
|
} from '@/features/chat/features/input/utils/ncp-chat-input-availability.utils';
|
|
9
|
-
import type { ChatInputSnapshot } from '@/features/chat/stores/chat-input.store';
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
type AvailabilitySnapshot = {
|
|
11
|
+
readonly isProviderStateResolved: boolean;
|
|
12
|
+
readonly modelOptions: readonly unknown[];
|
|
13
|
+
readonly sessionTypeUnavailable: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function createSnapshot(overrides: Partial<AvailabilitySnapshot> = {}): AvailabilitySnapshot {
|
|
14
17
|
return {
|
|
15
18
|
isProviderStateResolved: false,
|
|
16
|
-
composerNodes: [],
|
|
17
|
-
attachments: [],
|
|
18
|
-
draft: '',
|
|
19
|
-
pendingSessionType: 'native',
|
|
20
|
-
pendingProjectRoot: null,
|
|
21
|
-
pendingProjectRootSessionKey: null,
|
|
22
|
-
defaultProjectRoot: null,
|
|
23
|
-
defaultSessionType: 'native',
|
|
24
|
-
canStopGeneration: false,
|
|
25
|
-
stopDisabledReason: null,
|
|
26
|
-
sendError: null,
|
|
27
|
-
isSending: false,
|
|
28
19
|
modelOptions: [],
|
|
29
|
-
selectedModel: '',
|
|
30
|
-
selectedThinkingLevel: null,
|
|
31
|
-
sessionTypeOptions: [],
|
|
32
|
-
selectedSessionType: 'native',
|
|
33
|
-
stopSupported: false,
|
|
34
|
-
stopReason: undefined,
|
|
35
|
-
canEditSessionType: true,
|
|
36
20
|
sessionTypeUnavailable: false,
|
|
37
|
-
sessionTypeUnavailableMessage: null,
|
|
38
|
-
skillRecords: [],
|
|
39
|
-
isSkillsLoading: false,
|
|
40
|
-
selectedSkills: [],
|
|
41
|
-
composerFocusRequest: null,
|
|
42
21
|
...overrides,
|
|
43
22
|
};
|
|
44
23
|
}
|
|
@@ -12,6 +12,7 @@ import type { NcpMessagePart } from '@nextclaw/ncp';
|
|
|
12
12
|
import type { NcpDraftAttachment } from '@nextclaw/ncp-react';
|
|
13
13
|
|
|
14
14
|
const CHAT_SKILL_TOKEN_PREFIX = '$';
|
|
15
|
+
const CHAT_PANEL_APP_TOKEN_PREFIX = '@panel-app:';
|
|
15
16
|
|
|
16
17
|
function appendTextPart(parts: NcpMessagePart[], text: string): NcpMessagePart[] {
|
|
17
18
|
if (text.length === 0) {
|
|
@@ -114,6 +115,11 @@ export function deriveNcpMessagePartsFromComposer(
|
|
|
114
115
|
continue;
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
if (node.tokenKind === 'panel_app') {
|
|
119
|
+
parts = appendTextPart(parts, `${CHAT_PANEL_APP_TOKEN_PREFIX}${node.tokenKey}`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
117
123
|
if (node.tokenKind !== 'file') {
|
|
118
124
|
continue;
|
|
119
125
|
}
|
|
@@ -2,13 +2,11 @@ import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
|
|
|
2
2
|
|
|
3
3
|
export const CHAT_UI_INLINE_TOKENS_METADATA_KEY = 'ui_inline_tokens';
|
|
4
4
|
const CHAT_SKILL_TOKEN_PREFIX = '$';
|
|
5
|
+
const CHAT_PANEL_APP_TOKEN_PREFIX = '@panel-app:';
|
|
6
|
+
const CHAT_PANEL_APP_TOKEN_PATTERN = /@panel-app:([A-Za-z0-9_-]+)/g;
|
|
5
7
|
|
|
6
8
|
export type ChatInlineTokenSource = { kind: string; key: string; label: string; rawText: string };
|
|
7
9
|
|
|
8
|
-
export type ChatInlineTextFragment =
|
|
9
|
-
| { type: 'text'; text: string }
|
|
10
|
-
| { type: 'token'; token: ChatInlineTokenSource };
|
|
11
|
-
|
|
12
10
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
13
11
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
12
|
}
|
|
@@ -51,6 +49,30 @@ export function buildInlineSkillTokensFromComposer(nodes: readonly ChatComposerN
|
|
|
51
49
|
return dedupeInlineTokens(tokens);
|
|
52
50
|
}
|
|
53
51
|
|
|
52
|
+
export function buildInlineTokensFromTextProtocol(text: string): ChatInlineTokenSource[] {
|
|
53
|
+
const tokens: ChatInlineTokenSource[] = [];
|
|
54
|
+
for (const match of text.matchAll(CHAT_PANEL_APP_TOKEN_PATTERN)) {
|
|
55
|
+
const key = match[1];
|
|
56
|
+
if (!key) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
tokens.push({
|
|
60
|
+
kind: 'panel_app',
|
|
61
|
+
key,
|
|
62
|
+
label: key,
|
|
63
|
+
rawText: `${CHAT_PANEL_APP_TOKEN_PREFIX}${key}`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return dedupeInlineTokens(tokens);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function resolveInlineTokensForText(
|
|
70
|
+
text: string,
|
|
71
|
+
tokens: readonly ChatInlineTokenSource[]
|
|
72
|
+
): ChatInlineTokenSource[] {
|
|
73
|
+
return dedupeInlineTokens([...tokens, ...buildInlineTokensFromTextProtocol(text)]);
|
|
74
|
+
}
|
|
75
|
+
|
|
54
76
|
export function readInlineTokensFromMetadata(
|
|
55
77
|
metadata: Record<string, unknown> | undefined
|
|
56
78
|
): ChatInlineTokenSource[] {
|
|
@@ -80,50 +102,3 @@ export function readInlineTokensFromMetadata(
|
|
|
80
102
|
|
|
81
103
|
return dedupeInlineTokens(tokens);
|
|
82
104
|
}
|
|
83
|
-
|
|
84
|
-
export function splitTextByInlineTokens(
|
|
85
|
-
text: string,
|
|
86
|
-
tokens: readonly ChatInlineTokenSource[]
|
|
87
|
-
): ChatInlineTextFragment[] {
|
|
88
|
-
if (text.length === 0 || tokens.length === 0) {
|
|
89
|
-
return text.length === 0 ? [] : [{ type: 'text', text }];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const orderedTokens = [...tokens].sort((left, right) => right.rawText.length - left.rawText.length);
|
|
93
|
-
const fragments: ChatInlineTextFragment[] = [];
|
|
94
|
-
let cursor = 0;
|
|
95
|
-
|
|
96
|
-
while (cursor < text.length) {
|
|
97
|
-
let matchedToken: ChatInlineTokenSource | null = null;
|
|
98
|
-
for (const token of orderedTokens) {
|
|
99
|
-
if (text.startsWith(token.rawText, cursor)) {
|
|
100
|
-
matchedToken = token;
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (!matchedToken) {
|
|
106
|
-
let nextCursor = cursor + 1;
|
|
107
|
-
while (nextCursor < text.length) {
|
|
108
|
-
if (orderedTokens.some((token) => text.startsWith(token.rawText, nextCursor))) {
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
nextCursor += 1;
|
|
112
|
-
}
|
|
113
|
-
fragments.push({
|
|
114
|
-
type: 'text',
|
|
115
|
-
text: text.slice(cursor, nextCursor)
|
|
116
|
-
});
|
|
117
|
-
cursor = nextCursor;
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
fragments.push({
|
|
122
|
-
type: 'token',
|
|
123
|
-
token: matchedToken
|
|
124
|
-
});
|
|
125
|
-
cursor += matchedToken.rawText.length;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return fragments;
|
|
129
|
-
}
|
|
@@ -178,6 +178,8 @@ export function buildChatSlashItems(
|
|
|
178
178
|
`${texts.slashSkillSpecLabel}: ${record.key}`,
|
|
179
179
|
...(record.scopeLabel ? [`${texts.slashSkillScopeLabel}: ${record.scopeLabel}`] : [])
|
|
180
180
|
],
|
|
181
|
+
tokenKind: 'skill',
|
|
182
|
+
tokenKey: record.key,
|
|
181
183
|
value: record.key
|
|
182
184
|
}));
|
|
183
185
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
>;
|
|
1
|
+
type ChatInputAvailabilitySnapshot = {
|
|
2
|
+
readonly isProviderStateResolved: boolean;
|
|
3
|
+
readonly modelOptions: readonly unknown[];
|
|
4
|
+
readonly sessionTypeUnavailable?: boolean;
|
|
5
|
+
};
|
|
7
6
|
|
|
8
7
|
export function hasNcpChatModelOptions(
|
|
9
8
|
snapshot: ChatInputAvailabilitySnapshot
|
package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { render } from "@testing-library/react";
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
2
|
import type { NcpMessage } from "@nextclaw/ncp";
|
|
3
3
|
import { beforeEach, expect, it, vi } from "vitest";
|
|
4
4
|
import { ChatMessageListContainer } from "@/features/chat/features/message/components/chat-message-list.container";
|
|
@@ -129,7 +129,7 @@ it("keeps historical adapted message references stable when only the streaming m
|
|
|
129
129
|
expect(secondMessages[1]).not.toBe(firstMessages[1]);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
it("adapts persisted inline token metadata into
|
|
132
|
+
it("adapts persisted inline token metadata into markdown token data", () => {
|
|
133
133
|
const message = {
|
|
134
134
|
id: "user-inline-token",
|
|
135
135
|
sessionId: "session-1",
|
|
@@ -156,24 +156,59 @@ it("adapts persisted inline token metadata into rich message parts", () => {
|
|
|
156
156
|
expect(renderedMessages[0]).toMatchObject({
|
|
157
157
|
parts: [
|
|
158
158
|
{
|
|
159
|
-
type: "
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
type: "markdown",
|
|
160
|
+
text: "please use $weather now",
|
|
161
|
+
inlineTokens: [
|
|
162
162
|
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
label: "Weather",
|
|
168
|
-
},
|
|
163
|
+
kind: "skill",
|
|
164
|
+
key: "weather",
|
|
165
|
+
label: "Weather",
|
|
166
|
+
rawText: "$weather",
|
|
169
167
|
},
|
|
170
|
-
{ type: "markdown", text: " now" },
|
|
171
168
|
],
|
|
172
169
|
},
|
|
173
170
|
],
|
|
174
171
|
});
|
|
175
172
|
});
|
|
176
173
|
|
|
174
|
+
it("renders context inheritance as a divider without repeating inherited messages", () => {
|
|
175
|
+
const inheritedMessage = {
|
|
176
|
+
id: "child-session:inherited:1",
|
|
177
|
+
sessionId: "child-session",
|
|
178
|
+
role: "user",
|
|
179
|
+
status: "final",
|
|
180
|
+
timestamp: "2026-03-31T10:00:00.000Z",
|
|
181
|
+
metadata: {
|
|
182
|
+
inherited_from_session_id: "parent-session",
|
|
183
|
+
inherited_from_message_id: "parent-message-1",
|
|
184
|
+
},
|
|
185
|
+
parts: [{ type: "text", text: "parent context" }],
|
|
186
|
+
} satisfies NcpMessage;
|
|
187
|
+
const childMessage = {
|
|
188
|
+
id: "child-message-1",
|
|
189
|
+
sessionId: "child-session",
|
|
190
|
+
role: "user",
|
|
191
|
+
status: "final",
|
|
192
|
+
timestamp: "2026-03-31T10:00:01.000Z",
|
|
193
|
+
parts: [{ type: "text", text: "child visible request" }],
|
|
194
|
+
} satisfies NcpMessage;
|
|
195
|
+
|
|
196
|
+
const { container } = render(
|
|
197
|
+
<ChatMessageListContainer
|
|
198
|
+
messages={[inheritedMessage, childMessage]}
|
|
199
|
+
isSending={false}
|
|
200
|
+
/>,
|
|
201
|
+
);
|
|
202
|
+
const renderedMessages = captures.renders.flatMap((rendered) => rendered.messages);
|
|
203
|
+
const renderedPayload = JSON.stringify(renderedMessages);
|
|
204
|
+
|
|
205
|
+
expect(screen.getByText("chatContextInheritanceInherited")).toBeTruthy();
|
|
206
|
+
expect(container.querySelector('[title*="parent-session"]')).toBeTruthy();
|
|
207
|
+
expect(container.querySelector('[title*="chatContextInheritanceMessages: 1"]')).toBeTruthy();
|
|
208
|
+
expect(renderedPayload).toContain("child visible request");
|
|
209
|
+
expect(renderedPayload).not.toContain("parent context");
|
|
210
|
+
});
|
|
211
|
+
|
|
177
212
|
it("keeps Hermes tool invocation parts as tool cards instead of flattening them into plain text", () => {
|
|
178
213
|
const message = {
|
|
179
214
|
id: "assistant-hermes-tool-1",
|
|
@@ -77,28 +77,28 @@ export function ChatInlinePanelAppCard({
|
|
|
77
77
|
if (!url) {
|
|
78
78
|
return (
|
|
79
79
|
<div
|
|
80
|
-
className="h-[320px] min-h-[220px] w-full animate-pulse rounded-lg border border-
|
|
80
|
+
className="h-[320px] min-h-[220px] w-full animate-pulse rounded-lg border border-border bg-muted/45"
|
|
81
81
|
style={{ maxHeight: PANEL_APP_INLINE_CARD_MAX_HEIGHT_PX }}
|
|
82
82
|
/>
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
return (
|
|
87
|
-
<div className="w-full max-w-[42rem] overflow-hidden rounded-lg border border-
|
|
88
|
-
<div className="flex h-9 items-center justify-between gap-2 border-b border-
|
|
89
|
-
<div className="flex min-w-0 items-center gap-2 text-xs font-medium text-
|
|
87
|
+
<div className="w-full max-w-[42rem] overflow-hidden rounded-lg border border-border bg-card shadow-sm">
|
|
88
|
+
<div className="flex h-9 items-center justify-between gap-2 border-b border-border bg-muted/45 px-2.5">
|
|
89
|
+
<div className="flex min-w-0 items-center gap-2 text-xs font-medium text-foreground">
|
|
90
90
|
<AppWindow className="h-3.5 w-3.5 shrink-0 text-primary" />
|
|
91
91
|
<span className="truncate">{title}</span>
|
|
92
92
|
</div>
|
|
93
93
|
<IconActionButton
|
|
94
|
-
className="h-7 w-7 rounded-md text-
|
|
94
|
+
className="h-7 w-7 rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
95
95
|
icon={<Maximize2 className="h-3.5 w-3.5" />}
|
|
96
96
|
label={t('chatPanelCardExpand')}
|
|
97
97
|
onClick={openExpanded}
|
|
98
98
|
/>
|
|
99
99
|
</div>
|
|
100
100
|
<div
|
|
101
|
-
className="relative h-[420px] min-h-[220px] max-h-[min(60vh,420px)] overflow-hidden bg-
|
|
101
|
+
className="relative h-[420px] min-h-[220px] max-h-[min(60vh,420px)] overflow-hidden bg-card"
|
|
102
102
|
>
|
|
103
103
|
<iframe
|
|
104
104
|
ref={iframeRef}
|
|
@@ -107,7 +107,7 @@ export function ChatInlinePanelAppCard({
|
|
|
107
107
|
title={title}
|
|
108
108
|
sandbox={PANEL_APP_INLINE_IFRAME_SANDBOX}
|
|
109
109
|
scrolling="auto"
|
|
110
|
-
className="h-full w-full border-0 bg-
|
|
110
|
+
className="h-full w-full border-0 bg-card"
|
|
111
111
|
/>
|
|
112
112
|
</div>
|
|
113
113
|
</div>
|
|
@@ -27,11 +27,22 @@ type ChatMessageListContainerProps = {
|
|
|
27
27
|
className?: string;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
const INHERITED_FROM_SESSION_METADATA_KEY = "inherited_from_session_id";
|
|
31
|
+
|
|
30
32
|
const messageViewModelCache = new WeakMap<
|
|
31
33
|
NcpMessage,
|
|
32
34
|
{ language: string; viewModel: ChatMessageViewModel }
|
|
33
35
|
>();
|
|
34
36
|
|
|
37
|
+
type ContextInheritanceTimelineView = {
|
|
38
|
+
sourceSessionId: string;
|
|
39
|
+
inheritedMessageCount: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type ContextInheritanceTimelineBoundary = ContextInheritanceTimelineView & {
|
|
43
|
+
boundaryIndex: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
35
46
|
type ChatTimelineItem =
|
|
36
47
|
| {
|
|
37
48
|
kind: "messages";
|
|
@@ -42,6 +53,11 @@ type ChatTimelineItem =
|
|
|
42
53
|
kind: "compaction";
|
|
43
54
|
key: string;
|
|
44
55
|
checkpoint: ContextCompactionTimelineView;
|
|
56
|
+
}
|
|
57
|
+
| {
|
|
58
|
+
kind: "context-inheritance";
|
|
59
|
+
key: string;
|
|
60
|
+
inheritance: ContextInheritanceTimelineView;
|
|
45
61
|
};
|
|
46
62
|
|
|
47
63
|
function buildChatMessageAdapterTexts(
|
|
@@ -110,13 +126,13 @@ function ChatContextCompactionDivider({
|
|
|
110
126
|
`${t("chatContextCompactionProjectedTokens")}: ${checkpoint.projectedEstimatedTokens}`,
|
|
111
127
|
].join("\n");
|
|
112
128
|
return (
|
|
113
|
-
<div className="my-4 flex items-center gap-3 text-[11px] text-
|
|
114
|
-
<div className="h-px flex-1 bg-
|
|
115
|
-
<div className="inline-flex items-center gap-2 rounded-full border border-
|
|
129
|
+
<div className="my-4 flex items-center gap-3 text-[11px] text-muted-foreground" title={title}>
|
|
130
|
+
<div className="h-px flex-1 bg-border" />
|
|
131
|
+
<div className="inline-flex items-center gap-2 rounded-full border border-border bg-muted px-3 py-1">
|
|
116
132
|
{checkpoint.status === "compressing" ? (
|
|
117
|
-
<span className="h-1.5 w-1.5 rounded-full bg-
|
|
133
|
+
<span className="h-1.5 w-1.5 animate-pulse rounded-full bg-muted-foreground" />
|
|
118
134
|
) : (
|
|
119
|
-
<span className="h-1.5 w-1.5 rounded-full bg-
|
|
135
|
+
<span className="h-1.5 w-1.5 rounded-full bg-muted-foreground/45" />
|
|
120
136
|
)}
|
|
121
137
|
<span>
|
|
122
138
|
{checkpoint.status === "compressing"
|
|
@@ -124,40 +140,86 @@ function ChatContextCompactionDivider({
|
|
|
124
140
|
: t("chatContextCompactionCompressed")}
|
|
125
141
|
</span>
|
|
126
142
|
</div>
|
|
127
|
-
<div className="h-px flex-1 bg-
|
|
143
|
+
<div className="h-px flex-1 bg-border" />
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function ChatContextInheritanceDivider({
|
|
149
|
+
inheritance,
|
|
150
|
+
}: {
|
|
151
|
+
inheritance: ContextInheritanceTimelineView;
|
|
152
|
+
}) {
|
|
153
|
+
const title = [
|
|
154
|
+
`${t("chatContextInheritanceSourceSession")}: ${inheritance.sourceSessionId}`,
|
|
155
|
+
`${t("chatContextInheritanceMessages")}: ${inheritance.inheritedMessageCount}`,
|
|
156
|
+
].join("\n");
|
|
157
|
+
return (
|
|
158
|
+
<div className="my-4 flex items-center gap-3 text-[11px] text-emerald-700" title={title}>
|
|
159
|
+
<div className="h-px flex-1 bg-emerald-100" />
|
|
160
|
+
<div className="inline-flex items-center gap-2 rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1">
|
|
161
|
+
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400" />
|
|
162
|
+
<span>{t("chatContextInheritanceInherited")}</span>
|
|
163
|
+
</div>
|
|
164
|
+
<div className="h-px flex-1 bg-emerald-100" />
|
|
128
165
|
</div>
|
|
129
166
|
);
|
|
130
167
|
}
|
|
131
168
|
|
|
132
169
|
function resolveCompactionBoundaryIndex(params: {
|
|
133
170
|
rawMessages: readonly NcpMessage[];
|
|
134
|
-
|
|
171
|
+
visibleRawMessages: readonly NcpMessage[];
|
|
135
172
|
rawMessageId: string;
|
|
136
173
|
}): number {
|
|
137
174
|
const {
|
|
138
|
-
normalRawMessages,
|
|
139
175
|
rawMessageId,
|
|
140
176
|
rawMessages,
|
|
177
|
+
visibleRawMessages,
|
|
141
178
|
} = params;
|
|
142
179
|
const physicalIndex = rawMessages.findIndex(
|
|
143
180
|
(message) => message.id === rawMessageId,
|
|
144
181
|
);
|
|
145
182
|
if (physicalIndex < 0) {
|
|
146
|
-
return
|
|
183
|
+
return visibleRawMessages.length - 1;
|
|
147
184
|
}
|
|
148
|
-
const
|
|
185
|
+
const previousVisibleCount = rawMessages
|
|
149
186
|
.slice(0, physicalIndex)
|
|
150
|
-
.filter(
|
|
151
|
-
return
|
|
187
|
+
.filter(isVisibleChatMessage).length;
|
|
188
|
+
return previousVisibleCount - 1;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function readInheritedSourceSessionId(message: NcpMessage): string | null {
|
|
192
|
+
const value = message.metadata?.[INHERITED_FROM_SESSION_METADATA_KEY];
|
|
193
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isVisibleChatMessage(message: NcpMessage): boolean {
|
|
197
|
+
return !readContextCompactionTimeline(message) && !readInheritedSourceSessionId(message);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function resolveContextInheritanceBoundary(
|
|
201
|
+
messages: readonly NcpMessage[],
|
|
202
|
+
): ContextInheritanceTimelineBoundary | null {
|
|
203
|
+
const boundaryIndex = messages.findIndex((message) => readInheritedSourceSessionId(message));
|
|
204
|
+
if (boundaryIndex < 0) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const sourceSessionId = readInheritedSourceSessionId(messages[boundaryIndex]);
|
|
208
|
+
if (!sourceSessionId) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
boundaryIndex: messages.slice(0, boundaryIndex).filter(isVisibleChatMessage).length,
|
|
213
|
+
sourceSessionId,
|
|
214
|
+
inheritedMessageCount: messages.filter((message) => readInheritedSourceSessionId(message) === sourceSessionId).length,
|
|
215
|
+
};
|
|
152
216
|
}
|
|
153
217
|
|
|
154
218
|
function buildTimelineItems(params: {
|
|
155
219
|
rawMessages: readonly NcpMessage[];
|
|
156
220
|
messages: ChatMessageViewModel[];
|
|
157
221
|
}): ChatTimelineItem[] {
|
|
158
|
-
const
|
|
159
|
-
(message) => !readContextCompactionTimeline(message),
|
|
160
|
-
);
|
|
222
|
+
const visibleRawMessages = params.rawMessages.filter(isVisibleChatMessage);
|
|
161
223
|
const checkpoints = params.rawMessages
|
|
162
224
|
.map((message) => ({
|
|
163
225
|
rawMessageId: message.id,
|
|
@@ -172,11 +234,12 @@ function buildTimelineItems(params: {
|
|
|
172
234
|
checkpoint: entry.checkpoint,
|
|
173
235
|
boundaryIndex: resolveCompactionBoundaryIndex({
|
|
174
236
|
rawMessages: params.rawMessages,
|
|
175
|
-
|
|
237
|
+
visibleRawMessages,
|
|
176
238
|
rawMessageId: entry.rawMessageId,
|
|
177
239
|
}),
|
|
178
240
|
}))
|
|
179
241
|
.sort((left, right) => left.boundaryIndex - right.boundaryIndex);
|
|
242
|
+
const contextInheritance = resolveContextInheritanceBoundary(params.rawMessages);
|
|
180
243
|
|
|
181
244
|
const items: ChatTimelineItem[] = [];
|
|
182
245
|
let pendingMessages: ChatMessageViewModel[] = [];
|
|
@@ -193,7 +256,15 @@ function buildTimelineItems(params: {
|
|
|
193
256
|
pendingMessages = [];
|
|
194
257
|
};
|
|
195
258
|
|
|
196
|
-
|
|
259
|
+
visibleRawMessages.forEach((rawMessage, index) => {
|
|
260
|
+
if (contextInheritance?.boundaryIndex === index) {
|
|
261
|
+
flushPendingMessages("messages-before-context-inheritance");
|
|
262
|
+
items.push({
|
|
263
|
+
kind: "context-inheritance",
|
|
264
|
+
key: "context-inheritance",
|
|
265
|
+
inheritance: contextInheritance,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
197
268
|
const message = params.messages[index];
|
|
198
269
|
if (message) {
|
|
199
270
|
pendingMessages.push(message);
|
|
@@ -209,6 +280,14 @@ function buildTimelineItems(params: {
|
|
|
209
280
|
checkpointCursor += 1;
|
|
210
281
|
}
|
|
211
282
|
});
|
|
283
|
+
if (contextInheritance?.boundaryIndex === visibleRawMessages.length) {
|
|
284
|
+
flushPendingMessages("messages-before-context-inheritance");
|
|
285
|
+
items.push({
|
|
286
|
+
kind: "context-inheritance",
|
|
287
|
+
key: "context-inheritance",
|
|
288
|
+
inheritance: contextInheritance,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
212
291
|
while (checkpointCursor < checkpoints.length) {
|
|
213
292
|
const currentCheckpoint = checkpoints[checkpointCursor];
|
|
214
293
|
flushPendingMessages(`messages-before-${currentCheckpoint.key}`);
|
|
@@ -244,7 +323,7 @@ export function ChatMessageListContainer({
|
|
|
244
323
|
|
|
245
324
|
const messages = useMemo(() => {
|
|
246
325
|
return rawMessages.flatMap((message) => {
|
|
247
|
-
if (
|
|
326
|
+
if (!isVisibleChatMessage(message)) {
|
|
248
327
|
return [];
|
|
249
328
|
}
|
|
250
329
|
const cached = messageViewModelCache.get(message);
|
|
@@ -295,6 +374,8 @@ export function ChatMessageListContainer({
|
|
|
295
374
|
{timelineItems.map((item, index) =>
|
|
296
375
|
item.kind === "compaction" ? (
|
|
297
376
|
<ChatContextCompactionDivider key={item.key} checkpoint={item.checkpoint} />
|
|
377
|
+
) : item.kind === "context-inheritance" ? (
|
|
378
|
+
<ChatContextInheritanceDivider key={item.key} inheritance={item.inheritance} />
|
|
298
379
|
) : (
|
|
299
380
|
<ChatMessageList
|
|
300
381
|
key={item.key}
|