@nextclaw/ui 0.11.13 → 0.11.14

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 (35) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/assets/{ChannelsList-BlQD1VuM.js → ChannelsList-CvK4qHfg.js} +1 -1
  3. package/dist/assets/{ChatPage-DBvm558n.js → ChatPage-Co3GqIVP.js} +15 -15
  4. package/dist/assets/{DocBrowser-DTww3NZc.js → DocBrowser-BFmW6e-4.js} +1 -1
  5. package/dist/assets/{LogoBadge-D0ogG1ut.js → LogoBadge-DZL-zQTr.js} +1 -1
  6. package/dist/assets/{MarketplacePage-DTHw6n0X.js → MarketplacePage-B__MZRrD.js} +1 -1
  7. package/dist/assets/{McpMarketplacePage-BikE0mBl.js → McpMarketplacePage-C_VKm1uq.js} +1 -1
  8. package/dist/assets/{ModelConfig-CvM__Pz1.js → ModelConfig-CqJubuwU.js} +1 -1
  9. package/dist/assets/{ProvidersList-DtZWZlL0.js → ProvidersList-BoSsFBk5.js} +1 -1
  10. package/dist/assets/{RemoteAccessPage-E5fT1pem.js → RemoteAccessPage-S1ChRWMX.js} +1 -1
  11. package/dist/assets/{RuntimeConfig-DyZNiqYT.js → RuntimeConfig-WnFUsayT.js} +1 -1
  12. package/dist/assets/{SearchConfig-C1bhOCNX.js → SearchConfig-D9V07oqj.js} +1 -1
  13. package/dist/assets/{SecretsConfig-CYmy1Sqy.js → SecretsConfig-Ci8sEzaV.js} +1 -1
  14. package/dist/assets/{SessionsConfig-DSlhPpIE.js → SessionsConfig-5Nznhx9P.js} +1 -1
  15. package/dist/assets/{chat-session-display-D9YuDGe3.js → chat-session-display-D0ZcEkUq.js} +1 -1
  16. package/dist/assets/index-BvCYcN48.js +8 -0
  17. package/dist/assets/{label-C7Xd_hqz.js → label-AurG3ZpO.js} +1 -1
  18. package/dist/assets/{page-layout-VxCaUcrD.js → page-layout-Q2hHkfJy.js} +1 -1
  19. package/dist/assets/{popover-CC4znqAM.js → popover-BKInm43u.js} +1 -1
  20. package/dist/assets/{security-config-7eVxJq8b.js → security-config-BbPGNJAB.js} +1 -1
  21. package/dist/assets/{skeleton-DhZRDdHm.js → skeleton-CuKw6-Ww.js} +1 -1
  22. package/dist/assets/{status-dot-Bi7Ze-LS.js → status-dot-DLk8UxLB.js} +1 -1
  23. package/dist/assets/{switch-COBEivEX.js → switch-BxMSKsQS.js} +1 -1
  24. package/dist/assets/{tabs-custom-B9j40wuu.js → tabs-custom-B6gK-RY6.js} +1 -1
  25. package/dist/assets/{useConfirmDialog-N8nuxOq-.js → useConfirmDialog-Dth62a0a.js} +1 -1
  26. package/dist/index.html +1 -1
  27. package/package.json +3 -3
  28. package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +39 -0
  29. package/src/components/chat/adapters/chat-input-bar.adapter.ts +4 -1
  30. package/src/components/chat/containers/chat-input-bar.container.tsx +8 -10
  31. package/src/components/chat/ncp/NcpChatPage.tsx +11 -1
  32. package/src/components/chat/ncp/ncp-chat-realtime-reload.test.ts +44 -0
  33. package/src/components/chat/ncp/ncp-chat-realtime-reload.ts +20 -0
  34. package/src/lib/i18n.ts +0 -2
  35. package/dist/assets/index-BBz4mi7g.js +0 -8
package/dist/index.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
8
  <title>NextClaw</title>
9
- <script type="module" crossorigin src="/assets/index-BBz4mi7g.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-BvCYcN48.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-MCpnpiKt.js">
11
11
  <link rel="stylesheet" crossorigin href="/assets/index-CfVmBgkf.css">
12
12
  </head>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.11.13",
3
+ "version": "0.11.14",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,10 +28,10 @@
28
28
  "tailwind-merge": "^2.5.4",
29
29
  "zod": "^3.23.8",
30
30
  "zustand": "^5.0.2",
31
- "@nextclaw/ncp": "0.4.1",
31
+ "@nextclaw/agent-chat": "0.1.4",
32
32
  "@nextclaw/agent-chat-ui": "0.2.14",
33
+ "@nextclaw/ncp": "0.4.1",
33
34
  "@nextclaw/ncp-react": "0.4.5",
34
- "@nextclaw/agent-chat": "0.1.4",
35
35
  "@nextclaw/ncp-http-agent-client": "0.3.5"
