@nextclaw/ui 0.8.0 → 0.9.0

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 (68) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/assets/ChannelsList-C7F_As4r.js +1 -0
  3. package/dist/assets/ChatPage-Oo7-OUsx.js +37 -0
  4. package/dist/assets/{DocBrowser-DDX2HMXW.js → DocBrowser-Dsd8Dlq8.js} +1 -1
  5. package/dist/assets/{LogoBadge-J53F_3JA.js → LogoBadge-2ChEc_oz.js} +1 -1
  6. package/dist/assets/{MarketplacePage-0BZ4bza0.js → MarketplacePage-BXck6-X3.js} +3 -3
  7. package/dist/assets/{ModelConfig-Wzq9wGHV.js → ModelConfig-CgHRSD0b.js} +1 -1
  8. package/dist/assets/ProvidersList-PPfZucvS.js +1 -0
  9. package/dist/assets/{RuntimeConfig-N771_AM6.js → RuntimeConfig-ClLEKNTN.js} +1 -1
  10. package/dist/assets/{SearchConfig-DVt5QVa_.js → SearchConfig-CuXVCbrf.js} +1 -1
  11. package/dist/assets/{SecretsConfig-CkwauPa8.js → SecretsConfig-udJz6Ake.js} +1 -1
  12. package/dist/assets/{SessionsConfig-C3mnHzkZ.js → SessionsConfig-C1XnFfiC.js} +2 -2
  13. package/dist/assets/{chat-message-pxr79GDs.js → chat-message-BETwXLD4.js} +1 -1
  14. package/dist/assets/{index-GdpEEKnz.js → index-COJdlL0e.js} +1 -1
  15. package/dist/assets/index-CsvP4CER.js +8 -0
  16. package/dist/assets/index-D-bXl7qL.css +1 -0
  17. package/dist/assets/{label-CmksBHgc.js → label-BGL-ztxh.js} +1 -1
  18. package/dist/assets/{page-layout-Db0GbnhS.js → page-layout-aw88k7tG.js} +1 -1
  19. package/dist/assets/popover-DyEvzhmV.js +1 -0
  20. package/dist/assets/{security-config-CjLFME5Q.js → security-config-BuPAQn82.js} +1 -1
  21. package/dist/assets/skeleton-drzO_tdU.js +1 -0
  22. package/dist/assets/{switch-C24d-UJU.js → switch-BK8jIzto.js} +1 -1
  23. package/dist/assets/tabs-custom-Da3cEOji.js +1 -0
  24. package/dist/assets/{useConfirmDialog-BeP35LcG.js → useConfirmDialog-z0CE92iS.js} +1 -1
  25. package/dist/assets/{vendor-psXJBy9u.js → vendor-CkJHmX1g.js} +1 -1
  26. package/dist/index.html +3 -3
  27. package/package.json +2 -2
  28. package/src/api/config.ts +9 -38
  29. package/src/api/ncp-session.ts +50 -0
  30. package/src/api/types.ts +1 -0
  31. package/src/components/chat/ChatConversationPanel.test.tsx +65 -0
  32. package/src/components/chat/ChatConversationPanel.tsx +21 -12
  33. package/src/components/chat/ChatSidebar.test.tsx +203 -0
  34. package/src/components/chat/ChatSidebar.tsx +97 -7
  35. package/src/components/chat/adapters/chat-message.adapter.test.ts +132 -82
  36. package/src/components/chat/adapters/chat-message.adapter.ts +27 -9
  37. package/src/components/chat/chat-page-data.ts +30 -1
  38. package/src/components/chat/chat-page-runtime.test.ts +181 -0
  39. package/src/components/chat/chat-page-runtime.ts +101 -15
  40. package/src/components/chat/chat-session-preference-sync.test.ts +62 -0
  41. package/src/components/chat/chat-session-preference-sync.ts +75 -0
  42. package/src/components/chat/containers/chat-input-bar.container.tsx +0 -22
  43. package/src/components/chat/containers/chat-message-list.container.tsx +31 -27
  44. package/src/components/chat/legacy/LegacyChatPage.tsx +24 -0
  45. package/src/components/chat/managers/chat-input.manager.ts +5 -0
  46. package/src/components/chat/managers/chat-session-list.manager.test.ts +39 -0
  47. package/src/components/chat/managers/chat-session-list.manager.ts +9 -3
  48. package/src/components/chat/ncp/NcpChatPage.tsx +42 -10
  49. package/src/components/chat/ncp/ncp-chat-input.manager.ts +6 -0
  50. package/src/components/chat/ncp/ncp-chat-page-data.ts +34 -2
  51. package/src/components/chat/ncp/ncp-chat-thread.manager.ts +1 -1
  52. package/src/components/chat/ncp/ncp-session-adapter.test.ts +27 -1
  53. package/src/components/chat/ncp/ncp-session-adapter.ts +20 -0
  54. package/src/components/chat/stores/chat-thread.store.ts +2 -0
  55. package/src/components/chat/useChatSessionTypeState.test.tsx +58 -0
  56. package/src/components/chat/useChatSessionTypeState.ts +25 -8
  57. package/src/hooks/use-ncp-chat-session-types.ts +11 -0
  58. package/src/hooks/useConfig.ts +2 -4
  59. package/src/hooks/useMarketplace.ts +7 -4
  60. package/src/hooks/useWebSocket.ts +23 -2
  61. package/dist/assets/ChannelsList-DBcoVJRW.js +0 -1
  62. package/dist/assets/ChatPage-CD3cxyyM.js +0 -37
  63. package/dist/assets/ProvidersList-kwzRS8_M.js +0 -1
  64. package/dist/assets/index-BIvFMkN4.js +0 -1
  65. package/dist/assets/index-CzkY1reu.js +0 -8
  66. package/dist/assets/index-RZ0kHHRI.css +0 -1
  67. package/dist/assets/skeleton-CkpQeVWN.js +0 -1
  68. package/dist/assets/tabs-custom-D89bh-fc.js +0 -1
