@nextclaw/ui 0.6.10 → 0.6.12
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/.eslintrc.cjs +10 -0
- package/CHANGELOG.md +16 -0
- package/dist/assets/ChannelsList-DBDjwf-X.js +1 -0
- package/dist/assets/ChatPage-C18sGGk1.js +36 -0
- package/dist/assets/DocBrowser-ZOplDEMS.js +1 -0
- package/dist/assets/LogoBadge-2LMzEMwe.js +1 -0
- package/dist/assets/MarketplacePage-D4JHYcB5.js +49 -0
- package/dist/assets/ModelConfig-DZVvdLFq.js +1 -0
- package/dist/assets/ProvidersList-Dum31480.js +1 -0
- package/dist/assets/{RuntimeConfig-BO6s-ls-.js → RuntimeConfig-4sb3mpkd.js} +1 -1
- package/dist/assets/SearchConfig-B4u_MxRG.js +1 -0
- package/dist/assets/{SecretsConfig-mayFdxpM.js → SecretsConfig-BQXblZvb.js} +2 -2
- package/dist/assets/SessionsConfig-Jk29xjQU.js +2 -0
- package/dist/assets/{card-BP5YnL-G.js → card-BekAnCgX.js} +1 -1
- package/dist/assets/config-layout-BHnOoweL.js +1 -0
- package/dist/assets/index-BXwjfCEO.css +1 -0
- package/dist/assets/index-Dl6t70wA.js +8 -0
- package/dist/assets/{input-B1D2QX0O.js → input-MMn_Na9q.js} +1 -1
- package/dist/assets/{label-DW0j-fXA.js → label-Dg2ydpN0.js} +1 -1
- package/dist/assets/{page-layout-Ch-H9gD-.js → page-layout-7K0rcz0I.js} +1 -1
- package/dist/assets/session-run-status-CAdjSqeb.js +3 -0
- package/dist/assets/{switch-_cZHlGKB.js → switch-DnDMlDVu.js} +1 -1
- package/dist/assets/{tabs-custom-ARxqYYjG.js → tabs-custom-khLM8lWj.js} +1 -1
- package/dist/assets/{useConfirmDialog-BaU7nIat.js → useConfirmDialog-BYA1XnVU.js} +2 -2
- package/dist/assets/{vendor-C--HHaLf.js → vendor-d7E8OgNx.js} +84 -84
- package/dist/index.html +3 -3
- package/package.json +4 -2
- package/src/App.tsx +3 -2
- package/src/api/config.ts +212 -200
- package/src/api/types.ts +93 -24
- package/src/components/chat/ChatConversationPanel.tsx +102 -121
- package/src/components/chat/ChatPage.tsx +165 -437
- package/src/components/chat/ChatSidebar.tsx +30 -36
- package/src/components/chat/ChatThread.tsx +73 -131
- package/src/components/chat/chat-input/ChatInputBarView.tsx +82 -0
- package/src/components/chat/chat-input/ChatInputBottomToolbar.tsx +71 -0
- package/src/components/chat/chat-input/components/ChatInputModelStateHint.tsx +39 -0
- package/src/components/chat/chat-input/components/ChatInputSelectedSkillsSection.tsx +31 -0
- package/src/components/chat/chat-input/components/ChatInputSlashPanelSection.tsx +112 -0
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputAttachButton.tsx +24 -0
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputModelSelector.tsx +58 -0
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSendControls.tsx +56 -0
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSessionTypeSelector.tsx +40 -0
- package/src/components/chat/chat-input/useChatInputBarController.ts +313 -0
- package/src/components/chat/chat-input.types.ts +15 -0
- package/src/components/chat/chat-page-data.ts +121 -0
- package/src/components/chat/chat-page-runtime.ts +221 -0
- package/src/components/chat/chat-session-route.ts +59 -0
- package/src/components/chat/chat-stream/nextbot-parsers.ts +52 -0
- package/src/components/chat/chat-stream/nextbot-runtime-agent.ts +413 -0
- package/src/components/chat/chat-stream/stream-event-adapter.ts +98 -0
- package/src/components/chat/chat-stream/transport.ts +159 -0
- package/src/components/chat/chat-stream/types.ts +76 -0
- package/src/components/chat/managers/chat-input.manager.ts +142 -0
- package/src/components/chat/managers/chat-run-status.manager.ts +32 -0
- package/src/components/chat/managers/chat-session-list.manager.ts +77 -0
- package/src/components/chat/managers/chat-stream-actions.manager.ts +34 -0
- package/src/components/chat/managers/chat-thread.manager.ts +86 -0
- package/src/components/chat/managers/chat-ui.manager.ts +65 -0
- package/src/components/chat/presenter/chat-presenter-context.tsx +25 -0
- package/src/components/chat/presenter/chat.presenter.ts +32 -0
- package/src/components/chat/stores/chat-input.store.ts +62 -0
- package/src/components/chat/stores/chat-run-status.store.ts +30 -0
- package/src/components/chat/stores/chat-session-list.store.ts +34 -0
- package/src/components/chat/stores/chat-thread.store.ts +52 -0
- package/src/components/chat/useChatRuntimeController.ts +134 -0
- package/src/components/chat/useChatSessionTypeState.ts +148 -0
- package/src/components/common/MaskedInput.tsx +1 -1
- package/src/components/config/SearchConfig.tsx +297 -0
- package/src/components/layout/Sidebar.tsx +6 -1
- package/src/hooks/useConfig.ts +48 -1
- package/src/hooks/useObservable.ts +20 -0
- package/src/lib/chat-message.ts +2 -202
- package/src/lib/chat-runtime-utils.ts +250 -0
- package/src/lib/i18n.ts +31 -0
- package/tsconfig.json +2 -1
- package/vite.config.ts +2 -1
- package/dist/assets/ChannelsList-TyMb5Mgz.js +0 -1
- package/dist/assets/ChatPage-CQerYqvy.js +0 -34
- package/dist/assets/DocBrowser-CNtrA0ps.js +0 -1
- package/dist/assets/LogoBadge-BLqiOM5D.js +0 -1
- package/dist/assets/MarketplacePage-CotZxxNe.js +0 -49
- package/dist/assets/ModelConfig-CCsQ8KFq.js +0 -1
- package/dist/assets/ProvidersList-BYYX5K_g.js +0 -1
- package/dist/assets/SessionsConfig-DAIczdBj.js +0 -2
- package/dist/assets/index-BUiahmWm.css +0 -1
- package/dist/assets/index-D6_5HaDl.js +0 -7
- package/dist/assets/session-run-status-BUYsQeWs.js +0 -5
- package/src/components/chat/ChatInputBar.tsx +0 -590
- package/src/components/chat/useChatStreamController.ts +0 -591
package/src/api/types.ts
CHANGED
|
@@ -74,6 +74,52 @@ export type ProviderConnectionTestResult = {
|
|
|
74
74
|
hint?: string;
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
+
export type SearchProviderName = "bocha" | "brave";
|
|
78
|
+
export type BochaFreshnessValue = "noLimit" | "oneDay" | "oneWeek" | "oneMonth" | "oneYear" | string;
|
|
79
|
+
|
|
80
|
+
export type SearchProviderConfigView = {
|
|
81
|
+
enabled: boolean;
|
|
82
|
+
apiKeySet: boolean;
|
|
83
|
+
apiKeyMasked?: string;
|
|
84
|
+
baseUrl: string;
|
|
85
|
+
docsUrl?: string;
|
|
86
|
+
summary?: boolean;
|
|
87
|
+
freshness?: BochaFreshnessValue;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type SearchConfigView = {
|
|
91
|
+
provider: SearchProviderName;
|
|
92
|
+
enabledProviders: SearchProviderName[];
|
|
93
|
+
defaults: {
|
|
94
|
+
maxResults: number;
|
|
95
|
+
};
|
|
96
|
+
providers: {
|
|
97
|
+
bocha: SearchProviderConfigView;
|
|
98
|
+
brave: SearchProviderConfigView;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type SearchConfigUpdate = {
|
|
103
|
+
provider?: SearchProviderName;
|
|
104
|
+
enabledProviders?: SearchProviderName[];
|
|
105
|
+
defaults?: {
|
|
106
|
+
maxResults?: number;
|
|
107
|
+
};
|
|
108
|
+
providers?: {
|
|
109
|
+
bocha?: {
|
|
110
|
+
apiKey?: string | null;
|
|
111
|
+
baseUrl?: string | null;
|
|
112
|
+
docsUrl?: string | null;
|
|
113
|
+
summary?: boolean;
|
|
114
|
+
freshness?: BochaFreshnessValue | null;
|
|
115
|
+
};
|
|
116
|
+
brave?: {
|
|
117
|
+
apiKey?: string | null;
|
|
118
|
+
baseUrl?: string | null;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
77
123
|
export type ProviderAuthStartResult = {
|
|
78
124
|
provider: string;
|
|
79
125
|
kind: "device_code";
|
|
@@ -146,6 +192,8 @@ export type SessionEntryView = {
|
|
|
146
192
|
updatedAt: string;
|
|
147
193
|
label?: string;
|
|
148
194
|
preferredModel?: string;
|
|
195
|
+
sessionType: string;
|
|
196
|
+
sessionTypeMutable: boolean;
|
|
149
197
|
messageCount: number;
|
|
150
198
|
lastRole?: string;
|
|
151
199
|
lastTimestamp?: string;
|
|
@@ -177,6 +225,8 @@ export type SessionHistoryView = {
|
|
|
177
225
|
key: string;
|
|
178
226
|
totalMessages: number;
|
|
179
227
|
totalEvents: number;
|
|
228
|
+
sessionType: string;
|
|
229
|
+
sessionTypeMutable: boolean;
|
|
180
230
|
metadata: Record<string, unknown>;
|
|
181
231
|
messages: SessionMessageView[];
|
|
182
232
|
events: SessionEventView[];
|
|
@@ -185,6 +235,7 @@ export type SessionHistoryView = {
|
|
|
185
235
|
export type SessionPatchUpdate = {
|
|
186
236
|
label?: string | null;
|
|
187
237
|
preferredModel?: string | null;
|
|
238
|
+
sessionType?: string | null;
|
|
188
239
|
clearHistory?: boolean;
|
|
189
240
|
};
|
|
190
241
|
|
|
@@ -208,11 +259,42 @@ export type ChatTurnView = {
|
|
|
208
259
|
durationMs: number;
|
|
209
260
|
};
|
|
210
261
|
|
|
262
|
+
export type ChatTurnStreamReadyEvent = {
|
|
263
|
+
sessionKey: string;
|
|
264
|
+
requestedAt?: string;
|
|
265
|
+
runId?: string;
|
|
266
|
+
stopSupported?: boolean;
|
|
267
|
+
stopReason?: string;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export type ChatTurnStreamDeltaEvent = {
|
|
271
|
+
delta: string;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export type ChatTurnStreamSessionEvent = {
|
|
275
|
+
data: SessionEventView;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export type ChatTurnStreamErrorEvent = {
|
|
279
|
+
code?: string;
|
|
280
|
+
message?: string;
|
|
281
|
+
};
|
|
282
|
+
|
|
211
283
|
export type ChatCapabilitiesView = {
|
|
212
284
|
stopSupported: boolean;
|
|
213
285
|
stopReason?: string;
|
|
214
286
|
};
|
|
215
287
|
|
|
288
|
+
export type ChatSessionTypeOptionView = {
|
|
289
|
+
value: string;
|
|
290
|
+
label: string;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export type ChatSessionTypesView = {
|
|
294
|
+
defaultType: string;
|
|
295
|
+
options: ChatSessionTypeOptionView[];
|
|
296
|
+
};
|
|
297
|
+
|
|
216
298
|
export type ChatCommandOptionView = {
|
|
217
299
|
name: string;
|
|
218
300
|
description: string;
|
|
@@ -267,30 +349,6 @@ export type ChatRunListView = {
|
|
|
267
349
|
total: number;
|
|
268
350
|
};
|
|
269
351
|
|
|
270
|
-
export type ChatTurnStreamReadyEvent = {
|
|
271
|
-
event: "ready";
|
|
272
|
-
sessionKey: string;
|
|
273
|
-
requestedAt: string;
|
|
274
|
-
runId?: string;
|
|
275
|
-
stopSupported?: boolean;
|
|
276
|
-
stopReason?: string;
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
export type ChatTurnStreamDeltaEvent = {
|
|
280
|
-
event: "delta";
|
|
281
|
-
delta: string;
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
export type ChatTurnStreamSessionEvent = {
|
|
285
|
-
event: "session_event";
|
|
286
|
-
data: SessionEventView;
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
export type ChatTurnStreamFinalEvent = {
|
|
290
|
-
event: "final";
|
|
291
|
-
data: ChatTurnView;
|
|
292
|
-
};
|
|
293
|
-
|
|
294
352
|
export type CronScheduleView =
|
|
295
353
|
| { kind: "at"; atMs?: number | null }
|
|
296
354
|
| { kind: "every"; everyMs?: number | null }
|
|
@@ -433,6 +491,7 @@ export type ConfigView = {
|
|
|
433
491
|
};
|
|
434
492
|
};
|
|
435
493
|
providers: Record<string, ProviderConfigView>;
|
|
494
|
+
search: SearchConfigView;
|
|
436
495
|
channels: Record<string, Record<string, unknown>>;
|
|
437
496
|
bindings?: AgentBindingView[];
|
|
438
497
|
session?: SessionConfigView;
|
|
@@ -495,8 +554,18 @@ export type ChannelSpecView = {
|
|
|
495
554
|
};
|
|
496
555
|
};
|
|
497
556
|
|
|
557
|
+
export type SearchProviderSpecView = {
|
|
558
|
+
name: SearchProviderName;
|
|
559
|
+
displayName: string;
|
|
560
|
+
description: string;
|
|
561
|
+
docsUrl?: string;
|
|
562
|
+
isDefault?: boolean;
|
|
563
|
+
supportsSummary?: boolean;
|
|
564
|
+
};
|
|
565
|
+
|
|
498
566
|
export type ConfigMetaView = {
|
|
499
567
|
providers: ProviderSpecView[];
|
|
568
|
+
search: SearchProviderSpecView[];
|
|
500
569
|
channels: ChannelSpecView[];
|
|
501
570
|
};
|
|
502
571
|
|
|
@@ -1,44 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { MarketplaceInstalledRecord, SessionEventView } from '@/api/types';
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
|
|
3
2
|
import { Button } from '@/components/ui/button';
|
|
4
3
|
import { ChatThread } from '@/components/chat/ChatThread';
|
|
5
|
-
import { ChatInputBar, type ChatModelOption } from '@/components/chat/ChatInputBar';
|
|
6
4
|
import { ChatWelcome } from '@/components/chat/ChatWelcome';
|
|
5
|
+
import { ChatInputBarView } from '@/components/chat/chat-input/ChatInputBarView';
|
|
6
|
+
import { usePresenter } from '@/components/chat/presenter/chat-presenter-context';
|
|
7
|
+
import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
|
|
7
8
|
import { t } from '@/lib/i18n';
|
|
9
|
+
import { cn } from '@/lib/utils';
|
|
8
10
|
import { Trash2 } from 'lucide-react';
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
isProviderStateResolved: boolean;
|
|
12
|
-
modelOptions: ChatModelOption[];
|
|
13
|
-
selectedModel: string;
|
|
14
|
-
onSelectedModelChange: (value: string) => void;
|
|
15
|
-
onGoToProviders: () => void;
|
|
16
|
-
skillRecords: MarketplaceInstalledRecord[];
|
|
17
|
-
isSkillsLoading?: boolean;
|
|
18
|
-
selectedSkills: string[];
|
|
19
|
-
onSelectedSkillsChange: (next: string[]) => void;
|
|
20
|
-
selectedSessionKey: string | null;
|
|
21
|
-
sessionDisplayName?: string;
|
|
22
|
-
canDeleteSession: boolean;
|
|
23
|
-
isDeletePending: boolean;
|
|
24
|
-
onDeleteSession: () => void;
|
|
25
|
-
onCreateSession: () => void;
|
|
26
|
-
threadRef: MutableRefObject<HTMLDivElement | null>;
|
|
27
|
-
onThreadScroll: () => void;
|
|
28
|
-
isHistoryLoading: boolean;
|
|
29
|
-
mergedEvents: SessionEventView[];
|
|
30
|
-
isSending: boolean;
|
|
31
|
-
isAwaitingAssistantOutput: boolean;
|
|
32
|
-
streamingAssistantText: string;
|
|
33
|
-
draft: string;
|
|
34
|
-
onDraftChange: (value: string) => void;
|
|
35
|
-
onSend: () => Promise<void> | void;
|
|
36
|
-
onStop: () => Promise<void> | void;
|
|
37
|
-
canStopGeneration: boolean;
|
|
38
|
-
stopDisabledReason?: string | null;
|
|
39
|
-
sendError?: string | null;
|
|
40
|
-
queuedCount: number;
|
|
41
|
-
};
|
|
12
|
+
const STICKY_BOTTOM_THRESHOLD_PX = 10;
|
|
42
13
|
|
|
43
14
|
function ChatConversationSkeleton() {
|
|
44
15
|
return (
|
|
@@ -68,80 +39,101 @@ function ChatConversationSkeleton() {
|
|
|
68
39
|
);
|
|
69
40
|
}
|
|
70
41
|
|
|
71
|
-
export function ChatConversationPanel({
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
onCreateSession,
|
|
87
|
-
threadRef,
|
|
88
|
-
onThreadScroll,
|
|
89
|
-
isHistoryLoading,
|
|
90
|
-
mergedEvents,
|
|
91
|
-
isSending,
|
|
92
|
-
isAwaitingAssistantOutput,
|
|
93
|
-
streamingAssistantText,
|
|
94
|
-
draft,
|
|
95
|
-
onDraftChange,
|
|
96
|
-
onSend,
|
|
97
|
-
onStop,
|
|
98
|
-
canStopGeneration,
|
|
99
|
-
stopDisabledReason,
|
|
100
|
-
sendError,
|
|
101
|
-
queuedCount,
|
|
102
|
-
}: ChatConversationPanelProps) {
|
|
103
|
-
const showWelcome = !selectedSessionKey && mergedEvents.length === 0;
|
|
104
|
-
const hasConfiguredModel = modelOptions.length > 0;
|
|
105
|
-
const shouldShowProviderHint = isProviderStateResolved && !hasConfiguredModel;
|
|
42
|
+
export function ChatConversationPanel() {
|
|
43
|
+
const presenter = usePresenter();
|
|
44
|
+
const snapshot = useChatThreadStore((state) => state.snapshot);
|
|
45
|
+
const fallbackThreadRef = useRef<HTMLDivElement | null>(null);
|
|
46
|
+
const threadRef = snapshot.threadRef ?? fallbackThreadRef;
|
|
47
|
+
|
|
48
|
+
// --- Sticky-to-bottom scroll state ---
|
|
49
|
+
const isStickyRef = useRef(true);
|
|
50
|
+
const isProgrammaticScrollRef = useRef(false);
|
|
51
|
+
const previousSessionKeyRef = useRef<string | null>(null);
|
|
52
|
+
const pendingInitialScrollRef = useRef(false);
|
|
53
|
+
|
|
54
|
+
const showWelcome = !snapshot.selectedSessionKey && snapshot.uiMessages.length === 0 && !snapshot.isSending;
|
|
55
|
+
const hasConfiguredModel = snapshot.modelOptions.length > 0;
|
|
56
|
+
const shouldShowProviderHint = snapshot.isProviderStateResolved && !hasConfiguredModel;
|
|
106
57
|
const hideEmptyHint =
|
|
107
|
-
isHistoryLoading &&
|
|
108
|
-
|
|
109
|
-
!isSending &&
|
|
110
|
-
!isAwaitingAssistantOutput
|
|
111
|
-
|
|
58
|
+
snapshot.isHistoryLoading &&
|
|
59
|
+
snapshot.uiMessages.length === 0 &&
|
|
60
|
+
!snapshot.isSending &&
|
|
61
|
+
!snapshot.isAwaitingAssistantOutput;
|
|
62
|
+
|
|
63
|
+
const scrollToBottom = useCallback(() => {
|
|
64
|
+
const el = threadRef.current;
|
|
65
|
+
if (!el) return;
|
|
66
|
+
isProgrammaticScrollRef.current = true;
|
|
67
|
+
el.scrollTop = el.scrollHeight;
|
|
68
|
+
}, [threadRef]);
|
|
69
|
+
|
|
70
|
+
const handleScroll = useCallback(() => {
|
|
71
|
+
// Skip sticky check for programmatic scrolls
|
|
72
|
+
if (isProgrammaticScrollRef.current) {
|
|
73
|
+
isProgrammaticScrollRef.current = false;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const el = threadRef.current;
|
|
77
|
+
if (!el) return;
|
|
78
|
+
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
79
|
+
isStickyRef.current = distanceFromBottom <= STICKY_BOTTOM_THRESHOLD_PX;
|
|
80
|
+
}, [threadRef]);
|
|
81
|
+
|
|
82
|
+
// Session change → force sticky + schedule initial scroll
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (previousSessionKeyRef.current === snapshot.selectedSessionKey) return;
|
|
85
|
+
previousSessionKeyRef.current = snapshot.selectedSessionKey;
|
|
86
|
+
isStickyRef.current = true;
|
|
87
|
+
pendingInitialScrollRef.current = true;
|
|
88
|
+
}, [snapshot.selectedSessionKey]);
|
|
89
|
+
|
|
90
|
+
// Initial scroll after history loads for a new session
|
|
91
|
+
useLayoutEffect(() => {
|
|
92
|
+
if (!pendingInitialScrollRef.current) return;
|
|
93
|
+
if (snapshot.isHistoryLoading || snapshot.uiMessages.length === 0) return;
|
|
94
|
+
pendingInitialScrollRef.current = false;
|
|
95
|
+
scrollToBottom();
|
|
96
|
+
}, [scrollToBottom, snapshot.isHistoryLoading, snapshot.uiMessages]);
|
|
97
|
+
|
|
98
|
+
// Streaming updates: keep bottom visible while still sticky.
|
|
99
|
+
useLayoutEffect(() => {
|
|
100
|
+
if (!isStickyRef.current) return;
|
|
101
|
+
if (snapshot.uiMessages.length === 0) return;
|
|
102
|
+
scrollToBottom();
|
|
103
|
+
}, [scrollToBottom, snapshot.uiMessages]);
|
|
112
104
|
|
|
113
|
-
if (!isProviderStateResolved) {
|
|
105
|
+
if (!snapshot.isProviderStateResolved) {
|
|
114
106
|
return <ChatConversationSkeleton />;
|
|
115
107
|
}
|
|
116
108
|
|
|
117
109
|
return (
|
|
118
110
|
<section className="flex-1 min-h-0 flex flex-col overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
|
|
119
|
-
{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
</
|
|
127
|
-
<Button
|
|
128
|
-
variant="ghost"
|
|
129
|
-
size="icon"
|
|
130
|
-
className="rounded-lg shrink-0 text-gray-400 hover:text-destructive"
|
|
131
|
-
onClick={onDeleteSession}
|
|
132
|
-
disabled={!canDeleteSession || isDeletePending}
|
|
133
|
-
>
|
|
134
|
-
<Trash2 className="h-4 w-4" />
|
|
135
|
-
</Button>
|
|
111
|
+
<div className={cn(
|
|
112
|
+
"px-5 border-b border-gray-200/60 bg-white/80 backdrop-blur-sm flex items-center justify-between shrink-0 overflow-hidden transition-all duration-200",
|
|
113
|
+
snapshot.selectedSessionKey ? "py-3 opacity-100" : "h-0 py-0 opacity-0 border-b-0"
|
|
114
|
+
)}>
|
|
115
|
+
<div className="min-w-0 flex-1">
|
|
116
|
+
<span className="text-sm font-medium text-gray-700 truncate">
|
|
117
|
+
{snapshot.sessionDisplayName || snapshot.selectedSessionKey}
|
|
118
|
+
</span>
|
|
136
119
|
</div>
|
|
137
|
-
|
|
120
|
+
<Button
|
|
121
|
+
variant="ghost"
|
|
122
|
+
size="icon"
|
|
123
|
+
className="rounded-lg shrink-0 text-gray-400 hover:text-destructive"
|
|
124
|
+
onClick={presenter.chatThreadManager.deleteSession}
|
|
125
|
+
disabled={!snapshot.canDeleteSession || snapshot.isDeletePending}
|
|
126
|
+
>
|
|
127
|
+
<Trash2 className="h-4 w-4" />
|
|
128
|
+
</Button>
|
|
129
|
+
</div>
|
|
138
130
|
|
|
139
131
|
{shouldShowProviderHint && (
|
|
140
132
|
<div className="px-5 py-2.5 border-b border-amber-200/70 bg-amber-50/70 flex items-center justify-between gap-3 shrink-0">
|
|
141
133
|
<span className="text-xs text-amber-800">{t('chatModelNoOptions')}</span>
|
|
142
134
|
<button
|
|
143
135
|
type="button"
|
|
144
|
-
onClick={
|
|
136
|
+
onClick={presenter.chatThreadManager.goToProviders}
|
|
145
137
|
className="text-xs font-semibold text-amber-900 underline-offset-2 hover:underline"
|
|
146
138
|
>
|
|
147
139
|
{t('chatGoConfigureProvider')}
|
|
@@ -149,42 +141,31 @@ export function ChatConversationPanel({
|
|
|
149
141
|
</div>
|
|
150
142
|
)}
|
|
151
143
|
|
|
152
|
-
{
|
|
153
|
-
|
|
144
|
+
{snapshot.sessionTypeUnavailable && snapshot.sessionTypeUnavailableMessage?.trim() && (
|
|
145
|
+
<div className="px-5 py-2.5 border-b border-amber-200/70 bg-amber-50/70 shrink-0">
|
|
146
|
+
<span className="text-xs text-amber-800">{snapshot.sessionTypeUnavailableMessage}</span>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
<div
|
|
151
|
+
ref={threadRef}
|
|
152
|
+
onScroll={handleScroll}
|
|
153
|
+
className="flex-1 min-h-0 overflow-y-auto custom-scrollbar"
|
|
154
|
+
>
|
|
154
155
|
{showWelcome ? (
|
|
155
|
-
<ChatWelcome onCreateSession={
|
|
156
|
+
<ChatWelcome onCreateSession={presenter.chatThreadManager.createSession} />
|
|
156
157
|
) : hideEmptyHint ? (
|
|
157
158
|
<div className="h-full" />
|
|
158
|
-
) :
|
|
159
|
+
) : snapshot.uiMessages.length === 0 ? (
|
|
159
160
|
<div className="px-5 py-5 text-sm text-gray-500">{t('chatNoMessages')}</div>
|
|
160
161
|
) : (
|
|
161
162
|
<div className="mx-auto w-full max-w-[min(1120px,100%)] px-6 py-5">
|
|
162
|
-
<ChatThread
|
|
163
|
+
<ChatThread uiMessages={snapshot.uiMessages} isSending={snapshot.isSending && snapshot.isAwaitingAssistantOutput} />
|
|
163
164
|
</div>
|
|
164
165
|
)}
|
|
165
166
|
</div>
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
<ChatInputBar
|
|
169
|
-
isProviderStateResolved={isProviderStateResolved}
|
|
170
|
-
draft={draft}
|
|
171
|
-
onDraftChange={onDraftChange}
|
|
172
|
-
onSend={onSend}
|
|
173
|
-
onStop={onStop}
|
|
174
|
-
onGoToProviders={onGoToProviders}
|
|
175
|
-
canStopGeneration={canStopGeneration}
|
|
176
|
-
stopDisabledReason={stopDisabledReason}
|
|
177
|
-
sendError={sendError}
|
|
178
|
-
isSending={isSending}
|
|
179
|
-
queuedCount={queuedCount}
|
|
180
|
-
modelOptions={modelOptions}
|
|
181
|
-
selectedModel={selectedModel}
|
|
182
|
-
onSelectedModelChange={onSelectedModelChange}
|
|
183
|
-
skillRecords={skillRecords}
|
|
184
|
-
isSkillsLoading={isSkillsLoading}
|
|
185
|
-
selectedSkills={selectedSkills}
|
|
186
|
-
onSelectedSkillsChange={onSelectedSkillsChange}
|
|
187
|
-
/>
|
|
168
|
+
<ChatInputBarView />
|
|
188
169
|
</section>
|
|
189
170
|
);
|
|
190
171
|
}
|