vzcode 2.15.0 → 2.16.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.
- package/dist/assets/{index-BpUpNto4.js → index-C2BLPyQP.js} +138 -125
- package/dist/assets/{index-DLm5FQ0E.css → index-DVR90LwF.css} +1 -1
- package/dist/index.html +2 -2
- package/dist/server/aiChatHandler/chatOperations.js +168 -35
- package/dist/server/aiChatHandler/index.js +11 -30
- package/dist/server/aiChatHandler/llmStreaming.js +105 -118
- package/package.json +11 -11
- package/src/client/AIAssist/AIAssistWidget/index.tsx +12 -1
- package/src/client/App/useShareDB.ts +1 -1
- package/src/client/CodeEditor/index.tsx +9 -10
- package/src/client/VZCodeContext/types.ts +3 -1
- package/src/client/VZCodeContext/useVZCodeState.ts +7 -5
- package/src/client/VZRight.tsx +10 -2
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +1 -7
- package/src/client/VZSidebar/AIChat/DiffView.scss +1 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +72 -29
- package/src/client/VZSidebar/AIChat/FileEditingIndicator.tsx +81 -0
- package/src/client/VZSidebar/AIChat/IndividualFileDiff.tsx +86 -0
- package/src/client/VZSidebar/AIChat/JumpToLatestButton.tsx +64 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +68 -53
- package/src/client/VZSidebar/AIChat/MessageList.tsx +139 -118
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +63 -21
- package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +4 -118
- package/src/client/VZSidebar/AIChat/index.tsx +62 -19
- package/src/client/VZSidebar/AIChat/styles.scss +159 -16
- package/src/client/VZSidebar/Item.tsx +2 -2
- package/src/client/VZSidebar/Search.tsx +13 -6
- package/src/client/VZSidebar/VisualEditor/VisualEditor.tsx +39 -23
- package/src/client/VZSidebar/VisualEditor/utils.ts +1 -1
- package/src/client/VZSidebar/index.tsx +12 -10
- package/src/client/VZSidebar/useDragAndDrop.tsx +225 -214
- package/src/client/featureFlags.ts +3 -0
- package/src/client/hooks/useAutoScroll.ts +329 -0
- package/src/client/tabsSearchParameters.ts +1 -17
- package/src/client/useFileCRUD.ts +5 -2
- package/src/client/useKeyboardShortcuts.ts +7 -0
- package/src/client/useOpenDirectories.ts +2 -1
- package/src/client/usePrettier/index.ts +1 -1
- package/src/client/useURLSync.ts +1 -0
- package/src/client/utils/scrollUtils.ts +170 -0
- package/src/client/vzReducer/searchReducer.ts +6 -3
- package/src/server/aiChatHandler/chatOperations.ts +224 -43
- package/src/server/aiChatHandler/index.ts +8 -48
- package/src/server/aiChatHandler/llmStreaming.ts +149 -170
- package/src/types.ts +81 -1
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
memo,
|
|
3
|
-
useRef,
|
|
4
|
-
useEffect,
|
|
5
|
-
useCallback,
|
|
6
|
-
useState,
|
|
7
|
-
} from 'react';
|
|
1
|
+
import { memo } from 'react';
|
|
8
2
|
import Markdown from 'react-markdown';
|
|
9
3
|
import remarkGfm from 'remark-gfm';
|
|
10
4
|
|
|
@@ -17,115 +11,6 @@ const ThinkingScratchpadComponent = ({
|
|
|
17
11
|
content,
|
|
18
12
|
isVisible,
|
|
19
13
|
}: ThinkingScratchpadProps) => {
|
|
20
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
21
|
-
const [isUserScrolled, setIsUserScrolled] =
|
|
22
|
-
useState(false);
|
|
23
|
-
const [autoScrollEnabled, setAutoScrollEnabled] =
|
|
24
|
-
useState(true);
|
|
25
|
-
const scrollTimeoutRef =
|
|
26
|
-
useRef<ReturnType<typeof setTimeout>>();
|
|
27
|
-
|
|
28
|
-
// Check if the user is scrolled to the bottom
|
|
29
|
-
const isScrolledToBottom = useCallback(() => {
|
|
30
|
-
const container = contentRef.current;
|
|
31
|
-
if (!container) return true;
|
|
32
|
-
|
|
33
|
-
const threshold = 10; // Allow 10px tolerance for "at bottom"
|
|
34
|
-
const { scrollTop, scrollHeight, clientHeight } =
|
|
35
|
-
container;
|
|
36
|
-
return (
|
|
37
|
-
scrollHeight - scrollTop - clientHeight < threshold
|
|
38
|
-
);
|
|
39
|
-
}, []);
|
|
40
|
-
|
|
41
|
-
// Smooth scroll to bottom
|
|
42
|
-
const scrollToBottom = useCallback(() => {
|
|
43
|
-
if (!autoScrollEnabled) return;
|
|
44
|
-
|
|
45
|
-
const container = contentRef.current;
|
|
46
|
-
if (container) {
|
|
47
|
-
container.scrollTo({
|
|
48
|
-
top: container.scrollHeight,
|
|
49
|
-
behavior: 'smooth',
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}, [autoScrollEnabled]);
|
|
53
|
-
|
|
54
|
-
// Handle scroll events to detect user manual scrolling
|
|
55
|
-
const handleScroll = useCallback(() => {
|
|
56
|
-
const container = contentRef.current;
|
|
57
|
-
if (!container) return;
|
|
58
|
-
|
|
59
|
-
const isAtBottom = isScrolledToBottom();
|
|
60
|
-
|
|
61
|
-
// Clear any existing timeout
|
|
62
|
-
if (scrollTimeoutRef.current) {
|
|
63
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// If user scrolled up from bottom, disable auto-scroll
|
|
67
|
-
if (!isAtBottom && !isUserScrolled) {
|
|
68
|
-
setIsUserScrolled(true);
|
|
69
|
-
setAutoScrollEnabled(false);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// If user scrolled back to bottom, re-enable auto-scroll after a brief delay
|
|
73
|
-
if (isAtBottom && isUserScrolled) {
|
|
74
|
-
scrollTimeoutRef.current = setTimeout(() => {
|
|
75
|
-
setIsUserScrolled(false);
|
|
76
|
-
setAutoScrollEnabled(true);
|
|
77
|
-
}, 300); // 300ms delay to prevent flickering
|
|
78
|
-
}
|
|
79
|
-
}, [isUserScrolled, isScrolledToBottom]);
|
|
80
|
-
|
|
81
|
-
// Auto-scroll when content changes, but only if auto-scroll is enabled
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
if (
|
|
84
|
-
autoScrollEnabled &&
|
|
85
|
-
!isUserScrolled &&
|
|
86
|
-
isVisible &&
|
|
87
|
-
content
|
|
88
|
-
) {
|
|
89
|
-
// Use a short debounce to prevent multiple scroll calls during rapid updates
|
|
90
|
-
if (scrollTimeoutRef.current) {
|
|
91
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
scrollTimeoutRef.current = setTimeout(() => {
|
|
95
|
-
scrollToBottom();
|
|
96
|
-
}, 50); // 50ms debounce
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return () => {
|
|
100
|
-
if (scrollTimeoutRef.current) {
|
|
101
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
}, [
|
|
105
|
-
content,
|
|
106
|
-
autoScrollEnabled,
|
|
107
|
-
isUserScrolled,
|
|
108
|
-
isVisible,
|
|
109
|
-
scrollToBottom,
|
|
110
|
-
]);
|
|
111
|
-
|
|
112
|
-
// Clean up timeout on unmount
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
return () => {
|
|
115
|
-
if (scrollTimeoutRef.current) {
|
|
116
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
}, []);
|
|
120
|
-
|
|
121
|
-
// Reset scroll state when component becomes visible
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
if (isVisible) {
|
|
124
|
-
setIsUserScrolled(false);
|
|
125
|
-
setAutoScrollEnabled(true);
|
|
126
|
-
}
|
|
127
|
-
}, [isVisible]);
|
|
128
|
-
|
|
129
14
|
if (!isVisible || !content) {
|
|
130
15
|
return null;
|
|
131
16
|
}
|
|
@@ -140,8 +25,9 @@ const ThinkingScratchpadComponent = ({
|
|
|
140
25
|
</div>
|
|
141
26
|
<div
|
|
142
27
|
className="thinking-scratchpad-content"
|
|
143
|
-
|
|
144
|
-
|
|
28
|
+
role="log"
|
|
29
|
+
aria-live="polite"
|
|
30
|
+
aria-relevant="additions"
|
|
145
31
|
>
|
|
146
32
|
<Markdown remarkPlugins={[remarkGfm]}>
|
|
147
33
|
{content}
|
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
import { VZCodeContext } from '../../VZCodeContext';
|
|
8
8
|
import { MessageList } from './MessageList';
|
|
9
9
|
import { ChatInput } from './ChatInput';
|
|
10
|
+
import { ExtendedVizChat } from '../../../types.js';
|
|
11
|
+
import { useAutoScroll } from '../../hooks/useAutoScroll';
|
|
12
|
+
import { JumpToLatestButton } from './JumpToLatestButton';
|
|
10
13
|
import './styles.scss';
|
|
11
14
|
|
|
12
15
|
const DEBUG = false;
|
|
@@ -50,13 +53,11 @@ export const AIChat = () => {
|
|
|
50
53
|
content,
|
|
51
54
|
aiChatMode,
|
|
52
55
|
aiChatMessage,
|
|
53
|
-
isLoading,
|
|
54
56
|
currentChatId,
|
|
55
57
|
selectedChatId,
|
|
56
58
|
setSelectedChatId,
|
|
57
59
|
aiErrorMessage,
|
|
58
60
|
setAIChatMode,
|
|
59
|
-
clearStoredAIPrompt,
|
|
60
61
|
getStoredAIPrompt,
|
|
61
62
|
setAIChatMessage,
|
|
62
63
|
handleSendMessage,
|
|
@@ -64,17 +65,45 @@ export const AIChat = () => {
|
|
|
64
65
|
navigateMessageHistoryUp,
|
|
65
66
|
navigateMessageHistoryDown,
|
|
66
67
|
resetMessageHistoryNavigation,
|
|
68
|
+
enableMinimalEditFlow,
|
|
67
69
|
} = useContext(VZCodeContext);
|
|
68
70
|
|
|
71
|
+
// Use the new simplified auto-scroll hook
|
|
72
|
+
const {
|
|
73
|
+
containerRef: messagesContainerRef,
|
|
74
|
+
showJumpButton,
|
|
75
|
+
onNewEvent,
|
|
76
|
+
onJumpToLatest,
|
|
77
|
+
beforeRender,
|
|
78
|
+
afterRender,
|
|
79
|
+
} = useAutoScroll({ threshold: 24 });
|
|
80
|
+
|
|
81
|
+
// TODO fix this - currently always false
|
|
82
|
+
console.log('showJumpButton:', showJumpButton);
|
|
83
|
+
|
|
69
84
|
// Get the active chat ID and chat data
|
|
70
85
|
// If selectedChatId is set, use it; otherwise, if no chat selected, don't default to currentChatId
|
|
71
86
|
const activeChatId = selectedChatId;
|
|
72
87
|
const currentChat = activeChatId
|
|
73
88
|
? content?.chats?.[activeChatId]
|
|
74
89
|
: null;
|
|
75
|
-
const rawMessages =
|
|
90
|
+
const rawMessages = useMemo(
|
|
91
|
+
() => currentChat?.messages || [],
|
|
92
|
+
[currentChat?.messages],
|
|
93
|
+
);
|
|
76
94
|
const aiStatus = currentChat?.aiStatus;
|
|
77
95
|
const aiScratchpad = currentChat?.aiScratchpad;
|
|
96
|
+
const currentStatus = (currentChat as ExtendedVizChat)
|
|
97
|
+
?.currentStatus;
|
|
98
|
+
|
|
99
|
+
// Debug logging for AI status
|
|
100
|
+
DEBUG && console.log('AIChat: currentChat:', currentChat);
|
|
101
|
+
DEBUG && console.log('AIChat: aiStatus:', aiStatus);
|
|
102
|
+
DEBUG &&
|
|
103
|
+
console.log(
|
|
104
|
+
'AIChat: enableMinimalEditFlow:',
|
|
105
|
+
enableMinimalEditFlow,
|
|
106
|
+
);
|
|
78
107
|
|
|
79
108
|
// Transform messages to ensure they have required id field - memoized to avoid recreation
|
|
80
109
|
const messages = useMemo(
|
|
@@ -166,7 +195,10 @@ export const AIChat = () => {
|
|
|
166
195
|
return (
|
|
167
196
|
<div className="ai-chat-container">
|
|
168
197
|
<div className="ai-chat-content">
|
|
169
|
-
<div
|
|
198
|
+
<div
|
|
199
|
+
className="ai-chat-messages-container"
|
|
200
|
+
ref={messagesContainerRef}
|
|
201
|
+
>
|
|
170
202
|
{isEmptyState ? (
|
|
171
203
|
<div className="ai-chat-empty">
|
|
172
204
|
<div className="ai-chat-empty-icon">✨</div>
|
|
@@ -198,7 +230,7 @@ export const AIChat = () => {
|
|
|
198
230
|
)
|
|
199
231
|
}
|
|
200
232
|
>
|
|
201
|
-
|
|
233
|
+
"Explain how this works"
|
|
202
234
|
</button>
|
|
203
235
|
<button
|
|
204
236
|
className="ai-chat-suggested-prompt"
|
|
@@ -208,8 +240,8 @@ export const AIChat = () => {
|
|
|
208
240
|
)
|
|
209
241
|
}
|
|
210
242
|
>
|
|
211
|
-
|
|
212
|
-
circles are bigger
|
|
243
|
+
"How could I change it so
|
|
244
|
+
that the circles are bigger?"
|
|
213
245
|
</button>
|
|
214
246
|
<button
|
|
215
247
|
className="ai-chat-suggested-prompt"
|
|
@@ -219,7 +251,8 @@ export const AIChat = () => {
|
|
|
219
251
|
)
|
|
220
252
|
}
|
|
221
253
|
>
|
|
222
|
-
|
|
254
|
+
"What does this function
|
|
255
|
+
do?"
|
|
223
256
|
</button>
|
|
224
257
|
<button
|
|
225
258
|
className="ai-chat-suggested-prompt"
|
|
@@ -229,8 +262,8 @@ export const AIChat = () => {
|
|
|
229
262
|
)
|
|
230
263
|
}
|
|
231
264
|
>
|
|
232
|
-
|
|
233
|
-
accessible
|
|
265
|
+
"How can I make this more
|
|
266
|
+
accessible?"
|
|
234
267
|
</button>
|
|
235
268
|
</div>
|
|
236
269
|
</>
|
|
@@ -246,7 +279,8 @@ export const AIChat = () => {
|
|
|
246
279
|
)
|
|
247
280
|
}
|
|
248
281
|
>
|
|
249
|
-
|
|
282
|
+
"Change the circles to
|
|
283
|
+
squares"
|
|
250
284
|
</button>
|
|
251
285
|
<button
|
|
252
286
|
className="ai-chat-suggested-prompt"
|
|
@@ -256,8 +290,8 @@ export const AIChat = () => {
|
|
|
256
290
|
)
|
|
257
291
|
}
|
|
258
292
|
>
|
|
259
|
-
|
|
260
|
-
animation
|
|
293
|
+
"Add a button that toggles
|
|
294
|
+
the animation"
|
|
261
295
|
</button>
|
|
262
296
|
<button
|
|
263
297
|
className="ai-chat-suggested-prompt"
|
|
@@ -267,8 +301,8 @@ export const AIChat = () => {
|
|
|
267
301
|
)
|
|
268
302
|
}
|
|
269
303
|
>
|
|
270
|
-
|
|
271
|
-
responsive
|
|
304
|
+
"Fix the CSS so the layout is
|
|
305
|
+
responsive"
|
|
272
306
|
</button>
|
|
273
307
|
<button
|
|
274
308
|
className="ai-chat-suggested-prompt"
|
|
@@ -278,8 +312,8 @@ export const AIChat = () => {
|
|
|
278
312
|
)
|
|
279
313
|
}
|
|
280
314
|
>
|
|
281
|
-
|
|
282
|
-
async/await
|
|
315
|
+
"Refactor this function to
|
|
316
|
+
use async/await"
|
|
283
317
|
</button>
|
|
284
318
|
</div>
|
|
285
319
|
</>
|
|
@@ -297,9 +331,14 @@ export const AIChat = () => {
|
|
|
297
331
|
) : (
|
|
298
332
|
<MessageList
|
|
299
333
|
messages={messages}
|
|
300
|
-
isLoading={
|
|
334
|
+
isLoading={false}
|
|
301
335
|
chatId={selectedChatId || currentChatId}
|
|
302
336
|
aiScratchpad={aiScratchpad}
|
|
337
|
+
currentStatus={currentStatus}
|
|
338
|
+
onNewEvent={onNewEvent}
|
|
339
|
+
onJumpToLatest={onJumpToLatest}
|
|
340
|
+
beforeRender={beforeRender}
|
|
341
|
+
afterRender={afterRender}
|
|
303
342
|
/>
|
|
304
343
|
)}
|
|
305
344
|
{aiErrorMessage && (
|
|
@@ -321,6 +360,11 @@ export const AIChat = () => {
|
|
|
321
360
|
</div>
|
|
322
361
|
</div>
|
|
323
362
|
)}
|
|
363
|
+
{/* Jump to Latest Button */}
|
|
364
|
+
<JumpToLatestButton
|
|
365
|
+
visible={showJumpButton}
|
|
366
|
+
onClick={onJumpToLatest}
|
|
367
|
+
/>
|
|
324
368
|
</div>
|
|
325
369
|
</div>
|
|
326
370
|
<div className="ai-chat-input-fixed">
|
|
@@ -328,7 +372,6 @@ export const AIChat = () => {
|
|
|
328
372
|
aiChatMessage={aiChatMessage}
|
|
329
373
|
setAIChatMessage={setAIChatMessage}
|
|
330
374
|
onSendMessage={handleSendMessageWithSelection}
|
|
331
|
-
isLoading={isLoading}
|
|
332
375
|
focused={aiChatFocused}
|
|
333
376
|
aiChatMode={aiChatMode}
|
|
334
377
|
setAIChatMode={setAIChatMode}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
overflow: hidden;
|
|
11
11
|
display: flex;
|
|
12
12
|
flex-direction: column;
|
|
13
|
+
position: relative;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
.ai-chat-messages-container {
|
|
@@ -19,6 +20,21 @@
|
|
|
19
20
|
padding-bottom: 0;
|
|
20
21
|
display: flex;
|
|
21
22
|
flex-direction: column;
|
|
23
|
+
scroll-behavior: smooth;
|
|
24
|
+
|
|
25
|
+
&::-webkit-scrollbar {
|
|
26
|
+
width: 6px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&::-webkit-scrollbar-track {
|
|
30
|
+
background: var(--vh-color-neutral-02);
|
|
31
|
+
border-radius: 3px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&::-webkit-scrollbar-thumb {
|
|
35
|
+
background: var(--vh-color-neutral-04);
|
|
36
|
+
border-radius: 3px;
|
|
37
|
+
}
|
|
22
38
|
}
|
|
23
39
|
|
|
24
40
|
.ai-chat-input-fixed {
|
|
@@ -111,6 +127,94 @@
|
|
|
111
127
|
}
|
|
112
128
|
}
|
|
113
129
|
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// Streaming Message Components
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
.streaming-message {
|
|
135
|
+
.text-chunk {
|
|
136
|
+
margin-bottom: 1rem;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.file-editing-indicator {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
padding: 0.75rem;
|
|
144
|
+
margin: 0.5rem 0;
|
|
145
|
+
background-color: var(--vscode-editor-background);
|
|
146
|
+
border: 1px solid var(--vscode-panel-border);
|
|
147
|
+
border-radius: 4px;
|
|
148
|
+
|
|
149
|
+
.file-editing-header {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: 0.5rem;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.file-editing-icon {
|
|
156
|
+
display: flex;
|
|
157
|
+
align-items: center;
|
|
158
|
+
justify-content: center;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.file-editing-text {
|
|
162
|
+
color: var(--vscode-foreground);
|
|
163
|
+
font-size: 0.9rem;
|
|
164
|
+
|
|
165
|
+
code {
|
|
166
|
+
background-color: var(
|
|
167
|
+
--vscode-textCodeBlock-background
|
|
168
|
+
);
|
|
169
|
+
padding: 0.2rem 0.4rem;
|
|
170
|
+
border-radius: 3px;
|
|
171
|
+
font-family: var(--vscode-editor-font-family);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.status-indicator {
|
|
177
|
+
display: flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
padding: 0.5rem;
|
|
180
|
+
margin: 0.5rem 0;
|
|
181
|
+
background-color: var(--vscode-notifications-background);
|
|
182
|
+
border-left: 3px solid
|
|
183
|
+
var(--vscode-progressBar-foreground);
|
|
184
|
+
border-radius: 0 4px 4px 0;
|
|
185
|
+
|
|
186
|
+
.status-content {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 0.5rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.status-icon {
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
justify-content: center;
|
|
196
|
+
|
|
197
|
+
.done-icon {
|
|
198
|
+
font-size: 1rem;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.status-text {
|
|
203
|
+
color: var(--vscode-foreground);
|
|
204
|
+
font-size: 0.9rem;
|
|
205
|
+
font-weight: 500;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@keyframes spin {
|
|
210
|
+
0% {
|
|
211
|
+
transform: rotate(0deg);
|
|
212
|
+
}
|
|
213
|
+
100% {
|
|
214
|
+
transform: rotate(360deg);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
114
218
|
.ai-chat-list {
|
|
115
219
|
margin-top: 20px;
|
|
116
220
|
|
|
@@ -126,24 +230,8 @@
|
|
|
126
230
|
|
|
127
231
|
.ai-chat-messages {
|
|
128
232
|
flex: 1;
|
|
129
|
-
overflow-y: auto;
|
|
130
233
|
margin-bottom: 10px;
|
|
131
234
|
padding-right: 5px;
|
|
132
|
-
scroll-behavior: smooth;
|
|
133
|
-
|
|
134
|
-
&::-webkit-scrollbar {
|
|
135
|
-
width: 6px;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
&::-webkit-scrollbar-track {
|
|
139
|
-
background: var(--vh-color-neutral-02);
|
|
140
|
-
border-radius: 3px;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
&::-webkit-scrollbar-thumb {
|
|
144
|
-
background: var(--vh-color-neutral-04);
|
|
145
|
-
border-radius: 3px;
|
|
146
|
-
}
|
|
147
235
|
}
|
|
148
236
|
|
|
149
237
|
.ai-chat-message {
|
|
@@ -549,6 +637,61 @@
|
|
|
549
637
|
}
|
|
550
638
|
}
|
|
551
639
|
|
|
640
|
+
// Jump to latest button styles
|
|
641
|
+
.jump-to-latest-button {
|
|
642
|
+
position: absolute;
|
|
643
|
+
left: 50%;
|
|
644
|
+
transform: translateX(-50%);
|
|
645
|
+
bottom: 20px; // Position above the input area
|
|
646
|
+
width: 44px;
|
|
647
|
+
height: 44px;
|
|
648
|
+
border-radius: 50%;
|
|
649
|
+
background: var(--vh-color-primary-01);
|
|
650
|
+
border: none;
|
|
651
|
+
color: white;
|
|
652
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
653
|
+
cursor: pointer;
|
|
654
|
+
display: flex;
|
|
655
|
+
align-items: center;
|
|
656
|
+
justify-content: center;
|
|
657
|
+
z-index: 10;
|
|
658
|
+
transition: all 0.2s ease;
|
|
659
|
+
|
|
660
|
+
&:hover {
|
|
661
|
+
background: var(--vh-color-primary-02);
|
|
662
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
663
|
+
transform: translateX(-50%) scale(1.05);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
&:focus {
|
|
667
|
+
outline: 2px solid var(--vh-color-primary-01);
|
|
668
|
+
outline-offset: 2px;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
&:active {
|
|
672
|
+
transform: translateX(-50%) scale(0.95);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.jump-to-latest-icon {
|
|
676
|
+
font-size: 16px;
|
|
677
|
+
font-weight: bold;
|
|
678
|
+
line-height: 1;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Respect prefers-reduced-motion
|
|
682
|
+
@media (prefers-reduced-motion: reduce) {
|
|
683
|
+
transition: none;
|
|
684
|
+
|
|
685
|
+
&:hover {
|
|
686
|
+
transform: translateX(-50%);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
&:active {
|
|
690
|
+
transform: translateX(-50%);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
552
695
|
// Thinking scratchpad styles
|
|
553
696
|
.thinking-scratchpad {
|
|
554
697
|
background: var(--vh-color-neutral-02);
|
|
@@ -140,11 +140,11 @@ export const Item = ({
|
|
|
140
140
|
|
|
141
141
|
const handleMouseEnter = useCallback(() => {
|
|
142
142
|
setIsHovered(true);
|
|
143
|
-
}, []);
|
|
143
|
+
}, [setIsHovered]);
|
|
144
144
|
|
|
145
145
|
const handleMouseLeave = useCallback(() => {
|
|
146
146
|
setIsHovered(false);
|
|
147
|
-
}, []);
|
|
147
|
+
}, [setIsHovered]);
|
|
148
148
|
|
|
149
149
|
const onChange = useCallback(() => {
|
|
150
150
|
setRenameValue(renameInputRef.current.value);
|
|
@@ -98,7 +98,7 @@ export const Search = () => {
|
|
|
98
98
|
} else {
|
|
99
99
|
setIsMounted(true);
|
|
100
100
|
}
|
|
101
|
-
}, [pattern]);
|
|
101
|
+
}, [pattern, isMounted, setSearchResults, shareDBDoc]);
|
|
102
102
|
|
|
103
103
|
const flattenResult = useCallback(
|
|
104
104
|
(fileId: string, file: SearchFile) => {
|
|
@@ -111,19 +111,26 @@ export const Search = () => {
|
|
|
111
111
|
: 'open',
|
|
112
112
|
);
|
|
113
113
|
},
|
|
114
|
-
[
|
|
114
|
+
[
|
|
115
|
+
setSearchFileVisibility,
|
|
116
|
+
shareDBDoc,
|
|
117
|
+
focusedChildIndex,
|
|
118
|
+
],
|
|
115
119
|
);
|
|
116
120
|
|
|
117
|
-
const closeResult = useCallback(
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
const closeResult = useCallback(
|
|
122
|
+
(fileId: VizFileId) => {
|
|
123
|
+
setSearchFileVisibility(shareDBDoc, fileId, 'closed');
|
|
124
|
+
},
|
|
125
|
+
[setSearchFileVisibility, shareDBDoc],
|
|
126
|
+
);
|
|
120
127
|
|
|
121
128
|
const focusFileElement = useCallback(
|
|
122
129
|
(fileId: VizFileId, index: number) => {
|
|
123
130
|
setActiveFileId(fileId);
|
|
124
131
|
setSearchFocusedIndex(index, null);
|
|
125
132
|
},
|
|
126
|
-
[],
|
|
133
|
+
[setActiveFileId, setSearchFocusedIndex],
|
|
127
134
|
);
|
|
128
135
|
|
|
129
136
|
const handleKeyDown = (event) => {
|