@nextclaw/ui 0.6.12 → 0.6.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.
- package/CHANGELOG.md +12 -0
- package/dist/assets/{ChannelsList-DBDjwf-X.js → ChannelsList-DiSnpiW0.js} +1 -1
- package/dist/assets/ChatPage-DsaIrNHN.js +36 -0
- package/dist/assets/{DocBrowser-ZOplDEMS.js → DocBrowser-CnfcptGM.js} +1 -1
- package/dist/assets/{LogoBadge-2LMzEMwe.js → LogoBadge-BxZJ9BJT.js} +1 -1
- package/dist/assets/{MarketplacePage-D4JHYcB5.js → MarketplacePage-BI_J_DBQ.js} +2 -2
- package/dist/assets/ModelConfig-DfL8F4tN.js +1 -0
- package/dist/assets/ProvidersList-DpT_oFHZ.js +1 -0
- package/dist/assets/{RuntimeConfig-4sb3mpkd.js → RuntimeConfig-BNYR_Iag.js} +1 -1
- package/dist/assets/{SearchConfig-B4u_MxRG.js → SearchConfig-TDBl7Fjh.js} +1 -1
- package/dist/assets/{SecretsConfig-BQXblZvb.js → SecretsConfig-9OABNssV.js} +2 -2
- package/dist/assets/SessionsConfig-BRwntUDz.js +2 -0
- package/dist/assets/{card-BekAnCgX.js → card-BYnT3Mxo.js} +1 -1
- package/dist/assets/index-BCfS4UY1.css +1 -0
- package/dist/assets/index-BnUxgevr.js +8 -0
- package/dist/assets/index-CkqvHQAt.js +1 -0
- package/dist/assets/{input-MMn_Na9q.js → input-oaepEtqu.js} +1 -1
- package/dist/assets/{label-Dg2ydpN0.js → label-BIjHWZUm.js} +1 -1
- package/dist/assets/{page-layout-7K0rcz0I.js → page-layout-B6JXiSQB.js} +1 -1
- package/dist/assets/popover-LJQgv5l1.js +1 -0
- package/dist/assets/provider-models-D3B_xWXx.js +1 -0
- package/dist/assets/{session-run-status-CAdjSqeb.js → session-run-status-BZEH0QZp.js} +1 -1
- package/dist/assets/{switch-DnDMlDVu.js → switch-CnGQpdTp.js} +1 -1
- package/dist/assets/{tabs-custom-khLM8lWj.js → tabs-custom-CpSv7pDl.js} +1 -1
- package/dist/assets/useConfirmDialog-pqAlPdQZ.js +5 -0
- package/dist/assets/{vendor-d7E8OgNx.js → vendor-BKtTvQYU.js} +69 -64
- package/dist/index.html +3 -3
- package/package.json +1 -1
- package/src/api/types.ts +4 -0
- package/src/components/chat/ChatPage.tsx +16 -0
- package/src/components/chat/ChatSidebar.tsx +10 -1
- package/src/components/chat/chat-input/ChatInputBottomToolbar.tsx +12 -0
- package/src/components/chat/chat-input/components/ChatInputSlashPanelSection.tsx +3 -3
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputThinkingSelector.tsx +74 -0
- package/src/components/chat/chat-input/useChatInputBarController.ts +11 -2
- package/src/components/chat/chat-input.types.ts +8 -0
- package/src/components/chat/chat-page-data.ts +40 -3
- package/src/components/chat/chat-stream/transport.ts +3 -0
- package/src/components/chat/chat-stream/types.ts +3 -1
- package/src/components/chat/managers/chat-input.manager.ts +51 -0
- package/src/components/chat/stores/chat-input.store.ts +5 -1
- package/src/components/common/SearchableModelInput.tsx +22 -5
- package/src/components/config/ModelConfig.tsx +13 -12
- package/src/components/config/ProviderForm.tsx +292 -19
- package/src/lib/i18n.ts +15 -0
- package/src/lib/provider-models.ts +91 -3
- package/dist/assets/ChatPage-C18sGGk1.js +0 -36
- package/dist/assets/ModelConfig-DZVvdLFq.js +0 -1
- package/dist/assets/ProvidersList-Dum31480.js +0 -1
- package/dist/assets/SessionsConfig-Jk29xjQU.js +0 -2
- package/dist/assets/index-BXwjfCEO.css +0 -1
- package/dist/assets/index-Dl6t70wA.js +0 -8
- package/dist/assets/provider-models-y4mUDcGF.js +0 -1
- package/dist/assets/useConfirmDialog-BYA1XnVU.js +0 -5
package/dist/index.html
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
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-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-BnUxgevr.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-BKtTvQYU.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BCfS4UY1.css">
|
|
12
12
|
</head>
|
|
13
13
|
|
|
14
14
|
<body>
|
package/package.json
CHANGED
package/src/api/types.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type AppMetaView = {
|
|
|
14
14
|
productVersion: string;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "adaptive" | "xhigh";
|
|
18
|
+
|
|
17
19
|
export type ProviderConfigView = {
|
|
18
20
|
displayName?: string;
|
|
19
21
|
apiKeySet: boolean;
|
|
@@ -22,6 +24,7 @@ export type ProviderConfigView = {
|
|
|
22
24
|
extraHeaders?: Record<string, string> | null;
|
|
23
25
|
wireApi?: "auto" | "chat" | "responses" | null;
|
|
24
26
|
models?: string[];
|
|
27
|
+
modelThinking?: Record<string, { supported: ThinkingLevel[]; default?: ThinkingLevel | null }>;
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
export type ProviderConfigUpdate = {
|
|
@@ -31,6 +34,7 @@ export type ProviderConfigUpdate = {
|
|
|
31
34
|
extraHeaders?: Record<string, string> | null;
|
|
32
35
|
wireApi?: "auto" | "chat" | "responses" | null;
|
|
33
36
|
models?: string[] | null;
|
|
37
|
+
modelThinking?: Record<string, { supported?: ThinkingLevel[]; default?: ThinkingLevel | null }> | null;
|
|
34
38
|
};
|
|
35
39
|
|
|
36
40
|
export type ProviderConnectionTestRequest = ProviderConfigUpdate & {
|
|
@@ -122,6 +122,7 @@ export function ChatPage({ view }: ChatPageProps) {
|
|
|
122
122
|
const { sessionId: routeSessionIdParam } = useParams<{ sessionId?: string }>();
|
|
123
123
|
const threadRef = useRef<HTMLDivElement | null>(null);
|
|
124
124
|
const selectedSessionKeyRef = useRef<string | null>(selectedSessionKey);
|
|
125
|
+
const thinkingHydratedSessionKeyRef = useRef<string | null>(null);
|
|
125
126
|
const routeSessionKey = useMemo(
|
|
126
127
|
() => parseSessionKeyFromRoute(routeSessionIdParam),
|
|
127
128
|
[routeSessionIdParam]
|
|
@@ -137,6 +138,7 @@ export function ChatPage({ view }: ChatPageProps) {
|
|
|
137
138
|
skillRecords,
|
|
138
139
|
selectedSession,
|
|
139
140
|
historyMessages,
|
|
141
|
+
selectedSessionThinkingLevel,
|
|
140
142
|
sessionTypeOptions,
|
|
141
143
|
defaultSessionType,
|
|
142
144
|
selectedSessionType,
|
|
@@ -232,6 +234,12 @@ export function ChatPage({ view }: ChatPageProps) {
|
|
|
232
234
|
]);
|
|
233
235
|
|
|
234
236
|
useEffect(() => {
|
|
237
|
+
const shouldHydrateThinkingFromHistory =
|
|
238
|
+
!isSending &&
|
|
239
|
+
!isAwaitingAssistantOutput &&
|
|
240
|
+
!historyQuery.isLoading &&
|
|
241
|
+
selectedSessionKey !== thinkingHydratedSessionKeyRef.current;
|
|
242
|
+
|
|
235
243
|
presenter.chatInputManager.syncSnapshot({
|
|
236
244
|
isProviderStateResolved,
|
|
237
245
|
defaultSessionType,
|
|
@@ -244,11 +252,18 @@ export function ChatPage({ view }: ChatPageProps) {
|
|
|
244
252
|
modelOptions,
|
|
245
253
|
sessionTypeOptions,
|
|
246
254
|
selectedSessionType,
|
|
255
|
+
...(shouldHydrateThinkingFromHistory ? { selectedThinkingLevel: selectedSessionThinkingLevel } : {}),
|
|
247
256
|
canEditSessionType,
|
|
248
257
|
sessionTypeUnavailable,
|
|
249
258
|
skillRecords,
|
|
250
259
|
isSkillsLoading: installedSkillsQuery.isLoading
|
|
251
260
|
});
|
|
261
|
+
if (shouldHydrateThinkingFromHistory) {
|
|
262
|
+
thinkingHydratedSessionKeyRef.current = selectedSessionKey;
|
|
263
|
+
}
|
|
264
|
+
if (!selectedSessionKey) {
|
|
265
|
+
thinkingHydratedSessionKeyRef.current = null;
|
|
266
|
+
}
|
|
252
267
|
presenter.chatSessionListManager.syncSnapshot({
|
|
253
268
|
sessions,
|
|
254
269
|
query,
|
|
@@ -292,6 +307,7 @@ export function ChatPage({ view }: ChatPageProps) {
|
|
|
292
307
|
presenter,
|
|
293
308
|
query,
|
|
294
309
|
selectedSession,
|
|
310
|
+
selectedSessionThinkingLevel,
|
|
295
311
|
selectedSessionKey,
|
|
296
312
|
selectedAgentId,
|
|
297
313
|
selectedSessionType,
|
|
@@ -13,8 +13,9 @@ import { LANGUAGE_OPTIONS, formatDateTime, t, type I18nLanguage } from '@/lib/i1
|
|
|
13
13
|
import { THEME_OPTIONS, type UiTheme } from '@/lib/theme';
|
|
14
14
|
import { useI18n } from '@/components/providers/I18nProvider';
|
|
15
15
|
import { useTheme } from '@/components/providers/ThemeProvider';
|
|
16
|
+
import { useDocBrowser } from '@/components/doc-browser';
|
|
16
17
|
import { NavLink } from 'react-router-dom';
|
|
17
|
-
import { AlarmClock, BrainCircuit, Languages, MessageSquareText, Palette, Plus, Search, Settings } from 'lucide-react';
|
|
18
|
+
import { AlarmClock, BookOpen, BrainCircuit, Languages, MessageSquareText, Palette, Plus, Search, Settings } from 'lucide-react';
|
|
18
19
|
|
|
19
20
|
type DateGroup = {
|
|
20
21
|
label: string;
|
|
@@ -68,6 +69,7 @@ const navItems = [
|
|
|
68
69
|
|
|
69
70
|
export function ChatSidebar() {
|
|
70
71
|
const presenter = usePresenter();
|
|
72
|
+
const docBrowser = useDocBrowser();
|
|
71
73
|
const listSnapshot = useChatSessionListStore((state) => state.snapshot);
|
|
72
74
|
const runSnapshot = useChatRunStatusStore((state) => state.snapshot);
|
|
73
75
|
const { language, setLanguage } = useI18n();
|
|
@@ -207,6 +209,13 @@ export function ChatSidebar() {
|
|
|
207
209
|
</>
|
|
208
210
|
)}
|
|
209
211
|
</NavLink>
|
|
212
|
+
<button
|
|
213
|
+
onClick={() => docBrowser.open(undefined, { kind: 'docs', newTab: true, title: 'Docs' })}
|
|
214
|
+
className="w-full flex items-center gap-2.5 px-3 py-2 rounded-xl text-[13px] font-medium transition-all duration-150 text-gray-600 hover:bg-gray-200/60 hover:text-gray-800"
|
|
215
|
+
>
|
|
216
|
+
<BookOpen className="h-4 w-4 text-gray-400" />
|
|
217
|
+
<span>{t('docBrowserHelp')}</span>
|
|
218
|
+
</button>
|
|
210
219
|
<Select value={theme} onValueChange={(value) => setTheme(value as UiTheme)}>
|
|
211
220
|
<SelectTrigger className="w-full h-auto rounded-xl border-0 bg-transparent shadow-none px-3 py-2 text-[13px] font-medium text-gray-600 hover:bg-gray-200/60 focus:ring-0">
|
|
212
221
|
<div className="flex items-center gap-2.5 min-w-0">
|
|
@@ -5,6 +5,7 @@ import { ChatInputAttachButton } from '@/components/chat/chat-input/components/b
|
|
|
5
5
|
import { ChatInputModelSelector } from '@/components/chat/chat-input/components/bottom-toolbar/ChatInputModelSelector';
|
|
6
6
|
import { ChatInputSendControls } from '@/components/chat/chat-input/components/bottom-toolbar/ChatInputSendControls';
|
|
7
7
|
import { ChatInputSessionTypeSelector } from '@/components/chat/chat-input/components/bottom-toolbar/ChatInputSessionTypeSelector';
|
|
8
|
+
import { ChatInputThinkingSelector } from '@/components/chat/chat-input/components/bottom-toolbar/ChatInputThinkingSelector';
|
|
8
9
|
import { t } from '@/lib/i18n';
|
|
9
10
|
|
|
10
11
|
export function ChatInputBottomToolbar() {
|
|
@@ -27,6 +28,9 @@ export function ChatInputBottomToolbar() {
|
|
|
27
28
|
snapshot.stopDisabledReason === '__preparing__'
|
|
28
29
|
? t('chatStopPreparing')
|
|
29
30
|
: snapshot.stopDisabledReason?.trim() || t('chatStopUnavailable');
|
|
31
|
+
const selectedModelThinkingCapability = selectedModelOption?.thinkingCapability;
|
|
32
|
+
const thinkingSupportedLevels = selectedModelThinkingCapability?.supported ?? [];
|
|
33
|
+
const shouldShowThinkingSelector = thinkingSupportedLevels.length > 0;
|
|
30
34
|
|
|
31
35
|
return (
|
|
32
36
|
<div className="flex items-center justify-between px-3 pb-3">
|
|
@@ -53,6 +57,14 @@ export function ChatInputBottomToolbar() {
|
|
|
53
57
|
isModelOptionsLoading={isModelOptionsLoading}
|
|
54
58
|
hasModelOptions={hasModelOptions}
|
|
55
59
|
/>
|
|
60
|
+
{shouldShowThinkingSelector ? (
|
|
61
|
+
<ChatInputThinkingSelector
|
|
62
|
+
supportedLevels={thinkingSupportedLevels}
|
|
63
|
+
selectedThinkingLevel={snapshot.selectedThinkingLevel}
|
|
64
|
+
defaultThinkingLevel={selectedModelThinkingCapability?.default ?? null}
|
|
65
|
+
onSelectedThinkingLevelChange={presenter.chatInputManager.selectThinkingLevel}
|
|
66
|
+
/>
|
|
67
|
+
) : null}
|
|
56
68
|
<ChatInputAttachButton />
|
|
57
69
|
</div>
|
|
58
70
|
<ChatInputSendControls
|
|
@@ -43,10 +43,10 @@ export function ChatInputSlashPanelSection({
|
|
|
43
43
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
44
44
|
style={resolvedSlashPanelWidth ? { width: `${resolvedSlashPanelWidth}px` } : undefined}
|
|
45
45
|
>
|
|
46
|
-
<div className="grid min-h-[240px] grid-cols-[minmax(
|
|
46
|
+
<div className="grid min-h-[240px] grid-cols-[minmax(220px,300px)_minmax(0,1fr)]">
|
|
47
47
|
<div
|
|
48
48
|
ref={slashListRef}
|
|
49
|
-
className="max-h-[320px] overflow-y-auto border-r border-gray-200 p-
|
|
49
|
+
className="max-h-[320px] overflow-y-auto border-r border-gray-200 p-2.5 custom-scrollbar"
|
|
50
50
|
>
|
|
51
51
|
{isSlashPanelLoading ? (
|
|
52
52
|
<div className="p-2 text-xs text-gray-500">{t('chatSlashLoading')}</div>
|
|
@@ -82,7 +82,7 @@ export function ChatInputSlashPanelSection({
|
|
|
82
82
|
</>
|
|
83
83
|
)}
|
|
84
84
|
</div>
|
|
85
|
-
<div className="p-
|
|
85
|
+
<div className="max-w-[320px] p-3.5">
|
|
86
86
|
{activeSlashItem ? (
|
|
87
87
|
<div className="space-y-3">
|
|
88
88
|
<div className="flex items-center gap-2">
|
package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputThinkingSelector.tsx
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ThinkingLevel } from '@/api/types';
|
|
2
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
3
|
+
import { t } from '@/lib/i18n';
|
|
4
|
+
import { Brain } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
type ChatInputThinkingSelectorProps = {
|
|
7
|
+
supportedLevels: ThinkingLevel[];
|
|
8
|
+
selectedThinkingLevel: ThinkingLevel | null;
|
|
9
|
+
defaultThinkingLevel?: ThinkingLevel | null;
|
|
10
|
+
onSelectedThinkingLevelChange: (value: ThinkingLevel) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function thinkingLabel(level: ThinkingLevel): string {
|
|
14
|
+
if (level === 'off') {
|
|
15
|
+
return t('chatThinkingLevelOff');
|
|
16
|
+
}
|
|
17
|
+
if (level === 'minimal') {
|
|
18
|
+
return t('chatThinkingLevelMinimal');
|
|
19
|
+
}
|
|
20
|
+
if (level === 'low') {
|
|
21
|
+
return t('chatThinkingLevelLow');
|
|
22
|
+
}
|
|
23
|
+
if (level === 'medium') {
|
|
24
|
+
return t('chatThinkingLevelMedium');
|
|
25
|
+
}
|
|
26
|
+
if (level === 'high') {
|
|
27
|
+
return t('chatThinkingLevelHigh');
|
|
28
|
+
}
|
|
29
|
+
if (level === 'adaptive') {
|
|
30
|
+
return t('chatThinkingLevelAdaptive');
|
|
31
|
+
}
|
|
32
|
+
return t('chatThinkingLevelXhigh');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeLevels(levels: ThinkingLevel[]): ThinkingLevel[] {
|
|
36
|
+
const deduped: ThinkingLevel[] = [];
|
|
37
|
+
for (const level of ['off', ...levels] as ThinkingLevel[]) {
|
|
38
|
+
if (!deduped.includes(level)) {
|
|
39
|
+
deduped.push(level);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return deduped;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function ChatInputThinkingSelector(props: ChatInputThinkingSelectorProps) {
|
|
46
|
+
if (props.supportedLevels.length === 0) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const options = normalizeLevels(props.supportedLevels);
|
|
51
|
+
const fallback = options.includes('off') ? 'off' : options[0];
|
|
52
|
+
const resolvedValue =
|
|
53
|
+
(props.selectedThinkingLevel && options.includes(props.selectedThinkingLevel) && props.selectedThinkingLevel) ||
|
|
54
|
+
(props.defaultThinkingLevel && options.includes(props.defaultThinkingLevel) && props.defaultThinkingLevel) ||
|
|
55
|
+
fallback;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Select value={resolvedValue} onValueChange={(value) => props.onSelectedThinkingLevelChange(value as ThinkingLevel)}>
|
|
59
|
+
<SelectTrigger className="h-8 w-auto min-w-[150px] rounded-lg border-0 bg-transparent px-3 text-xs font-medium text-gray-600 shadow-none hover:bg-gray-100 focus:ring-0">
|
|
60
|
+
<div className="flex min-w-0 items-center gap-2 text-left">
|
|
61
|
+
<Brain className="h-3.5 w-3.5 shrink-0 text-gray-500" />
|
|
62
|
+
<span className="truncate text-xs font-semibold text-gray-700">{thinkingLabel(resolvedValue)}</span>
|
|
63
|
+
</div>
|
|
64
|
+
</SelectTrigger>
|
|
65
|
+
<SelectContent className="w-[180px]">
|
|
66
|
+
{options.map((level) => (
|
|
67
|
+
<SelectItem key={level} value={level}>
|
|
68
|
+
{thinkingLabel(level)}
|
|
69
|
+
</SelectItem>
|
|
70
|
+
))}
|
|
71
|
+
</SelectContent>
|
|
72
|
+
</Select>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -4,7 +4,9 @@ import type { MarketplaceInstalledRecord } from '@/api/types';
|
|
|
4
4
|
import { t } from '@/lib/i18n';
|
|
5
5
|
import type { ChatInputBarSlashItem } from '@/components/chat/chat-input.types';
|
|
6
6
|
|
|
7
|
-
const SLASH_PANEL_MAX_WIDTH =
|
|
7
|
+
const SLASH_PANEL_MAX_WIDTH = 680;
|
|
8
|
+
const SLASH_PANEL_DESKTOP_SHRINK_RATIO = 0.82;
|
|
9
|
+
const SLASH_PANEL_DESKTOP_MIN_WIDTH = 560;
|
|
8
10
|
|
|
9
11
|
type RankedSkill = {
|
|
10
12
|
record: MarketplaceInstalledRecord;
|
|
@@ -162,7 +164,14 @@ function useSlashPanelController(params: SlashPanelControllerParams) {
|
|
|
162
164
|
const isSlashPanelOpen = slashQuery !== null && !dismissedSlashPanel;
|
|
163
165
|
const activeSlashItem = slashItems[activeSlashIndex] ?? null;
|
|
164
166
|
const isSlashPanelLoading = params.isSkillsLoading;
|
|
165
|
-
const resolvedSlashPanelWidth = slashPanelWidth
|
|
167
|
+
const resolvedSlashPanelWidth = slashPanelWidth
|
|
168
|
+
? Math.min(
|
|
169
|
+
slashPanelWidth > SLASH_PANEL_DESKTOP_MIN_WIDTH
|
|
170
|
+
? slashPanelWidth * SLASH_PANEL_DESKTOP_SHRINK_RATIO
|
|
171
|
+
: slashPanelWidth,
|
|
172
|
+
SLASH_PANEL_MAX_WIDTH
|
|
173
|
+
)
|
|
174
|
+
: undefined;
|
|
166
175
|
|
|
167
176
|
useEffect(() => {
|
|
168
177
|
const anchor = slashAnchorRef.current;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import type { ThinkingLevel } from '@/api/types';
|
|
2
|
+
|
|
3
|
+
export type ChatModelThinkingCapability = {
|
|
4
|
+
supported: ThinkingLevel[];
|
|
5
|
+
default?: ThinkingLevel | null;
|
|
6
|
+
};
|
|
7
|
+
|
|
1
8
|
export type ChatModelOption = {
|
|
2
9
|
value: string;
|
|
3
10
|
modelLabel: string;
|
|
4
11
|
providerLabel: string;
|
|
12
|
+
thinkingCapability?: ChatModelThinkingCapability | null;
|
|
5
13
|
};
|
|
6
14
|
|
|
7
15
|
export type ChatInputBarSlashItem = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import type { Dispatch, SetStateAction } from 'react';
|
|
3
|
-
import type { SessionEntryView } from '@/api/types';
|
|
3
|
+
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 { useSyncSelectedModel } from '@/components/chat/chat-page-runtime';
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
useSessions,
|
|
14
14
|
} from '@/hooks/useConfig';
|
|
15
15
|
import { useMarketplaceInstalled } from '@/hooks/useMarketplace';
|
|
16
|
-
import { buildProviderModelCatalog, composeProviderModel } from '@/lib/provider-models';
|
|
16
|
+
import { buildProviderModelCatalog, composeProviderModel, resolveModelThinkingCapability } from '@/lib/provider-models';
|
|
17
17
|
|
|
18
18
|
type UseChatPageDataParams = {
|
|
19
19
|
query: string;
|
|
@@ -24,6 +24,19 @@ type UseChatPageDataParams = {
|
|
|
24
24
|
setSelectedModel: Dispatch<SetStateAction<string>>;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
const THINKING_LEVEL_SET = new Set<string>(['off', 'minimal', 'low', 'medium', 'high', 'adaptive', 'xhigh']);
|
|
28
|
+
|
|
29
|
+
function parseThinkingLevel(value: unknown): ThinkingLevel | null {
|
|
30
|
+
if (typeof value !== 'string') {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const normalized = value.trim().toLowerCase();
|
|
34
|
+
if (!normalized) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return THINKING_LEVEL_SET.has(normalized) ? (normalized as ThinkingLevel) : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
export function useChatPageData(params: UseChatPageDataParams) {
|
|
28
41
|
const configQuery = useConfig();
|
|
29
42
|
const configMetaQuery = useConfigMeta();
|
|
@@ -57,7 +70,8 @@ export function useChatPageData(params: UseChatPageDataParams) {
|
|
|
57
70
|
options.push({
|
|
58
71
|
value,
|
|
59
72
|
modelLabel: localModel,
|
|
60
|
-
providerLabel: provider.displayName
|
|
73
|
+
providerLabel: provider.displayName,
|
|
74
|
+
thinkingCapability: resolveModelThinkingCapability(provider.modelThinking, localModel, provider.aliases)
|
|
61
75
|
});
|
|
62
76
|
}
|
|
63
77
|
}
|
|
@@ -93,6 +107,28 @@ export function useChatPageData(params: UseChatPageDataParams) {
|
|
|
93
107
|
});
|
|
94
108
|
|
|
95
109
|
const historyMessages = useMemo(() => historyQuery.data?.messages ?? [], [historyQuery.data?.messages]);
|
|
110
|
+
const selectedSessionThinkingLevel = useMemo(() => {
|
|
111
|
+
if (!params.selectedSessionKey) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const metadata = historyQuery.data?.metadata;
|
|
115
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const candidates = [
|
|
119
|
+
metadata.preferred_thinking,
|
|
120
|
+
metadata.thinking,
|
|
121
|
+
metadata.thinking_level,
|
|
122
|
+
metadata.thinkingLevel
|
|
123
|
+
];
|
|
124
|
+
for (const value of candidates) {
|
|
125
|
+
const level = parseThinkingLevel(value);
|
|
126
|
+
if (level) {
|
|
127
|
+
return level;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}, [historyQuery.data?.metadata, params.selectedSessionKey]);
|
|
96
132
|
|
|
97
133
|
return {
|
|
98
134
|
configQuery,
|
|
@@ -108,6 +144,7 @@ export function useChatPageData(params: UseChatPageDataParams) {
|
|
|
108
144
|
skillRecords,
|
|
109
145
|
selectedSession,
|
|
110
146
|
historyMessages,
|
|
147
|
+
selectedSessionThinkingLevel,
|
|
111
148
|
...sessionTypeState
|
|
112
149
|
};
|
|
113
150
|
}
|
|
@@ -6,6 +6,9 @@ function buildSendTurnPayload(item: SendMessageParams, requestedSkills: string[]
|
|
|
6
6
|
if (item.sessionType) {
|
|
7
7
|
metadata.session_type = item.sessionType;
|
|
8
8
|
}
|
|
9
|
+
if (item.thinkingLevel) {
|
|
10
|
+
metadata.thinking = item.thinkingLevel;
|
|
11
|
+
}
|
|
9
12
|
if (requestedSkills.length > 0) {
|
|
10
13
|
metadata.requested_skills = requestedSkills;
|
|
11
14
|
}
|
|
@@ -4,7 +4,8 @@ import type {
|
|
|
4
4
|
ChatTurnStreamDeltaEvent,
|
|
5
5
|
ChatTurnStreamReadyEvent,
|
|
6
6
|
ChatTurnStreamSessionEvent,
|
|
7
|
-
SessionMessageView
|
|
7
|
+
SessionMessageView,
|
|
8
|
+
ThinkingLevel
|
|
8
9
|
} from '@/api/types';
|
|
9
10
|
|
|
10
11
|
export type SendMessageParams = {
|
|
@@ -14,6 +15,7 @@ export type SendMessageParams = {
|
|
|
14
15
|
agentId: string;
|
|
15
16
|
sessionType?: string;
|
|
16
17
|
model?: string;
|
|
18
|
+
thinkingLevel?: ThinkingLevel;
|
|
17
19
|
requestedSkills?: string[];
|
|
18
20
|
stopSupported?: boolean;
|
|
19
21
|
stopReason?: string;
|
|
@@ -7,6 +7,8 @@ import { normalizeSessionType } from '@/components/chat/useChatSessionTypeState'
|
|
|
7
7
|
import type { ChatInputSnapshot } from '@/components/chat/stores/chat-input.store';
|
|
8
8
|
import type { SetStateAction } from 'react';
|
|
9
9
|
import type { ChatStreamActionsManager } from '@/components/chat/managers/chat-stream-actions.manager';
|
|
10
|
+
import type { ThinkingLevel } from '@/api/types';
|
|
11
|
+
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
10
12
|
|
|
11
13
|
export class ChatInputManager {
|
|
12
14
|
constructor(
|
|
@@ -36,6 +38,14 @@ export class ChatInputManager {
|
|
|
36
38
|
return;
|
|
37
39
|
}
|
|
38
40
|
useChatInputStore.getState().setSnapshot(patch);
|
|
41
|
+
if (
|
|
42
|
+
Object.prototype.hasOwnProperty.call(patch, 'modelOptions') ||
|
|
43
|
+
Object.prototype.hasOwnProperty.call(patch, 'selectedModel') ||
|
|
44
|
+
Object.prototype.hasOwnProperty.call(patch, 'selectedThinkingLevel')
|
|
45
|
+
) {
|
|
46
|
+
const snapshot = useChatInputStore.getState().snapshot;
|
|
47
|
+
this.reconcileThinkingForModel(snapshot.selectedModel);
|
|
48
|
+
}
|
|
39
49
|
};
|
|
40
50
|
|
|
41
51
|
setDraft = (next: SetStateAction<string>) => {
|
|
@@ -77,6 +87,7 @@ export class ChatInputManager {
|
|
|
77
87
|
agentId: sessionSnapshot.selectedAgentId,
|
|
78
88
|
sessionType: inputSnapshot.selectedSessionType,
|
|
79
89
|
model: inputSnapshot.selectedModel || undefined,
|
|
90
|
+
thinkingLevel: inputSnapshot.selectedThinkingLevel ?? undefined,
|
|
80
91
|
stopSupported: inputSnapshot.stopSupported,
|
|
81
92
|
stopReason: inputSnapshot.stopReason,
|
|
82
93
|
requestedSkills,
|
|
@@ -99,6 +110,16 @@ export class ChatInputManager {
|
|
|
99
110
|
return;
|
|
100
111
|
}
|
|
101
112
|
useChatInputStore.getState().setSnapshot({ selectedModel: value });
|
|
113
|
+
this.reconcileThinkingForModel(value);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
setSelectedThinkingLevel = (next: SetStateAction<ThinkingLevel | null>) => {
|
|
117
|
+
const prev = useChatInputStore.getState().snapshot.selectedThinkingLevel;
|
|
118
|
+
const value = this.resolveUpdateValue(prev, next);
|
|
119
|
+
if (value === prev) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
useChatInputStore.getState().setSnapshot({ selectedThinkingLevel: value });
|
|
102
123
|
};
|
|
103
124
|
|
|
104
125
|
selectSessionType = (value: string) => {
|
|
@@ -120,10 +141,40 @@ export class ChatInputManager {
|
|
|
120
141
|
this.setSelectedModel(value);
|
|
121
142
|
};
|
|
122
143
|
|
|
144
|
+
selectThinkingLevel = (value: ThinkingLevel) => {
|
|
145
|
+
this.setSelectedThinkingLevel(value);
|
|
146
|
+
};
|
|
147
|
+
|
|
123
148
|
selectSkills = (next: string[]) => {
|
|
124
149
|
this.setSelectedSkills(next);
|
|
125
150
|
};
|
|
126
151
|
|
|
152
|
+
private resolveThinkingForModel(modelOption: ChatModelOption | undefined, current: ThinkingLevel | null): ThinkingLevel | null {
|
|
153
|
+
const capability = modelOption?.thinkingCapability;
|
|
154
|
+
if (!capability || capability.supported.length === 0) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
if (current === 'off') {
|
|
158
|
+
return 'off';
|
|
159
|
+
}
|
|
160
|
+
if (current && capability.supported.includes(current)) {
|
|
161
|
+
return current;
|
|
162
|
+
}
|
|
163
|
+
if (capability.default && capability.supported.includes(capability.default)) {
|
|
164
|
+
return capability.default;
|
|
165
|
+
}
|
|
166
|
+
return 'off';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private reconcileThinkingForModel(model: string): void {
|
|
170
|
+
const snapshot = useChatInputStore.getState().snapshot;
|
|
171
|
+
const modelOption = snapshot.modelOptions.find((option) => option.value === model);
|
|
172
|
+
const nextThinking = this.resolveThinkingForModel(modelOption, snapshot.selectedThinkingLevel);
|
|
173
|
+
if (nextThinking !== snapshot.selectedThinkingLevel) {
|
|
174
|
+
useChatInputStore.getState().setSnapshot({ selectedThinkingLevel: nextThinking });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
127
178
|
private syncRemoteSessionType = async (normalizedType: string) => {
|
|
128
179
|
const sessionSnapshot = useChatSessionListStore.getState().snapshot;
|
|
129
180
|
const selectedSessionKey = sessionSnapshot.selectedSessionKey;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
2
|
import type { MarketplaceInstalledRecord } from '@/api/types';
|
|
3
|
+
import type { ThinkingLevel } from '@/api/types';
|
|
4
|
+
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
3
5
|
|
|
4
6
|
export type ChatInputSnapshot = {
|
|
5
7
|
isProviderStateResolved: boolean;
|
|
@@ -10,8 +12,9 @@ export type ChatInputSnapshot = {
|
|
|
10
12
|
stopDisabledReason: string | null;
|
|
11
13
|
sendError: string | null;
|
|
12
14
|
isSending: boolean;
|
|
13
|
-
modelOptions:
|
|
15
|
+
modelOptions: ChatModelOption[];
|
|
14
16
|
selectedModel: string;
|
|
17
|
+
selectedThinkingLevel: ThinkingLevel | null;
|
|
15
18
|
sessionTypeOptions: Array<{ value: string; label: string }>;
|
|
16
19
|
selectedSessionType?: string;
|
|
17
20
|
stopSupported: boolean;
|
|
@@ -39,6 +42,7 @@ const initialSnapshot: ChatInputSnapshot = {
|
|
|
39
42
|
isSending: false,
|
|
40
43
|
modelOptions: [],
|
|
41
44
|
selectedModel: '',
|
|
45
|
+
selectedThinkingLevel: null,
|
|
42
46
|
sessionTypeOptions: [],
|
|
43
47
|
selectedSessionType: undefined,
|
|
44
48
|
stopSupported: false,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo, useState } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { Check, ChevronsUpDown } from 'lucide-react';
|
|
3
3
|
import { Input } from '@/components/ui/input';
|
|
4
4
|
import { cn } from '@/lib/utils';
|
|
@@ -8,6 +8,7 @@ type SearchableModelInputProps = {
|
|
|
8
8
|
value: string;
|
|
9
9
|
onChange: (value: string) => void;
|
|
10
10
|
options: string[];
|
|
11
|
+
disabled?: boolean;
|
|
11
12
|
placeholder?: string;
|
|
12
13
|
className?: string;
|
|
13
14
|
inputClassName?: string;
|
|
@@ -33,6 +34,7 @@ export function SearchableModelInput({
|
|
|
33
34
|
value,
|
|
34
35
|
onChange,
|
|
35
36
|
options,
|
|
37
|
+
disabled = false,
|
|
36
38
|
placeholder,
|
|
37
39
|
className,
|
|
38
40
|
inputClassName,
|
|
@@ -43,6 +45,12 @@ export function SearchableModelInput({
|
|
|
43
45
|
}: SearchableModelInputProps) {
|
|
44
46
|
const [open, setOpen] = useState(false);
|
|
45
47
|
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (disabled && open) {
|
|
50
|
+
setOpen(false);
|
|
51
|
+
}
|
|
52
|
+
}, [disabled, open]);
|
|
53
|
+
|
|
46
54
|
const normalizedOptions = useMemo(() => normalizeOptions(options), [options]);
|
|
47
55
|
const query = value.trim().toLowerCase();
|
|
48
56
|
|
|
@@ -80,10 +88,15 @@ export function SearchableModelInput({
|
|
|
80
88
|
<Input
|
|
81
89
|
id={id}
|
|
82
90
|
value={value}
|
|
83
|
-
|
|
91
|
+
disabled={disabled}
|
|
92
|
+
onFocus={() => {
|
|
93
|
+
if (!disabled) {
|
|
94
|
+
setOpen(true);
|
|
95
|
+
}
|
|
96
|
+
}}
|
|
84
97
|
onChange={(event) => {
|
|
85
98
|
onChange(event.target.value);
|
|
86
|
-
if (!open) {
|
|
99
|
+
if (!open && !disabled) {
|
|
87
100
|
setOpen(true);
|
|
88
101
|
}
|
|
89
102
|
}}
|
|
@@ -103,13 +116,17 @@ export function SearchableModelInput({
|
|
|
103
116
|
type="button"
|
|
104
117
|
onMouseDown={(event) => event.preventDefault()}
|
|
105
118
|
onClick={() => setOpen((prev) => !prev)}
|
|
106
|
-
|
|
119
|
+
disabled={disabled}
|
|
120
|
+
className={cn(
|
|
121
|
+
'absolute inset-y-0 right-0 inline-flex w-10 items-center justify-center',
|
|
122
|
+
disabled ? 'cursor-not-allowed text-gray-300' : 'text-gray-400 hover:text-gray-600'
|
|
123
|
+
)}
|
|
107
124
|
aria-label="toggle model options"
|
|
108
125
|
>
|
|
109
126
|
<ChevronsUpDown className="h-4 w-4" />
|
|
110
127
|
</button>
|
|
111
128
|
|
|
112
|
-
{open && (
|
|
129
|
+
{open && !disabled && (
|
|
113
130
|
<div className="absolute z-20 mt-1 w-full overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg">
|
|
114
131
|
<div className="max-h-60 overflow-y-auto py-1">
|
|
115
132
|
{!hasExactMatch && value.trim().length > 0 && (
|