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.
Files changed (44) hide show
  1. package/dist/assets/{buildWorker-Bi6wsfQk.js → buildWorker-DylWm2VX.js} +64 -72
  2. package/dist/assets/index-CkFweu5-.js +403 -0
  3. package/dist/assets/{index-fSroLVgi.css → index-Dyt9tpmu.css} +1 -5
  4. package/dist/assets/{worker-BSzbM0Uo.js → worker-VPLhQVba.js} +230 -230
  5. package/dist/assets/worker-b9bkZkXK.js +116 -0
  6. package/dist/assets/{worker-Cm3I3tnR.js → worker-k7HZ3t-V.js} +3 -20
  7. package/dist/index.html +2 -2
  8. package/dist/llm-streaming-server/chatOperations.js +19 -0
  9. package/dist/llm-streaming-server/llmStreaming.js +18 -13
  10. package/dist/server/aiChatHandler/index.js +4 -2
  11. package/package.json +22 -21
  12. package/src/llm-streaming-server/chatOperations.ts +27 -0
  13. package/src/llm-streaming-server/llmStreaming.ts +24 -14
  14. package/src/llm-streaming-ui/components/Message.tsx +8 -7
  15. package/src/llm-streaming-ui/components/MessageList.tsx +5 -0
  16. package/src/llm-streaming-ui/components/index.tsx +2 -2
  17. package/src/llm-streaming-ui/components/styles.scss +10 -0
  18. package/src/server/aiChatHandler/index.ts +6 -0
  19. package/src/types.ts +1 -0
  20. package/dist/assets/index-CfDs-dAu.js +0 -476
  21. package/dist/assets/worker-KiuLM-X4.js +0 -136
  22. package/dist/server/aiChatHandler/aiEditing.js +0 -58
  23. package/dist/server/aiChatHandler/chatOperations.js +0 -494
  24. package/dist/server/aiChatHandler/errorHandling.js +0 -49
  25. package/dist/server/aiChatHandler/llmStreaming.js +0 -265
  26. package/dist/server/aiChatHandler/validation.js +0 -19
  27. package/src/client/VZSidebar/AIChat/ChatInput.tsx +0 -237
  28. package/src/client/VZSidebar/AIChat/DiffView.scss +0 -173
  29. package/src/client/VZSidebar/AIChat/DiffView.tsx +0 -236
  30. package/src/client/VZSidebar/AIChat/FileEditingIndicator.tsx +0 -89
  31. package/src/client/VZSidebar/AIChat/IndividualFileDiff.tsx +0 -86
  32. package/src/client/VZSidebar/AIChat/JumpToLatestButton.tsx +0 -64
  33. package/src/client/VZSidebar/AIChat/Message.tsx +0 -145
  34. package/src/client/VZSidebar/AIChat/MessageList.tsx +0 -241
  35. package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +0 -42
  36. package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +0 -19
  37. package/src/client/VZSidebar/AIChat/index.tsx +0 -388
  38. package/src/client/VZSidebar/AIChat/styles.scss +0 -831
  39. package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +0 -116
  40. package/src/server/aiChatHandler/aiEditing.ts +0 -102
  41. package/src/server/aiChatHandler/chatOperations.ts +0 -655
  42. package/src/server/aiChatHandler/errorHandling.ts +0 -66
  43. package/src/server/aiChatHandler/llmStreaming.ts +0 -403
  44. package/src/server/aiChatHandler/validation.ts +0 -24
