vzcode 1.44.0 → 1.46.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/{buildWorker-CRLuRnCr.js → buildWorker-C_tGhWXG.js} +135 -74
- package/dist/assets/index-CC0CUUDi.js +457 -0
- package/dist/assets/{index-Beqaj63x.css → index-CQyHWTWE.css} +1 -1
- package/dist/assets/worker-CekYspLa.js +453 -0
- package/dist/index.html +2 -2
- package/package.json +13 -7
- package/src/client/CodeEditor/{getOrCreateEditor.ts → getOrCreateEditor.tsx} +129 -1
- package/src/client/CodeEditor/index.tsx +11 -4
- package/src/client/Icons/SparklesSVG.tsx +9 -3
- package/src/client/RunCodeWidget/index.tsx +18 -15
- package/src/client/SplitPaneResizeContext.tsx +26 -108
- package/src/client/VZCodeContext.tsx +15 -1
- package/src/client/VZRight.tsx +32 -4
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +80 -10
- package/src/client/VZSidebar/AIChat/DiffView.scss +154 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +133 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +24 -0
- package/src/client/VZSidebar/AIChat/MessageList.tsx +134 -12
- package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +37 -0
- package/src/client/VZSidebar/AIChat/index.tsx +155 -17
- package/src/client/VZSidebar/AIChat/styles.scss +223 -1
- package/src/client/VZSidebar/aiCopyPaste.ts +15 -2
- package/src/client/VZSidebar/index.tsx +10 -2
- package/src/client/bootstrap.ts +11 -1
- package/src/client/featureFlags.ts +2 -0
- package/src/client/useActions.ts +12 -0
- package/src/client/usePrettier/index.ts +1 -3
- package/src/client/vzReducer/aiChatReducer.ts +13 -0
- package/src/client/vzReducer/createInitialState.ts +1 -0
- package/src/client/vzReducer/index.ts +9 -0
- package/src/client/vzReducer/searchReducer.test.ts +44 -2
- package/src/runCode.ts +5 -17
- package/src/server/aiChatHandler/aiEditing.ts +56 -0
- package/src/server/aiChatHandler/chatOperations.ts +78 -1
- package/src/server/aiChatHandler/index.ts +33 -9
- package/src/server/aiChatHandler/llmStreaming.ts +101 -19
- package/src/server/computeInitialDocument.ts +25 -6
- package/src/server/generateAIResponse.ts +4 -0
- package/src/server/index.ts +83 -2
- package/src/utils/fileDiff.ts +114 -0
- package/dist/assets/index-C46Hp5ym.js +0 -340
- package/dist/assets/worker-DkYJN5WQ.js +0 -453
|
@@ -3,31 +3,118 @@ import {
|
|
|
3
3
|
useEffect,
|
|
4
4
|
useCallback,
|
|
5
5
|
memo,
|
|
6
|
+
useState,
|
|
6
7
|
} from 'react';
|
|
7
8
|
import { Message } from './Message';
|
|
8
9
|
import { TypingIndicator } from './TypingIndicator';
|
|
10
|
+
import { ThinkingScratchpad } from './ThinkingScratchpad';
|
|
9
11
|
import { VizChatMessage } from '@vizhub/viz-types';
|
|
10
12
|
|
|
11
13
|
const MessageListComponent = ({
|
|
12
14
|
messages,
|
|
13
15
|
aiStatus,
|
|
14
16
|
isLoading,
|
|
17
|
+
chatId, // Add chatId prop
|
|
18
|
+
aiScratchpad, // Add aiScratchpad prop
|
|
15
19
|
}: {
|
|
16
20
|
messages: VizChatMessage[];
|
|
17
21
|
aiStatus?: string;
|
|
18
22
|
isLoading: boolean;
|
|
23
|
+
chatId?: string; // Add chatId to the type
|
|
24
|
+
aiScratchpad?: string; // Add aiScratchpad to the type
|
|
19
25
|
}) => {
|
|
20
26
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
27
|
+
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
const [isUserScrolled, setIsUserScrolled] =
|
|
29
|
+
useState(false);
|
|
30
|
+
const [autoScrollEnabled, setAutoScrollEnabled] =
|
|
31
|
+
useState(true);
|
|
32
|
+
const scrollTimeoutRef =
|
|
33
|
+
useRef<ReturnType<typeof setTimeout>>();
|
|
21
34
|
|
|
35
|
+
// Check if the user is scrolled to the bottom
|
|
36
|
+
const isScrolledToBottom = useCallback(() => {
|
|
37
|
+
const container = messagesContainerRef.current;
|
|
38
|
+
if (!container) return true;
|
|
39
|
+
|
|
40
|
+
const threshold = 50; // Allow 50px tolerance for "at bottom"
|
|
41
|
+
const { scrollTop, scrollHeight, clientHeight } =
|
|
42
|
+
container;
|
|
43
|
+
return (
|
|
44
|
+
scrollHeight - scrollTop - clientHeight < threshold
|
|
45
|
+
);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
// Smooth scroll to bottom with linear transition
|
|
22
49
|
const scrollToBottom = useCallback(() => {
|
|
50
|
+
if (!autoScrollEnabled) return;
|
|
51
|
+
|
|
23
52
|
messagesEndRef.current?.scrollIntoView({
|
|
24
53
|
behavior: 'smooth',
|
|
54
|
+
block: 'end',
|
|
25
55
|
});
|
|
26
|
-
}, []);
|
|
56
|
+
}, [autoScrollEnabled]);
|
|
57
|
+
|
|
58
|
+
// Handle scroll events to detect user manual scrolling
|
|
59
|
+
const handleScroll = useCallback(() => {
|
|
60
|
+
const container = messagesContainerRef.current;
|
|
61
|
+
if (!container) return;
|
|
62
|
+
|
|
63
|
+
const isAtBottom = isScrolledToBottom();
|
|
64
|
+
|
|
65
|
+
// Clear any existing timeout
|
|
66
|
+
if (scrollTimeoutRef.current) {
|
|
67
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// If user scrolled up from bottom, disable auto-scroll
|
|
71
|
+
if (!isAtBottom && !isUserScrolled) {
|
|
72
|
+
setIsUserScrolled(true);
|
|
73
|
+
setAutoScrollEnabled(false);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// If user scrolled back to bottom, re-enable auto-scroll after a brief delay
|
|
77
|
+
if (isAtBottom && isUserScrolled) {
|
|
78
|
+
scrollTimeoutRef.current = setTimeout(() => {
|
|
79
|
+
setIsUserScrolled(false);
|
|
80
|
+
setAutoScrollEnabled(true);
|
|
81
|
+
}, 500); // 500ms delay to prevent flickering
|
|
82
|
+
}
|
|
83
|
+
}, [isUserScrolled, isScrolledToBottom]);
|
|
27
84
|
|
|
85
|
+
// Auto-scroll when messages change, but only if auto-scroll is enabled
|
|
28
86
|
useEffect(() => {
|
|
29
|
-
|
|
30
|
-
|
|
87
|
+
if (autoScrollEnabled && !isUserScrolled) {
|
|
88
|
+
// Use a short debounce to prevent multiple scroll calls during rapid updates
|
|
89
|
+
if (scrollTimeoutRef.current) {
|
|
90
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
scrollTimeoutRef.current = setTimeout(() => {
|
|
94
|
+
scrollToBottom();
|
|
95
|
+
}, 100); // 100ms debounce
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
if (scrollTimeoutRef.current) {
|
|
100
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}, [
|
|
104
|
+
messages,
|
|
105
|
+
autoScrollEnabled,
|
|
106
|
+
isUserScrolled,
|
|
107
|
+
scrollToBottom,
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
// Clean up timeout on unmount
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
return () => {
|
|
113
|
+
if (scrollTimeoutRef.current) {
|
|
114
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}, []);
|
|
31
118
|
|
|
32
119
|
// Check if AI generation has started (last message is from assistant)
|
|
33
120
|
const lastMessage = messages[messages.length - 1];
|
|
@@ -39,17 +126,52 @@ const MessageListComponent = ({
|
|
|
39
126
|
const showTypingIndicator =
|
|
40
127
|
isLoading && !aiGenerationStarted;
|
|
41
128
|
|
|
129
|
+
// Show thinking scratchpad when AI is thinking (has scratchpad content)
|
|
130
|
+
const showThinkingScratchpad = Boolean(
|
|
131
|
+
aiScratchpad && aiScratchpad.trim(),
|
|
132
|
+
);
|
|
133
|
+
|
|
42
134
|
return (
|
|
43
|
-
<div
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
135
|
+
<div
|
|
136
|
+
className="ai-chat-messages"
|
|
137
|
+
ref={messagesContainerRef}
|
|
138
|
+
onScroll={handleScroll}
|
|
139
|
+
>
|
|
140
|
+
{messages.map((msg, index) => {
|
|
141
|
+
// Only the most recent assistant message with diffData can be undone
|
|
142
|
+
const isLastAssistantMessage =
|
|
143
|
+
msg.role === 'assistant' &&
|
|
144
|
+
index === messages.length - 1 &&
|
|
145
|
+
!isLoading; // Can't undo while AI is still generating
|
|
146
|
+
|
|
147
|
+
const canUndo =
|
|
148
|
+
isLastAssistantMessage &&
|
|
149
|
+
(msg as any).diffData &&
|
|
150
|
+
(msg as any).beforeFiles &&
|
|
151
|
+
Object.keys((msg as any).diffData || {}).length >
|
|
152
|
+
0;
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Message
|
|
156
|
+
key={msg.id}
|
|
157
|
+
id={msg.id}
|
|
158
|
+
role={msg.role}
|
|
159
|
+
content={msg.content}
|
|
160
|
+
timestamp={msg.timestamp}
|
|
161
|
+
diffData={(msg as any).diffData}
|
|
162
|
+
beforeFiles={(msg as any).beforeFiles}
|
|
163
|
+
chatId={chatId}
|
|
164
|
+
canUndo={canUndo}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
})}
|
|
168
|
+
|
|
169
|
+
{showThinkingScratchpad && (
|
|
170
|
+
<ThinkingScratchpad
|
|
171
|
+
content={aiScratchpad || ''}
|
|
172
|
+
isVisible={showThinkingScratchpad}
|
|
51
173
|
/>
|
|
52
|
-
)
|
|
174
|
+
)}
|
|
53
175
|
|
|
54
176
|
{showTypingIndicator && <TypingIndicator />}
|
|
55
177
|
<div ref={messagesEndRef} />
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import Markdown from 'react-markdown';
|
|
3
|
+
import remarkGfm from 'remark-gfm';
|
|
4
|
+
|
|
5
|
+
interface ThinkingScratchpadProps {
|
|
6
|
+
content: string;
|
|
7
|
+
isVisible: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ThinkingScratchpadComponent = ({
|
|
11
|
+
content,
|
|
12
|
+
isVisible,
|
|
13
|
+
}: ThinkingScratchpadProps) => {
|
|
14
|
+
if (!isVisible || !content) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="thinking-scratchpad">
|
|
20
|
+
<div className="thinking-scratchpad-header">
|
|
21
|
+
<span className="thinking-scratchpad-icon">🧠</span>
|
|
22
|
+
<span className="thinking-scratchpad-title">
|
|
23
|
+
VizBot is thinking...
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
<div className="thinking-scratchpad-content">
|
|
27
|
+
<Markdown remarkPlugins={[remarkGfm]}>
|
|
28
|
+
{content}
|
|
29
|
+
</Markdown>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const ThinkingScratchpad = memo(
|
|
36
|
+
ThinkingScratchpadComponent,
|
|
37
|
+
);
|
|
@@ -13,7 +13,9 @@ import './styles.scss';
|
|
|
13
13
|
const defaultAIChatEndpoint = '/ai-chat-message';
|
|
14
14
|
|
|
15
15
|
export const AIChat = () => {
|
|
16
|
-
const
|
|
16
|
+
const { aiChatMessage, setAIChatMessage } =
|
|
17
|
+
useContext(VZCodeContext);
|
|
18
|
+
|
|
17
19
|
const [isLoading, setIsLoading] = useState(false);
|
|
18
20
|
const [currentChatId] = useState(() => uuidv4());
|
|
19
21
|
|
|
@@ -22,12 +24,15 @@ export const AIChat = () => {
|
|
|
22
24
|
content,
|
|
23
25
|
aiChatEndpoint = defaultAIChatEndpoint,
|
|
24
26
|
aiChatOptions = {},
|
|
27
|
+
aiChatMode,
|
|
28
|
+
setAIChatMode,
|
|
25
29
|
} = useContext(VZCodeContext);
|
|
26
30
|
|
|
27
31
|
// Get current chat data from content
|
|
28
32
|
const currentChat = content?.chats?.[currentChatId];
|
|
29
33
|
const rawMessages = currentChat?.messages || [];
|
|
30
34
|
const aiStatus = currentChat?.aiStatus;
|
|
35
|
+
const aiScratchpad = currentChat?.aiScratchpad;
|
|
31
36
|
|
|
32
37
|
// Transform messages to ensure they have required id field - memoized to avoid recreation
|
|
33
38
|
const messages = useMemo(
|
|
@@ -39,10 +44,13 @@ export const AIChat = () => {
|
|
|
39
44
|
[rawMessages],
|
|
40
45
|
);
|
|
41
46
|
|
|
47
|
+
// Check if this is the first time opening the chat (no messages)
|
|
48
|
+
const isEmptyState = rawMessages.length === 0;
|
|
49
|
+
|
|
42
50
|
const handleSendMessage = useCallback(async () => {
|
|
43
|
-
if (!
|
|
51
|
+
if (!aiChatMessage.trim() || isLoading) return;
|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
setAIChatMessage('');
|
|
46
54
|
setIsLoading(true);
|
|
47
55
|
|
|
48
56
|
// Call backend endpoint for AI response
|
|
@@ -55,8 +63,9 @@ export const AIChat = () => {
|
|
|
55
63
|
},
|
|
56
64
|
body: JSON.stringify({
|
|
57
65
|
...aiChatOptions,
|
|
58
|
-
content:
|
|
66
|
+
content: aiChatMessage.trim(),
|
|
59
67
|
chatId: currentChatId,
|
|
68
|
+
mode: aiChatMode,
|
|
60
69
|
}),
|
|
61
70
|
});
|
|
62
71
|
|
|
@@ -75,7 +84,7 @@ export const AIChat = () => {
|
|
|
75
84
|
setIsLoading(false);
|
|
76
85
|
}
|
|
77
86
|
}, [
|
|
78
|
-
|
|
87
|
+
aiChatMessage,
|
|
79
88
|
isLoading,
|
|
80
89
|
aiChatEndpoint,
|
|
81
90
|
aiChatOptions,
|
|
@@ -84,18 +93,147 @@ export const AIChat = () => {
|
|
|
84
93
|
|
|
85
94
|
return (
|
|
86
95
|
<div className="ai-chat-container">
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
<div
|
|
97
|
+
style={{
|
|
98
|
+
padding: '10px',
|
|
99
|
+
flex: 1,
|
|
100
|
+
display: 'flex',
|
|
101
|
+
flexDirection: 'column',
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
{isEmptyState ? (
|
|
105
|
+
<div className="ai-chat-empty">
|
|
106
|
+
<div className="ai-chat-empty-icon">✨</div>
|
|
107
|
+
<h3 className="ai-chat-empty-title">
|
|
108
|
+
Hi, I'm VizBot!
|
|
109
|
+
</h3>
|
|
110
|
+
<div className="ai-chat-empty-text">
|
|
111
|
+
How can I help you?
|
|
112
|
+
</div>
|
|
113
|
+
<div className="ai-chat-empty-examples">
|
|
114
|
+
{aiChatMode === 'ask' ? (
|
|
115
|
+
<>
|
|
116
|
+
<h4>Try asking questions like:</h4>
|
|
117
|
+
<div className="ai-chat-suggested-prompts">
|
|
118
|
+
<button
|
|
119
|
+
className="ai-chat-suggested-prompt"
|
|
120
|
+
onClick={() =>
|
|
121
|
+
setAIChatMessage(
|
|
122
|
+
'Explain how this works',
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
>
|
|
126
|
+
"Explain how this works"
|
|
127
|
+
</button>
|
|
128
|
+
<button
|
|
129
|
+
className="ai-chat-suggested-prompt"
|
|
130
|
+
onClick={() =>
|
|
131
|
+
setAIChatMessage(
|
|
132
|
+
'How could I change it so that the circles are bigger?',
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
>
|
|
136
|
+
"How could I change it so that the
|
|
137
|
+
circles are bigger?"
|
|
138
|
+
</button>
|
|
139
|
+
<button
|
|
140
|
+
className="ai-chat-suggested-prompt"
|
|
141
|
+
onClick={() =>
|
|
142
|
+
setAIChatMessage(
|
|
143
|
+
'What does this function do?',
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
>
|
|
147
|
+
"What does this function do?"
|
|
148
|
+
</button>
|
|
149
|
+
<button
|
|
150
|
+
className="ai-chat-suggested-prompt"
|
|
151
|
+
onClick={() =>
|
|
152
|
+
setAIChatMessage(
|
|
153
|
+
'How can I make this more accessible?',
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
>
|
|
157
|
+
"How can I make this more accessible?"
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
</>
|
|
161
|
+
) : (
|
|
162
|
+
<>
|
|
163
|
+
<h4>Try edit requests like these:</h4>
|
|
164
|
+
<div className="ai-chat-suggested-prompts">
|
|
165
|
+
<button
|
|
166
|
+
className="ai-chat-suggested-prompt"
|
|
167
|
+
onClick={() =>
|
|
168
|
+
setAIChatMessage(
|
|
169
|
+
'Change the circles to squares',
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
>
|
|
173
|
+
"Change the circles to squares"
|
|
174
|
+
</button>
|
|
175
|
+
<button
|
|
176
|
+
className="ai-chat-suggested-prompt"
|
|
177
|
+
onClick={() =>
|
|
178
|
+
setAIChatMessage(
|
|
179
|
+
'Add a button that toggles the animation',
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
>
|
|
183
|
+
"Add a button that toggles the
|
|
184
|
+
animation"
|
|
185
|
+
</button>
|
|
186
|
+
<button
|
|
187
|
+
className="ai-chat-suggested-prompt"
|
|
188
|
+
onClick={() =>
|
|
189
|
+
setAIChatMessage(
|
|
190
|
+
'Fix the CSS so the layout is responsive',
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
>
|
|
194
|
+
"Fix the CSS so the layout is
|
|
195
|
+
responsive"
|
|
196
|
+
</button>
|
|
197
|
+
<button
|
|
198
|
+
className="ai-chat-suggested-prompt"
|
|
199
|
+
onClick={() =>
|
|
200
|
+
setAIChatMessage(
|
|
201
|
+
'Refactor this function to use async/await',
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
>
|
|
205
|
+
"Refactor this function to use
|
|
206
|
+
async/await"
|
|
207
|
+
</button>
|
|
208
|
+
</div>
|
|
209
|
+
</>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
<div className="ai-chat-empty-text">
|
|
213
|
+
{aiChatMode === 'ask'
|
|
214
|
+
? 'Type your question below to get started!'
|
|
215
|
+
: 'Type your edit request below to get started!'}
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
) : (
|
|
219
|
+
<MessageList
|
|
220
|
+
messages={messages}
|
|
221
|
+
aiStatus={aiStatus}
|
|
222
|
+
isLoading={isLoading}
|
|
223
|
+
chatId={currentChatId}
|
|
224
|
+
aiScratchpad={aiScratchpad}
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
<ChatInput
|
|
228
|
+
aiChatMessage={aiChatMessage}
|
|
229
|
+
setAIChatMessage={setAIChatMessage}
|
|
230
|
+
onSendMessage={handleSendMessage}
|
|
231
|
+
isLoading={isLoading}
|
|
232
|
+
focused={aiChatFocused}
|
|
233
|
+
aiChatMode={aiChatMode}
|
|
234
|
+
setAIChatMode={setAIChatMode}
|
|
235
|
+
/>
|
|
236
|
+
</div>
|
|
99
237
|
</div>
|
|
100
238
|
);
|
|
101
239
|
};
|
|
@@ -2,7 +2,84 @@
|
|
|
2
2
|
height: 100%;
|
|
3
3
|
display: flex;
|
|
4
4
|
flex-direction: column;
|
|
5
|
-
padding:
|
|
5
|
+
padding: 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.ai-chat-empty {
|
|
9
|
+
flex: 1;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
align-items: center;
|
|
13
|
+
text-align: center;
|
|
14
|
+
padding: 20px;
|
|
15
|
+
overflow-y: auto;
|
|
16
|
+
|
|
17
|
+
.ai-chat-empty-icon {
|
|
18
|
+
font-size: 42px;
|
|
19
|
+
margin-bottom: 15px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.ai-chat-empty-title {
|
|
23
|
+
font-size: 18px;
|
|
24
|
+
font-weight: 600;
|
|
25
|
+
color: var(--vh-color-neutral-04);
|
|
26
|
+
margin-bottom: 15px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ai-chat-empty-text {
|
|
30
|
+
font-size: 14px;
|
|
31
|
+
color: var(--vh-color-neutral-04);
|
|
32
|
+
margin-bottom: 20px;
|
|
33
|
+
line-height: 1.5;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ai-chat-empty-examples {
|
|
37
|
+
background: var(--vh-color-neutral-02);
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
padding: 15px;
|
|
40
|
+
margin-bottom: 20px;
|
|
41
|
+
text-align: left;
|
|
42
|
+
width: 100%;
|
|
43
|
+
|
|
44
|
+
h4 {
|
|
45
|
+
font-size: 15px;
|
|
46
|
+
font-weight: 600;
|
|
47
|
+
color: var(--vh-color-neutral-04);
|
|
48
|
+
margin-bottom: 10px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ai-chat-suggested-prompts {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: 8px;
|
|
55
|
+
margin: 0;
|
|
56
|
+
|
|
57
|
+
.ai-chat-suggested-prompt {
|
|
58
|
+
background: var(--vh-color-neutral-03);
|
|
59
|
+
border: 1px solid var(--vh-color-neutral-04);
|
|
60
|
+
border-radius: 6px;
|
|
61
|
+
padding: 8px 12px;
|
|
62
|
+
font-size: 13px;
|
|
63
|
+
color: var(--vh-color-neutral-04);
|
|
64
|
+
text-align: left;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
transition: all 0.2s ease;
|
|
67
|
+
line-height: 1.4;
|
|
68
|
+
|
|
69
|
+
&:hover {
|
|
70
|
+
background: var(--vh-color-neutral-04);
|
|
71
|
+
color: var(--vh-color-neutral-01);
|
|
72
|
+
border-color: var(--vh-color-primary-01);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&:active {
|
|
76
|
+
background: var(--vh-color-primary-01);
|
|
77
|
+
color: white;
|
|
78
|
+
border-color: var(--vh-color-primary-01);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
6
83
|
}
|
|
7
84
|
|
|
8
85
|
.ai-chat-messages {
|
|
@@ -10,6 +87,7 @@
|
|
|
10
87
|
overflow-y: auto;
|
|
11
88
|
margin-bottom: 10px;
|
|
12
89
|
padding-right: 5px;
|
|
90
|
+
scroll-behavior: smooth;
|
|
13
91
|
|
|
14
92
|
&::-webkit-scrollbar {
|
|
15
93
|
width: 6px;
|
|
@@ -199,8 +277,57 @@
|
|
|
199
277
|
padding-top: 10px;
|
|
200
278
|
}
|
|
201
279
|
|
|
280
|
+
.ai-chat-mode-toggle {
|
|
281
|
+
display: flex;
|
|
282
|
+
flex-direction: column;
|
|
283
|
+
gap: 4px;
|
|
284
|
+
|
|
285
|
+
.btn-group {
|
|
286
|
+
justify-content: center;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.btn-group .btn {
|
|
290
|
+
font-size: 12px;
|
|
291
|
+
font-weight: 500;
|
|
292
|
+
padding: 4px 12px;
|
|
293
|
+
border: 1px solid var(--vh-color-neutral-03);
|
|
294
|
+
background: var(--vh-color-neutral-02);
|
|
295
|
+
color: var(--vh-color-neutral-04);
|
|
296
|
+
transition: all 0.2s ease;
|
|
297
|
+
|
|
298
|
+
&:hover {
|
|
299
|
+
background: var(--vh-color-neutral-03);
|
|
300
|
+
border-color: var(--vh-color-neutral-04);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
&.btn-primary {
|
|
304
|
+
background: var(--vh-color-primary-01);
|
|
305
|
+
border-color: var(--vh-color-primary-01);
|
|
306
|
+
color: white;
|
|
307
|
+
|
|
308
|
+
&:hover {
|
|
309
|
+
background: var(--vh-color-primary-02);
|
|
310
|
+
border-color: var(--vh-color-primary-02);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
&:disabled {
|
|
315
|
+
opacity: 0.6;
|
|
316
|
+
cursor: not-allowed;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.ai-chat-mode-description {
|
|
321
|
+
font-size: 11px;
|
|
322
|
+
color: var(--vh-color-neutral-03);
|
|
323
|
+
text-align: center;
|
|
324
|
+
font-style: italic;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
202
328
|
.ai-chat-input-group {
|
|
203
329
|
display: flex;
|
|
330
|
+
flex-direction: column;
|
|
204
331
|
gap: 8px;
|
|
205
332
|
margin-bottom: 0;
|
|
206
333
|
|
|
@@ -233,6 +360,18 @@
|
|
|
233
360
|
}
|
|
234
361
|
}
|
|
235
362
|
|
|
363
|
+
.ai-chat-input-info {
|
|
364
|
+
display: flex;
|
|
365
|
+
justify-content: space-between;
|
|
366
|
+
font-size: 12px;
|
|
367
|
+
color: var(--vh-color-neutral-03);
|
|
368
|
+
margin-bottom: 6px;
|
|
369
|
+
|
|
370
|
+
.ai-chat-hint {
|
|
371
|
+
font-style: italic;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
236
375
|
.ai-chat-send-button {
|
|
237
376
|
background: var(--vh-color-primary-01);
|
|
238
377
|
border: none;
|
|
@@ -243,6 +382,8 @@
|
|
|
243
382
|
font-weight: 500;
|
|
244
383
|
cursor: pointer;
|
|
245
384
|
transition: background-color 0.2s ease;
|
|
385
|
+
align-self: flex-end;
|
|
386
|
+
min-width: 80px;
|
|
246
387
|
|
|
247
388
|
&:hover:not(:disabled) {
|
|
248
389
|
background: var(--vh-color-primary-02);
|
|
@@ -254,3 +395,84 @@
|
|
|
254
395
|
}
|
|
255
396
|
}
|
|
256
397
|
}
|
|
398
|
+
|
|
399
|
+
// Thinking scratchpad styles
|
|
400
|
+
.thinking-scratchpad {
|
|
401
|
+
background: var(--vh-color-neutral-02);
|
|
402
|
+
border: 1px solid var(--vh-color-neutral-03);
|
|
403
|
+
border-radius: 8px;
|
|
404
|
+
margin: 10px 0;
|
|
405
|
+
padding: 0;
|
|
406
|
+
overflow: hidden;
|
|
407
|
+
font-size: 13px;
|
|
408
|
+
|
|
409
|
+
.thinking-scratchpad-header {
|
|
410
|
+
background: var(--vh-color-neutral-03);
|
|
411
|
+
color: var(--vh-color-neutral-04);
|
|
412
|
+
padding: 8px 12px;
|
|
413
|
+
font-weight: 600;
|
|
414
|
+
display: flex;
|
|
415
|
+
align-items: center;
|
|
416
|
+
gap: 8px;
|
|
417
|
+
|
|
418
|
+
.thinking-scratchpad-icon {
|
|
419
|
+
font-size: 16px;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.thinking-scratchpad-title {
|
|
423
|
+
font-size: 13px;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.thinking-scratchpad-content {
|
|
428
|
+
padding: 12px;
|
|
429
|
+
max-height: 200px;
|
|
430
|
+
overflow-y: auto;
|
|
431
|
+
background: var(--vh-color-neutral-02);
|
|
432
|
+
|
|
433
|
+
// Style markdown content in thinking scratchpad
|
|
434
|
+
p {
|
|
435
|
+
margin: 0 0 8px 0;
|
|
436
|
+
line-height: 1.4;
|
|
437
|
+
color: var(--vh-color-neutral-04);
|
|
438
|
+
|
|
439
|
+
&:last-child {
|
|
440
|
+
margin-bottom: 0;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
code {
|
|
445
|
+
background: rgba(0, 0, 0, 0.3);
|
|
446
|
+
color: #e6e6e6;
|
|
447
|
+
padding: 1px 4px;
|
|
448
|
+
border-radius: 3px;
|
|
449
|
+
font-size: 12px;
|
|
450
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
ul,
|
|
454
|
+
ol {
|
|
455
|
+
margin: 0 0 8px 0;
|
|
456
|
+
padding-left: 20px;
|
|
457
|
+
|
|
458
|
+
li {
|
|
459
|
+
margin-bottom: 4px;
|
|
460
|
+
color: var(--vh-color-neutral-04);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
h1,
|
|
465
|
+
h2,
|
|
466
|
+
h3,
|
|
467
|
+
h4,
|
|
468
|
+
h5,
|
|
469
|
+
h6 {
|
|
470
|
+
margin: 8px 0 6px 0;
|
|
471
|
+
color: var(--vh-color-neutral-04);
|
|
472
|
+
|
|
473
|
+
&:first-child {
|
|
474
|
+
margin-top: 0;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|