vzcode 2.22.0 → 2.25.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-Bi6wsfQk.js → buildWorker-DylWm2VX.js} +64 -72
- package/dist/assets/index-CkFweu5-.js +403 -0
- package/dist/assets/{index-fSroLVgi.css → index-Dyt9tpmu.css} +1 -5
- package/dist/assets/{worker-BSzbM0Uo.js → worker-VPLhQVba.js} +230 -230
- package/dist/assets/worker-b9bkZkXK.js +116 -0
- package/dist/assets/{worker-Cm3I3tnR.js → worker-k7HZ3t-V.js} +3 -20
- package/dist/index.html +2 -2
- package/dist/llm-streaming-server/chatOperations.js +19 -0
- package/dist/llm-streaming-server/llmStreaming.js +18 -13
- package/dist/server/aiChatHandler/index.js +4 -2
- package/package.json +22 -21
- package/src/llm-streaming-server/chatOperations.ts +27 -0
- package/src/llm-streaming-server/llmStreaming.ts +24 -14
- package/src/llm-streaming-ui/components/Message.tsx +8 -7
- package/src/llm-streaming-ui/components/MessageList.tsx +5 -0
- package/src/llm-streaming-ui/components/index.tsx +2 -2
- package/src/llm-streaming-ui/components/styles.scss +10 -0
- package/src/server/aiChatHandler/index.ts +6 -0
- package/src/types.ts +1 -0
- package/dist/assets/index-CfDs-dAu.js +0 -476
- package/dist/assets/worker-KiuLM-X4.js +0 -136
- package/dist/server/aiChatHandler/aiEditing.js +0 -58
- package/dist/server/aiChatHandler/chatOperations.js +0 -494
- package/dist/server/aiChatHandler/errorHandling.js +0 -49
- package/dist/server/aiChatHandler/llmStreaming.js +0 -265
- package/dist/server/aiChatHandler/validation.js +0 -19
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +0 -237
- package/src/client/VZSidebar/AIChat/DiffView.scss +0 -173
- package/src/client/VZSidebar/AIChat/DiffView.tsx +0 -236
- package/src/client/VZSidebar/AIChat/FileEditingIndicator.tsx +0 -89
- package/src/client/VZSidebar/AIChat/IndividualFileDiff.tsx +0 -86
- package/src/client/VZSidebar/AIChat/JumpToLatestButton.tsx +0 -64
- package/src/client/VZSidebar/AIChat/Message.tsx +0 -145
- package/src/client/VZSidebar/AIChat/MessageList.tsx +0 -241
- package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +0 -42
- package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +0 -19
- package/src/client/VZSidebar/AIChat/index.tsx +0 -388
- package/src/client/VZSidebar/AIChat/styles.scss +0 -831
- package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +0 -116
- package/src/server/aiChatHandler/aiEditing.ts +0 -102
- package/src/server/aiChatHandler/chatOperations.ts +0 -655
- package/src/server/aiChatHandler/errorHandling.ts +0 -66
- package/src/server/aiChatHandler/llmStreaming.ts +0 -403
- package/src/server/aiChatHandler/validation.ts +0 -24
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useRef,
|
|
3
|
-
useEffect,
|
|
4
|
-
memo,
|
|
5
|
-
useState,
|
|
6
|
-
useContext,
|
|
7
|
-
} from 'react';
|
|
8
|
-
import { Message } from './Message';
|
|
9
|
-
import { TypingIndicator } from './TypingIndicator';
|
|
10
|
-
import { ThinkingScratchpad } from './ThinkingScratchpad';
|
|
11
|
-
import { AIEditingStatusIndicator } from './FileEditingIndicator';
|
|
12
|
-
import { VizChatMessage } from '@vizhub/viz-types';
|
|
13
|
-
import { ExtendedVizChatMessage } from '../../../types.js';
|
|
14
|
-
import { DiffViewRef } from './DiffView.js';
|
|
15
|
-
import { VZCodeContext } from '../../VZCodeContext';
|
|
16
|
-
|
|
17
|
-
const MessageListComponent = ({
|
|
18
|
-
messages,
|
|
19
|
-
isLoading,
|
|
20
|
-
chatId, // Add chatId prop
|
|
21
|
-
aiScratchpad, // Add aiScratchpad prop
|
|
22
|
-
currentStatus, // Add current status prop
|
|
23
|
-
onNewEvent,
|
|
24
|
-
onJumpToLatest,
|
|
25
|
-
beforeRender,
|
|
26
|
-
afterRender,
|
|
27
|
-
}: {
|
|
28
|
-
messages: VizChatMessage[];
|
|
29
|
-
isLoading: boolean;
|
|
30
|
-
chatId?: string; // Add chatId to the type
|
|
31
|
-
aiScratchpad?: string; // Add aiScratchpad to the type
|
|
32
|
-
currentStatus?: string; // Add current status to the type
|
|
33
|
-
onNewEvent: (targetElement?: HTMLElement) => void;
|
|
34
|
-
onJumpToLatest: (targetElement?: HTMLElement) => void;
|
|
35
|
-
beforeRender: () => number;
|
|
36
|
-
afterRender: (prevScrollHeight: number) => void;
|
|
37
|
-
}) => {
|
|
38
|
-
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
39
|
-
const diffViewRef = useRef<DiffViewRef>(null);
|
|
40
|
-
|
|
41
|
-
// Get additional widgets from context
|
|
42
|
-
const { additionalWidgets, handleSendMessage } =
|
|
43
|
-
useContext(VZCodeContext);
|
|
44
|
-
|
|
45
|
-
// Track previous loading state to detect when AI generation completes
|
|
46
|
-
const [prevIsLoading, setPrevIsLoading] =
|
|
47
|
-
useState(isLoading);
|
|
48
|
-
|
|
49
|
-
// Auto-scroll when messages change
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
// Get scroll height before render for anchoring
|
|
52
|
-
const prevScrollHeight = beforeRender();
|
|
53
|
-
|
|
54
|
-
// Determine if we should scroll to a specific diff or to the bottom
|
|
55
|
-
const lastMessageHasDiff =
|
|
56
|
-
messages.length > 0 &&
|
|
57
|
-
(messages[messages.length - 1] as any).diffData;
|
|
58
|
-
|
|
59
|
-
let targetElement: HTMLElement | null = null;
|
|
60
|
-
if (lastMessageHasDiff && diffViewRef.current) {
|
|
61
|
-
targetElement =
|
|
62
|
-
diffViewRef.current.getFirstHunkElement();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Trigger auto-scroll if enabled
|
|
66
|
-
onNewEvent(targetElement);
|
|
67
|
-
|
|
68
|
-
// Adjust scroll position after render for anchoring
|
|
69
|
-
afterRender(prevScrollHeight);
|
|
70
|
-
}, [messages, onNewEvent, beforeRender, afterRender]);
|
|
71
|
-
|
|
72
|
-
// Scroll to bottom when AI generation completes (loading changes from true to false)
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
// If AI was generating and now it's finished, force scroll to bottom
|
|
75
|
-
if (prevIsLoading && !isLoading) {
|
|
76
|
-
// Force jump to latest regardless of current auto-scroll state
|
|
77
|
-
onJumpToLatest();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
setPrevIsLoading(isLoading);
|
|
81
|
-
}, [isLoading, prevIsLoading, onJumpToLatest]);
|
|
82
|
-
|
|
83
|
-
// Check if AI generation has started (last message is from assistant)
|
|
84
|
-
const lastMessage = messages[messages.length - 1];
|
|
85
|
-
const aiGenerationStarted =
|
|
86
|
-
lastMessage?.role === 'assistant' &&
|
|
87
|
-
lastMessage.content !== '';
|
|
88
|
-
|
|
89
|
-
// Show typing indicator only when loading and AI generation hasn't started yet
|
|
90
|
-
const showTypingIndicator =
|
|
91
|
-
isLoading && !aiGenerationStarted;
|
|
92
|
-
|
|
93
|
-
// Show thinking scratchpad when AI is thinking (has scratchpad content)
|
|
94
|
-
const showThinkingScratchpad = Boolean(
|
|
95
|
-
aiScratchpad && aiScratchpad.trim(),
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
// Determine the single, most relevant status to display with priority:
|
|
99
|
-
// 1. Active file editing status from streaming events
|
|
100
|
-
// 2. General AI status from currentStatus
|
|
101
|
-
const getConsolidatedStatus = () => {
|
|
102
|
-
// Check if there's an active streaming message with file editing
|
|
103
|
-
if (lastMessage && lastMessage.role === 'assistant') {
|
|
104
|
-
const extendedMsg =
|
|
105
|
-
lastMessage as ExtendedVizChatMessage;
|
|
106
|
-
if (
|
|
107
|
-
extendedMsg.streamingEvents &&
|
|
108
|
-
extendedMsg.streamingEvents.length > 0
|
|
109
|
-
) {
|
|
110
|
-
// Look for active file editing (file_start without corresponding file_complete)
|
|
111
|
-
const fileStates = new Map<string, boolean>();
|
|
112
|
-
|
|
113
|
-
extendedMsg.streamingEvents.forEach((event) => {
|
|
114
|
-
if (event.type === 'file_start') {
|
|
115
|
-
fileStates.set(event.fileName, false); // Mark as editing
|
|
116
|
-
} else if (event.type === 'file_complete') {
|
|
117
|
-
fileStates.set(event.fileName, true); // Mark as complete
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Find the first file that's still being edited
|
|
122
|
-
for (const [fileName, isComplete] of fileStates) {
|
|
123
|
-
if (!isComplete) {
|
|
124
|
-
return {
|
|
125
|
-
status: `Editing ${fileName}...`,
|
|
126
|
-
fileName,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Fall back to general AI status if no active file editing
|
|
134
|
-
if (currentStatus) {
|
|
135
|
-
return { status: currentStatus };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return null;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const consolidatedStatus = getConsolidatedStatus();
|
|
142
|
-
|
|
143
|
-
// Show consolidated status indicator when there's a status to display
|
|
144
|
-
const showConsolidatedStatusIndicator = Boolean(
|
|
145
|
-
consolidatedStatus,
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
// Find the most recent assistant message index
|
|
149
|
-
const lastAssistantMessageIndex = messages
|
|
150
|
-
.map((m, i) => (m.role === 'assistant' ? i : -1))
|
|
151
|
-
.filter((i) => i !== -1)
|
|
152
|
-
.pop();
|
|
153
|
-
|
|
154
|
-
return (
|
|
155
|
-
<div
|
|
156
|
-
className="ai-chat-messages"
|
|
157
|
-
role="log"
|
|
158
|
-
aria-live="polite"
|
|
159
|
-
aria-relevant="additions"
|
|
160
|
-
>
|
|
161
|
-
{messages.map((msg, index) => {
|
|
162
|
-
const isLastMessage = index === messages.length - 1;
|
|
163
|
-
// Cast to extended message to check for streaming events
|
|
164
|
-
const extendedMsg = msg as ExtendedVizChatMessage;
|
|
165
|
-
|
|
166
|
-
// Check if this is a streaming message
|
|
167
|
-
const isStreamingMessage =
|
|
168
|
-
!!extendedMsg.isProgressive;
|
|
169
|
-
|
|
170
|
-
// Only show additionalWidgets for the most recent assistant message
|
|
171
|
-
const showAdditionalWidgets =
|
|
172
|
-
msg.role === 'assistant' &&
|
|
173
|
-
index === lastAssistantMessageIndex;
|
|
174
|
-
|
|
175
|
-
// Use Message for all messages now
|
|
176
|
-
return (
|
|
177
|
-
<Message
|
|
178
|
-
key={msg.id}
|
|
179
|
-
id={msg.id}
|
|
180
|
-
role={msg.role}
|
|
181
|
-
content={
|
|
182
|
-
isStreamingMessage ? undefined : msg.content
|
|
183
|
-
}
|
|
184
|
-
timestamp={msg.timestamp}
|
|
185
|
-
events={
|
|
186
|
-
isStreamingMessage
|
|
187
|
-
? extendedMsg.streamingEvents || []
|
|
188
|
-
: []
|
|
189
|
-
}
|
|
190
|
-
isActive={index === lastAssistantMessageIndex}
|
|
191
|
-
chatId={chatId}
|
|
192
|
-
showAdditionalWidgets={showAdditionalWidgets}
|
|
193
|
-
isStreaming={isStreamingMessage}
|
|
194
|
-
diffData={(msg as any).diffData}
|
|
195
|
-
ref={
|
|
196
|
-
isLastMessage && (msg as any).diffData
|
|
197
|
-
? diffViewRef
|
|
198
|
-
: null
|
|
199
|
-
}
|
|
200
|
-
>
|
|
201
|
-
{isStreamingMessage &&
|
|
202
|
-
showThinkingScratchpad && (
|
|
203
|
-
<ThinkingScratchpad
|
|
204
|
-
content={aiScratchpad || ''}
|
|
205
|
-
isVisible={showThinkingScratchpad}
|
|
206
|
-
/>
|
|
207
|
-
)}
|
|
208
|
-
|
|
209
|
-
{isStreamingMessage &&
|
|
210
|
-
showConsolidatedStatusIndicator && (
|
|
211
|
-
<AIEditingStatusIndicator
|
|
212
|
-
status={consolidatedStatus?.status || ''}
|
|
213
|
-
fileName={consolidatedStatus?.fileName}
|
|
214
|
-
additionalWidgets={
|
|
215
|
-
consolidatedStatus?.status === 'Done' &&
|
|
216
|
-
showAdditionalWidgets &&
|
|
217
|
-
additionalWidgets &&
|
|
218
|
-
chatId
|
|
219
|
-
? additionalWidgets({
|
|
220
|
-
messageId: msg.id,
|
|
221
|
-
chatId: chatId,
|
|
222
|
-
handleSendMessage,
|
|
223
|
-
})
|
|
224
|
-
: undefined
|
|
225
|
-
}
|
|
226
|
-
/>
|
|
227
|
-
)}
|
|
228
|
-
|
|
229
|
-
{isStreamingMessage && showTypingIndicator && (
|
|
230
|
-
<TypingIndicator />
|
|
231
|
-
)}
|
|
232
|
-
</Message>
|
|
233
|
-
);
|
|
234
|
-
})}
|
|
235
|
-
|
|
236
|
-
<div ref={messagesEndRef} />
|
|
237
|
-
</div>
|
|
238
|
-
);
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
export const MessageList = memo(MessageListComponent);
|
|
@@ -1,42 +0,0 @@
|
|
|
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
|
-
AI is thinking...
|
|
24
|
-
</span>
|
|
25
|
-
</div>
|
|
26
|
-
<div
|
|
27
|
-
className="thinking-scratchpad-content"
|
|
28
|
-
role="log"
|
|
29
|
-
aria-live="polite"
|
|
30
|
-
aria-relevant="additions"
|
|
31
|
-
>
|
|
32
|
-
<Markdown remarkPlugins={[remarkGfm]}>
|
|
33
|
-
{content}
|
|
34
|
-
</Markdown>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const ThinkingScratchpad = memo(
|
|
41
|
-
ThinkingScratchpadComponent,
|
|
42
|
-
);
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { memo } from 'react';
|
|
2
|
-
|
|
3
|
-
const TypingIndicatorComponent = () => {
|
|
4
|
-
return (
|
|
5
|
-
<div className="ai-chat-message assistant">
|
|
6
|
-
<div className="ai-chat-message-content">
|
|
7
|
-
<div className="ai-chat-typing">
|
|
8
|
-
<span></span>
|
|
9
|
-
<span></span>
|
|
10
|
-
<span></span>
|
|
11
|
-
</div>
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
);
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const TypingIndicator = memo(
|
|
18
|
-
TypingIndicatorComponent,
|
|
19
|
-
);
|
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useContext,
|
|
3
|
-
useMemo,
|
|
4
|
-
useEffect,
|
|
5
|
-
useCallback,
|
|
6
|
-
} from 'react';
|
|
7
|
-
import { VZCodeContext } from '../../VZCodeContext';
|
|
8
|
-
import { MessageList } from './MessageList';
|
|
9
|
-
import { ChatInput } from './ChatInput';
|
|
10
|
-
import { ExtendedVizChat } from '../../../types.js';
|
|
11
|
-
import { useAutoScroll } from '../../hooks/useAutoScroll';
|
|
12
|
-
import { JumpToLatestButton } from './JumpToLatestButton';
|
|
13
|
-
import './styles.scss';
|
|
14
|
-
|
|
15
|
-
const DEBUG = false;
|
|
16
|
-
|
|
17
|
-
const showSuggestedRequests = false;
|
|
18
|
-
|
|
19
|
-
const showPreviousChats = false;
|
|
20
|
-
|
|
21
|
-
// Component for displaying the list of existing chats
|
|
22
|
-
const ChatList = ({
|
|
23
|
-
chats,
|
|
24
|
-
selectedChatId,
|
|
25
|
-
onSelectChat,
|
|
26
|
-
getChatTitle,
|
|
27
|
-
}) => {
|
|
28
|
-
return (
|
|
29
|
-
<div className="ai-chat-list">
|
|
30
|
-
<div className="ai-chat-list-header">
|
|
31
|
-
<h4>Previous Chats</h4>
|
|
32
|
-
</div>
|
|
33
|
-
<div className="ai-chat-suggested-prompts">
|
|
34
|
-
{chats.map((chat) => (
|
|
35
|
-
<button
|
|
36
|
-
key={chat.id}
|
|
37
|
-
className={`ai-chat-suggested-prompt ${
|
|
38
|
-
selectedChatId === chat.id ? 'selected' : ''
|
|
39
|
-
}`}
|
|
40
|
-
onClick={() => onSelectChat(chat.id)}
|
|
41
|
-
>
|
|
42
|
-
{getChatTitle(chat)}
|
|
43
|
-
</button>
|
|
44
|
-
))}
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
);
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export const AIChat = () => {
|
|
51
|
-
const {
|
|
52
|
-
aiChatFocused,
|
|
53
|
-
content,
|
|
54
|
-
aiChatMode,
|
|
55
|
-
aiChatMessage,
|
|
56
|
-
currentChatId,
|
|
57
|
-
selectedChatId,
|
|
58
|
-
setSelectedChatId,
|
|
59
|
-
aiErrorMessage,
|
|
60
|
-
setAIChatMode,
|
|
61
|
-
getStoredAIPrompt,
|
|
62
|
-
setAIChatMessage,
|
|
63
|
-
handleSendMessage,
|
|
64
|
-
setAIErrorMessage,
|
|
65
|
-
navigateMessageHistoryUp,
|
|
66
|
-
navigateMessageHistoryDown,
|
|
67
|
-
resetMessageHistoryNavigation,
|
|
68
|
-
enableMinimalEditFlow,
|
|
69
|
-
} = useContext(VZCodeContext);
|
|
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
|
-
// Get the active chat ID and chat data
|
|
82
|
-
// If selectedChatId is set, use it; otherwise, if no chat selected, don't default to currentChatId
|
|
83
|
-
const activeChatId = selectedChatId;
|
|
84
|
-
const currentChat = activeChatId
|
|
85
|
-
? content?.chats?.[activeChatId]
|
|
86
|
-
: null;
|
|
87
|
-
const rawMessages = useMemo(
|
|
88
|
-
() => currentChat?.messages || [],
|
|
89
|
-
[currentChat?.messages],
|
|
90
|
-
);
|
|
91
|
-
const aiStatus = currentChat?.aiStatus;
|
|
92
|
-
const aiScratchpad = currentChat?.aiScratchpad;
|
|
93
|
-
const currentStatus = (currentChat as ExtendedVizChat)
|
|
94
|
-
?.currentStatus;
|
|
95
|
-
|
|
96
|
-
// Debug logging for AI status
|
|
97
|
-
DEBUG && console.log('AIChat: currentChat:', currentChat);
|
|
98
|
-
DEBUG && console.log('AIChat: aiStatus:', aiStatus);
|
|
99
|
-
DEBUG &&
|
|
100
|
-
console.log(
|
|
101
|
-
'AIChat: enableMinimalEditFlow:',
|
|
102
|
-
enableMinimalEditFlow,
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
// Transform messages to ensure they have required id field - memoized to avoid recreation
|
|
106
|
-
const messages = useMemo(
|
|
107
|
-
() =>
|
|
108
|
-
rawMessages.map((msg, index) => ({
|
|
109
|
-
...msg,
|
|
110
|
-
id: msg.id || `msg-${index}`,
|
|
111
|
-
})),
|
|
112
|
-
[rawMessages],
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
// Check if this is the first time opening the chat (no messages) or no chat selected
|
|
116
|
-
const isEmptyState =
|
|
117
|
-
!selectedChatId || rawMessages.length === 0;
|
|
118
|
-
|
|
119
|
-
// Get all existing chats
|
|
120
|
-
const allChats = content?.chats || {};
|
|
121
|
-
const existingChats = Object.values(allChats).filter(
|
|
122
|
-
(chat) => chat.messages.length > 0,
|
|
123
|
-
);
|
|
124
|
-
const hasExistingChats = existingChats.length > 0;
|
|
125
|
-
|
|
126
|
-
// Generate title for a chat (first 50 characters of first user message)
|
|
127
|
-
const getChatTitle = (chat) => {
|
|
128
|
-
const firstUserMessage = chat.messages.find(
|
|
129
|
-
(msg) => msg.role === 'user',
|
|
130
|
-
);
|
|
131
|
-
if (!firstUserMessage) return 'New Chat';
|
|
132
|
-
return (
|
|
133
|
-
firstUserMessage.content.slice(0, 50) +
|
|
134
|
-
(firstUserMessage.content.length > 50 ? '...' : '')
|
|
135
|
-
);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// Wrapper for handleSendMessage to automatically select the chat when sending
|
|
139
|
-
const handleSendMessageWithSelection = useCallback(() => {
|
|
140
|
-
// If no chat is selected, select the currentChatId when sending a message
|
|
141
|
-
if (!selectedChatId) {
|
|
142
|
-
setSelectedChatId(currentChatId);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Call the original handleSendMessage without parameters
|
|
146
|
-
return handleSendMessage();
|
|
147
|
-
}, [
|
|
148
|
-
selectedChatId,
|
|
149
|
-
setSelectedChatId,
|
|
150
|
-
currentChatId,
|
|
151
|
-
handleSendMessage,
|
|
152
|
-
]);
|
|
153
|
-
|
|
154
|
-
// Check for stored AI prompt on component mount (post-fork restoration)
|
|
155
|
-
// This logic is now handled by the AutoSendAIMessage component in VizHub
|
|
156
|
-
// to ensure proper timing and coordination with editor opening
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
DEBUG &&
|
|
159
|
-
console.log('AIChat: Checking for stored AI prompt');
|
|
160
|
-
if (getStoredAIPrompt) {
|
|
161
|
-
const storedPrompt = getStoredAIPrompt();
|
|
162
|
-
DEBUG &&
|
|
163
|
-
console.log(
|
|
164
|
-
'AIChat: Stored prompt result:',
|
|
165
|
-
storedPrompt,
|
|
166
|
-
);
|
|
167
|
-
if (storedPrompt) {
|
|
168
|
-
// Only restore the prompt and mode, but don't auto-send
|
|
169
|
-
// The AutoSendAIMessage component will handle the sending
|
|
170
|
-
DEBUG &&
|
|
171
|
-
console.log(
|
|
172
|
-
'AIChat: Restoring prompt and mode only',
|
|
173
|
-
);
|
|
174
|
-
setAIChatMessage(storedPrompt.prompt);
|
|
175
|
-
setAIChatMode(
|
|
176
|
-
storedPrompt.modelName === 'ask' ? 'ask' : 'edit',
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
// Don't clear the stored prompt here - let AutoSendAIMessage handle it
|
|
180
|
-
// Don't auto-submit here - let AutoSendAIMessage handle the timing
|
|
181
|
-
DEBUG &&
|
|
182
|
-
console.log(
|
|
183
|
-
'AIChat: Prompt restored, waiting for AutoSendAIMessage to handle sending',
|
|
184
|
-
);
|
|
185
|
-
} else {
|
|
186
|
-
DEBUG &&
|
|
187
|
-
console.log('AIChat: No stored prompt found');
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}, [getStoredAIPrompt, setAIChatMessage, setAIChatMode]);
|
|
191
|
-
|
|
192
|
-
return (
|
|
193
|
-
<div className="ai-chat-container">
|
|
194
|
-
<div className="ai-chat-content">
|
|
195
|
-
<div
|
|
196
|
-
className="ai-chat-messages-container"
|
|
197
|
-
ref={messagesContainerRef}
|
|
198
|
-
>
|
|
199
|
-
{isEmptyState ? (
|
|
200
|
-
<div className="ai-chat-empty">
|
|
201
|
-
<div className="ai-chat-empty-icon">✨</div>
|
|
202
|
-
<h3 className="ai-chat-empty-title">
|
|
203
|
-
Edit with AI
|
|
204
|
-
</h3>
|
|
205
|
-
<div className="ai-chat-empty-text">
|
|
206
|
-
How can I help you?
|
|
207
|
-
</div>
|
|
208
|
-
{hasExistingChats && showPreviousChats && (
|
|
209
|
-
<ChatList
|
|
210
|
-
chats={existingChats}
|
|
211
|
-
selectedChatId={selectedChatId}
|
|
212
|
-
onSelectChat={setSelectedChatId}
|
|
213
|
-
getChatTitle={getChatTitle}
|
|
214
|
-
/>
|
|
215
|
-
)}
|
|
216
|
-
{showSuggestedRequests && (
|
|
217
|
-
<div className="ai-chat-empty-examples">
|
|
218
|
-
{aiChatMode === 'ask' ? (
|
|
219
|
-
<>
|
|
220
|
-
<h4>Try asking questions like:</h4>
|
|
221
|
-
<div className="ai-chat-suggested-prompts">
|
|
222
|
-
<button
|
|
223
|
-
className="ai-chat-suggested-prompt"
|
|
224
|
-
onClick={() =>
|
|
225
|
-
handleSendMessage(
|
|
226
|
-
'Explain how this works',
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
>
|
|
230
|
-
"Explain how this works"
|
|
231
|
-
</button>
|
|
232
|
-
<button
|
|
233
|
-
className="ai-chat-suggested-prompt"
|
|
234
|
-
onClick={() =>
|
|
235
|
-
handleSendMessage(
|
|
236
|
-
'How could I change it so that the circles are bigger?',
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
>
|
|
240
|
-
"How could I change it so
|
|
241
|
-
that the circles are bigger?"
|
|
242
|
-
</button>
|
|
243
|
-
<button
|
|
244
|
-
className="ai-chat-suggested-prompt"
|
|
245
|
-
onClick={() =>
|
|
246
|
-
handleSendMessage(
|
|
247
|
-
'What does this function do?',
|
|
248
|
-
)
|
|
249
|
-
}
|
|
250
|
-
>
|
|
251
|
-
"What does this function
|
|
252
|
-
do?"
|
|
253
|
-
</button>
|
|
254
|
-
<button
|
|
255
|
-
className="ai-chat-suggested-prompt"
|
|
256
|
-
onClick={() =>
|
|
257
|
-
handleSendMessage(
|
|
258
|
-
'How can I make this more accessible?',
|
|
259
|
-
)
|
|
260
|
-
}
|
|
261
|
-
>
|
|
262
|
-
"How can I make this more
|
|
263
|
-
accessible?"
|
|
264
|
-
</button>
|
|
265
|
-
</div>
|
|
266
|
-
</>
|
|
267
|
-
) : (
|
|
268
|
-
<>
|
|
269
|
-
<h4>Try edit requests like these:</h4>
|
|
270
|
-
<div className="ai-chat-suggested-prompts">
|
|
271
|
-
<button
|
|
272
|
-
className="ai-chat-suggested-prompt"
|
|
273
|
-
onClick={() =>
|
|
274
|
-
handleSendMessage(
|
|
275
|
-
'Change the circles to squares',
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
>
|
|
279
|
-
"Change the circles to
|
|
280
|
-
squares"
|
|
281
|
-
</button>
|
|
282
|
-
<button
|
|
283
|
-
className="ai-chat-suggested-prompt"
|
|
284
|
-
onClick={() =>
|
|
285
|
-
handleSendMessage(
|
|
286
|
-
'Add a button that toggles the animation',
|
|
287
|
-
)
|
|
288
|
-
}
|
|
289
|
-
>
|
|
290
|
-
"Add a button that toggles
|
|
291
|
-
the animation"
|
|
292
|
-
</button>
|
|
293
|
-
<button
|
|
294
|
-
className="ai-chat-suggested-prompt"
|
|
295
|
-
onClick={() =>
|
|
296
|
-
handleSendMessage(
|
|
297
|
-
'Fix the CSS so the layout is responsive',
|
|
298
|
-
)
|
|
299
|
-
}
|
|
300
|
-
>
|
|
301
|
-
"Fix the CSS so the layout is
|
|
302
|
-
responsive"
|
|
303
|
-
</button>
|
|
304
|
-
<button
|
|
305
|
-
className="ai-chat-suggested-prompt"
|
|
306
|
-
onClick={() =>
|
|
307
|
-
handleSendMessage(
|
|
308
|
-
'Refactor this function to use async/await',
|
|
309
|
-
)
|
|
310
|
-
}
|
|
311
|
-
>
|
|
312
|
-
"Refactor this function to
|
|
313
|
-
use async/await"
|
|
314
|
-
</button>
|
|
315
|
-
</div>
|
|
316
|
-
</>
|
|
317
|
-
)}
|
|
318
|
-
</div>
|
|
319
|
-
)}
|
|
320
|
-
{showSuggestedRequests && (
|
|
321
|
-
<div className="ai-chat-empty-text">
|
|
322
|
-
{aiChatMode === 'ask'
|
|
323
|
-
? 'Type your question below to get started!'
|
|
324
|
-
: 'Type your edit request below to get started!'}
|
|
325
|
-
</div>
|
|
326
|
-
)}
|
|
327
|
-
</div>
|
|
328
|
-
) : (
|
|
329
|
-
<MessageList
|
|
330
|
-
messages={messages}
|
|
331
|
-
isLoading={false}
|
|
332
|
-
chatId={selectedChatId || currentChatId}
|
|
333
|
-
aiScratchpad={aiScratchpad}
|
|
334
|
-
currentStatus={currentStatus}
|
|
335
|
-
onNewEvent={onNewEvent}
|
|
336
|
-
onJumpToLatest={onJumpToLatest}
|
|
337
|
-
beforeRender={beforeRender}
|
|
338
|
-
afterRender={afterRender}
|
|
339
|
-
/>
|
|
340
|
-
)}
|
|
341
|
-
{aiErrorMessage && (
|
|
342
|
-
<div className="ai-chat-error">
|
|
343
|
-
<div className="ai-chat-error-content">
|
|
344
|
-
<span className="ai-chat-error-icon">
|
|
345
|
-
⚠️
|
|
346
|
-
</span>
|
|
347
|
-
<span className="ai-chat-error-message">
|
|
348
|
-
{aiErrorMessage}
|
|
349
|
-
</span>
|
|
350
|
-
<button
|
|
351
|
-
className="ai-chat-error-dismiss"
|
|
352
|
-
onClick={() => setAIErrorMessage(null)}
|
|
353
|
-
aria-label="Dismiss error"
|
|
354
|
-
>
|
|
355
|
-
×
|
|
356
|
-
</button>
|
|
357
|
-
</div>
|
|
358
|
-
</div>
|
|
359
|
-
)}
|
|
360
|
-
{/* Jump to Latest Button */}
|
|
361
|
-
<JumpToLatestButton
|
|
362
|
-
visible={showJumpButton}
|
|
363
|
-
onClick={onJumpToLatest}
|
|
364
|
-
/>
|
|
365
|
-
</div>
|
|
366
|
-
</div>
|
|
367
|
-
<div className="ai-chat-input-fixed">
|
|
368
|
-
<ChatInput
|
|
369
|
-
aiChatMessage={aiChatMessage}
|
|
370
|
-
setAIChatMessage={setAIChatMessage}
|
|
371
|
-
onSendMessage={handleSendMessageWithSelection}
|
|
372
|
-
focused={aiChatFocused}
|
|
373
|
-
aiChatMode={aiChatMode}
|
|
374
|
-
setAIChatMode={setAIChatMode}
|
|
375
|
-
navigateMessageHistoryUp={
|
|
376
|
-
navigateMessageHistoryUp
|
|
377
|
-
}
|
|
378
|
-
navigateMessageHistoryDown={
|
|
379
|
-
navigateMessageHistoryDown
|
|
380
|
-
}
|
|
381
|
-
resetMessageHistoryNavigation={
|
|
382
|
-
resetMessageHistoryNavigation
|
|
383
|
-
}
|
|
384
|
-
/>
|
|
385
|
-
</div>
|
|
386
|
-
</div>
|
|
387
|
-
);
|
|
388
|
-
};
|