36
36
  },
37
37
  "devDependencies": {
@@ -175,4 +175,43 @@ describe('buildModelToolbarSelect', () => {
175
175
  }
176
176
  ]);
177
177
  });
178
+
179
+ it('preserves recent model order from newest to oldest', () => {
180
+ const select = buildModelToolbarSelect({
181
+ modelOptions: [
182
+ {
183
+ value: 'openai/gpt-5',
184
+ modelLabel: 'gpt-5',
185
+ providerLabel: 'OpenAI'
186
+ },
187
+ {
188
+ value: 'anthropic/claude-sonnet-4',
189
+ modelLabel: 'claude-sonnet-4',
190
+ providerLabel: 'Anthropic'
191
+ },
192
+ {
193
+ value: 'deepseek/deepseek-chat',
194
+ modelLabel: 'deepseek-chat',
195
+ providerLabel: 'DeepSeek'
196
+ }
197
+ ],
198
+ recentModelValues: ['deepseek/deepseek-chat', 'openai/gpt-5', 'anthropic/claude-sonnet-4'],
199
+ selectedModel: 'openai/gpt-5',
200
+ isModelOptionsLoading: false,
201
+ hasModelOptions: true,
202
+ onValueChange: vi.fn(),
203
+ texts: {
204
+ modelSelectPlaceholder: 'Select model',
205
+ modelNoOptionsLabel: 'No models',
206
+ recentModelsLabel: 'Recent',
207
+ allModelsLabel: 'All models'
208
+ }
209
+ });
210
+
211
+ expect(select.groups?.[0]?.options.map((option) => option.value)).toEqual([
212
+ 'deepseek/deepseek-chat',
213
+ 'openai/gpt-5',
214
+ 'anthropic/claude-sonnet-4'
215
+ ]);
216
+ });
178
217
  });
