@nextclaw/ui 0.15.0 → 0.15.2

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 (191) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/assets/{api-DLtsiBKm.js → api-B7Qigvtm.js} +2 -2
  3. package/dist/assets/{appearance-settings-page-CtkeYXux.js → appearance-settings-page-CuE0Iiqj.js} +1 -1
  4. package/dist/assets/{book-open-BQwWBDKb.js → book-open-Ba2f7YiG.js} +1 -1
  5. package/dist/assets/channels-list-page-CTsM6TcP.js +8 -0
  6. package/dist/assets/chat-page-C_5dcuE3.js +1 -0
  7. package/dist/assets/config-split-page-Fbb4PtTf.js +1 -0
  8. package/dist/assets/{confirm-dialog-iIJFFp2T.js → confirm-dialog-D2EG3nqE.js} +1 -1
  9. package/dist/assets/desktop-update-config-BGR3HUi1.js +1 -0
  10. package/dist/assets/{dist-D2eZC3OR.js → dist-7JTjycxE.js} +1 -1
  11. package/dist/assets/{dist-CBxWOazz.js → dist-wXLhk4hl.js} +1 -1
  12. package/dist/assets/doc-browser-bHyXnxlj.js +1 -0
  13. package/dist/assets/doc-browser-context-CYAGm5d5.js +1 -0
  14. package/dist/assets/doc-browser-gTrQ3jCF.js +1 -0
  15. package/dist/assets/{es2015-DhQo_oS6.js → es2015-j1SRCIX5.js} +1 -1
  16. package/dist/assets/{external-link-CM5dnUI6.js → external-link-DxuPaDoK.js} +1 -1
  17. package/dist/assets/form-actions-BztL7P_T.js +1 -0
  18. package/dist/assets/{i18n-provider-di1waTGX.js → i18n-provider-B4oMxOFx.js} +1 -1
  19. package/dist/assets/index-CvrvblgX.css +1 -0
  20. package/dist/assets/index-Obd4JYU2.js +104 -0
  21. package/dist/assets/loader-circle-CdzgK4eH.js +1 -0
  22. package/dist/assets/{mcp-marketplace-page-C_MVQUbT.js → mcp-marketplace-page-Bt7CuOoq.js} +2 -2
  23. package/dist/assets/mcp-marketplace-page-Ck3t78_n.js +1 -0
  24. package/dist/assets/model-config-page-DeKk9gvE.js +1 -0
  25. package/dist/assets/plus-Bx7wQbhG.js +1 -0
  26. package/dist/assets/{provider-scoped-model-input-g1WkGiJ8.js → provider-scoped-model-input-DsH2LF1q.js} +1 -1
  27. package/dist/assets/providers-config-page-BFnGp68F.js +1 -0
  28. package/dist/assets/{react-DjpzoZXB.js → react-UirzUuST.js} +1 -1
  29. package/dist/assets/{refresh-cw-DaTv3zxE.js → refresh-cw-eT8BF-8B.js} +1 -1
  30. package/dist/assets/remote-CDkHZtA8.js +1 -0
  31. package/dist/assets/rotate-cw-1JqxHTuw.js +1 -0
  32. package/dist/assets/runtime-config-page-BelAqiNG.js +1 -0
  33. package/dist/assets/{save-BZof1uie.js → save-Cb2g0BOi.js} +1 -1
  34. package/dist/assets/{search-MxAarS_g.js → search-BcHjwdsb.js} +1 -1
  35. package/dist/assets/search-config-page-zPm6--Ig.js +1 -0
  36. package/dist/assets/secrets-config-page-Cxyp-KuQ.js +3 -0
  37. package/dist/assets/security-config-B_gCq9fn.js +1 -0
  38. package/dist/assets/{select-D7GcNP0z.js → select-CdSEOwVM.js} +1 -1
  39. package/dist/assets/{setting-row-CuozlnJd.js → setting-row-CTAtGexr.js} +1 -1
  40. package/dist/assets/settings-2-2DWauOjY.js +1 -0
  41. package/dist/assets/{side-dock-BcLau5S_.js → side-dock-DPvakJ0a.js} +2 -2
  42. package/dist/assets/{skeleton-shfmXuYp.js → skeleton-P5oM14E1.js} +1 -1
  43. package/dist/assets/{switch-C1BWMU5c.js → switch-BU_UEULb.js} +1 -1
  44. package/dist/assets/{tag-chip-DCUrQUCP.js → tag-chip-D-WsKoT1.js} +1 -1
  45. package/dist/assets/{tooltip-DfAHHA76.js → tooltip-CjkOaHW7.js} +1 -1
  46. package/dist/assets/{use-config-DAdbT5QJ.js → use-config--hx5oxQn.js} +1 -1
  47. package/dist/assets/{use-confirm-dialog-eWcJ6V-s.js → use-confirm-dialog-CD9a_SK1.js} +1 -1
  48. package/dist/assets/x-DT5x45JU.js +1 -0
  49. package/dist/index.html +38 -30
  50. package/index.html +12 -4
  51. package/package.json +9 -9
  52. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +29 -1
  53. package/src/app/components/layout/sidebar-items.tsx +43 -4
  54. package/src/app/components/layout/sidebar.tsx +83 -61
  55. package/src/app/configs/app-navigation.config.ts +49 -0
  56. package/src/app/styles/design-system.css +82 -0
  57. package/src/features/agents/components/__tests__/agents-page.test.tsx +135 -2
  58. package/src/features/agents/components/agent-advanced-config-fields.tsx +70 -0
  59. package/src/features/agents/components/agent-details-dialog.tsx +398 -0
  60. package/src/features/agents/components/agent-dialogs.tsx +20 -47
  61. package/src/features/agents/components/agents-page.tsx +76 -10
  62. package/src/features/agents/types/agent-form.types.ts +21 -0
  63. package/src/features/agents/utils/agent-form.utils.ts +39 -0
  64. package/src/features/channels/components/config/channel-form.tsx +14 -11
  65. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +172 -1
  66. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +11 -1
  67. package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -3
  68. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -1
  69. package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +48 -0
  70. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +55 -0
  71. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +0 -3
  72. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +3 -4
  73. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +29 -30
  74. package/src/features/chat/components/layout/chat-sidebar.tsx +21 -7
  75. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +18 -3
  76. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +2 -1
  77. package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +22 -1
  78. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +1 -1
  79. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +10 -5
  80. package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -11
  81. package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +23 -1
  82. package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-session-list-view.test.tsx +74 -0
  83. package/src/features/chat/features/ncp/hooks/use-ncp-session-list-view.ts +3 -2
  84. package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +116 -0
  85. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +108 -46
  86. package/src/features/chat/features/session/components/chat-sidebar-session-entry.tsx +6 -0
  87. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +48 -24
  88. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +0 -3
  89. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +0 -4
  90. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +294 -0
  91. package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts +15 -1
  92. package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +24 -4
  93. package/src/features/chat/features/session/utils/chat-session-display.utils.ts +3 -0
  94. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +25 -3
  95. package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +1 -0
  96. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +9 -4
  97. package/src/features/chat/features/welcome/components/chat-welcome.tsx +0 -5
  98. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +193 -56
  99. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -0
  100. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +144 -0
  101. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +99 -0
  102. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +3 -1
  103. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +67 -14
  104. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +3 -0
  105. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +44 -1
  106. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +45 -3
  107. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +46 -0
  108. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +33 -2
  109. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +47 -0
  110. package/src/features/chat/managers/chat-session-list.manager.ts +32 -0
  111. package/src/features/chat/managers/chat-thread.manager.ts +8 -2
  112. package/src/features/chat/stores/chat-session-list.store.ts +28 -5
  113. package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +5 -12
  114. package/src/features/panel-apps/components/panel-app-list-item.tsx +6 -6
  115. package/src/features/panel-apps/index.ts +1 -0
  116. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +2 -10
  117. package/src/features/panel-apps/utils/panel-app-iframe.utils.ts +6 -0
  118. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -3
  119. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +3 -1
  120. package/src/features/service-apps/components/service-apps-panel.tsx +145 -35
  121. package/src/features/settings/components/config/provider-auth-section.tsx +1 -1
  122. package/src/features/settings/components/config/provider-form.tsx +11 -8
  123. package/src/features/settings/components/config/provider-models-section.tsx +7 -7
  124. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  125. package/src/features/settings/components/config/secrets-config-form.tsx +5 -4
  126. package/src/features/settings/pages/model-config-page.tsx +26 -25
  127. package/src/features/settings/pages/providers-config-page.tsx +10 -13
  128. package/src/features/settings/pages/search-config-page.tsx +8 -5
  129. package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +97 -5
  130. package/src/features/system-status/components/config/runtime-config-editor.tsx +5 -4
  131. package/src/features/system-status/components/desktop-update-config.tsx +91 -2
  132. package/src/features/system-status/utils/update-release-notes.utils.ts +94 -0
  133. package/src/index.css +18 -0
  134. package/src/shared/components/config-split-page.tsx +6 -6
  135. package/src/shared/components/doc-browser/__tests__/doc-browser-url.utils.test.ts +73 -0
  136. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +48 -0
  137. package/src/shared/components/doc-browser/doc-browser-context.tsx +4 -0
  138. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +5 -1
  139. package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +2 -1
  140. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +10 -10
  141. package/src/shared/components/doc-browser/doc-browser.tsx +6 -4
  142. package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +62 -4
  143. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +18 -0
  144. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +28 -3
  145. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +36 -0
  146. package/src/shared/components/ui/actions/form-actions.tsx +25 -0
  147. package/src/shared/components/ui/actions/icon-action-button.tsx +25 -2
  148. package/src/shared/components/ui/card.tsx +12 -4
  149. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +2 -1
  150. package/src/shared/hooks/use-server-path-browse.ts +26 -4
  151. package/src/shared/hooks/use-server-path-read.ts +11 -1
  152. package/src/shared/lib/api/ncp-session.types.ts +1 -1
  153. package/src/shared/lib/api/types.ts +12 -3
  154. package/src/shared/lib/api/utils/server-path.utils.ts +1 -0
  155. package/src/shared/lib/i18n/locales/en-US/agents.json +41 -0
  156. package/src/shared/lib/i18n/locales/en-US/chat.json +19 -0
  157. package/src/shared/lib/i18n/locales/en-US/core.json +3 -0
  158. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +8 -0
  159. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +5 -0
  160. package/src/shared/lib/i18n/locales/zh-CN/agents.json +41 -0
  161. package/src/shared/lib/i18n/locales/zh-CN/chat.json +19 -0
  162. package/src/shared/lib/i18n/locales/zh-CN/core.json +3 -0
  163. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +8 -0
  164. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +5 -0
  165. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +9 -5
  166. package/src/shared/lib/theme/index.test.ts +21 -0
  167. package/src/shared/lib/theme/index.ts +6 -4
  168. package/src/vite-env.d.ts +2 -0
  169. package/dist/assets/channels-list-page-Cv8o9Hxg.js +0 -8
  170. package/dist/assets/chat-page-BtA7H1Ea.js +0 -1
  171. package/dist/assets/config-split-page-EidRBpP2.js +0 -1
  172. package/dist/assets/desktop-update-config-C6n_TB1r.js +0 -1
  173. package/dist/assets/doc-browser-B76XOXmW.js +0 -1
  174. package/dist/assets/doc-browser-DBnj9UMB.js +0 -1
  175. package/dist/assets/doc-browser-context-Sv-044Cl.js +0 -1
  176. package/dist/assets/ellipsis-2uzhypw5.js +0 -1
  177. package/dist/assets/index-CBRP-7-a.css +0 -1
  178. package/dist/assets/index-Z1fYpbNd.js +0 -104
  179. package/dist/assets/loader-circle-BjmN5DeJ.js +0 -1
  180. package/dist/assets/mcp-marketplace-page-CoZIiD6G.js +0 -1
  181. package/dist/assets/model-config-page-Cdr9P6TQ.js +0 -1
  182. package/dist/assets/plus-3ztRZD_l.js +0 -1
  183. package/dist/assets/providers-config-page-CWlO7J4A.js +0 -1
  184. package/dist/assets/remote-CKWZfVVB.js +0 -1
  185. package/dist/assets/rotate-cw-CHcaRUDz.js +0 -1
  186. package/dist/assets/runtime-config-page-CfTqh1nv.js +0 -1
  187. package/dist/assets/search-config-page-Dm8daYny.js +0 -1
  188. package/dist/assets/secrets-config-page-D8AbcR05.js +0 -3
  189. package/dist/assets/security-config-Ds0LsJmo.js +0 -1
  190. package/dist/assets/x-DqL1mYLC.js +0 -1
  191. /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-C_iP1zl_.js} +0 -0
