vzcode 1.56.0 → 1.57.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-COSAnnol.css → index-CE_xP06t.css} +1 -1
- package/dist/assets/{index-Cp6YlJo2.js → index-DqIc-0Gp.js} +21 -20
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +20 -21
- package/src/client/VZSidebar/AIChat/DiffView.scss +8 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +105 -1
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +1 -1
- package/src/client/VZSidebar/AIChat/index.tsx +158 -153
- package/src/client/VZSidebar/AIChat/styles.scss +52 -11
- package/src/server/aiChatHandler/llmStreaming.ts +2 -2
package/dist/index.html
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
|
|
21
21
|
rel="stylesheet"
|
|
22
22
|
/>
|
|
23
|
-
<script type="module" crossorigin src="/assets/index-
|
|
24
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
23
|
+
<script type="module" crossorigin src="/assets/index-DqIc-0Gp.js"></script>
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CE_xP06t.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
|
27
27
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -112,7 +112,7 @@ const ChatInputComponent = ({
|
|
|
112
112
|
<Form.Group className="ai-chat-input-group">
|
|
113
113
|
<Form.Control
|
|
114
114
|
as="textarea"
|
|
115
|
-
rows={
|
|
115
|
+
rows={5}
|
|
116
116
|
value={aiChatMessage}
|
|
117
117
|
onChange={handleChange}
|
|
118
118
|
onKeyDown={handleKeyDown}
|
|
@@ -126,26 +126,25 @@ const ChatInputComponent = ({
|
|
|
126
126
|
disabled={isLoading}
|
|
127
127
|
aria-label="Chat message input"
|
|
128
128
|
/>
|
|
129
|
-
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
</Button>
|
|
129
|
+
<div className="ai-chat-input-footer">
|
|
130
|
+
<span className="ai-chat-hint">
|
|
131
|
+
{aiChatMessage ? 'Press Enter to send' : ''}
|
|
132
|
+
</span>
|
|
133
|
+
<Button
|
|
134
|
+
variant={
|
|
135
|
+
aiChatMessage.trim()
|
|
136
|
+
? 'primary'
|
|
137
|
+
: 'outline-secondary'
|
|
138
|
+
}
|
|
139
|
+
onClick={handleSendClick}
|
|
140
|
+
disabled={!aiChatMessage.trim() || isLoading}
|
|
141
|
+
className="ai-chat-send-button"
|
|
142
|
+
aria-label="Send message"
|
|
143
|
+
title="Send message (Enter)"
|
|
144
|
+
>
|
|
145
|
+
Send
|
|
146
|
+
</Button>
|
|
147
|
+
</div>
|
|
149
148
|
</Form.Group>
|
|
150
149
|
</div>
|
|
151
150
|
);
|
|
@@ -119,6 +119,14 @@
|
|
|
119
119
|
border-color: var(--d2h-ins-border-color) !important;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// Style clickable file names
|
|
123
|
+
.d2h-file-name {
|
|
124
|
+
&:hover {
|
|
125
|
+
text-decoration: underline !important;
|
|
126
|
+
opacity: 0.8;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
122
130
|
// Fix wrapper border
|
|
123
131
|
.d2h-file-wrapper {
|
|
124
132
|
border: 1px solid var(--d2h-border-color) !important;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React, {
|
|
2
|
+
useContext,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
} from 'react';
|
|
2
7
|
import {
|
|
3
8
|
UnifiedFilesDiff,
|
|
4
9
|
parseUnifiedDiffStats,
|
|
@@ -7,6 +12,8 @@ import {
|
|
|
7
12
|
import * as Diff2Html from 'diff2html';
|
|
8
13
|
import 'diff2html/bundles/css/diff2html.min.css';
|
|
9
14
|
import './DiffView.scss';
|
|
15
|
+
import { VZCodeContext } from '../../VZCodeContext';
|
|
16
|
+
import { VizFileId, VizFiles } from '@vizhub/viz-types';
|
|
10
17
|
|
|
11
18
|
interface DiffViewProps {
|
|
12
19
|
diffData: UnifiedFilesDiff;
|
|
@@ -15,6 +22,10 @@ interface DiffViewProps {
|
|
|
15
22
|
export const DiffView: React.FC<DiffViewProps> = ({
|
|
16
23
|
diffData,
|
|
17
24
|
}) => {
|
|
25
|
+
const { content, openTab, setIsAIChatOpen } =
|
|
26
|
+
useContext(VZCodeContext);
|
|
27
|
+
const diffContainerRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
|
|
18
29
|
const unifiedDiffs = Object.values(diffData).filter(
|
|
19
30
|
(diff) => diff.length > 0,
|
|
20
31
|
);
|
|
@@ -42,6 +53,98 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
42
53
|
outputFormat: 'line-by-line',
|
|
43
54
|
});
|
|
44
55
|
|
|
56
|
+
// Create a mapping from file name to file ID for click handling
|
|
57
|
+
const fileNameToIdMap = useMemo(() => {
|
|
58
|
+
const map = new Map<string, VizFileId>();
|
|
59
|
+
Object.entries(diffData).forEach(([fileId, diff]) => {
|
|
60
|
+
// Extract file name from the unified diff
|
|
61
|
+
// The diff contains lines like "--- a/filename" and "+++ b/filename"
|
|
62
|
+
const lines = diff.split('\n');
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
if (
|
|
65
|
+
line.startsWith('--- a/') ||
|
|
66
|
+
line.startsWith('+++ b/')
|
|
67
|
+
) {
|
|
68
|
+
const fileName = line.substring(6); // Remove "--- a/" or "+++ b/"
|
|
69
|
+
map.set(fileName, fileId as VizFileId);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return map;
|
|
75
|
+
}, [diffData]);
|
|
76
|
+
|
|
77
|
+
// Add click handlers to file names after HTML is rendered
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!diffContainerRef.current) return;
|
|
80
|
+
|
|
81
|
+
const handleFileNameClick = (event: MouseEvent) => {
|
|
82
|
+
const target = event.target as HTMLElement;
|
|
83
|
+
if (target.classList.contains('d2h-file-name')) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
event.stopPropagation();
|
|
86
|
+
|
|
87
|
+
console.log('File name clicked');
|
|
88
|
+
|
|
89
|
+
const fileName = target.textContent?.trim();
|
|
90
|
+
|
|
91
|
+
console.log('Clicked file name:', fileName);
|
|
92
|
+
if (fileName) {
|
|
93
|
+
const files: VizFiles = content?.files;
|
|
94
|
+
if (files) {
|
|
95
|
+
// export type VizFiles = {
|
|
96
|
+
// [fileId: VizFileId]: VizFile;
|
|
97
|
+
// };
|
|
98
|
+
// export type VizFileId = string;
|
|
99
|
+
// export type VizFile = {
|
|
100
|
+
// name: string;
|
|
101
|
+
// text: string;
|
|
102
|
+
// };
|
|
103
|
+
|
|
104
|
+
// TODO get fileId from content.files
|
|
105
|
+
const fileId = Object.entries(files).find(
|
|
106
|
+
([id, file]) => file.name === fileName,
|
|
107
|
+
)?.[0];
|
|
108
|
+
console.log('Mapped file ID:', fileId);
|
|
109
|
+
|
|
110
|
+
if (fileId) {
|
|
111
|
+
// Open the file tab and switch to files view
|
|
112
|
+
openTab({ fileId, isTransient: false });
|
|
113
|
+
setIsAIChatOpen(false);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const container = diffContainerRef.current;
|
|
121
|
+
const fileNameElements = container.querySelectorAll(
|
|
122
|
+
'.d2h-file-name',
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Add click event listeners and cursor pointer style
|
|
126
|
+
fileNameElements.forEach((element) => {
|
|
127
|
+
element.addEventListener(
|
|
128
|
+
'click',
|
|
129
|
+
handleFileNameClick,
|
|
130
|
+
);
|
|
131
|
+
(element as HTMLElement).style.cursor = 'pointer';
|
|
132
|
+
(element as HTMLElement).style.textDecoration =
|
|
133
|
+
'underline';
|
|
134
|
+
(element as HTMLElement).style.color = '#58a6ff'; // GitHub blue link color
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
// Cleanup event listeners
|
|
139
|
+
fileNameElements.forEach((element) => {
|
|
140
|
+
element.removeEventListener(
|
|
141
|
+
'click',
|
|
142
|
+
handleFileNameClick,
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
}, [diffHtml, content, openTab, setIsAIChatOpen]);
|
|
147
|
+
|
|
45
148
|
return (
|
|
46
149
|
<div className="diff-view">
|
|
47
150
|
<div className="diff-summary">
|
|
@@ -65,6 +168,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
65
168
|
|
|
66
169
|
<div
|
|
67
170
|
className="diff-files"
|
|
171
|
+
ref={diffContainerRef}
|
|
68
172
|
dangerouslySetInnerHTML={{ __html: diffHtml }}
|
|
69
173
|
/>
|
|
70
174
|
</div>
|
|
@@ -146,9 +146,15 @@ export const AIChat = () => {
|
|
|
146
146
|
// The backend handles all ShareDB operations for successful responses
|
|
147
147
|
} catch (error) {
|
|
148
148
|
console.error('Error getting AI response:', error);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
// Fail silently in case of timeout,
|
|
150
|
+
// which happens frequently with the longer running LLM calls.
|
|
151
|
+
// TODO refactor this so that the network request to the server
|
|
152
|
+
// always returns immediately, and we track the `isLoading` state
|
|
153
|
+
// within the ShareDB document itself.
|
|
154
|
+
//
|
|
155
|
+
// setErrorMessage(
|
|
156
|
+
// 'Failed to send message. Please try again.',
|
|
157
|
+
// );
|
|
152
158
|
} finally {
|
|
153
159
|
setIsLoading(false);
|
|
154
160
|
}
|
|
@@ -214,159 +220,158 @@ export const AIChat = () => {
|
|
|
214
220
|
|
|
215
221
|
return (
|
|
216
222
|
<div className="ai-chat-container">
|
|
217
|
-
<div
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
{isEmptyState ? (
|
|
226
|
-
<div className="ai-chat-empty">
|
|
227
|
-
<div className="ai-chat-empty-icon">✨</div>
|
|
228
|
-
<h3 className="ai-chat-empty-title">
|
|
229
|
-
Edit with AI
|
|
230
|
-
</h3>
|
|
231
|
-
<div className="ai-chat-empty-text">
|
|
232
|
-
How can I help you?
|
|
233
|
-
</div>
|
|
234
|
-
{showSuggestedRequests && (
|
|
235
|
-
<div className="ai-chat-empty-examples">
|
|
236
|
-
{aiChatMode === 'ask' ? (
|
|
237
|
-
<>
|
|
238
|
-
<h4>Try asking questions like:</h4>
|
|
239
|
-
<div className="ai-chat-suggested-prompts">
|
|
240
|
-
<button
|
|
241
|
-
className="ai-chat-suggested-prompt"
|
|
242
|
-
onClick={() =>
|
|
243
|
-
handleSendMessage(
|
|
244
|
-
'Explain how this works',
|
|
245
|
-
)
|
|
246
|
-
}
|
|
247
|
-
>
|
|
248
|
-
"Explain how this works"
|
|
249
|
-
</button>
|
|
250
|
-
<button
|
|
251
|
-
className="ai-chat-suggested-prompt"
|
|
252
|
-
onClick={() =>
|
|
253
|
-
handleSendMessage(
|
|
254
|
-
'How could I change it so that the circles are bigger?',
|
|
255
|
-
)
|
|
256
|
-
}
|
|
257
|
-
>
|
|
258
|
-
"How could I change it so that the
|
|
259
|
-
circles are bigger?"
|
|
260
|
-
</button>
|
|
261
|
-
<button
|
|
262
|
-
className="ai-chat-suggested-prompt"
|
|
263
|
-
onClick={() =>
|
|
264
|
-
handleSendMessage(
|
|
265
|
-
'What does this function do?',
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
>
|
|
269
|
-
"What does this function do?"
|
|
270
|
-
</button>
|
|
271
|
-
<button
|
|
272
|
-
className="ai-chat-suggested-prompt"
|
|
273
|
-
onClick={() =>
|
|
274
|
-
handleSendMessage(
|
|
275
|
-
'How can I make this more accessible?',
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
>
|
|
279
|
-
"How can I make this more
|
|
280
|
-
accessible?"
|
|
281
|
-
</button>
|
|
282
|
-
</div>
|
|
283
|
-
</>
|
|
284
|
-
) : (
|
|
285
|
-
<>
|
|
286
|
-
<h4>Try edit requests like these:</h4>
|
|
287
|
-
<div className="ai-chat-suggested-prompts">
|
|
288
|
-
<button
|
|
289
|
-
className="ai-chat-suggested-prompt"
|
|
290
|
-
onClick={() =>
|
|
291
|
-
handleSendMessage(
|
|
292
|
-
'Change the circles to squares',
|
|
293
|
-
)
|
|
294
|
-
}
|
|
295
|
-
>
|
|
296
|
-
"Change the circles to squares"
|
|
297
|
-
</button>
|
|
298
|
-
<button
|
|
299
|
-
className="ai-chat-suggested-prompt"
|
|
300
|
-
onClick={() =>
|
|
301
|
-
handleSendMessage(
|
|
302
|
-
'Add a button that toggles the animation',
|
|
303
|
-
)
|
|
304
|
-
}
|
|
305
|
-
>
|
|
306
|
-
"Add a button that toggles the
|
|
307
|
-
animation"
|
|
308
|
-
</button>
|
|
309
|
-
<button
|
|
310
|
-
className="ai-chat-suggested-prompt"
|
|
311
|
-
onClick={() =>
|
|
312
|
-
handleSendMessage(
|
|
313
|
-
'Fix the CSS so the layout is responsive',
|
|
314
|
-
)
|
|
315
|
-
}
|
|
316
|
-
>
|
|
317
|
-
"Fix the CSS so the layout is
|
|
318
|
-
responsive"
|
|
319
|
-
</button>
|
|
320
|
-
<button
|
|
321
|
-
className="ai-chat-suggested-prompt"
|
|
322
|
-
onClick={() =>
|
|
323
|
-
handleSendMessage(
|
|
324
|
-
'Refactor this function to use async/await',
|
|
325
|
-
)
|
|
326
|
-
}
|
|
327
|
-
>
|
|
328
|
-
"Refactor this function to use
|
|
329
|
-
async/await"
|
|
330
|
-
</button>
|
|
331
|
-
</div>
|
|
332
|
-
</>
|
|
333
|
-
)}
|
|
334
|
-
</div>
|
|
335
|
-
)}
|
|
336
|
-
{showSuggestedRequests && (
|
|
223
|
+
<div className="ai-chat-content">
|
|
224
|
+
<div className="ai-chat-messages-container">
|
|
225
|
+
{isEmptyState ? (
|
|
226
|
+
<div className="ai-chat-empty">
|
|
227
|
+
<div className="ai-chat-empty-icon">✨</div>
|
|
228
|
+
<h3 className="ai-chat-empty-title">
|
|
229
|
+
Edit with AI
|
|
230
|
+
</h3>
|
|
337
231
|
<div className="ai-chat-empty-text">
|
|
338
|
-
|
|
339
|
-
? 'Type your question below to get started!'
|
|
340
|
-
: 'Type your edit request below to get started!'}
|
|
232
|
+
How can I help you?
|
|
341
233
|
</div>
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
234
|
+
{showSuggestedRequests && (
|
|
235
|
+
<div className="ai-chat-empty-examples">
|
|
236
|
+
{aiChatMode === 'ask' ? (
|
|
237
|
+
<>
|
|
238
|
+
<h4>Try asking questions like:</h4>
|
|
239
|
+
<div className="ai-chat-suggested-prompts">
|
|
240
|
+
<button
|
|
241
|
+
className="ai-chat-suggested-prompt"
|
|
242
|
+
onClick={() =>
|
|
243
|
+
handleSendMessage(
|
|
244
|
+
'Explain how this works',
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
>
|
|
248
|
+
"Explain how this works"
|
|
249
|
+
</button>
|
|
250
|
+
<button
|
|
251
|
+
className="ai-chat-suggested-prompt"
|
|
252
|
+
onClick={() =>
|
|
253
|
+
handleSendMessage(
|
|
254
|
+
'How could I change it so that the circles are bigger?',
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
>
|
|
258
|
+
"How could I change it so that the
|
|
259
|
+
circles are bigger?"
|
|
260
|
+
</button>
|
|
261
|
+
<button
|
|
262
|
+
className="ai-chat-suggested-prompt"
|
|
263
|
+
onClick={() =>
|
|
264
|
+
handleSendMessage(
|
|
265
|
+
'What does this function do?',
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
>
|
|
269
|
+
"What does this function do?"
|
|
270
|
+
</button>
|
|
271
|
+
<button
|
|
272
|
+
className="ai-chat-suggested-prompt"
|
|
273
|
+
onClick={() =>
|
|
274
|
+
handleSendMessage(
|
|
275
|
+
'How can I make this more accessible?',
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
>
|
|
279
|
+
"How can I make this more
|
|
280
|
+
accessible?"
|
|
281
|
+
</button>
|
|
282
|
+
</div>
|
|
283
|
+
</>
|
|
284
|
+
) : (
|
|
285
|
+
<>
|
|
286
|
+
<h4>Try edit requests like these:</h4>
|
|
287
|
+
<div className="ai-chat-suggested-prompts">
|
|
288
|
+
<button
|
|
289
|
+
className="ai-chat-suggested-prompt"
|
|
290
|
+
onClick={() =>
|
|
291
|
+
handleSendMessage(
|
|
292
|
+
'Change the circles to squares',
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
>
|
|
296
|
+
"Change the circles to squares"
|
|
297
|
+
</button>
|
|
298
|
+
<button
|
|
299
|
+
className="ai-chat-suggested-prompt"
|
|
300
|
+
onClick={() =>
|
|
301
|
+
handleSendMessage(
|
|
302
|
+
'Add a button that toggles the animation',
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
>
|
|
306
|
+
"Add a button that toggles the
|
|
307
|
+
animation"
|
|
308
|
+
</button>
|
|
309
|
+
<button
|
|
310
|
+
className="ai-chat-suggested-prompt"
|
|
311
|
+
onClick={() =>
|
|
312
|
+
handleSendMessage(
|
|
313
|
+
'Fix the CSS so the layout is responsive',
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
>
|
|
317
|
+
"Fix the CSS so the layout is
|
|
318
|
+
responsive"
|
|
319
|
+
</button>
|
|
320
|
+
<button
|
|
321
|
+
className="ai-chat-suggested-prompt"
|
|
322
|
+
onClick={() =>
|
|
323
|
+
handleSendMessage(
|
|
324
|
+
'Refactor this function to use async/await',
|
|
325
|
+
)
|
|
326
|
+
}
|
|
327
|
+
>
|
|
328
|
+
"Refactor this function to use
|
|
329
|
+
async/await"
|
|
330
|
+
</button>
|
|
331
|
+
</div>
|
|
332
|
+
</>
|
|
333
|
+
)}
|
|
334
|
+
</div>
|
|
335
|
+
)}
|
|
336
|
+
{showSuggestedRequests && (
|
|
337
|
+
<div className="ai-chat-empty-text">
|
|
338
|
+
{aiChatMode === 'ask'
|
|
339
|
+
? 'Type your question below to get started!'
|
|
340
|
+
: 'Type your edit request below to get started!'}
|
|
341
|
+
</div>
|
|
342
|
+
)}
|
|
367
343
|
</div>
|
|
368
|
-
|
|
369
|
-
|
|
344
|
+
) : (
|
|
345
|
+
<MessageList
|
|
346
|
+
messages={messages}
|
|
347
|
+
aiStatus={aiStatus}
|
|
348
|
+
isLoading={isLoading}
|
|
349
|
+
chatId={currentChatId}
|
|
350
|
+
aiScratchpad={aiScratchpad}
|
|
351
|
+
/>
|
|
352
|
+
)}
|
|
353
|
+
{errorMessage && (
|
|
354
|
+
<div className="ai-chat-error">
|
|
355
|
+
<div className="ai-chat-error-content">
|
|
356
|
+
<span className="ai-chat-error-icon">
|
|
357
|
+
⚠️
|
|
358
|
+
</span>
|
|
359
|
+
<span className="ai-chat-error-message">
|
|
360
|
+
{errorMessage}
|
|
361
|
+
</span>
|
|
362
|
+
<button
|
|
363
|
+
className="ai-chat-error-dismiss"
|
|
364
|
+
onClick={() => setErrorMessage(null)}
|
|
365
|
+
aria-label="Dismiss error"
|
|
366
|
+
>
|
|
367
|
+
×
|
|
368
|
+
</button>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
)}
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
<div className="ai-chat-input-fixed">
|
|
370
375
|
<ChatInput
|
|
371
376
|
aiChatMessage={aiChatMessage}
|
|
372
377
|
setAIChatMessage={setAIChatMessage}
|
|
@@ -5,6 +5,26 @@
|
|
|
5
5
|
padding: 0;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
.ai-chat-content {
|
|
9
|
+
flex: 1;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.ai-chat-messages-container {
|
|
16
|
+
flex: 1;
|
|
17
|
+
overflow-y: auto;
|
|
18
|
+
padding: 10px;
|
|
19
|
+
padding-bottom: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.ai-chat-input-fixed {
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
padding: 10px;
|
|
25
|
+
padding-top: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
.ai-chat-empty {
|
|
9
29
|
flex: 1;
|
|
10
30
|
display: flex;
|
|
@@ -365,6 +385,7 @@
|
|
|
365
385
|
.ai-chat-input-container {
|
|
366
386
|
border-top: 1px solid var(--vh-color-neutral-02);
|
|
367
387
|
padding-top: 10px;
|
|
388
|
+
background: var(--vh-color-neutral-01);
|
|
368
389
|
}
|
|
369
390
|
|
|
370
391
|
.ai-chat-mode-toggle {
|
|
@@ -427,11 +448,12 @@
|
|
|
427
448
|
border: 1px solid var(--vh-color-neutral-03) !important;
|
|
428
449
|
color: var(--vh-color-neutral-04) !important;
|
|
429
450
|
border-radius: 8px;
|
|
430
|
-
padding:
|
|
451
|
+
padding: 10px 12px;
|
|
431
452
|
font-size: 14px;
|
|
432
453
|
font-family: var(--vzcode-font-family);
|
|
433
454
|
resize: none;
|
|
434
|
-
min-height:
|
|
455
|
+
min-height: 48px;
|
|
456
|
+
line-height: 1.4;
|
|
435
457
|
|
|
436
458
|
&:focus {
|
|
437
459
|
outline: none !important;
|
|
@@ -450,37 +472,56 @@
|
|
|
450
472
|
}
|
|
451
473
|
}
|
|
452
474
|
|
|
453
|
-
.ai-chat-input-
|
|
475
|
+
.ai-chat-input-footer {
|
|
454
476
|
display: flex;
|
|
455
477
|
justify-content: space-between;
|
|
478
|
+
align-items: center;
|
|
456
479
|
font-size: 12px;
|
|
457
480
|
color: var(--vh-color-neutral-03);
|
|
458
|
-
|
|
481
|
+
min-height: 40px; // Prevent layout jumping
|
|
459
482
|
|
|
460
483
|
.ai-chat-hint {
|
|
461
484
|
font-style: italic;
|
|
485
|
+
flex: 1;
|
|
462
486
|
}
|
|
463
487
|
}
|
|
464
488
|
|
|
465
489
|
.ai-chat-send-button {
|
|
466
|
-
background: var(--vh-color-primary-01);
|
|
467
|
-
border: none;
|
|
468
|
-
color: white;
|
|
469
490
|
border-radius: 8px;
|
|
470
491
|
padding: 8px 16px;
|
|
471
492
|
font-size: 14px;
|
|
472
493
|
font-weight: 500;
|
|
473
494
|
cursor: pointer;
|
|
474
|
-
transition:
|
|
475
|
-
align-self: flex-end;
|
|
495
|
+
transition: all 0.2s ease;
|
|
476
496
|
min-width: 80px;
|
|
497
|
+
flex-shrink: 0;
|
|
477
498
|
|
|
478
|
-
|
|
479
|
-
background: var(--vh-color-primary-
|
|
499
|
+
&.btn-primary {
|
|
500
|
+
background: var(--vh-color-primary-01);
|
|
501
|
+
border-color: var(--vh-color-primary-01);
|
|
502
|
+
color: white;
|
|
503
|
+
|
|
504
|
+
&:hover:not(:disabled) {
|
|
505
|
+
background: var(--vh-color-primary-02);
|
|
506
|
+
border-color: var(--vh-color-primary-02);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
&.btn-outline-secondary {
|
|
511
|
+
background: transparent;
|
|
512
|
+
border-color: var(--vh-color-neutral-03);
|
|
513
|
+
color: var(--vh-color-neutral-03);
|
|
514
|
+
|
|
515
|
+
&:hover:not(:disabled) {
|
|
516
|
+
background: var(--vh-color-neutral-03);
|
|
517
|
+
color: var(--vh-color-neutral-01);
|
|
518
|
+
}
|
|
480
519
|
}
|
|
481
520
|
|
|
482
521
|
&:disabled {
|
|
483
522
|
background: var(--vh-color-neutral-03);
|
|
523
|
+
border-color: var(--vh-color-neutral-03);
|
|
524
|
+
color: var(--vh-color-neutral-02);
|
|
484
525
|
cursor: not-allowed;
|
|
485
526
|
}
|
|
486
527
|
}
|