@@ -251,7 +251,10 @@ export function buildModelToolbarSelect(params: {
251
251
  const resolvedModelOption = selectedModelOption ?? fallbackModelOption;
252
252
  const resolvedValue = params.hasModelOptions ? resolvedModelOption?.value : undefined;
253
253
  const recentValueSet = new Set(params.recentModelValues ?? []);
254
- const recentOptions = params.modelOptions.filter((option) => recentValueSet.has(option.value));
254
+ const modelOptionMap = new Map(params.modelOptions.map((option) => [option.value, option] as const));
255
+ const recentOptions = (params.recentModelValues ?? [])
256
+ .map((value) => modelOptionMap.get(value))
257
+ .filter((option): option is ChatModelRecord => Boolean(option));
255
258
  const remainingOptions = params.modelOptions.filter((option) => !recentValueSet.has(option.value));
256
259
  const optionGroups =
257
260
  recentOptions.length > 0
@@ -108,14 +108,10 @@ export function ChatInputBarContainer() {
108
108
  [snapshot.skillRecords, officialSkillBadgeLabel]
109
109
  );
110
110
  const modelRecords = useMemo(() => toModelRecords(snapshot.modelOptions), [snapshot.modelOptions]);
111
- const recentModelValues = useMemo(
112
- () =>
113
- chatRecentModelsManager.resolveVisible({
114
- availableValues: modelRecords.map((option) => option.value),
115
- minAvailableCount: CHAT_RECENT_MODELS_MIN_OPTIONS
116
- }),
117
- [modelRecords, snapshot.selectedModel]
118
- );
111
+ const recentModelValues = chatRecentModelsManager.resolveVisible({
112
+ availableValues: modelRecords.map((option) => option.value),
113
+ minAvailableCount: CHAT_RECENT_MODELS_MIN_OPTIONS
114
+ });
119
115
 
120
116
  const hasModelOptions = modelRecords.length > 0;
121
117
  const isModelOptionsLoading = !snapshot.isProviderStateResolved && !hasModelOptions;
@@ -128,6 +124,8 @@ export function ChatInputBarContainer() {
128
124
  : hasModelOptions
129
125
  ? t('chatInputPlaceholder')
130
126
  : t('chatModelNoOptions');
127
+ const recentModelsLabel = language === 'zh' ? '最近选择' : 'Recent';
128
+ const allModelsLabel = language === 'zh' ? '全部模型' : 'All models';
131
129
 
132
130
  const slashItems = useMemo(
133
131
  () => buildChatSlashItems(skillRecords, slashQuery ?? '', slashTexts),
@@ -191,8 +189,8 @@ export function ChatInputBarContainer() {
191
189
  texts: {
192
190
  modelSelectPlaceholder: t('chatSelectModel'),
193
191
  modelNoOptionsLabel: t('chatModelNoOptions'),
194
- recentModelsLabel: t('chatRecentModels'),
195
- allModelsLabel: t('chatAllModels')
192
+ recentModelsLabel,
193
+ allModelsLabel
196
194
  }
197
195
  }),
198
196
  buildThinkingToolbarSelect({
@@ -23,6 +23,7 @@ import { resolveSessionTypeLabel } from '@/components/chat/useChatSessionTypeSta
23
23
  import { useConfirmDialog } from '@/hooks/useConfirmDialog';
24
24
  import { normalizeRequestedSkills } from '@/lib/chat-runtime-utils';
25
25
  import { appClient } from '@/transport';
26
+ import { resolveNcpChatRealtimeReloadAction } from '@/components/chat/ncp/ncp-chat-realtime-reload';
26
27
 
27
28
  function buildNcpSendMetadata(payload: {
28
29
  model?: string;
@@ -158,10 +159,19 @@ export function NcpChatPage({ view }: ChatPageProps) {
158
159
 
159
160
  useEffect(() => {
160
161
  const flushRealtimeReload = () => {
161
- if (agent.isHydrating || agent.isRunning || agent.isSending) {
162
+ const action = resolveNcpChatRealtimeReloadAction({
163
+ isHydrating: agent.isHydrating,
164
+ isRunning: agent.isRunning,
165
+ isSending: agent.isSending,
166
+ });
167
+ if (action === 'defer') {
162
168
  pendingRealtimeReloadRef.current = true;
163
169
  return;
164
170
  }
171
+ if (action === 'skip') {
172
+ pendingRealtimeReloadRef.current = false;
173
+ return;
174
+ }
165
175
  pendingRealtimeReloadRef.current = false;
166
176
  void agent.reloadSeed();
167
177
  };
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { resolveNcpChatRealtimeReloadAction } from '@/components/chat/ncp/ncp-chat-realtime-reload';
3
+
4
+ describe('resolveNcpChatRealtimeReloadAction', () => {
5
+ it('defers reload while the page is hydrating', () => {
6
+ expect(
7
+ resolveNcpChatRealtimeReloadAction({
8
+ isHydrating: true,
9
+ isRunning: false,
10
+ isSending: false,
11
+ }),
12
+ ).toBe('defer');
13
+ });
14
+
15
+ it('skips reload while the current session run is still active', () => {
16
+ expect(
17
+ resolveNcpChatRealtimeReloadAction({
18
+ isHydrating: false,
19
+ isRunning: true,
20
+ isSending: false,
21
+ }),
22
+ ).toBe('skip');
23
+ });
24
+
25
+ it('skips reload while the current page is still sending', () => {
26
+ expect(
27
+ resolveNcpChatRealtimeReloadAction({
28
+ isHydrating: false,
29
+ isRunning: false,
30
+ isSending: true,
31
+ }),
32
+ ).toBe('skip');
33
+ });
34
+
35
+ it('reloads immediately once the current page is idle', () => {
36
+ expect(
37
+ resolveNcpChatRealtimeReloadAction({
38
+ isHydrating: false,
39
+ isRunning: false,
40
+ isSending: false,
41
+ }),
42
+ ).toBe('reload');
43
+ });
44
+ });
@@ -0,0 +1,20 @@
1
+ export type NcpChatRealtimeReloadAction = "reload" | "defer" | "skip";
2
+
3
+ export function resolveNcpChatRealtimeReloadAction(params: {
4
+ isHydrating: boolean;
5
+ isRunning: boolean;
6
+ isSending: boolean;
7
+ }): NcpChatRealtimeReloadAction {
8
+ if (params.isHydrating) {
9
+ return "defer";
10
+ }
11
+
12
+ // While the current page owns the active run, live stream events already
13
+ // update the conversation state. Rehydrating from realtime session summaries
14
+ // here can reintroduce transient "running" state after the run has ended.
15
+ if (params.isRunning || params.isSending) {
16
+ return "skip";
17
+ }
18
+
19
+ return "reload";
20
+ }
package/src/lib/i18n.ts CHANGED
@@ -185,8 +185,6 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
185
185
  zh: 'Agent 默认模型标识,使用带 provider 前缀的格式。例如:openai/gpt-5.1、anthropic/claude-opus-4-1、deepseek/deepseek-chat、minimax/MiniMax-M2.5、openrouter/openai/gpt-5.3-codex。',
186
186
  en: 'Default model identifier used by the agent. Use provider-prefixed format. Examples: openai/gpt-5.1 · anthropic/claude-opus-4-1 · deepseek/deepseek-chat · minimax/MiniMax-M2.5 · openrouter/openai/gpt-5.3-codex.'
187
187
  },
188
- chatRecentModels: { zh: '最近选择', en: 'Recent' },
189
- chatAllModels: { zh: '全部模型', en: 'All models' },
190
188
  maxToolIterations: { zh: '最大工具迭代次数', en: 'Max Tool Iterations' },
191
189
  saveChanges: { zh: '保存变更', en: 'Save Changes' },
192
190