@@ -0,0 +1,398 @@
1
+ import type { ReactNode } from "react";
2
+ import type { AgentProfileView, ConfigView } from "@/shared/lib/api";
3
+ import { normalizeSessionType, resolveSessionTypeLabel } from "@/features/chat";
4
+ import { AgentAvatar } from "@/shared/components/common/agent-avatar";
5
+ import { Button } from "@/shared/components/ui/button";
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ } from "@/shared/components/ui/dialog";
14
+ import { t } from "@/shared/lib/i18n";
15
+ import { cn } from "@/shared/lib/utils";
16
+ import { Bot, Brain, Gauge, Pencil, Sparkles, type LucideIcon } from "lucide-react";
17
+
18
+ type AgentDefaultsView = ConfigView["agents"]["defaults"];
19
+
20
+ type AgentDetailsDialogProps = {
21
+ agent: AgentProfileView | null;
22
+ defaults?: AgentDefaultsView;
23
+ runtimeOptions: { value: string; label: string }[];
24
+ defaultRuntime: string;
25
+ defaultRuntimeLabel: string;
26
+ onOpenChange: (open: boolean) => void;
27
+ onEdit: (agent: AgentProfileView) => void;
28
+ };
29
+
30
+ const numberFormatter = new Intl.NumberFormat();
31
+
32
+ export function AgentDetailsDialog({
33
+ agent,
34
+ defaults,
35
+ runtimeOptions,
36
+ defaultRuntime,
37
+ defaultRuntimeLabel,
38
+ onOpenChange,
39
+ onEdit,
40
+ }: AgentDetailsDialogProps) {
41
+ if (!agent) {
42
+ return null;
43
+ }
44
+
45
+ const model = resolveTextValue(agent.model, defaults?.model);
46
+ const runtime = resolveRuntimeValue({
47
+ agent,
48
+ defaults,
49
+ runtimeOptions,
50
+ defaultRuntime,
51
+ defaultRuntimeLabel,
52
+ });
53
+ const contextTokens = resolveNumberValue(
54
+ agent.contextTokens,
55
+ defaults?.contextTokens ?? 200000,
56
+ );
57
+ const reservedContextTokens = resolveNumberValue(
58
+ agent.reservedContextTokens,
59
+ defaults?.reservedContextTokens,
60
+ );
61
+ const maxToolIterations = resolveNumberValue(
62
+ agent.maxToolIterations,
63
+ defaults?.maxToolIterations ?? 1000,
64
+ );
65
+ const thinkingDefault = resolveTextValue(
66
+ agent.thinkingDefault,
67
+ defaults?.thinkingDefault ?? "off",
68
+ );
69
+ const runtimeConfig = resolveRecordValue(
70
+ agent.runtimeConfig ?? agent.engineConfig,
71
+ defaults?.engineConfig,
72
+ );
73
+ const models = resolveRecordValue(agent.models, defaults?.models);
74
+
75
+ return (
76
+ <Dialog open={agent !== null} onOpenChange={onOpenChange}>
77
+ <DialogContent className="flex max-h-[calc(100vh-2rem)] flex-col overflow-hidden border-none bg-white p-0 sm:max-h-[720px] sm:max-w-2xl">
78
+ <div className="shrink-0 px-5 pb-3 pt-5">
79
+ <DialogHeader className="text-left">
80
+ <div className="flex min-w-0 items-center gap-3">
81
+ <AgentAvatar
82
+ agentId={agent.id}
83
+ displayName={agent.displayName}
84
+ avatarUrl={agent.avatarUrl}
85
+ className="h-10 w-10 shrink-0"
86
+ />
87
+ <div className="min-w-0">
88
+ <DialogTitle className="truncate text-base">
89
+ {agent.displayName?.trim() || agent.id}
90
+ </DialogTitle>
91
+ <DialogDescription className="truncate text-xs">
92
+ @{agent.id}
93
+ </DialogDescription>
94
+ </div>
95
+ </div>
96
+ </DialogHeader>
97
+ </div>
98
+ <div className="min-h-0 flex-1 space-y-4 overflow-y-auto overscroll-contain px-5 py-3">
99
+ <DetailSection
100
+ icon={Bot}
101
+ title={t("agentsDetailsIdentitySection")}
102
+ >
103
+ <DetailItem
104
+ label={t("agentsDetailsFieldDisplayName")}
105
+ value={agent.displayName?.trim() || agent.id}
106
+ />
107
+ <DetailItem
108
+ label={t("agentsDetailsFieldId")}
109
+ value={agent.id}
110
+ mono
111
+ />
112
+ <DetailItem
113
+ label={t("agentsDetailsFieldDescription")}
114
+ value={agent.description?.trim() || t("agentsDetailsEmptyValue")}
115
+ wide
116
+ />
117
+ <DetailItem
118
+ label={t("agentsDetailsFieldWorkspace")}
119
+ value={agent.workspace ?? t("agentsDetailsUnsetValue")}
120
+ mono
121
+ wide
122
+ />
123
+ </DetailSection>
124
+
125
+ <DetailSection
126
+ icon={Sparkles}
127
+ title={t("agentsDetailsRuntimeSection")}
128
+ >
129
+ <DetailItem
130
+ label={t("agentsDetailsFieldModel")}
131
+ value={model.value}
132
+ source={model.source}
133
+ />
134
+ <DetailItem
135
+ label={t("agentsDetailsFieldRuntime")}
136
+ value={runtime.value}
137
+ source={runtime.source}
138
+ />
139
+ <DetailItem
140
+ label={t("agentsDetailsFieldRuntimeConfig")}
141
+ value={runtimeConfig.value}
142
+ source={runtimeConfig.source}
143
+ mono
144
+ prewrap
145
+ />
146
+ </DetailSection>
147
+
148
+ <DetailSection
149
+ icon={Gauge}
150
+ title={t("agentsDetailsContextSection")}
151
+ >
152
+ <DetailItem
153
+ label={t("agentsDetailsFieldContextTokens")}
154
+ value={contextTokens.value}
155
+ source={contextTokens.source}
156
+ />
157
+ <DetailItem
158
+ label={t("agentsDetailsFieldReservedContextTokens")}
159
+ value={reservedContextTokens.value}
160
+ source={reservedContextTokens.source}
161
+ />
162
+ <DetailItem
163
+ label={t("agentsDetailsFieldMaxToolIterations")}
164
+ value={maxToolIterations.value}
165
+ source={maxToolIterations.source}
166
+ />
167
+ </DetailSection>
168
+
169
+ <DetailSection
170
+ icon={Brain}
171
+ title={t("agentsDetailsBehaviorSection")}
172
+ >
173
+ <DetailItem
174
+ label={t("agentsDetailsFieldThinkingDefault")}
175
+ value={thinkingDefault.value}
176
+ source={thinkingDefault.source}
177
+ />
178
+ <DetailItem
179
+ label={t("agentsDetailsFieldModelOverrides")}
180
+ value={models.value}
181
+ source={models.source}
182
+ mono
183
+ prewrap
184
+ />
185
+ <DetailItem
186
+ label={t("agentsDetailsFieldAvatar")}
187
+ value={agent.avatar ?? t("agentsDetailsUnsetValue")}
188
+ mono
189
+ />
190
+ </DetailSection>
191
+ </div>
192
+ <DialogFooter className="shrink-0 px-5 pb-4 pt-2">
193
+ <Button
194
+ type="button"
195
+ variant="ghost"
196
+ onClick={() => onOpenChange(false)}
197
+ >
198
+ {t("cancel")}
199
+ </Button>
200
+ <Button
201
+ type="button"
202
+ variant="primary"
203
+ className="px-4"
204
+ onClick={() => onEdit(agent)}
205
+ >
206
+ <Pencil className="mr-2 h-4 w-4" />
207
+ {t("agentsDetailsEditAction")}
208
+ </Button>
209
+ </DialogFooter>
210
+ </DialogContent>
211
+ </Dialog>
212
+ );
213
+ }
214
+
215
+ type DetailSource = "default" | "override";
216
+
217
+ type ResolvedValue = {
218
+ value: string;
219
+ source: DetailSource;
220
+ };
221
+
222
+ function resolveTextValue(
223
+ agentValue: string | undefined,
224
+ defaultValue: string | undefined,
225
+ ): ResolvedValue {
226
+ const normalizedAgentValue = agentValue?.trim();
227
+ if (normalizedAgentValue) {
228
+ return { value: normalizedAgentValue, source: "override" };
229
+ }
230
+ return {
231
+ value: defaultValue?.trim() || t("agentsDetailsUnsetValue"),
232
+ source: "default",
233
+ };
234
+ }
235
+
236
+ function resolveNumberValue(
237
+ agentValue: number | undefined,
238
+ defaultValue: number | undefined,
239
+ ): ResolvedValue {
240
+ if (typeof agentValue === "number") {
241
+ return { value: numberFormatter.format(agentValue), source: "override" };
242
+ }
243
+ return {
244
+ value:
245
+ typeof defaultValue === "number"
246
+ ? numberFormatter.format(defaultValue)
247
+ : t("agentsDetailsUnsetValue"),
248
+ source: "default",
249
+ };
250
+ }
251
+
252
+ function resolveRuntimeValue(params: {
253
+ agent: AgentProfileView;
254
+ defaults?: AgentDefaultsView;
255
+ runtimeOptions: { value: string; label: string }[];
256
+ defaultRuntime: string;
257
+ defaultRuntimeLabel: string;
258
+ }): ResolvedValue {
259
+ const { agent, defaults, runtimeOptions, defaultRuntime, defaultRuntimeLabel } =
260
+ params;
261
+ const runtime = agent.runtime?.trim() || agent.engine?.trim();
262
+ if (runtime) {
263
+ return {
264
+ value: resolveRuntimeLabel(runtime, runtimeOptions, defaultRuntimeLabel),
265
+ source: "override",
266
+ };
267
+ }
268
+ const inheritedRuntime = defaults?.engine?.trim() || defaultRuntime;
269
+ return {
270
+ value: inheritedRuntime
271
+ ? resolveRuntimeLabel(inheritedRuntime, runtimeOptions, defaultRuntimeLabel)
272
+ : defaultRuntimeLabel,
273
+ source: "default",
274
+ };
275
+ }
276
+
277
+ function resolveRuntimeLabel(
278
+ value: string,
279
+ runtimeOptions: { value: string; label: string }[],
280
+ defaultRuntimeLabel: string,
281
+ ): string {
282
+ const normalized = normalizeSessionType(value);
283
+ if (!normalized) {
284
+ return defaultRuntimeLabel;
285
+ }
286
+ return (
287
+ runtimeOptions.find((option) => option.value === normalized)?.label ??
288
+ resolveSessionTypeLabel(normalized)
289
+ );
290
+ }
291
+
292
+ function resolveRecordValue(
293
+ agentValue: Record<string, unknown> | undefined,
294
+ defaultValue: Record<string, unknown> | undefined,
295
+ ): ResolvedValue {
296
+ if (isNonEmptyRecord(agentValue)) {
297
+ return { value: JSON.stringify(agentValue, null, 2), source: "override" };
298
+ }
299
+ return {
300
+ value: isNonEmptyRecord(defaultValue)
301
+ ? JSON.stringify(defaultValue, null, 2)
302
+ : t("agentsDetailsUnsetValue"),
303
+ source: "default",
304
+ };
305
+ }
306
+
307
+ function isNonEmptyRecord(value: unknown): value is Record<string, unknown> {
308
+ return Boolean(
309
+ value &&
310
+ typeof value === "object" &&
311
+ !Array.isArray(value) &&
312
+ Object.keys(value).length > 0,
313
+ );
314
+ }
315
+
316
+ function DetailSection(props: {
317
+ icon: LucideIcon;
318
+ title: string;
319
+ children: ReactNode;
320
+ }) {
321
+ const { icon: Icon, title, children } = props;
322
+
323
+ return (
324
+ <section className="space-y-2 border-t border-gray-100 pt-3 first:border-t-0 first:pt-0">
325
+ <h3 className="flex items-center gap-1.5 text-xs font-semibold leading-5 text-gray-900">
326
+ <Icon className="h-3.5 w-3.5 text-gray-400" />
327
+ {title}
328
+ </h3>
329
+ <dl className="grid min-w-0 gap-x-6 gap-y-2.5 pl-5 sm:grid-cols-2">
330
+ {children}
331
+ </dl>
332
+ </section>
333
+ );
334
+ }
335
+
336
+ function DetailItem(props: {
337
+ label: string;
338
+ value: string;
339
+ source?: DetailSource;
340
+ mono?: boolean;
341
+ prewrap?: boolean;
342
+ wide?: boolean;
343
+ }) {
344
+ const {
345
+ label,
346
+ value,
347
+ source,
348
+ mono = false,
349
+ prewrap = false,
350
+ wide = false,
351
+ } = props;
352
+
353
+ const isStructuredValue = prewrap && value.trim().startsWith("{");
354
+ const shouldUseMono = mono && (!prewrap || isStructuredValue);
355
+
356
+ return (
357
+ <div
358
+ className={cn(
359
+ "grid min-w-0 grid-cols-[10rem_minmax(0,1fr)] items-baseline gap-x-2",
360
+ (wide || isStructuredValue) && "sm:col-span-2",
361
+ )}
362
+ >
363
+ <dt className="flex min-w-0 items-baseline leading-5">
364
+ <span className="whitespace-nowrap text-xs font-medium text-gray-500">
365
+ {label}
366
+ </span>
367
+ </dt>
368
+ <dd
369
+ className={cn(
370
+ "min-w-0 break-words text-xs leading-5 text-gray-900",
371
+ isStructuredValue
372
+ ? "max-h-28 overflow-auto whitespace-pre-wrap rounded bg-gray-50/70 px-2 py-1.5 text-left"
373
+ : "",
374
+ )}
375
+ >
376
+ <span className={shouldUseMono ? "font-mono" : undefined}>
377
+ {value}
378
+ </span>
379
+ {source ? <SourceBadge source={source} /> : null}
380
+ </dd>
381
+ </div>
382
+ );
383
+ }
384
+
385
+ function SourceBadge({ source }: { source: DetailSource }) {
386
+ return (
387
+ <span
388
+ className={cn(
389
+ "ml-1 whitespace-nowrap text-xs font-normal leading-5",
390
+ source === "override" ? "text-blue-500" : "text-gray-400",
391
+ )}
392
+ >
393
+ ({source === "override"
394
+ ? t("agentsDetailsAgentOverride")
395
+ : t("agentsDetailsInheritedDefault")})
396
+ </span>
397
+ );
398
+ }
@@ -5,6 +5,15 @@ import {
5
5
  resolveSessionTypeLabel,
6
6
  type ChatSessionTypeOption,
7
7
  } from "@/features/chat";