@@ -0,0 +1,181 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { SessionEntryView } from '@/api/types';
3
+ import { resolveRecentSessionPreferredModel, resolveSelectedModelValue } from '@/components/chat/chat-page-runtime';
4
+
5
+ const modelOptions = [
6
+ {
7
+ value: 'anthropic/claude-sonnet-4',
8
+ modelLabel: 'claude-sonnet-4',
9
+ providerLabel: 'Anthropic',
10
+ thinkingCapability: null
11
+ },
12
+ {
13
+ value: 'openai/gpt-5',
14
+ modelLabel: 'gpt-5',
15
+ providerLabel: 'OpenAI',
16
+ thinkingCapability: null
17
+ }
18
+ ];
19
+
20
+ function createSession(overrides: Partial<SessionEntryView> & Pick<SessionEntryView, 'key'>): SessionEntryView {
21
+ return {
22
+ key: overrides.key,
23
+ createdAt: overrides.createdAt ?? '2026-03-19T00:00:00.000Z',
24
+ updatedAt: overrides.updatedAt ?? '2026-03-19T00:00:00.000Z',
25
+ sessionType: overrides.sessionType ?? 'native',
26
+ sessionTypeMutable: overrides.sessionTypeMutable ?? false,
27
+ messageCount: overrides.messageCount ?? 0,
28
+ ...(overrides.label ? { label: overrides.label } : {}),
29
+ ...(overrides.preferredModel ? { preferredModel: overrides.preferredModel } : {}),
30
+ ...(overrides.lastRole ? { lastRole: overrides.lastRole } : {}),
31
+ ...(overrides.lastTimestamp ? { lastTimestamp: overrides.lastTimestamp } : {})
32
+ };
33
+ }
34
+
35
+ describe('resolveSelectedModelValue', () => {
36
+ it('keeps the current selected model when it is still available', () => {
37
+ expect(
38
+ resolveSelectedModelValue({
39
+ currentSelectedModel: 'openai/gpt-5',
40
+ modelOptions,
41
+ selectedSessionPreferredModel: 'anthropic/claude-sonnet-4',
42
+ fallbackPreferredModel: 'anthropic/claude-sonnet-4',
43
+ defaultModel: 'anthropic/claude-sonnet-4'
44
+ })
45
+ ).toBe('openai/gpt-5');
46
+ });
47
+
48
+ it('prefers the current session preferred model over runtime fallback and global default', () => {
49
+ expect(
50
+ resolveSelectedModelValue({
51
+ currentSelectedModel: 'missing/model',
52
+ modelOptions,
53
+ selectedSessionPreferredModel: 'openai/gpt-5',
54
+ fallbackPreferredModel: 'anthropic/claude-sonnet-4',
55
+ defaultModel: 'anthropic/claude-sonnet-4'
56
+ })
57
+ ).toBe('openai/gpt-5');
58
+ });
59
+
60
+ it('prefers the current session preferred model over a stale in-memory selection after switching sessions', () => {
61
+ expect(
62
+ resolveSelectedModelValue({
63
+ currentSelectedModel: 'anthropic/claude-sonnet-4',
64
+ modelOptions,
65
+ selectedSessionPreferredModel: 'openai/gpt-5',
66
+ fallbackPreferredModel: 'anthropic/claude-sonnet-4',
67
+ defaultModel: 'anthropic/claude-sonnet-4',
68
+ preferSessionPreferredModel: true
69
+ })
70
+ ).toBe('openai/gpt-5');
71
+ });
72
+
73
+ it('ignores the stale in-memory selection when a switched session has no explicit preferred model', () => {
74
+ expect(
75
+ resolveSelectedModelValue({
76
+ currentSelectedModel: 'anthropic/claude-sonnet-4',
77
+ modelOptions,
78
+ fallbackPreferredModel: 'openai/gpt-5',
79
+ defaultModel: 'anthropic/claude-sonnet-4',
80
+ preferSessionPreferredModel: true
81
+ })
82
+ ).toBe('openai/gpt-5');
83
+ });
84
+
85
+ it('uses the recent same-runtime model when the current session has no valid preferred model', () => {
86
+ expect(
87
+ resolveSelectedModelValue({
88
+ currentSelectedModel: 'missing/model',
89
+ modelOptions,
90
+ fallbackPreferredModel: 'openai/gpt-5',
91
+ defaultModel: 'anthropic/claude-sonnet-4'
92
+ })
93
+ ).toBe('openai/gpt-5');
94
+ });
95
+
96
+ it('falls back to the global default model when the recent same-runtime model is unavailable', () => {
97
+ expect(
98
+ resolveSelectedModelValue({
99
+ currentSelectedModel: 'missing/model',
100
+ modelOptions,
101
+ fallbackPreferredModel: 'missing/model',
102
+ defaultModel: 'anthropic/claude-sonnet-4'
103
+ })
104
+ ).toBe('anthropic/claude-sonnet-4');
105
+ });
106
+
107
+ it('falls back to the first available model when no candidate is valid', () => {
108
+ expect(
109
+ resolveSelectedModelValue({
110
+ currentSelectedModel: 'missing/model',
111
+ modelOptions,
112
+ selectedSessionPreferredModel: 'missing/model',
113
+ fallbackPreferredModel: 'missing/model',
114
+ defaultModel: 'missing/model'
115
+ })
116
+ ).toBe('anthropic/claude-sonnet-4');
117
+ });
118
+ });
119
+
120
+ describe('resolveRecentSessionPreferredModel', () => {
121
+ it('returns the most recent preferred model from the same runtime', () => {
122
+ const sessions = [
123
+ createSession({
124
+ key: 'native-1',
125
+ sessionType: 'native',
126
+ preferredModel: 'anthropic/claude-sonnet-4',
127
+ updatedAt: '2026-03-18T01:00:00.000Z'
128
+ }),
129
+ createSession({
130
+ key: 'codex-1',
131
+ sessionType: 'codex',
132
+ preferredModel: 'openai/gpt-5',
133
+ updatedAt: '2026-03-18T03:00:00.000Z'
134
+ }),
135
+ createSession({
136
+ key: 'codex-2',
137
+ sessionType: 'codex',
138
+ preferredModel: 'anthropic/claude-sonnet-4',
139
+ updatedAt: '2026-03-18T02:00:00.000Z'
140
+ })
141
+ ];
142
+
143
+ expect(
144
+ resolveRecentSessionPreferredModel({
145
+ sessions,
146
+ selectedSessionKey: 'draft',
147
+ sessionType: 'codex'
148
+ })
149
+ ).toBe('openai/gpt-5');
150
+ });
151
+
152
+ it('ignores the currently selected session and sessions without preferred models', () => {
153
+ const sessions = [
154
+ createSession({
155
+ key: 'codex-current',
156
+ sessionType: 'codex',
157
+ preferredModel: 'openai/gpt-5',
158
+ updatedAt: '2026-03-18T03:00:00.000Z'
159
+ }),
160
+ createSession({
161
+ key: 'codex-empty',
162
+ sessionType: 'codex',
163
+ updatedAt: '2026-03-18T04:00:00.000Z'
164
+ }),
165
+ createSession({
166
+ key: 'codex-fallback',
167
+ sessionType: 'codex',
168
+ preferredModel: 'anthropic/claude-sonnet-4',
169
+ updatedAt: '2026-03-18T02:00:00.000Z'
170
+ })
171
+ ];
172
+
173
+ expect(
174
+ resolveRecentSessionPreferredModel({
175
+ sessions,
176
+ selectedSessionKey: 'codex-current',
177
+ sessionType: 'codex'
178
+ })
179
+ ).toBe('anthropic/claude-sonnet-4');
180
+ });
181
+ });
@@ -1,39 +1,125 @@
1
1
  import { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import type { Dispatch, SetStateAction } from 'react';
3
- import type { ChatRunView } from '@/api/types';
3
+ import type { ChatRunView, SessionEntryView } from '@/api/types';
4
4
  import type { ChatModelOption } from '@/components/chat/chat-input.types';
5
5
  import { useChatRuns } from '@/hooks/useConfig';
6
6
  import { buildActiveRunBySessionKey, buildSessionRunStatusByKey } from '@/lib/session-run-status';
7
7
 
8
8
  export type ChatMainPanelView = 'chat' | 'cron' | 'skills';
9
9
 
10
+ function normalizeSessionType(value: string | null | undefined): string {
11
+ const normalized = value?.trim().toLowerCase();
12
+ return normalized || 'native';
13
+ }
14
+
15
+ function hasModelOption(modelOptions: ChatModelOption[], value: string | null | undefined): value is string {
16
+ const normalized = value?.trim();
17
+ if (!normalized) {
18
+ return false;
19
+ }
20
+ return modelOptions.some((option) => option.value === normalized);
21
+ }
22
+
23
+ export function resolveSelectedModelValue(params: {
24
+ currentSelectedModel?: string;
25
+ modelOptions: ChatModelOption[];
26
+ selectedSessionPreferredModel?: string;
27
+ fallbackPreferredModel?: string;
28
+ defaultModel?: string;
29
+ preferSessionPreferredModel?: boolean;
30
+ }): string {
31
+ const {
32
+ currentSelectedModel,
33
+ modelOptions,
34
+ selectedSessionPreferredModel,
35
+ fallbackPreferredModel,
36
+ defaultModel,
37
+ preferSessionPreferredModel = false
38
+ } = params;
39
+ if (modelOptions.length === 0) {
40
+ return '';
41
+ }
42
+ if (!preferSessionPreferredModel && hasModelOption(modelOptions, currentSelectedModel)) {
43
+ return currentSelectedModel.trim();
44
+ }
45
+ if (hasModelOption(modelOptions, selectedSessionPreferredModel)) {
46
+ return selectedSessionPreferredModel.trim();
47
+ }
48
+ if (hasModelOption(modelOptions, fallbackPreferredModel)) {
49
+ return fallbackPreferredModel.trim();
50
+ }
51
+ if (hasModelOption(modelOptions, defaultModel)) {
52
+ return defaultModel.trim();
53
+ }
54
+ return modelOptions[0]?.value ?? '';
55
+ }
56
+
57
+ export function resolveRecentSessionPreferredModel(params: {
58
+ sessions: readonly SessionEntryView[];
59
+ selectedSessionKey?: string | null;
60
+ sessionType?: string | null;
61
+ }): string | undefined {
62
+ const targetSessionType = normalizeSessionType(params.sessionType);
63
+ let bestSession: SessionEntryView | null = null;
64
+ let bestTimestamp = Number.NEGATIVE_INFINITY;
65
+ for (const session of params.sessions) {
66
+ if (session.key === params.selectedSessionKey) {
67
+ continue;
68
+ }
69
+ if (normalizeSessionType(session.sessionType) !== targetSessionType) {
70
+ continue;
71
+ }
72
+ const preferredModel = session.preferredModel?.trim();
73
+ if (!preferredModel) {
74
+ continue;
75
+ }
76
+ const updatedAtTimestamp = Date.parse(session.updatedAt);
77
+ const comparableTimestamp = Number.isFinite(updatedAtTimestamp) ? updatedAtTimestamp : Number.NEGATIVE_INFINITY;
78
+ if (!bestSession || comparableTimestamp > bestTimestamp) {
79
+ bestSession = session;
80
+ bestTimestamp = comparableTimestamp;
81
+ }
82
+ }
83
+ return bestSession?.preferredModel?.trim();
84
+ }
85
+
10
86
  export function useSyncSelectedModel(params: {
11
87
  modelOptions: ChatModelOption[];
88
+ selectedSessionKey?: string | null;
12
89
  selectedSessionPreferredModel?: string;
90
+ fallbackPreferredModel?: string;
13
91
  defaultModel?: string;
14
92
  setSelectedModel: Dispatch<SetStateAction<string>>;
15
93
  }) {
16
- const { modelOptions, selectedSessionPreferredModel, defaultModel, setSelectedModel } = params;
94
+ const {
95
+ modelOptions,
96
+ selectedSessionKey,
97
+ selectedSessionPreferredModel,
98
+ fallbackPreferredModel,
99
+ defaultModel,
100
+ setSelectedModel
101
+ } = params;
102
+ const previousSessionKeyRef = useRef<string | null | undefined>(undefined);
103
+
17
104
  useEffect(() => {
105
+ const sessionChanged = previousSessionKeyRef.current !== selectedSessionKey;
18
106
  if (modelOptions.length === 0) {
19
107
  setSelectedModel('');
108
+ previousSessionKeyRef.current = selectedSessionKey;
20
109
  return;
21
110
  }
22
111
  setSelectedModel((prev) => {
23
- if (modelOptions.some((option) => option.value === prev)) {
24
- return prev;
25
- }
26
- const sessionPreferred = selectedSessionPreferredModel?.trim();
27
- if (sessionPreferred && modelOptions.some((option) => option.value === sessionPreferred)) {
28
- return sessionPreferred;
29
- }
30
- const fallback = defaultModel?.trim();
31
- if (fallback && modelOptions.some((option) => option.value === fallback)) {
32
- return fallback;
33
- }
34
- return modelOptions[0]?.value ?? '';
112
+ return resolveSelectedModelValue({
113
+ currentSelectedModel: prev,
114
+ modelOptions,
115
+ selectedSessionPreferredModel,
116
+ fallbackPreferredModel,
117
+ defaultModel,
118
+ preferSessionPreferredModel: sessionChanged
119
+ });
35
120
  });
36
- }, [defaultModel, modelOptions, selectedSessionPreferredModel, setSelectedModel]);
121
+ previousSessionKeyRef.current = selectedSessionKey;
122
+ }, [defaultModel, fallbackPreferredModel, modelOptions, selectedSessionKey, selectedSessionPreferredModel, setSelectedModel]);
37
123
  }