@@ -1,236 +0,0 @@
1
- import React, {
2
- useContext,
3
- useEffect,
4
- useRef,
5
- forwardRef,
6
- useImperativeHandle,
7
- } from 'react';
8
- import {
9
- UnifiedFilesDiff,
10
- parseUnifiedDiffStats,
11
- combineUnifiedDiffs,
12
- isFileDeletion,
13
- getDeletedFileName,
14
- } from '../../../utils/fileDiff';
15
- import * as Diff2Html from 'diff2html';
16
- import 'diff2html/bundles/css/diff2html.min.css';
17
- import './DiffView.scss';
18
- import { VZCodeContext } from '../../VZCodeContext';
19
- import {
20
- scrollToFirstDiff,
21
- getHeaderOffset,
22
- announceDiffSummary,
23
- } from '../../utils/scrollUtils';
24
- import { getFileId } from '@vizhub/viz-utils';
25
-
26
- interface DiffViewProps {
27
- diffData: UnifiedFilesDiff;
28
- }
29
-
30
- // Methods that can be called on DiffView from parent components
31
- export interface DiffViewRef {
32
- scrollToFirstHunk: () => void;
33
- focusDiffContainer: () => void;
34
- announceSummary: () => void;
35
- getFirstHunkElement: () => HTMLElement | null;
36
- }
37
-
38
- export const DiffView = forwardRef<
39
- DiffViewRef,
40
- DiffViewProps
41
- >(({ diffData }, ref) => {
42
- const { content, openTab, setIsAIChatOpen } =
43
- useContext(VZCodeContext);
44
- const diffContainerRef = useRef<HTMLDivElement>(null);
45
-
46
- const allDiffs = Object.values(diffData).filter(
47
- (diff) => diff.length > 0,
48
- );
49
-
50
- // Separate deleted files from regular diffs
51
- const deletedFiles: string[] = [];
52
- const regularDiffs: string[] = [];
53
-
54
- for (const diff of allDiffs) {
55
- if (isFileDeletion(diff)) {
56
- deletedFiles.push(diff);
57
- } else {
58
- regularDiffs.push(diff);
59
- }
60
- }
61
-
62
- // Calculate statistics from regular unified diffs only
63
- let totalAdditions = 0;
64
- let totalDeletions = 0;
65
-
66
- for (const unifiedDiff of regularDiffs) {
67
- const stats = parseUnifiedDiffStats(unifiedDiff);
68
- totalAdditions += stats.additions;
69
- totalDeletions += stats.deletions;
70
- }
71
-
72
- // Combine regular unified diffs and convert to HTML using diff2html
73
- const combinedUnifiedDiff = combineUnifiedDiffs(diffData);
74
- const diffHtml = Diff2Html.html(combinedUnifiedDiff, {
75
- drawFileList: false,
76
- matching: 'words',
77
- diffStyle: 'word',
78
- outputFormat: 'line-by-line',
79
- });
80
-
81
- // Expose methods for parent components to control scrolling and focus
82
- useImperativeHandle(
83
- ref,
84
- () => ({
85
- scrollToFirstHunk: () => {
86
- if (diffContainerRef.current) {
87
- const headerOffset = getHeaderOffset();
88
- scrollToFirstDiff(
89
- diffContainerRef.current,
90
- headerOffset,
91
- );
92
- }
93
- },
94
- focusDiffContainer: () => {
95
- if (diffContainerRef.current) {
96
- diffContainerRef.current.tabIndex = -1;
97
- diffContainerRef.current.focus();
98
- }
99
- },
100
- announceSummary: () => {
101
- if (diffContainerRef.current) {
102
- announceDiffSummary(diffContainerRef.current);
103
- }
104
- },
105
- getFirstHunkElement: () => {
106
- if (diffContainerRef.current) {
107
- return diffContainerRef.current.querySelector(
108
- '.d2h-diff-tbody tr',
109
- );
110
- }
111
- return null;
112
- },
113
- }),
114
- [],
115
- );
116
-
117
- // Add click handlers to file names after HTML is rendered
118
- useEffect(() => {
119
- if (!diffContainerRef.current) return;
120
-
121
- const handleFileNameClick = (event: MouseEvent) => {
122
- const target = event.target as HTMLElement;
123
- if (target.classList.contains('d2h-file-name')) {
124
- event.preventDefault();
125
- event.stopPropagation();
126
-
127
- console.log('File name clicked');
128
-
129
- const fileName = target.textContent?.trim();
130
-
131
- console.log('Clicked file name:', fileName);
132
- if (fileName && content) {
133
- const fileId = getFileId(content, fileName);
134
-
135
- if (fileId) {
136
- // Open the file tab and switch to files view
137
- openTab({ fileId, isTransient: false });
138
- setIsAIChatOpen(false);
139
- }
140
- }
141
- }
142
- };
143
-
144
- const container = diffContainerRef.current;
145
- const fileNameElements = container.querySelectorAll(
146
- '.d2h-file-name',
147
- );
148
-
149
- // Add click event listeners and cursor pointer style
150
- fileNameElements.forEach((element) => {
151
- element.addEventListener(
152
- 'click',
153
- handleFileNameClick,
154
- );
155
- (element as HTMLElement).style.cursor = 'pointer';
156
- (element as HTMLElement).style.textDecoration =
157
- 'underline';
158
- (element as HTMLElement).style.color = '#58a6ff'; // GitHub blue link color
159
- });
160
-
161
- return () => {
162
- // Cleanup event listeners
163
- fileNameElements.forEach((element) => {
164
- element.removeEventListener(
165
- 'click',
166
- handleFileNameClick,
167
- );
168
- });
169
- };
170
- }, [diffHtml, content, openTab, setIsAIChatOpen]);
171
-
172
- if (allDiffs.length === 0) {
173
- return null;
174
- }
175
-
176
- return (
177
- <div className="diff-view">
178
- <div className="diff-summary" id="diff-summary">
179
- <div className="diff-stats">
180
- <span className="files-changed">
181
- {allDiffs.length} file
182
- {allDiffs.length !== 1 ? 's' : ''} changed
183
- </span>
184
- {totalAdditions > 0 && (
185
- <span className="additions">
186
- +{totalAdditions}
187
- </span>
188
- )}
189
- {totalDeletions > 0 && (
190
- <span className="deletions">
191
- -{totalDeletions}
192
- </span>
193
- )}
194
- {deletedFiles.length > 0 && (
195
- <span className="deletions">
196
- {deletedFiles.length} deleted
197
- </span>
198
- )}
199
- </div>
200
- </div>
201
-
202
- {/* Render deleted files */}
203
- {deletedFiles.map((deletionMarker, index) => {
204
- const fileName = getDeletedFileName(deletionMarker);
205
- return (
206
- <div key={index} className="deleted-file">
207
- <div className="deleted-file-header">
208
- <span className="deleted-file-name">
209
- {fileName}
210
- </span>
211
- <span className="deleted-file-status">
212
- File deleted
213
- </span>
214
- </div>
215
- </div>
216
- );
217
- })}
218
-
219
- {/* Render regular diffs */}
220
- {regularDiffs.length > 0 && (
221
- <div
222
- className="diff-files"
223
- ref={diffContainerRef}
224
- tabIndex={-1}
225
- role="region"
226
- aria-label="Code diff content"
227
- aria-describedby="diff-summary"
228
- dangerouslySetInnerHTML={{ __html: diffHtml }}
229
- />
230
- )}
231
- </div>
232
- );
233
- });
234
-
235
- // Add display name for debugging
236
- DiffView.displayName = 'DiffView';
@@ -1,89 +0,0 @@
1
- import { Spinner } from '../../AIAssist/Spinner';
2
-
3
- interface AIEditingStatusIndicatorProps {
4
- status: string;
5
- fileName?: string;
6
- additionalWidgets?: React.ReactNode;
7
- }
8
-
9
- export const AIEditingStatusIndicator: React.FC<
10
- AIEditingStatusIndicatorProps
11
- > = ({ status, fileName, additionalWidgets }) => {
12
- const getStatusDisplay = (status: string) => {
13
- switch (status) {
14
- case 'Analyzing request...':
15
- return (
16
- <>
17
- 🔍 <span>Analyzing request...</span>
18
- </>
19
- );
20
- case 'Formulating a plan...':
21
- return (
22
- <>
23
- 💭 <span>Formulating a plan...</span>
24
- </>
25
- );
26
- case 'Describing changes...':
27
- return (
28
- <>
29
- 📝 <span>Describing changes...</span>
30
- </>
31
- );
32
- case 'Thinking...':
33
- return (
34
- <>
35
- 🤔 <span>Thinking...</span>
36
- </>
37
- );
38
- case 'Done':
39
- return (
40
- <div className="file-editing-done-container">
41
- <div className="file-editing-done-status">
42
- ✅ <span>Done</span>
43
- </div>
44
- {additionalWidgets && (
45
- <div className="file-editing-done-widgets">
46
- {additionalWidgets}
47
- </div>
48
- )}
49
- </div>
50
- );
51
- default:
52
- // Handle file editing status (e.g., "Editing filename.js...")
53
- if (status.startsWith('Editing ') && fileName) {
54
- return (
55
- <>
56
- ✏️{' '}
57
- <span>
58
- Editing <code>{fileName}</code>...
59
- </span>
60
- </>
61
- );
62
- } else if (status.startsWith('Editing ')) {
63
- return (
64
- <>
65
- ✏️ <span>{status}</span>
66
- </>
67
- );
68
- }
69
- return <span>{status}</span>;
70
- }
71
- };
72
-
73
- const showSpinner = status !== 'Done';
74
-
75
- return (
76
- <div className="file-editing-indicator">
77
- <div className="file-editing-header">
78
- {showSpinner && (
79
- <div className="file-editing-icon">
80
- <Spinner height={16} fadeIn={false} />
81
- </div>
82
- )}
83
- <div className="file-editing-text">
84
- {getStatusDisplay(status)}
85
- </div>
86
- </div>
87
- </div>
88
- );
89
- };
@@ -1,86 +0,0 @@
1
- import React, { useMemo } from 'react';
2
- import * as Diff2Html from 'diff2html';
3
- import 'diff2html/bundles/css/diff2html.min.css';
4
- import './DiffView.scss';
5
- import { generateFileUnifiedDiff } from '../../../utils/fileDiff';
6
-
7
- interface IndividualFileDiffProps {
8
- fileName: string;
9
- beforeContent: string;
10
- afterContent: string;
11
- }
12
-
13
- export const IndividualFileDiff: React.FC<
14
- IndividualFileDiffProps
15
- > = ({ fileName, beforeContent, afterContent }) => {
16
- // Generate unified diff for this single file
17
- const unifiedDiff = useMemo(() => {
18
- return generateFileUnifiedDiff(
19
- 'temp-id',
20
- fileName,
21
- beforeContent,
22
- afterContent,
23
- );
24
- }, [fileName, beforeContent, afterContent]);
25
-
26
- // Convert to HTML using diff2html
27
- const diffHtml = useMemo(() => {
28
- if (!unifiedDiff) return '';
29
-
30
- return Diff2Html.html(unifiedDiff, {
31
- drawFileList: false,
32
- matching: 'words',
33
- diffStyle: 'word',
34
- outputFormat: 'line-by-line',
35
- });
36
- }, [unifiedDiff]);
37
-
38
- // Calculate simple stats
39
- const stats = useMemo(() => {
40
- const lines = unifiedDiff.split('\n');
41
- let additions = 0;
42
- let deletions = 0;
43
-
44
- lines.forEach((line) => {
45
- if (line.startsWith('+') && !line.startsWith('+++')) {
46
- additions++;
47
- } else if (
48
- line.startsWith('-') &&
49
- !line.startsWith('---')
50
- ) {
51
- deletions++;
52
- }
53
- });
54
-
55
- return { additions, deletions };
56
- }, [unifiedDiff]);
57
-
58
- if (!unifiedDiff) {
59
- return null;
60
- }
61
-
62
- return (
63
- <div className="diff-view" data-file={fileName}>
64
- <div className="diff-summary">
65
- <div className="diff-stats">
66
- <span className="files-changed">{fileName}</span>
67
- {stats.additions > 0 && (
68
- <span className="additions">
69
- +{stats.additions}
70
- </span>
71
- )}
72
- {stats.deletions > 0 && (
73
- <span className="deletions">
74
- -{stats.deletions}
75
- </span>
76
- )}
77
- </div>
78
- </div>
79
-
80
- <div
81
- className="diff-files"
82
- dangerouslySetInnerHTML={{ __html: diffHtml }}
83
- />
84
- </div>
85
- );
86
- };
@@ -1,64 +0,0 @@
1
- import React, { useCallback } from 'react';
2
-
3
- /**
4
- * Props for the JumpToLatestButton component
5
- */
6
- interface JumpToLatestButtonProps {
7
- /** Whether the button is visible */
8
- visible: boolean;
9
- /** Callback when button is clicked */
10
- onClick: () => void;
11
- /** Additional CSS class name */
12
- className?: string;
13
- }
14
-
15
- /**
16
- * Circular "down arrow" button that floats at the bottom center of scroll area
17
- * Appears when auto-scroll is disabled and user is not at bottom
18
- */
19
- export const JumpToLatestButton: React.FC<
20
- JumpToLatestButtonProps
21
- > = ({ visible, onClick, className = '' }) => {
22
- /**
23
- * Handle button click
24
- */
25
- const handleClick = useCallback(() => {
26
- onClick();
27
- }, [onClick]);
28
-
29
- /**
30
- * Handle keyboard events for accessibility
31
- */
32
- const handleKeyDown = useCallback(
33
- (event: React.KeyboardEvent) => {
34
- if (event.key === 'Enter' || event.key === ' ') {
35
- event.preventDefault();
36
- onClick();
37
- }
38
- },
39
- [onClick],
40
- );
41
-
42
- if (!visible) {
43
- return null;
44
- }
45
-
46
- return (
47
- <button
48
- className={`jump-to-latest-button ${className}`.trim()}
49
- onClick={handleClick}
50
- onKeyDown={handleKeyDown}
51
- aria-label="Jump to latest"
52
- tabIndex={0}
53
- type="button"
54
- >
55
- {/* Down arrow icon using Unicode or can be replaced with SVG */}
56
- <span
57
- className="jump-to-latest-icon"
58
- aria-hidden="true"
59
- >
60
-
61
- </span>
62
- </button>
63
- );
64
- };
@@ -1,145 +0,0 @@
1
- import React, {
2
- useMemo,
3
- useContext,
4
- forwardRef,
5
- } from 'react';
6
- import { timestampToDate } from '@vizhub/viz-utils';
7
- import Markdown from 'react-markdown';
8
- import remarkGfm from 'remark-gfm';
9
- import { StreamingEvent } from '../../../types.js';
10
- import { IndividualFileDiff } from './IndividualFileDiff';
11
- import { VZCodeContext } from '../../VZCodeContext';
12
- import { DiffView, DiffViewRef } from './DiffView';
13
- import { UnifiedFilesDiff } from '../../../utils/fileDiff';
14
- import { enableDiffView } from '../../featureFlags';
15
-
16
- const DEBUG = false;
17
-
18
- interface MessageProps {
19
- id: string;
20
- role: 'user' | 'assistant';
21
- content?: string; // For non-streaming messages
22
- timestamp: number;
23
- events?: StreamingEvent[]; // For streaming messages
24
- isActive?: boolean; // Is this the currently streaming message?
25
- chatId?: string;
26
- showAdditionalWidgets?: boolean;
27
- isStreaming?: boolean;
28
- diffData?: UnifiedFilesDiff;
29
- }
30
-
31
- export const Message = forwardRef<
32
- DiffViewRef,
33
- React.PropsWithChildren<MessageProps>
34
- >(
35
- (
36
- {
37
- id,
38
- role,
39
- content,
40
- timestamp,
41
- events = [],
42
- isActive,
43
- children,
44
- chatId,
45
- showAdditionalWidgets = false,
46
- isStreaming = false,
47
- diffData,
48
- },
49
- ref,
50
- ) => {
51
- const { additionalWidgets, handleSendMessage } =
52
- useContext(VZCodeContext);
53
-
54
- DEBUG &&
55
- console.log(
56
- 'StreamingMessage: Rendered with events:',
57
- events,
58
- );
59
-
60
- // Memoize date formatting to avoid repeated computation
61
- const formattedTime = useMemo(() => {
62
- return timestampToDate(timestamp).toLocaleTimeString(
63
- [],
64
- {
65
- hour: '2-digit',
66
- minute: '2-digit',
67
- },
68
- );
69
- }, [timestamp]);
70
-
71
- // Memoize the className string to avoid recreation
72
- const messageClassName = useMemo(() => {
73
- return `ai-chat-message ${role}${isStreaming ? ' streaming' : ''}`;
74
- }, [role, isStreaming]);
75
-
76
- return (
77
- <div className={messageClassName}>
78
- <div className="ai-chat-message-content">
79
- {/* Render regular content for non-streaming messages */}
80
- {content && (
81
- <Markdown remarkPlugins={[remarkGfm]}>
82
- {content}
83
- </Markdown>
84
- )}
85
-
86
- {/* Render diff view if present */}
87
- {enableDiffView &&
88
- diffData &&
89
- Object.keys(diffData).length > 0 && (
90
- <DiffView diffData={diffData} ref={ref} />
91
- )}
92
-
93
- {/* Render events in order for streaming messages */}
94
- {events.map((event, index) => {
95
- switch (event.type) {
96
- case 'text_chunk':
97
- return (
98
- <div
99
- key={`text-${index}`}
100
- className="text-chunk"
101
- >
102
- <Markdown remarkPlugins={[remarkGfm]}>
103
- {event.content}
104
- </Markdown>
105
- </div>
106
- );
107
- case 'file_complete':
108
- return (
109
- <IndividualFileDiff
110
- key={`file-${event.fileName}-${index}`}
111
- fileName={event.fileName}
112
- beforeContent={
113
- event.beforeContent || ''
114
- }
115
- afterContent={event.afterContent || ''}
116
- />
117
- );
118
- case 'file_start':
119
- // File start events are now handled by centralized status logic in MessageList
120
- return null;
121
- default:
122
- return null;
123
- }
124
- })}
125
- {/* Additional widgets are now shown within the "Done" status indicator */}
126
- {showAdditionalWidgets &&
127
- additionalWidgets &&
128
- chatId &&
129
- !isStreaming && // Only show here for non-streaming messages (completed messages)
130
- additionalWidgets({
131
- messageId: id,
132
- chatId: chatId,
133
- handleSendMessage,
134
- })}
135
- {isActive && children}
136
- </div>
137
- <div className="ai-chat-message-time">
138
- {formattedTime}
139
- </div>
140
- </div>
141
- );
142
- },
143
- );
144
-
145
- Message.displayName = 'Message';