@nextclaw/ui 0.14.0 → 0.14.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/{api-B50yVdT8.js → api-dQ6uQSZG.js} +3 -3
  3. package/dist/assets/channels-list-page-DfpjB8XB.js +8 -0
  4. package/dist/assets/chat-page-D3QaEqjs.js +105 -0
  5. package/dist/assets/config-split-page-CeFfbaqc.js +1 -0
  6. package/dist/assets/{confirm-dialog-DRSelLdI.js → confirm-dialog-C95Y4QPC.js} +1 -1
  7. package/dist/assets/desktop-update-config-CcK6jiEb.js +1 -0
  8. package/dist/assets/{dist-D19wiQwB.js → dist-DTGzeil6.js} +1 -1
  9. package/dist/assets/{dist-NZRV-BxD.js → dist-fGogBmPA.js} +1 -1
  10. package/dist/assets/doc-browser-CuBTcBEZ.js +1 -0
  11. package/dist/assets/doc-browser-DVHhEb96.js +1 -0
  12. package/dist/assets/{doc-browser-context-C8KPbj-p.js → doc-browser-context-BfMXwnrz.js} +1 -1
  13. package/dist/assets/{ellipsis-BYy8TP0A.js → ellipsis-D2F4KMiU.js} +1 -1
  14. package/dist/assets/{external-link-528k6MDD.js → external-link-CcgtBK26.js} +1 -1
  15. package/dist/assets/index-ByJCbQqr.css +1 -0
  16. package/dist/assets/index-DjXqiloY.js +103 -0
  17. package/dist/assets/mcp-marketplace-page-98-V__3u.js +1 -0
  18. package/dist/assets/mcp-marketplace-page-BNq_PNmO.js +40 -0
  19. package/dist/assets/model-config-page-aAUkZkSX.js +1 -0
  20. package/dist/assets/plus-1Rxl5FlV.js +1 -0
  21. package/dist/assets/provider-scoped-model-input-we4_Z4gP.js +1 -0
  22. package/dist/assets/providers-config-page-CtMkPSyu.js +1 -0
  23. package/dist/assets/{react-Dsotw5vM.js → react-CTqdhFMk.js} +1 -1
  24. package/dist/assets/remote-fQ7N_r8u.js +1 -0
  25. package/dist/assets/rotate-cw-CzfIeAJj.js +1 -0
  26. package/dist/assets/runtime-config-page-TD1UrTbG.js +1 -0
  27. package/dist/assets/{save-CHDnDTok.js → save-C9fKWfVB.js} +1 -1
  28. package/dist/assets/search-D5FQ5q0X.js +1 -0
  29. package/dist/assets/search-config-page-AMEJJNIS.js +1 -0
  30. package/dist/assets/secrets-config-page-CcgJnvBO.js +3 -0
  31. package/dist/assets/select-Cu5n0E-3.js +41 -0
  32. package/dist/assets/{tag-chip-Cr8GwpRE.js → tag-chip-C5aOtjxu.js} +1 -1
  33. package/dist/assets/use-config-D8TESeo6.js +1 -0
  34. package/dist/assets/x-1qETdQ70.js +1 -0
  35. package/dist/index.html +14 -27
  36. package/package.json +9 -9
  37. package/src/app/presenters/app.presenter.ts +2 -0
  38. package/src/features/account/components/account-panel.tsx +2 -2
  39. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +12 -63
  40. package/src/features/chat/components/conversation/chat-conversation-content.tsx +11 -50
  41. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +1 -0
  42. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +15 -2
  43. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +122 -85
  44. package/src/features/chat/features/input/hooks/use-chat-input-bar-query-state.ts +120 -0
  45. package/src/features/chat/features/input/hooks/use-chat-model-favorites.ts +87 -0
  46. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +66 -41
  47. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +1 -0
  48. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +22 -36
  49. package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +80 -52
  50. package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +89 -0
  51. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +9 -1
  52. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +115 -0
  53. package/src/features/chat/features/message/components/chat-message-list.container.tsx +4 -0
  54. package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +310 -0
  55. package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +119 -1
  56. package/src/features/chat/features/message/utils/__tests__/chat-message-test.utils.ts +43 -0
  57. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +1 -346
  58. package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +51 -0
  59. package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +28 -5
  60. package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +14 -9
  61. package/src/features/chat/features/message/utils/file-operation/diff.utils.ts +39 -36
  62. package/src/features/chat/features/message/utils/file-operation/line-builder.utils.ts +46 -38
  63. package/src/features/chat/features/message/utils/file-operation/parsed-block.utils.ts +18 -15
  64. package/src/features/chat/features/ncp/hooks/__tests__/use-ui-show-content-event.test.tsx +1 -0
  65. package/src/features/chat/features/ncp/utils/ncp-chat-query-derived.utils.ts +11 -3
  66. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +27 -0
  67. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +25 -25
  68. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +19 -1
  69. package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +46 -0
  70. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +15 -2
  71. package/src/features/chat/features/session/hooks/__tests__/use-selected-session-context-window-indicator.test.tsx +63 -0
  72. package/src/features/chat/features/session/hooks/use-selected-session-context-window-indicator.ts +7 -10
  73. package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +8 -0
  74. package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +2 -0
  75. package/src/features/chat/features/session-type/components/__tests__/chat-sidebar-create-menu.test.tsx +24 -0
  76. package/src/features/chat/features/session-type/components/chat-sidebar-create-menu.tsx +10 -1
  77. package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +1 -0
  78. package/src/features/chat/features/session-type/utils/chat-session-type.utils.ts +4 -0
  79. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +193 -0
  80. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +123 -0
  81. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +104 -0
  82. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +72 -0
  83. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +54 -0
  84. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +132 -0
  85. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +82 -0
  86. package/src/features/chat/features/welcome/components/chat-welcome.tsx +121 -0
  87. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +53 -0
  88. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-selection.utils.test.ts +63 -0
  89. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-visibility.utils.test.ts +34 -0
  90. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +71 -0
  91. package/src/features/chat/features/welcome/utils/chat-welcome-selection.utils.ts +79 -0
  92. package/src/features/chat/features/welcome/utils/chat-welcome-visibility.utils.ts +17 -0
  93. package/src/features/chat/index.ts +1 -0
  94. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +93 -0
  95. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +129 -0
  96. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +34 -0
  97. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +143 -1
  98. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +30 -0
  99. package/src/features/chat/managers/__tests__/recent-selection.manager.test.ts +37 -14
  100. package/src/features/chat/managers/chat-draft-intent.manager.ts +46 -0
  101. package/src/features/chat/managers/chat-input.manager.ts +37 -15
  102. package/src/features/chat/managers/chat-run.manager.ts +3 -0
  103. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +19 -2
  104. package/src/features/chat/managers/chat-thread.manager.ts +4 -0
  105. package/src/features/chat/managers/chat-ui.manager.ts +2 -6
  106. package/src/features/chat/managers/recent-selection.manager.ts +21 -67
  107. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +47 -1
  108. package/src/features/chat/pages/ncp-chat-page.tsx +18 -0
  109. package/src/features/chat/stores/chat-input.store.ts +9 -1
  110. package/src/features/chat/stores/recent-selection.store.ts +85 -0
  111. package/src/features/chat/types/chat-input-bar.types.ts +5 -1
  112. package/src/features/marketplace/utils/marketplace-installed-cache.utils.ts +27 -23
  113. package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +137 -0
  114. package/src/features/panel-apps/components/panel-apps-list.tsx +119 -5
  115. package/src/features/panel-apps/index.ts +2 -0
  116. package/src/features/panel-apps/utils/panel-app-entry-match.utils.ts +17 -0
  117. package/src/features/settings/components/config/provider-form.tsx +284 -387
  118. package/src/features/settings/components/config/provider-status-badge.tsx +4 -4
  119. package/src/features/settings/components/config/secrets-config-form.tsx +11 -135
  120. package/src/features/settings/hooks/use-provider-auth-flow.ts +140 -0
  121. package/src/features/settings/pages/__tests__/model-config-page.test.tsx +16 -0
  122. package/src/features/settings/pages/secrets-config-page.tsx +2 -1
  123. package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +60 -1
  124. package/src/features/settings/utils/__tests__/secrets-config-form.utils.test.ts +66 -0
  125. package/src/features/settings/utils/provider-form-context.utils.ts +131 -0
  126. package/src/features/settings/utils/provider-form-model.utils.ts +101 -0
  127. package/src/features/settings/utils/provider-form-support.utils.ts +102 -1
  128. package/src/features/settings/utils/secrets-config-form.utils.ts +139 -0
  129. package/src/features/system-status/components/config/runtime-agent-list-card.tsx +17 -11
  130. package/src/features/system-status/components/config/runtime-binding-list-card.tsx +14 -9
  131. package/src/features/system-status/components/config/runtime-config-editor.tsx +8 -5
  132. package/src/features/system-status/components/config/runtime-entry-list-card.tsx +14 -9
  133. package/src/features/system-status/components/config/runtime-settings-card.tsx +16 -7
  134. package/src/features/system-status/components/runtime-security-card.tsx +79 -25
  135. package/src/shared/components/common/__tests__/searchable-model-input.test.tsx +34 -0
  136. package/src/shared/components/common/provider-scoped-model-input.tsx +1 -0
  137. package/src/shared/components/common/searchable-model-input.tsx +102 -87
  138. package/src/shared/components/ui/README.md +7 -0
  139. package/src/shared/components/ui/popover.tsx +19 -3
  140. package/src/shared/components/ui/select.tsx +14 -4
  141. package/src/shared/hooks/use-infinite-scroll-loader.ts +14 -10
  142. package/src/shared/lib/api/chat-session-type.types.ts +7 -0
  143. package/src/shared/lib/api/index.ts +2 -0
  144. package/src/shared/lib/api/ncp-session.types.ts +1 -0
  145. package/src/shared/lib/api/preferences/preference.types.ts +22 -0
  146. package/src/shared/lib/api/preferences/preference.utils.ts +40 -0
  147. package/src/shared/lib/api/types.ts +3 -0
  148. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -1
  149. package/src/shared/lib/i18n/locales/en-US/core.json +1 -0
  150. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +10 -0
  151. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -1
  152. package/src/shared/lib/i18n/locales/zh-CN/core.json +1 -0
  153. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +10 -0
  154. package/dist/assets/app-presenter-provider-BI-ewLbO.js +0 -3
  155. package/dist/assets/arrow-left-BOCJyLbQ.js +0 -1
  156. package/dist/assets/book-open-Bd2Xbrrc.js +0 -1
  157. package/dist/assets/channels-list-page-moJkpxtU.js +0 -8
  158. package/dist/assets/chat-page-CO27cl4v.js +0 -105
  159. package/dist/assets/config-split-page-CsQ2rIdq.js +0 -1
  160. package/dist/assets/desktop-update-config-qa-SynOc.js +0 -1
  161. package/dist/assets/doc-browser-2ddUJCA0.js +0 -1
  162. package/dist/assets/doc-browser-BjHUczQb.js +0 -1
  163. package/dist/assets/es2015-BUIIKCNh.js +0 -41
  164. package/dist/assets/host-capabilities-9bB_998Z.js +0 -1
  165. package/dist/assets/index-CMNrRhnG.js +0 -101
  166. package/dist/assets/index-UTmTW1eU.css +0 -1
  167. package/dist/assets/key-round-D3vD9dj7.js +0 -1
  168. package/dist/assets/loader-circle-DV9TSRns.js +0 -1
  169. package/dist/assets/mcp-marketplace-page-CS5Lh3oA.js +0 -1
  170. package/dist/assets/mcp-marketplace-page-DEOMcWb0.js +0 -40
  171. package/dist/assets/model-config-page-BSc4o7pb.js +0 -1
  172. package/dist/assets/notice-card-qqlbsOoR.js +0 -1
  173. package/dist/assets/plus-Bf9qhPDO.js +0 -1
  174. package/dist/assets/popover-BnmPOXe3.js +0 -1
  175. package/dist/assets/provider-scoped-model-input-Ca56s475.js +0 -1
  176. package/dist/assets/providers-config-page-D8Kf4U3u.js +0 -1
  177. package/dist/assets/refresh-cw-Bmx4wc9P.js +0 -1
  178. package/dist/assets/remote-DNMEy_c4.js +0 -1
  179. package/dist/assets/rotate-cw-CL0sVBel.js +0 -1
  180. package/dist/assets/runtime-config-page-CUFtKr0U.js +0 -1
  181. package/dist/assets/search-D7L-TCH-.js +0 -1
  182. package/dist/assets/search-config-page-CA3RVAef.js +0 -1
  183. package/dist/assets/secrets-config-page-95yaH1fa.js +0 -3
  184. package/dist/assets/status-dot-Dm4h-bjn.js +0 -1
  185. package/dist/assets/tabs-CJvw5Lqp.js +0 -1
  186. package/dist/assets/tooltip-Bxzi6aDL.js +0 -1
  187. package/dist/assets/trash-2-CSfX-BvU.js +0 -1
  188. package/dist/assets/use-config-TzwzTgg0.js +0 -1
  189. package/dist/assets/x-fkIWvi07.js +0 -1
  190. package/src/features/chat/components/__tests__/chat-welcome.test.tsx +0 -45
  191. package/src/features/chat/components/chat-welcome.tsx +0 -102
  192. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-bar-controller.test.tsx +0 -152
  193. package/src/features/chat/features/input/hooks/use-chat-input-bar-controller.ts +0 -142
  194. /package/dist/assets/{config-hints-Cl2XAhzV.js → config-hints-CTqBnCDp.js} +0 -0