8
+ import { AgentAdvancedConfigFields } from "@/features/agents/components/agent-advanced-config-fields";
9
+ import type {
10
+ AgentCreateFormState,
11
+ AgentEditFormState,
12
+ } from "@/features/agents/types/agent-form.types";
13
+ import {
14
+ EMPTY_AGENT_CREATE_FORM,
15
+ toAgentEditFormState,
16
+ } from "@/features/agents/utils/agent-form.utils";
8
17
  import { ProviderScopedModelInput } from "@/shared/components/common/provider-scoped-model-input";
9
18
  import { Button } from "@/shared/components/ui/button";
10
19
  import { NoticeCard } from "@/shared/components/feedback/notice-card";
@@ -29,53 +38,7 @@ import { t } from "@/shared/lib/i18n";
29
38
  import type { ProviderModelCatalogItem } from "@/shared/lib/provider-models";
30
39
  import { Pencil, Plus } from "lucide-react";
31
40
 
32
- export type AgentCreateFormState = {
33
- id: string;
34
- displayName: string;
35
- description: string;
36
- avatar: string;
37
- home: string;
38
- model: string;
39
- runtime: string;
40
- };
41
-
42
- export type AgentEditFormState = {
43
- displayName: string;
44
- description: string;
45
- avatar: string;
46
- model: string;
47
- runtime: string;
48
- };
49
-
50
- export const EMPTY_AGENT_CREATE_FORM: AgentCreateFormState = {
51
- id: "",
52
- displayName: "",
53
- description: "",
54
- avatar: "",
55
- model: "",
56
- home: "",
57
- runtime: "",
58
- };
59
-
60
- export const EMPTY_AGENT_EDIT_FORM: AgentEditFormState = {
61
- displayName: "",
62
- description: "",
63
- avatar: "",
64
- model: "",
65
- runtime: "",
66
- };
67
-
68
- export function toAgentEditFormState(
69
- agent: AgentProfileView,
70
- ): AgentEditFormState {
71
- return {
72
- displayName: agent.displayName ?? "",
73
- description: agent.description ?? "",
74
- avatar: agent.avatar ?? "",
75
- model: agent.model ?? "",
76
- runtime: agent.runtime ?? agent.engine ?? "",
77
- };
78
- }
41
+ export type { AgentCreateFormState, AgentEditFormState };
79
42
 
