@nextclaw/ui 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/assets/ChannelsList-ZBPiF0y2.js +1 -0
- package/dist/assets/ChatPage-BOgoolWK.js +38 -0
- package/dist/assets/{DocBrowser-LpzGe8An.js → DocBrowser-BUYNHg0Y.js} +1 -1
- package/dist/assets/LogoBadge-DXPq99LJ.js +1 -0
- package/dist/assets/MarketplacePage-Dx7nexYN.js +49 -0
- package/dist/assets/McpMarketplacePage-064wdotP.js +40 -0
- package/dist/assets/{ModelConfig-DuImUHIX.js → ModelConfig-BDIfLesG.js} +1 -1
- package/dist/assets/ProvidersList-DrlIr46m.js +1 -0
- package/dist/assets/RemoteAccessPage-ZkUBA-Av.js +1 -0
- package/dist/assets/{RuntimeConfig-C6iqpJR_.js → RuntimeConfig-BPxXEGzM.js} +1 -1
- package/dist/assets/{SearchConfig-Dvp1TAXu.js → SearchConfig-BIqnlpne.js} +1 -1
- package/dist/assets/{SecretsConfig-D5Ymlvt9.js → SecretsConfig-jKZEVF2q.js} +2 -2
- package/dist/assets/{SessionsConfig-CIA_jA1P.js → SessionsConfig-C_FXgVe1.js} +2 -2
- package/dist/assets/{chat-message-B60Fh9kI.js → chat-message-DmzpZJc_.js} +1 -1
- package/dist/assets/index-Byfw276e.js +8 -0
- package/dist/assets/{index-CPDASUXh.js → index-Ct7FQpxN.js} +1 -1
- package/dist/assets/index-bhNuQis7.css +1 -0
- package/dist/assets/{label-D4fGx6Wb.js → label-B1MloEtn.js} +1 -1
- package/dist/assets/marketplace-localization-Dk31LJJJ.js +1 -0
- package/dist/assets/{page-layout-twy8gmBE.js → page-layout-BGg1EhM5.js} +1 -1
- package/dist/assets/{popover-DYbYpt1j.js → popover-jJMv74Fp.js} +1 -1
- package/dist/assets/{security-config-BcIZ4rpb.js → security-config-Boh9NIMz.js} +1 -1
- package/dist/assets/skeleton-CmATs_b3.js +1 -0
- package/dist/assets/status-dot-DNyCdxPZ.js +1 -0
- package/dist/assets/{switch-DqA6r5XR.js → switch-DE_MYk7x.js} +1 -1
- package/dist/assets/{tabs-custom-C6enKKs1.js → tabs-custom-B-zErYPr.js} +1 -1
- package/dist/assets/{useConfirmDialog-CHBf5Of7.js → useConfirmDialog-BqQ6QfhB.js} +2 -2
- package/dist/assets/{vendor-DKBNiC31.js → vendor-CwsIoNvJ.js} +128 -93
- package/dist/index.html +3 -3
- package/package.json +4 -4
- package/src/App.tsx +4 -0
- package/src/api/auth.types.ts +24 -0
- package/src/api/chat-session-type.types.ts +21 -0
- package/src/api/marketplace.ts +8 -2
- package/src/api/mcp-marketplace.ts +138 -0
- package/src/api/remote.ts +57 -0
- package/src/api/remote.types.ts +80 -0
- package/src/api/types.ts +91 -37
- package/src/components/chat/ChatSidebar.test.tsx +31 -2
- package/src/components/chat/ChatSidebar.tsx +26 -2
- package/src/components/chat/chat-page-data.ts +37 -53
- package/src/components/chat/chat-page-runtime.test.ts +122 -2
- package/src/components/chat/chat-page-runtime.ts +1 -118
- package/src/components/chat/chat-session-preference-governance.ts +303 -0
- package/src/components/chat/legacy/LegacyChatPage.tsx +4 -34
- package/src/components/chat/ncp/NcpChatPage.tsx +4 -34
- package/src/components/chat/ncp/ncp-chat-page-data.test.ts +36 -0
- package/src/components/chat/ncp/ncp-chat-page-data.ts +63 -36
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +2 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +2 -0
- package/src/components/chat/stores/chat-input.store.ts +14 -1
- package/src/components/chat/useChatSessionTypeState.test.tsx +29 -0
- package/src/components/chat/useChatSessionTypeState.ts +55 -12
- package/src/components/layout/Sidebar.tsx +11 -1
- package/src/components/marketplace/MarketplacePage.test.tsx +152 -0
- package/src/components/marketplace/MarketplacePage.tsx +52 -199
- package/src/components/marketplace/marketplace-installed-cache.test.ts +110 -0
- package/src/components/marketplace/marketplace-installed-cache.ts +149 -0
- package/src/components/marketplace/marketplace-localization.ts +77 -0
- package/src/components/marketplace/marketplace-page-parts.tsx +102 -0
- package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +208 -0
- package/src/components/marketplace/mcp/McpMarketplacePage.tsx +578 -0
- package/src/components/remote/RemoteAccessPage.tsx +320 -0
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/label.tsx +1 -1
- package/src/hooks/useMarketplace.ts +36 -7
- package/src/hooks/useMcpMarketplace.ts +99 -0
- package/src/hooks/useRemoteAccess.ts +92 -0
- package/src/hooks/useWebSocket.ts +25 -16
- package/src/lib/i18n.marketplace.ts +91 -0
- package/src/lib/i18n.remote.ts +115 -0
- package/src/lib/i18n.ts +10 -68
- package/dist/assets/ChannelsList-DhvjpZcs.js +0 -1
- package/dist/assets/ChatPage-B8VBaMQm.js +0 -38
- package/dist/assets/LogoBadge-Be4lktJN.js +0 -1
- package/dist/assets/MarketplacePage-Cx9AI3_h.js +0 -49
- package/dist/assets/ProvidersList-Ccleg25k.js +0 -1
- package/dist/assets/index-BiPDnzv0.js +0 -8
- package/dist/assets/index-C8GsgIUn.css +0 -1
- package/dist/assets/skeleton-DypBy7jp.js +0 -1
|
@@ -85,6 +85,16 @@ function resolveSessionTypeLabel(
|
|
|
85
85
|
.join(' ');
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function resolveSessionTypeStatusText(option: {
|
|
89
|
+
ready?: boolean;
|
|
90
|
+
reasonMessage?: string | null;
|
|
91
|
+
}): string {
|
|
92
|
+
if (option.ready === false) {
|
|
93
|
+
return option.reasonMessage?.trim() || t('statusSetup');
|
|
94
|
+
}
|
|
95
|
+
return t('statusReady');
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
const navItems = [
|
|
89
99
|
{ target: '/cron', label: () => t('chatSidebarScheduledTasks'), icon: AlarmClock },
|
|
90
100
|
{ target: '/skills', label: () => t('chatSidebarSkills'), icon: BrainCircuit },
|
|
@@ -168,8 +178,22 @@ export function ChatSidebar() {
|
|
|
168
178
|
}}
|
|
169
179
|
className="w-full rounded-xl px-3 py-2 text-left transition-colors hover:bg-gray-100"
|
|
170
180
|
>
|
|
171
|
-
<div className="
|
|
172
|
-
|
|
181
|
+
<div className="flex items-center justify-between gap-3">
|
|
182
|
+
<div className="text-[13px] font-medium text-gray-900">{option.label}</div>
|
|
183
|
+
<span
|
|
184
|
+
className={cn(
|
|
185
|
+
'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold',
|
|
186
|
+
option.ready === false
|
|
187
|
+
? 'bg-amber-100 text-amber-800'
|
|
188
|
+
: 'bg-emerald-100 text-emerald-700'
|
|
189
|
+
)}
|
|
190
|
+
>
|
|
191
|
+
{option.ready === false ? t('statusSetup') : t('statusReady')}
|
|
192
|
+
</span>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="mt-0.5 text-[11px] text-gray-500">
|
|
195
|
+
{resolveSessionTypeStatusText(option)}
|
|
196
|
+
</div>
|
|
173
197
|
</button>
|
|
174
198
|
))}
|
|
175
199
|
</div>
|
|
@@ -4,10 +4,11 @@ import type { SessionEntryView, ThinkingLevel } from '@/api/types';
|
|
|
4
4
|
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
5
5
|
import { useChatSessionTypeState } from '@/components/chat/useChatSessionTypeState';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
resolveRecentSessionPreferredThinking,
|
|
8
8
|
resolveRecentSessionPreferredModel,
|
|
9
|
-
useSyncSelectedModel
|
|
10
|
-
|
|
9
|
+
useSyncSelectedModel,
|
|
10
|
+
useSyncSelectedThinking
|
|
11
|
+
} from '@/components/chat/chat-session-preference-governance';
|
|
11
12
|
import {
|
|
12
13
|
useChatCapabilities,
|
|
13
14
|
useChatSessionTypes,
|
|
@@ -23,24 +24,13 @@ type UseChatPageDataParams = {
|
|
|
23
24
|
query: string;
|
|
24
25
|
selectedSessionKey: string | null;
|
|
25
26
|
selectedAgentId: string;
|
|
27
|
+
currentSelectedModel: string;
|
|
26
28
|
pendingSessionType: string;
|
|
27
29
|
setPendingSessionType: Dispatch<SetStateAction<string>>;
|
|
28
30
|
setSelectedModel: Dispatch<SetStateAction<string>>;
|
|
31
|
+
setSelectedThinkingLevel: Dispatch<SetStateAction<ThinkingLevel | null>>;
|
|
29
32
|
};
|
|
30
33
|
|
|
31
|
-
const THINKING_LEVEL_SET = new Set<string>(['off', 'minimal', 'low', 'medium', 'high', 'adaptive', 'xhigh']);
|
|
32
|
-
|
|
33
|
-
function parseThinkingLevel(value: unknown): ThinkingLevel | null {
|
|
34
|
-
if (typeof value !== 'string') {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
const normalized = value.trim().toLowerCase();
|
|
38
|
-
if (!normalized) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
return THINKING_LEVEL_SET.has(normalized) ? (normalized as ThinkingLevel) : null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
34
|
export function useChatPageData(params: UseChatPageDataParams) {
|
|
45
35
|
const configQuery = useConfig();
|
|
46
36
|
const configMetaQuery = useConfigMeta();
|
|
@@ -111,52 +101,48 @@ export function useChatPageData(params: UseChatPageDataParams) {
|
|
|
111
101
|
}),
|
|
112
102
|
[params.selectedSessionKey, sessionTypeState.selectedSessionType, sessions]
|
|
113
103
|
);
|
|
104
|
+
const currentModelOption = useMemo(
|
|
105
|
+
() => modelOptions.find((option) => option.value === params.currentSelectedModel),
|
|
106
|
+
[modelOptions, params.currentSelectedModel]
|
|
107
|
+
);
|
|
108
|
+
const supportedThinkingLevels = useMemo(
|
|
109
|
+
() => (currentModelOption?.thinkingCapability?.supported as ThinkingLevel[] | undefined) ?? [],
|
|
110
|
+
[currentModelOption?.thinkingCapability?.supported]
|
|
111
|
+
);
|
|
112
|
+
const defaultThinkingLevel = useMemo(
|
|
113
|
+
() => (currentModelOption?.thinkingCapability?.default as ThinkingLevel | null | undefined) ?? null,
|
|
114
|
+
[currentModelOption?.thinkingCapability?.default]
|
|
115
|
+
);
|
|
116
|
+
const recentSessionPreferredThinking = useMemo(
|
|
117
|
+
() =>
|
|
118
|
+
resolveRecentSessionPreferredThinking({
|
|
119
|
+
sessions,
|
|
120
|
+
selectedSessionKey: params.selectedSessionKey,
|
|
121
|
+
sessionType: sessionTypeState.selectedSessionType
|
|
122
|
+
}),
|
|
123
|
+
[params.selectedSessionKey, sessionTypeState.selectedSessionType, sessions]
|
|
124
|
+
);
|
|
114
125
|
|
|
115
126
|
useSyncSelectedModel({
|
|
116
127
|
modelOptions,
|
|
117
128
|
selectedSessionKey: params.selectedSessionKey,
|
|
129
|
+
selectedSessionExists: Boolean(selectedSession),
|
|
118
130
|
selectedSessionPreferredModel: selectedSession?.preferredModel,
|
|
119
131
|
fallbackPreferredModel: recentSessionPreferredModel,
|
|
120
132
|
defaultModel: configQuery.data?.agents.defaults.model,
|
|
121
133
|
setSelectedModel: params.setSelectedModel
|
|
122
134
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
preferSessionPreferredModel: true
|
|
133
|
-
}),
|
|
134
|
-
[configQuery.data?.agents.defaults.model, modelOptions, recentSessionPreferredModel, selectedSession?.preferredModel]
|
|
135
|
-
);
|
|
135
|
+
useSyncSelectedThinking({
|
|
136
|
+
supportedThinkingLevels,
|
|
137
|
+
selectedSessionKey: params.selectedSessionKey,
|
|
138
|
+
selectedSessionExists: Boolean(selectedSession),
|
|
139
|
+
selectedSessionPreferredThinking: selectedSession?.preferredThinking ?? null,
|
|
140
|
+
fallbackPreferredThinking: recentSessionPreferredThinking ?? null,
|
|
141
|
+
defaultThinkingLevel,
|
|
142
|
+
setSelectedThinkingLevel: params.setSelectedThinkingLevel
|
|
143
|
+
});
|
|
136
144
|
|
|
137
145
|
const historyMessages = useMemo(() => historyQuery.data?.messages ?? [], [historyQuery.data?.messages]);
|
|
138
|
-
const selectedSessionThinkingLevel = useMemo(() => {
|
|
139
|
-
if (!params.selectedSessionKey) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
const metadata = historyQuery.data?.metadata;
|
|
143
|
-
if (!metadata || typeof metadata !== 'object') {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
const candidates = [
|
|
147
|
-
metadata.preferred_thinking,
|
|
148
|
-
metadata.thinking,
|
|
149
|
-
metadata.thinking_level,
|
|
150
|
-
metadata.thinkingLevel
|
|
151
|
-
];
|
|
152
|
-
for (const value of candidates) {
|
|
153
|
-
const level = parseThinkingLevel(value);
|
|
154
|
-
if (level) {
|
|
155
|
-
return level;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return null;
|
|
159
|
-
}, [historyQuery.data?.metadata, params.selectedSessionKey]);
|
|
160
146
|
|
|
161
147
|
return {
|
|
162
148
|
configQuery,
|
|
@@ -171,9 +157,7 @@ export function useChatPageData(params: UseChatPageDataParams) {
|
|
|
171
157
|
sessions,
|
|
172
158
|
skillRecords,
|
|
173
159
|
selectedSession,
|
|
174
|
-
hydratedSessionModel,
|
|
175
160
|
historyMessages,
|
|
176
|
-
selectedSessionThinkingLevel,
|
|
177
161
|
...sessionTypeState
|
|
178
162
|
};
|
|
179
163
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import type { SessionEntryView } from '@/api/types';
|
|
3
|
-
import {
|
|
2
|
+
import type { SessionEntryView, ThinkingLevel } from '@/api/types';
|
|
3
|
+
import {
|
|
4
|
+
resolveRecentSessionPreferredModel,
|
|
5
|
+
resolveRecentSessionPreferredThinking,
|
|
6
|
+
resolveSelectedModelValue,
|
|
7
|
+
resolveSelectedThinkingLevelValue
|
|
8
|
+
} from '@/components/chat/chat-session-preference-governance';
|
|
4
9
|
|
|
5
10
|
const modelOptions = [
|
|
6
11
|
{
|
|
@@ -27,11 +32,16 @@ function createSession(overrides: Partial<SessionEntryView> & Pick<SessionEntryV
|
|
|
27
32
|
messageCount: overrides.messageCount ?? 0,
|
|
28
33
|
...(overrides.label ? { label: overrides.label } : {}),
|
|
29
34
|
...(overrides.preferredModel ? { preferredModel: overrides.preferredModel } : {}),
|
|
35
|
+
...(Object.prototype.hasOwnProperty.call(overrides, 'preferredThinking')
|
|
36
|
+
? { preferredThinking: overrides.preferredThinking ?? null }
|
|
37
|
+
: {}),
|
|
30
38
|
...(overrides.lastRole ? { lastRole: overrides.lastRole } : {}),
|
|
31
39
|
...(overrides.lastTimestamp ? { lastTimestamp: overrides.lastTimestamp } : {})
|
|
32
40
|
};
|
|
33
41
|
}
|
|
34
42
|
|
|
43
|
+
const thinkingLevels: ThinkingLevel[] = ['off', 'minimal', 'medium', 'high'];
|
|
44
|
+
|
|
35
45
|
describe('resolveSelectedModelValue', () => {
|
|
36
46
|
it('keeps the current selected model when it is still available', () => {
|
|
37
47
|
expect(
|
|
@@ -82,6 +92,32 @@ describe('resolveSelectedModelValue', () => {
|
|
|
82
92
|
).toBe('openai/gpt-5');
|
|
83
93
|
});
|
|
84
94
|
|
|
95
|
+
it('preserves the current valid model when a draft session materializes before the new session metadata exists', () => {
|
|
96
|
+
expect(
|
|
97
|
+
resolveSelectedModelValue({
|
|
98
|
+
currentSelectedModel: 'openai/gpt-5',
|
|
99
|
+
modelOptions,
|
|
100
|
+
fallbackPreferredModel: 'anthropic/claude-sonnet-4',
|
|
101
|
+
defaultModel: 'anthropic/claude-sonnet-4',
|
|
102
|
+
preferSessionPreferredModel: true,
|
|
103
|
+
preserveCurrentSelectedModelOnSessionChange: true
|
|
104
|
+
})
|
|
105
|
+
).toBe('openai/gpt-5');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('still falls back when the current model is no longer valid during draft session materialization', () => {
|
|
109
|
+
expect(
|
|
110
|
+
resolveSelectedModelValue({
|
|
111
|
+
currentSelectedModel: 'missing/model',
|
|
112
|
+
modelOptions,
|
|
113
|
+
fallbackPreferredModel: 'openai/gpt-5',
|
|
114
|
+
defaultModel: 'anthropic/claude-sonnet-4',
|
|
115
|
+
preferSessionPreferredModel: true,
|
|
116
|
+
preserveCurrentSelectedModelOnSessionChange: true
|
|
117
|
+
})
|
|
118
|
+
).toBe('openai/gpt-5');
|
|
119
|
+
});
|
|
120
|
+
|
|
85
121
|
it('uses the recent same-runtime model when the current session has no valid preferred model', () => {
|
|
86
122
|
expect(
|
|
87
123
|
resolveSelectedModelValue({
|
|
@@ -179,3 +215,87 @@ describe('resolveRecentSessionPreferredModel', () => {
|
|
|
179
215
|
).toBe('anthropic/claude-sonnet-4');
|
|
180
216
|
});
|
|
181
217
|
});
|
|
218
|
+
|
|
219
|
+
describe('resolveSelectedThinkingLevelValue', () => {
|
|
220
|
+
it('keeps the current selected thinking when it is still valid', () => {
|
|
221
|
+
expect(
|
|
222
|
+
resolveSelectedThinkingLevelValue({
|
|
223
|
+
currentSelectedThinkingLevel: 'high',
|
|
224
|
+
supportedThinkingLevels: thinkingLevels,
|
|
225
|
+
selectedSessionPreferredThinking: 'medium',
|
|
226
|
+
fallbackPreferredThinking: 'minimal',
|
|
227
|
+
defaultThinkingLevel: 'off'
|
|
228
|
+
})
|
|
229
|
+
).toBe('high');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('prefers the persisted session thinking after switching sessions', () => {
|
|
233
|
+
expect(
|
|
234
|
+
resolveSelectedThinkingLevelValue({
|
|
235
|
+
currentSelectedThinkingLevel: 'high',
|
|
236
|
+
supportedThinkingLevels: thinkingLevels,
|
|
237
|
+
selectedSessionPreferredThinking: 'medium',
|
|
238
|
+
fallbackPreferredThinking: 'minimal',
|
|
239
|
+
defaultThinkingLevel: 'off',
|
|
240
|
+
preferSessionPreferredThinking: true
|
|
241
|
+
})
|
|
242
|
+
).toBe('medium');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('preserves the current valid thinking when a draft session materializes before metadata exists', () => {
|
|
246
|
+
expect(
|
|
247
|
+
resolveSelectedThinkingLevelValue({
|
|
248
|
+
currentSelectedThinkingLevel: 'high',
|
|
249
|
+
supportedThinkingLevels: thinkingLevels,
|
|
250
|
+
fallbackPreferredThinking: 'minimal',
|
|
251
|
+
defaultThinkingLevel: 'off',
|
|
252
|
+
preferSessionPreferredThinking: true,
|
|
253
|
+
preserveCurrentSelectedThinkingOnSessionChange: true
|
|
254
|
+
})
|
|
255
|
+
).toBe('high');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('falls back to the model default when no current or persisted thinking is valid', () => {
|
|
259
|
+
expect(
|
|
260
|
+
resolveSelectedThinkingLevelValue({
|
|
261
|
+
currentSelectedThinkingLevel: null,
|
|
262
|
+
supportedThinkingLevels: thinkingLevels,
|
|
263
|
+
fallbackPreferredThinking: null,
|
|
264
|
+
defaultThinkingLevel: 'medium'
|
|
265
|
+
})
|
|
266
|
+
).toBe('medium');
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('resolveRecentSessionPreferredThinking', () => {
|
|
271
|
+
it('returns the most recent preferred thinking from the same runtime', () => {
|
|
272
|
+
const sessions = [
|
|
273
|
+
createSession({
|
|
274
|
+
key: 'native-1',
|
|
275
|
+
sessionType: 'native',
|
|
276
|
+
preferredThinking: 'low',
|
|
277
|
+
updatedAt: '2026-03-18T01:00:00.000Z'
|
|
278
|
+
}),
|
|
279
|
+
createSession({
|
|
280
|
+
key: 'codex-1',
|
|
281
|
+
sessionType: 'codex',
|
|
282
|
+
preferredThinking: 'high',
|
|
283
|
+
updatedAt: '2026-03-18T03:00:00.000Z'
|
|
284
|
+
}),
|
|
285
|
+
createSession({
|
|
286
|
+
key: 'codex-2',
|
|
287
|
+
sessionType: 'codex',
|
|
288
|
+
preferredThinking: 'medium',
|
|
289
|
+
updatedAt: '2026-03-18T02:00:00.000Z'
|
|
290
|
+
})
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
expect(
|
|
294
|
+
resolveRecentSessionPreferredThinking({
|
|
295
|
+
sessions,
|
|
296
|
+
selectedSessionKey: 'draft',
|
|
297
|
+
sessionType: 'codex'
|
|
298
|
+
})
|
|
299
|
+
).toBe('high');
|
|
300
|
+
});
|
|
301
|
+
});
|
|
@@ -1,127 +1,10 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
import type { ChatRunView, SessionEntryView } from '@/api/types';
|
|
4
|
-
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
2
|
+
import type { ChatRunView } from '@/api/types';
|
|
5
3
|
import { useChatRuns } from '@/hooks/useConfig';
|
|
6
4
|
import { buildActiveRunBySessionKey, buildSessionRunStatusByKey } from '@/lib/session-run-status';
|
|
7
5
|
|
|
8
6
|
export type ChatMainPanelView = 'chat' | 'cron' | 'skills';
|
|
9
7
|
|
|
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
|
-
|
|
86
|
-
export function useSyncSelectedModel(params: {
|
|
87
|
-
modelOptions: ChatModelOption[];
|
|
88
|
-
selectedSessionKey?: string | null;
|
|
89
|
-
selectedSessionPreferredModel?: string;
|
|
90
|
-
fallbackPreferredModel?: string;
|
|
91
|
-
defaultModel?: string;
|
|
92
|
-
setSelectedModel: Dispatch<SetStateAction<string>>;
|
|
93
|
-
}) {
|
|
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
|
-
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
const sessionChanged = previousSessionKeyRef.current !== selectedSessionKey;
|
|
106
|
-
if (modelOptions.length === 0) {
|
|
107
|
-
setSelectedModel('');
|
|
108
|
-
previousSessionKeyRef.current = selectedSessionKey;
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
setSelectedModel((prev) => {
|
|
112
|
-
return resolveSelectedModelValue({
|
|
113
|
-
currentSelectedModel: prev,
|
|
114
|
-
modelOptions,
|
|
115
|
-
selectedSessionPreferredModel,
|
|
116
|
-
fallbackPreferredModel,
|
|
117
|
-
defaultModel,
|
|
118
|
-
preferSessionPreferredModel: sessionChanged
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
previousSessionKeyRef.current = selectedSessionKey;
|
|
122
|
-
}, [defaultModel, fallbackPreferredModel, modelOptions, selectedSessionKey, selectedSessionPreferredModel, setSelectedModel]);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
8
|
export function useSessionRunStatus(params: {
|
|
126
9
|
view: ChatMainPanelView;
|
|
127
10
|
selectedSessionKey: string | null;
|