@@ -0,0 +1,115 @@
1
+ import { useEffect, useId, useMemo, useRef } from 'react';
2
+ import type { ChatPanelAppCardViewModel } from '@nextclaw/agent-chat-ui';
3
+ import { AppWindow, Maximize2 } from 'lucide-react';
4
+ import { getPresenter } from '@/app/presenters/app.presenter';
5
+ import { findPanelAppEntryByDisplayId, usePanelApps } from '@/features/panel-apps';
6
+ import { createPanelAppRightPanelResourceTarget } from '@/features/right-panel-resources';
7
+ import {
8
+ createInlinePanelAppCardUrl,
9
+ createFallbackPanelAppContentPath,
10
+ createInlinePanelAppTab,
11
+ PANEL_APP_INLINE_CARD_MAX_HEIGHT_PX,
12
+ PANEL_APP_INLINE_IFRAME_SANDBOX,
13
+ } from '@/features/chat/features/message/utils/chat-inline-panel-app-card.utils';
14
+ import { useDocBrowser } from '@/shared/components/doc-browser';
15
+ import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
16
+ import { t } from '@/shared/lib/i18n';
17
+
18
+ export function ChatInlinePanelAppCard({
19
+ panelApp,
20
+ }: {
21
+ panelApp: ChatPanelAppCardViewModel;
22
+ }) {
23
+ const docBrowser = useDocBrowser();
24
+ const iframeRef = useRef<HTMLIFrameElement>(null);
25
+ const iframeId = useId();
26
+ const panelApps = usePanelApps();
27
+ const entry = useMemo(
28
+ () => findPanelAppEntryByDisplayId(panelApps.data?.entries ?? [], panelApp.appId),
29
+ [panelApp.appId, panelApps.data?.entries],
30
+ );
31
+ const target = entry ? createPanelAppRightPanelResourceTarget(entry) : null;
32
+ const title = panelApp.title ?? target?.title ?? panelApp.appId;
33
+ const url = panelApps.isLoading
34
+ ? ''
35
+ : target?.url ?? createFallbackPanelAppContentPath(panelApp.appId);
36
+ const cardUrl = useMemo(
37
+ () => (url ? createInlinePanelAppCardUrl(url) : ''),
38
+ [url],
39
+ );
40
+ const tab = useMemo(
41
+ () => createInlinePanelAppTab({
42
+ appId: entry?.id ?? panelApp.appId,
43
+ title,
44
+ url,
45
+ }),
46
+ [entry?.id, panelApp.appId, title, url],
47
+ );
48
+ const iframeInstanceId = `${tab.id}:${tab.navVersion}:${iframeId}`;
49
+ const openExpanded = () => {
50
+ if (target) {
51
+ docBrowser.openTarget(target);
52
+ return;
53
+ }
54
+ docBrowser.open(url, {
55
+ dedupeKey: tab.dedupeKey,
56
+ kind: tab.kind,
57
+ title,
58
+ });
59
+ };
60
+
61
+ useEffect(() => {
62
+ if (!url) {
63
+ return undefined;
64
+ }
65
+ const handleMessage = (event: MessageEvent) => {
66
+ getPresenter().panelAppBridgeManager.handleIframeMessage({
67
+ event,
68
+ iframe: iframeRef.current,
69
+ iframeInstanceId,
70
+ tab,
71
+ });
72
+ };
73
+ window.addEventListener('message', handleMessage);
74
+ return () => window.removeEventListener('message', handleMessage);
75
+ }, [iframeInstanceId, tab, url]);
76
+
77
+ if (!url) {
78
+ return (
79
+ <div
80
+ className="h-[320px] min-h-[220px] w-full animate-pulse rounded-lg border border-[#eee3d1] bg-gradient-to-b from-white to-amber-50/60"
81
+ style={{ maxHeight: PANEL_APP_INLINE_CARD_MAX_HEIGHT_PX }}
82
+ />
83
+ );
84
+ }
85
+
86
+ return (
87
+ <div className="w-full max-w-[42rem] overflow-hidden rounded-lg border border-[#eee3d1] bg-white shadow-[0_1px_2px_rgba(30,20,10,0.04)]">
88
+ <div className="flex h-9 items-center justify-between gap-2 border-b border-[#f1e7d4] bg-[#fffaf2] px-2.5">
89
+ <div className="flex min-w-0 items-center gap-2 text-xs font-medium text-[#5c4933]">
90
+ <AppWindow className="h-3.5 w-3.5 shrink-0 text-primary" />
91
+ <span className="truncate">{title}</span>
92
+ </div>
93
+ <IconActionButton
94
+ className="h-7 w-7 rounded-md text-[#7a6145] hover:bg-white hover:text-[#2f2212]"
95
+ icon={<Maximize2 className="h-3.5 w-3.5" />}
96
+ label={t('chatPanelCardExpand')}
97
+ onClick={openExpanded}
98
+ />
99
+ </div>
100
+ <div
101
+ className="relative h-[420px] min-h-[220px] max-h-[min(60vh,420px)] overflow-hidden bg-white"
102
+ >
103
+ <iframe
104
+ ref={iframeRef}
105
+ key={iframeInstanceId}
106
+ src={cardUrl}
107
+ title={title}
108
+ sandbox={PANEL_APP_INLINE_IFRAME_SANDBOX}
109
+ scrolling="auto"
110
+ className="h-full w-full border-0 bg-white"
111
+ />
112
+ </div>
113
+ </div>
114
+ );
115
+ }
@@ -17,6 +17,7 @@ import {
17
17
  type ContextCompactionTimelineView,
18
18
  } from "@/features/chat/features/session/utils/ncp-session-context-metadata.utils";