38
124
 
39
125
  export function useSessionRunStatus(params: {
@@ -0,0 +1,62 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { updateSession } from '@/api/config';
3
+ import { ChatSessionPreferenceSync } from '@/components/chat/chat-session-preference-sync';
4
+ import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
5
+ import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
6
+
7
+ vi.mock('@/api/config', () => ({
8
+ updateSession: vi.fn(async () => ({
9
+ key: 'session-1',
10
+ totalMessages: 0,
11
+ totalEvents: 0,
12
+ sessionType: 'native',
13
+ sessionTypeMutable: false,
14
+ metadata: {},
15
+ messages: [],
16
+ events: []
17
+ }))
18
+ }));
19
+
20
+ describe('ChatSessionPreferenceSync', () => {
21
+ afterEach(() => {
22
+ useChatInputStore.setState((state) => ({
23
+ snapshot: {
24
+ ...state.snapshot,
25
+ selectedModel: '',
26
+ selectedThinkingLevel: null
27
+ }
28
+ }));
29
+ useChatSessionListStore.setState((state) => ({
30
+ snapshot: {
31
+ ...state.snapshot,
32
+ selectedSessionKey: null
33
+ }
34
+ }));
35
+ vi.clearAllMocks();
36
+ });
37
+
38
+ it('persists the selected model and thinking to the current session metadata', async () => {
39
+ useChatInputStore.setState((state) => ({
40
+ snapshot: {
41
+ ...state.snapshot,
42
+ selectedModel: 'openai/gpt-5',
43
+ selectedThinkingLevel: 'high'
44
+ }
45
+ }));
46
+ useChatSessionListStore.setState((state) => ({
47
+ snapshot: {
48
+ ...state.snapshot,
49
+ selectedSessionKey: 'session-1'
50
+ }
51
+ }));
52
+
53
+ const sync = new ChatSessionPreferenceSync(updateSession);
54
+ sync.syncSelectedSessionPreferences();
55
+ await vi.waitFor(() => {
56
+ expect(updateSession).toHaveBeenCalledWith('session-1', {
57
+ preferredModel: 'openai/gpt-5',
58
+ preferredThinking: 'high'
59
+ });
60
+ });
61
+ });
62
+ });
@@ -0,0 +1,75 @@
1
+ import type { SessionPatchUpdate, ThinkingLevel } from '@/api/types';
2
+ import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
3
+ import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
4
+
5
+ type QueuedSessionPreferenceSync = {
6
+ sessionKey: string;
7
+ patch: SessionPatchUpdate;
8
+ };
9
+
10
+ function normalizeOptionalModel(value: string): string | null {
11
+ const normalized = value.trim();
12
+ return normalized.length > 0 ? normalized : null;
13
+ }
14
+
15
+ function normalizeOptionalThinking(value: ThinkingLevel | null): ThinkingLevel | null {
16
+ return value ?? null;
17
+ }
18
+
19
+ export class ChatSessionPreferenceSync {
20
+ private inFlight: Promise<void> | null = null;
21
+ private queued: QueuedSessionPreferenceSync | null = null;
22
+
23
+ constructor(
24
+ private readonly updateSession: (
25
+ sessionKey: string,
26
+ patch: SessionPatchUpdate
27
+ ) => Promise<unknown>
28
+ ) {}
29
+
30
+ syncSelectedSessionPreferences = (): void => {
31
+ const inputSnapshot = useChatInputStore.getState().snapshot;
32
+ const sessionSnapshot = useChatSessionListStore.getState().snapshot;
33
+ const sessionKey = sessionSnapshot.selectedSessionKey;
34
+ if (!sessionKey) {
35
+ return;
36
+ }
37
+
38
+ this.enqueue({
39
+ sessionKey,
40
+ patch: {
41
+ preferredModel: normalizeOptionalModel(inputSnapshot.selectedModel),
42
+ preferredThinking: normalizeOptionalThinking(inputSnapshot.selectedThinkingLevel)
43
+ }
44
+ });
45
+ };
46
+
47
+ private enqueue(next: QueuedSessionPreferenceSync): void {
48
+ this.queued = next;
49
+ if (this.inFlight) {
50
+ return;
51
+ }
52
+ this.startFlush();
53
+ }
54
+
55
+ private startFlush(): void {
56
+ this.inFlight = this.flush()
57
+ .catch((error) => {
58
+ console.error(`Failed to sync chat session preferences: ${String(error)}`);
59
+ })
60
+ .finally(() => {
61
+ this.inFlight = null;
62
+ if (this.queued) {
63
+ this.startFlush();
64
+ }
65
+ });
66
+ }
67
+
68
+ private async flush(): Promise<void> {
69
+ while (this.queued) {
70
+ const current = this.queued;
71
+ this.queued = null;
72
+ await this.updateSession(current.sessionKey, current.patch);
73
+ }
74
+ }
75
+ }
@@ -5,7 +5,6 @@ import {
5
5
  buildModelStateHint,
6
6
  buildModelToolbarSelect,
7
7
  buildSelectedSkillItems,
8
- buildSessionTypeToolbarSelect,
9
8
  buildSkillPickerModel,
10
9
  buildThinkingToolbarSelect,
11
10
  resolveSlashQuery,
@@ -136,16 +135,6 @@ export function ChatInputBarContainer() {
136
135
  canStopGeneration: snapshot.canStopGeneration
137
136
  });
138
137
 
139
- const selectedSessionTypeOption =
140
- snapshot.sessionTypeOptions.find((option) => option.value === snapshot.selectedSessionType) ??
141
- (snapshot.selectedSessionType
142
- ? { value: snapshot.selectedSessionType, label: snapshot.selectedSessionType }
143
- : null);
144
- const shouldShowSessionTypeSelector =
145
- snapshot.canEditSessionType &&
146
- (snapshot.sessionTypeOptions.length > 1 ||
147
- Boolean(snapshot.selectedSessionType && snapshot.selectedSessionType !== 'native'));
148
-
149
138
  const selectedModelOption = modelRecords.find((option) => option.value === snapshot.selectedModel);
150
139
  const selectedModelThinkingCapability = selectedModelOption?.thinkingCapability;
151
140
  const thinkingSupportedLevels = selectedModelThinkingCapability?.supported ?? [];
@@ -156,17 +145,6 @@ export function ChatInputBarContainer() {
156
145
  : snapshot.stopDisabledReason?.trim() || t('chatStopUnavailable');
157
146
 
158
147
  const toolbarSelects = [
159
- buildSessionTypeToolbarSelect({
160
- selectedSessionType: snapshot.selectedSessionType,
161
- selectedSessionTypeOption,
162
- sessionTypeOptions: snapshot.sessionTypeOptions,
163
- onValueChange: presenter.chatInputManager.selectSessionType,
164
- canEditSessionType: snapshot.canEditSessionType,
165
- shouldShow: shouldShowSessionTypeSelector,
166
- texts: {
167
- sessionTypePlaceholder: t('chatSessionTypeLabel')
168
- }
169
- }),
170
148
  buildModelToolbarSelect({
171
149
  modelOptions: modelRecords,
172
150
  selectedModel: snapshot.selectedModel,
@@ -1,9 +1,12 @@
1
- import { useMemo } from 'react';
2
- import { type UiMessage } from '@nextclaw/agent-chat';
3
- import { ChatMessageList } from '@nextclaw/agent-chat-ui';
4
- import { adaptChatMessages, type ChatMessageSource } from '@/components/chat/adapters/chat-message.adapter';
5
- import { useI18n } from '@/components/providers/I18nProvider';
6
- import { formatDateTime, t } from '@/lib/i18n';
1
+ import { useMemo } from "react";
2
+ import { type UiMessage } from "@nextclaw/agent-chat";
3
+ import { ChatMessageList } from "@nextclaw/agent-chat-ui";
4
+ import {
5
+ adaptChatMessages,
6
+ type ChatMessageSource,
7
+ } from "@/components/chat/adapters/chat-message.adapter";
8
+ import { useI18n } from "@/components/providers/I18nProvider";
9
+ import { formatDateTime, t } from "@/lib/i18n";
7
10
 
8
11
  type ChatMessageListContainerProps = {
9
12
  uiMessages: UiMessage[];
@@ -20,11 +23,11 @@ export function ChatMessageListContainer(props: ChatMessageListContainerProps) {
20
23
  role: message.role,
21
24
  meta: {
22
25
  timestamp: message.meta?.timestamp,
23
- status: message.meta?.status
26
+ status: message.meta?.status,
24
27
  },
25
- parts: message.parts as unknown as ChatMessageSource['parts']
28
+ parts: message.parts as unknown as ChatMessageSource["parts"],
26
29
  })),
27
- [props.uiMessages]
30
+ [props.uiMessages],
28
31
  );
29
32
 
30
33
  const messages = useMemo(
@@ -34,21 +37,21 @@ export function ChatMessageListContainer(props: ChatMessageListContainerProps) {
34
37
  formatTimestamp: (value) => formatDateTime(value, language),
35
38
  texts: {
36
39
  roleLabels: {
37
- user: t('chatRoleUser'),
38
- assistant: t('chatRoleAssistant'),
39
- tool: t('chatRoleTool'),
40
- system: t('chatRoleSystem'),
41
- fallback: t('chatRoleMessage')
40
+ user: t("chatRoleUser"),
41
+ assistant: t("chatRoleAssistant"),
42
+ tool: t("chatRoleTool"),
43
+ system: t("chatRoleSystem"),
44
+ fallback: t("chatRoleMessage"),
42
45
  },
43
- reasoningLabel: t('chatReasoning'),
44
- toolCallLabel: t('chatToolCall'),
45
- toolResultLabel: t('chatToolResult'),
46
- toolNoOutputLabel: t('chatToolNoOutput'),
47
- toolOutputLabel: t('chatToolOutput'),
48
- unknownPartLabel: t('chatUnknownPart')
49
- }
46
+ reasoningLabel: t("chatReasoning"),
47
+ toolCallLabel: t("chatToolCall"),
48
+ toolResultLabel: t("chatToolResult"),
49
+ toolNoOutputLabel: t("chatToolNoOutput"),
50
+ toolOutputLabel: t("chatToolOutput"),
51
+ unknownPartLabel: t("chatUnknownPart"),
52
+ },
50
53
  }),
51
- [language, sourceMessages]
54
+ [language, sourceMessages],
52
55
  );
53
56
 
54
57
  return (
@@ -57,14 +60,15 @@ export function ChatMessageListContainer(props: ChatMessageListContainerProps) {
57
60
  isSending={props.isSending}
58
61
  hasAssistantDraft={props.uiMessages.some(
59
62
  (message) =>
60
- message.role === 'assistant' &&
61
- (message.meta?.status === 'streaming' || message.meta?.status === 'pending')
63
+ message.role === "assistant" &&
64
+ (message.meta?.status === "streaming" ||
65
+ message.meta?.status === "pending"),
62
66
  )}
63
67
  className={props.className}
64
68
  texts={{
65
- copyCodeLabel: t('chatCodeCopy'),
66
- copiedCodeLabel: t('chatCodeCopied'),
67
- typingLabel: t('chatTyping')
69
+ copyCodeLabel: t("chatCodeCopy"),
70
+ copiedCodeLabel: t("chatCodeCopied"),
71
+ typingLabel: t("chatTyping"),
68
72
  }}
69
73
  />
70
74
  );