80
43
  function buildRuntimeSelectOptions(params: {
81
44
  runtimeOptions: ChatSessionTypeOption[];
@@ -472,6 +435,16 @@ function AgentEditDialogContent(props: {
472
435
  disabled={pending}
473
436
  />
474
437
  </div>
438
+ <AgentAdvancedConfigFields
439
+ form={form}
440
+ disabled={pending}
441
+ onChange={(patch) =>
442
+ setForm((prev) => ({
443
+ ...prev,
444
+ ...patch,
445
+ }))
446
+ }
447
+ />
475
448
  </div>
476
449
  </div>
477
450
  <DialogFooter className="shrink-0 border-t border-[#f1e7d4] px-6 py-5">
@@ -6,7 +6,8 @@ import {
6
6
  useUpdateAgent,
7
7
  } from "@/shared/hooks/use-agents";
8
8
  import { useConfig, useProviderTemplates, useProviders } from "@/shared/hooks/use-config";
9
- import type { AgentProfileView } from "@/shared/lib/api";
9
+ import type { AgentProfileView, AgentUpdateRequest } from "@/shared/lib/api";
10
+ import { AgentDetailsDialog } from "@/features/agents/components/agent-details-dialog";
10
11
  import {
11
12
  AgentEditDialog,
12
13
  type AgentEditFormState,
@@ -35,6 +36,7 @@ import { buildProviderModelCatalog } from "@/shared/lib/provider-models";
35
36
  import { cn } from "@/shared/lib/utils";
36
37
  import {
37
38
  Bot,
39
+ Eye,
38
40
  House,
39
41
  MessageCircle,
40
42
  MoreHorizontal,
@@ -44,10 +46,38 @@ import {
44
46
  Sparkles,
45
47
  Trash2,
46
48
  } from "lucide-react";
49
+ import { toast } from "sonner";
47
50
 
48
51
  const AGENT_CREATION_PROMPT =
49
52
  "请直接创建一个默认示例 Agent,不要问我问题。创建完成后,简单告诉我它能做什么。";
50
53
 
54
+ function parseOptionalIntegerField(value: string, min: number): number | null {
55
+ const trimmed = value.trim();
56
+ if (!trimmed) {
57
+ return null;
58
+ }
59
+ const parsed = Number(trimmed);
60
+ if (!Number.isFinite(parsed)) {
61
+ throw new Error(t("agentsAdvancedInvalidNumberError"));
62
+ }
63
+ return Math.max(min, Math.trunc(parsed));
64
+ }
65
+
66
+ function buildAgentUpdateRequest(
67
+ form: AgentEditFormState,
68
+ ): AgentUpdateRequest {
69
+ return {
70
+ displayName: form.displayName,
71
+ description: form.description,
72
+ avatar: form.avatar,
73
+ model: form.model,
74
+ ...(form.runtime.trim()
75
+ ? { runtime: form.runtime.trim() }
76
+ : { runtime: "" }),
77
+ contextTokens: parseOptionalIntegerField(form.contextTokens, 1000),
78
+ };
79
+ }
80
+
51
81
  function AgentsHero(props: { agentCount: number; onCreate: () => void }) {
52
82
  const { agentCount, onCreate } = props;
53
83
 
@@ -119,6 +149,7 @@ function AgentListCard(props: {
119
149
  updatePending: boolean;
120
150
  deletePending: boolean;
121
151
  onStartChat: () => void;
152
+ onView: () => void;
122
153
  onEdit: () => void;
123
154
  onDelete: () => void;
124
155
  }) {
@@ -129,6 +160,7 @@ function AgentListCard(props: {
129
160
  updatePending,
130
161
  deletePending,
131
162
  onStartChat,
163
+ onView,
132
164
  onEdit,
133
165
  onDelete,
134
166
  } = props;
@@ -194,6 +226,11 @@ function AgentListCard(props: {
194
226
  </Button>
195
227
  </PopoverTrigger>
196
228
  <PopoverContent align="end" className="w-44 p-1.5">
229
+ <AgentActionMenuItem
230
+ icon={Eye}
231
+ label={t("agentsViewDetailsAction")}
232
+ onClick={onView}
233
+ />
197
234
  <AgentActionMenuItem
198
235
  icon={Pencil}
199
236
  label={t("agentsEditAction")}
@@ -246,6 +283,9 @@ export function AgentsPage() {
246
283
  const sessionTypesQuery = useNcpChatSessionTypes();
247
284
  const updateAgent = useUpdateAgent();
248
285
  const deleteAgent = useDeleteAgent();
286
+ const [viewingAgent, setViewingAgent] = useState<AgentProfileView | null>(
287
+ null,
288
+ );
249
289
  const [editingAgent, setEditingAgent] = useState<AgentProfileView | null>(
250
290
  null,
251
291
  );
@@ -286,23 +326,31 @@ export function AgentsPage() {
286
326
  resolveSessionTypeLabel(defaultRuntime),
287
327
  [defaultRuntime, runtimeOptions],
288
328
  );
329
+ const agentDefaults = configQuery.data?.agents.defaults;
330
+
331
+ const handleStartView = (agent: AgentProfileView) => {
332
+ setViewingAgent(agent);
333
+ };
289
334
 
290
335
  const handleStartEdit = (agent: AgentProfileView) => {
291
336
  setEditingAgent(agent);
292
337
  };
293
338
 
294
339
  const handleUpdate = async (agentId: string, form: AgentEditFormState) => {
340
+ let data: AgentUpdateRequest;
341
+ try {
342
+ data = buildAgentUpdateRequest(form);
343
+ } catch (error) {
344
+ toast.error(
345
+ error instanceof Error
346
+ ? error.message
347
+ : t("agentsAdvancedParseFailed"),
348
+ );
349
+ return;
350
+ }
295
351
  await updateAgent.mutateAsync({
296
352
  agentId,
297
- data: {
298
- displayName: form.displayName,
299
- description: form.description,
300
- avatar: form.avatar,
301
- model: form.model,
302
- ...(form.runtime.trim()
303
- ? { runtime: form.runtime.trim() }
304
- : { runtime: "" }),
305
- },
353
+ data,
306
354
  });
307
355
  setEditingAgent(null);
308
356
  };
@@ -365,6 +413,7 @@ export function AgentsPage() {
365
413
  updatePending={updateAgent.isPending}
366
414
  deletePending={deleteAgent.isPending}
367
415
  onStartChat={() => startChatWithAgent(agent)}
416
+ onView={() => handleStartView(agent)}
368
417
  onEdit={() => handleStartEdit(agent)}
369
418
  onDelete={() => deleteAgent.mutate({ agentId: agent.id })}
370
419
  />
@@ -372,6 +421,23 @@ export function AgentsPage() {
372
421
  )}
373
422
  </div>
374
423
 
424
+ <AgentDetailsDialog
425
+ agent={viewingAgent}
426
+ defaults={agentDefaults}
427
+ runtimeOptions={runtimeOptions}
428
+ defaultRuntime={defaultRuntime}
429
+ defaultRuntimeLabel={defaultRuntimeLabel}
430
+ onOpenChange={(open) => {
431
+ if (!open) {
432
+ setViewingAgent(null);
433
+ }
434
+ }}
435
+ onEdit={(agent) => {
436
+ setViewingAgent(null);
437
+ setEditingAgent(agent);
438
+ }}
439
+ />
440
+
375
441
  <AgentEditDialog
376
442
  agent={editingAgent}
377
443
  pending={updateAgent.isPending}
@@ -0,0 +1,21 @@
1
+ export type AgentCreateFormState = {
2
+ id: string;
3
+ displayName: string;
4
+ description: string;
5
+ avatar: string;
6
+ home: string;
7
+ model: string;
8
+ runtime: string;
9
+ };
10
+
11
+ export type AgentAdvancedFormState = {
12
+ contextTokens: string;
13
+ };
14
+
15
+ export type AgentEditFormState = {
16
+ displayName: string;
17
+ description: string;
18
+ avatar: string;
19
+ model: string;
20
+ runtime: string;
21
+ } & AgentAdvancedFormState;
@@ -0,0 +1,39 @@
1
+ import type { AgentProfileView } from "@/shared/lib/api";
2
+ import type {
3
+ AgentCreateFormState,
4
+ AgentEditFormState,
5
+ } from "@/features/agents/types/agent-form.types";
6
+
7
+ export const EMPTY_AGENT_CREATE_FORM: AgentCreateFormState = {
8
+ id: "",
9
+ displayName: "",
10
+ description: "",
11
+ avatar: "",
12
+ model: "",
13
+ home: "",
14
+ runtime: "",
15
+ };
16
+
17
+ export const EMPTY_AGENT_EDIT_FORM: AgentEditFormState = {
18
+ displayName: "",
19
+ description: "",
20
+ avatar: "",
21
+ model: "",
22
+ runtime: "",
23
+ contextTokens: "",
24
+ };
25
+
26
+ export function toAgentEditFormState(agent: AgentProfileView): AgentEditFormState {
27
+ return {
28
+ displayName: agent.displayName ?? "",
29
+ description: agent.description ?? "",
30
+ avatar: agent.avatar ?? "",
31
+ model: agent.model ?? "",
32
+ runtime: agent.runtime ?? agent.engine ?? "",
33
+ contextTokens: formatOptionalNumberField(agent.contextTokens),
34
+ };
35
+ }
36
+
37
+ function formatOptionalNumberField(value: number | undefined): string {
38
+ return typeof value === "number" ? String(value) : "";
39
+ }