19
19
  import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
20
+ import { ChatInlinePanelAppCard } from "@/features/chat/features/message/components/chat-inline-panel-app-card";
20
21
  import { useI18n } from "@/app/components/i18n-provider";
21
22
  import { formatDateTime, t } from "@/shared/lib/i18n";
22
23
 
@@ -309,6 +310,9 @@ export function ChatMessageListContainer({
309
310
  className="h-4 w-4 shrink-0"
310
311
  />
311
312
  )}
313
+ renderPanelAppCard={(panelApp) => (
314
+ <ChatInlinePanelAppCard panelApp={panelApp} />
315
+ )}
312
316
  />
313
317
  ),
314
318
  )}
@@ -0,0 +1,310 @@
1
+ import { ToolInvocationStatus } from "@nextclaw/agent-chat";
2
+ import { adapt, type ChatMessageSource } from "./chat-message-test.utils";
3
+
4
+ it("builds edit-file previews from structured args before the tool finishes", () => {
5
+ const adapted = adapt([
6
+ {
7
+ id: "assistant-edit-preview",
8
+ role: "assistant",
9
+ parts: [
10
+ {
11
+ type: "tool-invocation",
12
+ toolInvocation: {
13
+ status: ToolInvocationStatus.CALL,
14
+ toolCallId: "edit-call-1",
15
+ toolName: "edit_file",
16
+ args: JSON.stringify({
17
+ path: "src/app.ts",
18
+ oldText: "const color = 'red';",
19
+ newText: "const color = 'blue';",
20
+ }),
21
+ parsedArgs: {
22
+ path: "src/app.ts",
23
+ oldText: "const color = 'red';",
24
+ newText: "const color = 'blue';",
25
+ },
26
+ },
27
+ },
28
+ ],
29
+ },
30
+ ] as unknown as ChatMessageSource[]);
31
+
32
+ const editLines =
33
+ adapted[0]?.parts[0]?.type === "tool-card"
34
+ ? (adapted[0].parts[0].card.fileOperation?.blocks[0]?.lines ?? [])
35
+ : [];
36
+
37
+ expect(adapted[0]?.parts[0]).toMatchObject({
38
+ type: "tool-card",
39
+ card: {
40
+ toolName: "edit_file",
41
+ summary: "src/app.ts",
42
+ statusTone: "running",
43
+ fileOperation: {
44
+ blocks: [
45
+ {
46
+ path: "src/app.ts",
47
+ lines: [
48
+ {
49
+ kind: "remove",
50
+ text: "const color = 'red';",
51
+ },
52
+ {
53
+ kind: "add",
54
+ text: "const color = 'blue';",
55
+ },
56
+ ],
57
+ },
58
+ ],
59
+ },
60
+ },
61
+ });
62
+ expect(editLines[0]).not.toHaveProperty("oldLineNumber");
63
+ expect(editLines[1]).not.toHaveProperty("newLineNumber");
64
+ });
65
+
66
+ it("uses structured edit-file result line numbers after the tool finishes", () => {
67
+ const adapted = adapt([
68
+ {
69
+ id: "assistant-edit-result",
70
+ role: "assistant",
71
+ parts: [
72
+ {
73
+ type: "tool-invocation",
74
+ toolInvocation: {
75
+ status: ToolInvocationStatus.RESULT,
76
+ toolCallId: "edit-result-1",
77
+ toolName: "edit_file",
78
+ args: JSON.stringify({
79
+ path: "src/app.ts",
80
+ oldText: "const color = 'red';",
81
+ newText: "const color = 'blue';",
82
+ }),
83
+ parsedArgs: {
84
+ path: "src/app.ts",
85
+ oldText: "const color = 'red';",
86
+ newText: "const color = 'blue';",
87
+ },
88
+ result: {
89
+ path: "src/app.ts",
90
+ oldStartLine: 27,
91
+ newStartLine: 27,
92
+ message: "Edited src/app.ts",
93
+ },
94
+ },
95
+ },
96
+ ],
97
+ },
98
+ ] as unknown as ChatMessageSource[]);
99
+
100
+ expect(adapted[0]?.parts[0]).toMatchObject({
101
+ type: "tool-card",
102
+ card: {
103
+ toolName: "edit_file",
104
+ summary: "src/app.ts",
105
+ statusTone: "success",
106
+ fileOperation: {
107
+ blocks: [
108
+ {
109
+ path: "src/app.ts",
110
+ lines: [
111
+ {
112
+ kind: "remove",
113
+ text: "const color = 'red';",
114
+ oldLineNumber: 27,
115
+ },
116
+ {
117
+ kind: "add",
118
+ text: "const color = 'blue';",
119
+ newLineNumber: 27,
120
+ },
121
+ ],
122
+ },
123
+ ],
124
+ },
125
+ },
126
+ });
127
+ });
128
+
129
+ it("builds write-file previews from partial native args before the JSON is complete", () => {
130
+ const adapted = adapt([
131
+ {
132
+ id: "assistant-write-preview",
133
+ role: "assistant",
134
+ parts: [
135
+ {
136
+ type: "tool-invocation",
137
+ toolInvocation: {
138
+ status: ToolInvocationStatus.PARTIAL_CALL,
139
+ toolCallId: "write-call-1",
140
+ toolName: "write_file",
141
+ args: '{"path":"games/snake.html","content":"<!DOCTYPE html>\\n<canvas id=\\"game\\"></canvas>\\n<script>const score = 1;',
142
+ },
143
+ },
144
+ ],
145
+ },
146
+ ] as unknown as ChatMessageSource[]);
147
+
148
+ expect(adapted[0]?.parts[0]).toMatchObject({
149
+ type: "tool-card",
150
+ card: {
151
+ toolName: "write_file",
152
+ summary: "games/snake.html",
153
+ statusTone: "running",
154
+ statusLabel: "Running",
155
+ fileOperation: {
156
+ blocks: [
157
+ {
158
+ display: "preview",
159
+ path: "games/snake.html",
160
+ lines: expect.arrayContaining([
161
+ expect.objectContaining({
162
+ kind: "add",
163
+ text: "<!DOCTYPE html>",
164
+ }),
165
+ expect.objectContaining({
166
+ kind: "add",
167
+ text: '<canvas id="game"></canvas>',
168
+ }),
169
+ ]),
170
+ },
171
+ ],
172
+ },
173
+ },
174
+ });
175
+ });
176
+
177
+ it("keeps completed write-file cards in preview mode instead of falling back to raw byte summaries", () => {
178
+ const adapted = adapt([
179
+ {
180
+ id: "assistant-write-result",
181
+ role: "assistant",
182
+ parts: [
183
+ {
184
+ type: "tool-invocation",
185
+ toolInvocation: {
186
+ status: ToolInvocationStatus.RESULT,
187
+ toolCallId: "write-result-1",
188
+ toolName: "write_file",
189
+ args: JSON.stringify({
190
+ path: "games/snake.html",
191
+ content: '<!DOCTYPE html>\n<canvas id="game"></canvas>',
192
+ }),
193
+ result: "Wrote 3906 bytes to games/snake.html",
194
+ },
195
+ },
196
+ ],
197
+ },
198
+ ] as unknown as ChatMessageSource[]);
199
+
200
+ expect(adapted[0]?.parts[0]).toMatchObject({
201
+ type: "tool-card",
202
+ card: {
203
+ toolName: "write_file",
204
+ summary: "games/snake.html",
205
+ statusTone: "success",
206
+ fileOperation: {
207
+ blocks: [
208
+ {
209
+ display: "preview",
210
+ path: "games/snake.html",
211
+ lines: [
212
+ {
213
+ kind: "add",
214
+ text: "<!DOCTYPE html>",
215
+ newLineNumber: 1,
216
+ },
217
+ {
218
+ kind: "add",
219
+ text: '<canvas id="game"></canvas>',
220
+ newLineNumber: 2,
221
+ },
222
+ ],
223
+ },
224
+ ],
225
+ },
226
+ },
227
+ });
228
+ expect(adapted[0]?.parts[0]).not.toMatchObject({
229
+ type: "tool-card",
230
+ card: {
231
+ output: "Wrote 3906 bytes to games/snake.html",
232
+ },
233
+ });
234
+ });
235
+
236
+ it("renders codex file_change results as structured diff previews", () => {
237
+ const adapted = adapt([
238
+ {
239
+ id: "assistant-file-change",
240
+ role: "assistant",
241
+ parts: [
242
+ {
243
+ type: "tool-invocation",
244
+ toolInvocation: {
245
+ status: ToolInvocationStatus.RESULT,
246
+ toolCallId: "file-change-1",
247
+ toolName: "file_change",
248
+ args: JSON.stringify({
249
+ changes: [
250
+ {
251
+ path: "src/main.ts",
252
+ diff: [
253
+ "--- a/src/main.ts",
254
+ "+++ b/src/main.ts",
255
+ "@@ -109,1 +109,1 @@",
256
+ "-console.log('old');",
257
+ "+console.log('new');",
258
+ ].join("\n"),
259
+ },
260
+ ],
261
+ }),
262
+ result: {
263
+ status: "completed",
264
+ changes: [
265
+ {
266
+ path: "src/main.ts",
267
+ diff: [
268
+ "--- a/src/main.ts",
269
+ "+++ b/src/main.ts",
270
+ "@@ -109,1 +109,1 @@",
271
+ "-console.log('old');",
272
+ "+console.log('new');",
273
+ ].join("\n"),
274
+ },
275
+ ],
276
+ },
277
+ },
278
+ },
279
+ ],
280
+ },
281
+ ] as unknown as ChatMessageSource[]);
282
+
283
+ expect(adapted[0]?.parts[0]).toMatchObject({
284
+ type: "tool-card",
285
+ card: {
286
+ toolName: "file_change",
287
+ summary: "src/main.ts",
288
+ statusTone: "success",
289
+ fileOperation: {
290
+ blocks: [
291
+ {
292
+ path: "src/main.ts",
293
+ lines: [
294
+ {
295
+ kind: "remove",
296
+ text: "console.log('old');",
297
+ oldLineNumber: 109,
298
+ },
299
+ {
300
+ kind: "add",
301
+ text: "console.log('new');",
302
+ newLineNumber: 109,
303
+ },
304
+ ],
305
+ },
306
+ ],
307
+ },
308
+ },
309
+ });
310
+ });
@@ -1,5 +1,32 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { buildShowContentToolCard } from "../chat-message-show-content-tool-card.utils";
2
+ import { buildShowContentToolCard } from "@/features/chat/features/message/utils/chat-message-show-content-tool-card.utils";
3
+ import { buildToolCard } from "@/features/chat/features/message/utils/chat-message-tool-card.utils";
4
+ import type { ChatMessageAdapterTexts } from "@/features/chat/types/chat-message.types";
5
+
6
+ const TEXTS: ChatMessageAdapterTexts = {
7
+ fileAttachmentLabel: "File",
8
+ imageAttachmentLabel: "Image",
9
+ reasoningLabel: "Reasoning",
10
+ roleLabels: {
11
+ assistant: "Assistant",
12
+ fallback: "Message",
13
+ system: "System",
14
+ tool: "Tool",
15
+ user: "User",
16
+ },
17
+ showContentActionLabel: "Show content",
18
+ toolCallLabel: "Tool call",
19
+ toolInputLabel: "Input",
20
+ toolNoOutputLabel: "No output",
21
+ toolOutputLabel: "Output",
22
+ toolResultLabel: "Tool result",
23
+ toolStatusCancelledLabel: "Cancelled",
24
+ toolStatusCompletedLabel: "Completed",
25
+ toolStatusFailedLabel: "Failed",
26
+ toolStatusPreparingLabel: "Preparing",
27
+ toolStatusRunningLabel: "Running",
28
+ unknownPartLabel: "Unknown",
29
+ };
3
30
 
4
31
  describe("buildShowContentToolCard", () => {
5
32
  it("builds a show-content action from a normalized show_content result", () => {
@@ -47,6 +74,97 @@ describe("buildShowContentToolCard", () => {
47
74
  },
48
75
  title: "Example",
49
76
  purpose: "read",
77
+ placement: "side_panel",
78
+ },
79
+ },
80
+ });
81
+ });
82
+
83
+ it("builds an inline panel app card while keeping the expand action on side panel placement", () => {
84
+ const card = buildShowContentToolCard({
85
+ invocation: {
86
+ status: "result",
87
+ toolCallId: "call-show-content",
88
+ toolName: "show_content",
89
+ result: {
90
+ ok: true,
91
+ action: "showContent",
92
+ request: {
93
+ target: {
94
+ type: "panel_app",
95
+ payload: {
96
+ appId: "reader",
97
+ },
98
+ },
99
+ title: "Reader",
100
+ purpose: "interact",
101
+ placement: "inline",
102
+ },
103
+ },
104
+ },
105
+ actionLabel: "Show content",
106
+ statusLabel: "Completed",
107
+ });
108
+
109
+ expect(card).toMatchObject({
110
+ panelApp: {
111
+ appId: "reader",
112
+ title: "Reader",
113
+ action: {
114
+ kind: "show-content",
115
+ label: "Show content",
116
+ request: {
117
+ target: {
118
+ type: "panel_app",
119
+ payload: {
120
+ appId: "reader",
121
+ },
122
+ },
123
+ title: "Reader",
124
+ purpose: "interact",
125
+ placement: "side_panel",
126
+ },
127
+ },
128
+ },
129
+ });
130
+ });
131
+
132
+ it("preserves inline panel app data when converting to the shared chat tool view model", () => {
133
+ const card = buildShowContentToolCard({
134
+ invocation: {
135
+ status: "result",
136
+ toolCallId: "call-show-content",
137
+ toolName: "show_content",
138
+ result: {
139
+ ok: true,
140
+ action: "showContent",
141
+ request: {
142
+ target: {
143
+ type: "panel_app",
144
+ payload: {
145
+ appId: "weather-card",
146
+ },
147
+ },
148
+ title: "Weather",
149
+ purpose: "interact",
150
+ placement: "inline",
151
+ },
152
+ },
153
+ },
154
+ actionLabel: "Show content",
155
+ statusLabel: "Completed",
156
+ });
157
+
158
+ expect(card).not.toBeNull();
159
+ expect(buildToolCard(card!, TEXTS)).toMatchObject({
160
+ panelApp: {
161
+ appId: "weather-card",
162
+ title: "Weather",
163
+ action: {
164
+ kind: "show-content",
165
+ request: {
166
+ placement: "side_panel",
167
+ },
50
168
  },
51
169
  },
52
170
  });
@@ -0,0 +1,43 @@
1
+ import type { UiMessage } from "@nextclaw/agent-chat";
2
+ import {
3
+ adaptChatMessages,
4
+ type ChatMessageSource,
5
+ } from "@/features/chat/features/message/utils/chat-message.utils";
6
+
7
+ export type { ChatMessageSource } from "@/features/chat/features/message/utils/chat-message.utils";
8
+
9
+ export function toSource(uiMessages: UiMessage[]): ChatMessageSource[] {
10
+ return uiMessages as unknown as ChatMessageSource[];
11
+ }
12
+
13
+ const defaultTexts = {
14
+ roleLabels: {
15
+ user: "You",
16
+ assistant: "Assistant",
17
+ tool: "Tool",
18
+ system: "System",
19
+ fallback: "Message",
20
+ },
21
+ reasoningLabel: "Reasoning",
22
+ toolCallLabel: "Tool Call",
23
+ toolResultLabel: "Tool Result",
24
+ toolInputLabel: "Input",
25
+ toolNoOutputLabel: "No output",
26
+ toolOutputLabel: "Output",
27
+ toolStatusPreparingLabel: "Preparing",
28
+ toolStatusRunningLabel: "Running",
29
+ toolStatusCompletedLabel: "Completed",
30
+ toolStatusFailedLabel: "Failed",
31
+ toolStatusCancelledLabel: "Cancelled",
32
+ imageAttachmentLabel: "Image attachment",
33
+ fileAttachmentLabel: "File attachment",
34
+ unknownPartLabel: "Unknown Part",
35
+ };
36
+
37
+ export function adapt(uiMessages: ChatMessageSource[]) {
38
+ return adaptChatMessages({
39
+ uiMessages,
40
+ formatTimestamp: (value) => `formatted:${value}`,
41
+ texts: defaultTexts,
42
+ });
43
+ }