vzcode 2.18.0 → 2.22.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 (57) hide show
  1. package/README.md +359 -0
  2. package/dist/assets/index-CfDs-dAu.js +476 -0
  3. package/dist/assets/{index-BvGPtSrr.css → index-fSroLVgi.css} +1 -1
  4. package/dist/assets/{worker-y5jhqCTR.js → worker-Cm3I3tnR.js} +57 -57
  5. package/dist/assets/{worker-ClF0pBYr.js → worker-KiuLM-X4.js} +71 -71
  6. package/dist/index.html +2 -2
  7. package/dist/llm-streaming-server/aiEditing.js +58 -0
  8. package/dist/llm-streaming-server/chatOperations.js +494 -0
  9. package/dist/llm-streaming-server/errorHandling.js +49 -0
  10. package/dist/llm-streaming-server/index.js +20 -0
  11. package/dist/llm-streaming-server/llmStreaming.js +265 -0
  12. package/dist/llm-streaming-server/validation.js +19 -0
  13. package/dist/server/aiChatHandler/chatOperations.js +3 -4
  14. package/dist/server/aiChatHandler/index.js +5 -5
  15. package/dist/server/aiChatHandler/llmStreaming.js +57 -4
  16. package/dist/server/prettier.js +18 -16
  17. package/dist/utils/fileDiff.js +25 -1
  18. package/package.json +45 -45
  19. package/src/client/CodeEditor/getOrCreateEditor.tsx +272 -258
  20. package/src/client/CodeEditor/index.tsx +3 -3
  21. package/src/client/VZRight.tsx +4 -0
  22. package/src/client/VZSidebar/AIChat/ChatInput.tsx +1 -1
  23. package/src/client/VZSidebar/AIChat/DiffView.scss +37 -1
  24. package/src/client/VZSidebar/AIChat/DiffView.tsx +55 -16
  25. package/src/client/VZSidebar/AIChat/styles.scss +38 -0
  26. package/src/client/VZSidebar/FileTypeIcon.tsx +1 -0
  27. package/src/client/VZSidebar/index.tsx +1 -1
  28. package/src/client/featureFlags.ts +7 -0
  29. package/src/llm-streaming-server/README.md +71 -0
  30. package/src/llm-streaming-server/aiEditing.ts +102 -0
  31. package/src/llm-streaming-server/chatOperations.ts +655 -0
  32. package/src/llm-streaming-server/errorHandling.ts +66 -0
  33. package/src/llm-streaming-server/index.ts +51 -0
  34. package/src/llm-streaming-server/llmStreaming.ts +403 -0
  35. package/src/llm-streaming-server/validation.ts +24 -0
  36. package/src/llm-streaming-ui/README.md +103 -0
  37. package/src/llm-streaming-ui/components/ChatInput.tsx +237 -0
  38. package/src/llm-streaming-ui/components/DiffView.scss +173 -0
  39. package/src/llm-streaming-ui/components/DiffView.tsx +236 -0
  40. package/src/llm-streaming-ui/components/FileEditingIndicator.tsx +89 -0
  41. package/src/llm-streaming-ui/components/IndividualFileDiff.tsx +86 -0
  42. package/src/llm-streaming-ui/components/JumpToLatestButton.tsx +64 -0
  43. package/src/llm-streaming-ui/components/Message.tsx +145 -0
  44. package/src/llm-streaming-ui/components/MessageList.tsx +241 -0
  45. package/src/llm-streaming-ui/components/ThinkingScratchpad.tsx +42 -0
  46. package/src/llm-streaming-ui/components/TypingIndicator.tsx +19 -0
  47. package/src/llm-streaming-ui/components/index.tsx +388 -0
  48. package/src/llm-streaming-ui/components/styles.scss +831 -0
  49. package/src/llm-streaming-ui/components/useSpeechRecognition.ts +116 -0
  50. package/src/llm-streaming-ui/index.ts +34 -0
  51. package/src/server/aiChatHandler/chatOperations.ts +3 -4
  52. package/src/server/aiChatHandler/index.ts +5 -5
  53. package/src/server/aiChatHandler/llmStreaming.ts +87 -4
  54. package/src/server/prettier.ts +30 -31
  55. package/src/types.ts +5 -0
  56. package/src/utils/fileDiff.ts +36 -2
  57. package/dist/assets/index-DuDXdJWC.js +0 -477
@@ -11,7 +11,7 @@
11
11
  display: flex;
12
12
  align-items: center;
13
13
  gap: 8px;
14
- font-size: 12px;
14
+ font-size: 14px;
15
15
  color: var(--bs-secondary-color);
16
16
 
17
17
  .files-changed {
@@ -134,4 +134,40 @@
134
134
  background-color: var(--d2h-bg-color);
135
135
  }
136
136
  }
137
+
138
+ .deleted-file {
139
+ margin-bottom: 8px;
140
+ border: 1px solid #30363d;
141
+ border-radius: 6px;
142
+ background-color: rgb(41, 44, 52);
143
+ overflow: hidden;
144
+
145
+ .deleted-file-header {
146
+ display: flex;
147
+ justify-content: space-between;
148
+ align-items: center;
149
+ padding: 12px 16px;
150
+ background-color: #161b22;
151
+ border-bottom: 1px solid #30363d;
152
+
153
+ .deleted-file-name {
154
+ color: #e6edf3;
155
+ font-weight: 600;
156
+ font-family:
157
+ 'SFMono-Regular', Consolas, 'Liberation Mono',
158
+ Menlo, monospace;
159
+ font-size: 14px;
160
+ }
161
+
162
+ .deleted-file-status {
163
+ color: #f85149;
164
+ font-weight: 600;
165
+ font-size: 14px;
166
+ background-color: rgba(248, 81, 73, 0.1);
167
+ padding: 4px 8px;
168
+ border-radius: 4px;
169
+ border: 1px solid rgba(248, 81, 73, 0.4);
170
+ }
171
+ }
172
+ }
137
173
  }
@@ -9,6 +9,8 @@ import {
9
9
  UnifiedFilesDiff,
10
10
  parseUnifiedDiffStats,
11
11
  combineUnifiedDiffs,
12
+ isFileDeletion,
13
+ getDeletedFileName,
12
14
  } from '../../../utils/fileDiff';
13
15
  import * as Diff2Html from 'diff2html';
14
16
  import 'diff2html/bundles/css/diff2html.min.css';
@@ -41,21 +43,33 @@ export const DiffView = forwardRef<
41
43
  useContext(VZCodeContext);
42
44
  const diffContainerRef = useRef<HTMLDivElement>(null);
43
45
 
44
- const unifiedDiffs = Object.values(diffData).filter(
46
+ const allDiffs = Object.values(diffData).filter(
45
47
  (diff) => diff.length > 0,
46
48
  );
47
49
 
48
- // Calculate statistics from all unified diffs
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
49
63
  let totalAdditions = 0;
50
64
  let totalDeletions = 0;
51
65
 
52
- for (const unifiedDiff of unifiedDiffs) {
66
+ for (const unifiedDiff of regularDiffs) {
53
67
  const stats = parseUnifiedDiffStats(unifiedDiff);
54
68
  totalAdditions += stats.additions;
55
69
  totalDeletions += stats.deletions;
56
70
  }
57
71
 
58
- // Combine all unified diffs and convert to HTML using diff2html
72
+ // Combine regular unified diffs and convert to HTML using diff2html
59
73
  const combinedUnifiedDiff = combineUnifiedDiffs(diffData);
60
74
  const diffHtml = Diff2Html.html(combinedUnifiedDiff, {
61
75
  drawFileList: false,
@@ -155,7 +169,7 @@ export const DiffView = forwardRef<
155
169
  };
156
170
  }, [diffHtml, content, openTab, setIsAIChatOpen]);
157
171
 
158
- if (unifiedDiffs.length === 0) {
172
+ if (allDiffs.length === 0) {
159
173
  return null;
160
174
  }
161
175
 
@@ -164,8 +178,8 @@ export const DiffView = forwardRef<
164
178
  <div className="diff-summary" id="diff-summary">
165
179
  <div className="diff-stats">
166
180
  <span className="files-changed">
167
- {unifiedDiffs.length} file
168
- {unifiedDiffs.length !== 1 ? 's' : ''} changed
181
+ {allDiffs.length} file
182
+ {allDiffs.length !== 1 ? 's' : ''} changed
169
183
  </span>
170
184
  {totalAdditions > 0 && (
171
185
  <span className="additions">
@@ -177,18 +191,43 @@ export const DiffView = forwardRef<
177
191
  -{totalDeletions}
178
192
  </span>
179
193
  )}
194
+ {deletedFiles.length > 0 && (
195
+ <span className="deletions">
196
+ {deletedFiles.length} deleted
197
+ </span>
198
+ )}
180
199
  </div>
181
200
  </div>
182
201
 
183
- <div
184
- className="diff-files"
185
- ref={diffContainerRef}
186
- tabIndex={-1}
187
- role="region"
188
- aria-label="Code diff content"
189
- aria-describedby="diff-summary"
190
- dangerouslySetInnerHTML={{ __html: diffHtml }}
191
- />
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
+ )}
192
231
  </div>
193
232
  );
194
233
  });
@@ -362,6 +362,25 @@
362
362
  margin-top: 1rem;
363
363
  margin-bottom: 0.5rem;
364
364
  color: inherit;
365
+ font-weight: 600;
366
+ }
367
+
368
+ h1 {
369
+ font-size: 1.25rem; // 20px at 16px base
370
+ }
371
+
372
+ h2 {
373
+ font-size: 1.125rem; // 18px at 16px base
374
+ }
375
+
376
+ h3 {
377
+ font-size: 1rem; // 16px at 16px base
378
+ }
379
+
380
+ h4,
381
+ h5,
382
+ h6 {
383
+ font-size: 0.875rem; // 14px at 16px base
365
384
  }
366
385
 
367
386
  ul,
@@ -784,10 +803,29 @@
784
803
  h6 {
785
804
  margin: 8px 0 6px 0;
786
805
  color: var(--vh-color-neutral-04);
806
+ font-weight: 600;
787
807
 
788
808
  &:first-child {
789
809
  margin-top: 0;
790
810
  }
791
811
  }
812
+
813
+ h1 {
814
+ font-size: 1.125rem; // 18px at 16px base
815
+ }
816
+
817
+ h2 {
818
+ font-size: 1rem; // 16px at 16px base
819
+ }
820
+
821
+ h3 {
822
+ font-size: 0.9375rem; // 15px at 16px base
823
+ }
824
+
825
+ h4,
826
+ h5,
827
+ h6 {
828
+ font-size: 0.8125rem; // 13px at 16px base
829
+ }
792
830
  }
793
831
  }
@@ -25,6 +25,7 @@ export function getExtensionIcon(fileName: string) {
25
25
  case 'json':
26
26
  return <JsonSVG />;
27
27
  case 'md':
28
+ case 'prompt':
28
29
  return <MarkdownSVG />;
29
30
  case 'html':
30
31
  return <HtmlSVG />;
@@ -32,7 +32,7 @@ import { MicSVG } from '../Icons/MicSVG';
32
32
  import { sortFileTree } from '../sortFileTree';
33
33
  import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
34
34
  import { VZCodeContext } from '../VZCodeContext';
35
- import { AIChat } from './AIChat';
35
+ import { AIChat } from '../../llm-streaming-ui/components/index.js';
36
36
  import { Listing } from './Listing';
37
37
  import { Search } from './Search';
38
38
  import { DeleteConfirmationModal } from './DeleteConfirmationModal';
@@ -9,3 +9,10 @@ export const enableAskMode = false;
9
9
 
10
10
  // Phase 0: Feature flag for minimal AI edit flow
11
11
  export const enableMinimalEditFlow = true;
12
+
13
+ // If true, only include the minimal set of extensions,
14
+ // namely only the JSON1 OT extension and that's it.
15
+ // This is useful for testing and debugging the OT functionality
16
+ // without any other extensions getting in the way,
17
+ // just to rule them out as the source of a bug.
18
+ export const MINIMAL_EXTENSIONS = false;
@@ -0,0 +1,71 @@
1
+ # LLM Streaming Server
2
+
3
+ Server-side library for streaming LLM responses with ShareDB integration.
4
+
5
+ ## Overview
6
+
7
+ This module is being extracted from VZCode's AI chat functionality to become a standalone, reusable library. It provides server-side functionality for:
8
+
9
+ - **LLM Streaming**: Creating and managing streaming LLM functions with OpenAI/OpenRouter
10
+ - **AI Code Editing**: Performing AI-assisted code transformations and edits
11
+ - **ShareDB Integration**: Managing chat operations and real-time collaboration
12
+ - **Request Validation**: Validating incoming AI chat requests
13
+ - **Error Handling**: Handling errors gracefully in streaming contexts
14
+
15
+ ## Current Status
16
+
17
+ ⚠️ **Work in Progress**: This is Phase 1 of the extraction plan. The files have been copied to this directory and imports have been updated, but the code is not yet decoupled from VZCode.
18
+
19
+ ## Files
20
+
21
+ - `index.ts` - Main entry point exporting all public APIs
22
+ - `llmStreaming.ts` - Core LLM streaming functionality with OpenAI/OpenRouter
23
+ - `aiEditing.ts` - AI-assisted code editing operations
24
+ - `chatOperations.ts` - ShareDB chat operations (messages, events, streaming)
25
+ - `validation.ts` - Request validation utilities
26
+ - `errorHandling.ts` - Error handling for streaming contexts
27
+
28
+ ## Dependencies
29
+
30
+ Currently depends on:
31
+
32
+ - `openai` - OpenAI SDK for LLM interactions
33
+ - `sharedb` - Real-time collaborative editing framework
34
+ - `llm-code-format` - Markdown parsing for code blocks
35
+ - `editcodewithai` - Code editing utilities
36
+ - VZCode utilities: `../types.js`, `../ot.js`, `../randomId.js`, etc.
37
+
38
+ ## Future Plans
39
+
40
+ This module will eventually be:
41
+
42
+ 1. Decoupled from VZCode-specific types and utilities
43
+ 2. Published as `@vizhub/llm-streaming-server` on npm
44
+ 3. Made framework-agnostic (can work with Express, Fastify, etc.)
45
+ 4. Fully documented with usage examples
46
+
47
+ See the [main issue](https://github.com/vizhub-core/vzcode/issues/XXX) for the full extraction plan.
48
+
49
+ ## Usage (Current)
50
+
51
+ Currently, this module is imported by `src/server/aiChatHandler/index.ts`:
52
+
53
+ ```typescript
54
+ import { createLLMFunction } from '../../llm-streaming-server/llmStreaming.js';
55
+ import { performAIEditing } from '../../llm-streaming-server/aiEditing.js';
56
+ import {
57
+ ensureChatsExist,
58
+ ensureChatExists,
59
+ addUserMessage,
60
+ setAIStatus,
61
+ } from '../../llm-streaming-server/chatOperations.js';
62
+ ```
63
+
64
+ ## Contributing
65
+
66
+ As this module is being extracted, please:
67
+
68
+ - Keep changes minimal and focused
69
+ - Maintain backward compatibility with VZCode
70
+ - Update tests when modifying functionality
71
+ - Document any new dependencies or breaking changes
@@ -0,0 +1,102 @@
1
+ import {
2
+ assembleFullPrompt,
3
+ prepareFilesForPrompt,
4
+ } from 'editcodewithai';
5
+ import { formatMarkdownFiles } from 'llm-code-format';
6
+ import {
7
+ createFilesSnapshot,
8
+ generateFilesUnifiedDiff,
9
+ } from '../utils/fileDiff.js';
10
+
11
+ // Dev flag for waiting 1 second before starting the LLM function.
12
+ // Useful for debugging and testing purposes, e.g. checking the typing indicator.
13
+ const delayStart = false;
14
+
15
+ /**
16
+ * Performs AI chat without editing - just generates a response
17
+ */
18
+ export const performAIChat = async ({
19
+ prompt,
20
+ shareDBDoc,
21
+ llmFunction,
22
+ }) => {
23
+ const { files } = prepareFilesForPrompt(
24
+ shareDBDoc.data.files,
25
+ );
26
+ const filesContext = formatMarkdownFiles(files);
27
+
28
+ // 2. Assemble the final prompt for Q&A mode
29
+ const fullPrompt = assembleFullPrompt({
30
+ filesContext,
31
+ prompt,
32
+ editFormat: 'whole',
33
+ });
34
+
35
+ if (delayStart) {
36
+ await new Promise((resolve) =>
37
+ setTimeout(resolve, 1000),
38
+ );
39
+ }
40
+
41
+ // Call the LLM function which will handle streaming but won't edit files
42
+ const result = await llmFunction(fullPrompt);
43
+
44
+ return {
45
+ content: result.content,
46
+ generationId: result.generationId,
47
+ diffData: {}, // No file changes in ask mode
48
+ };
49
+ };
50
+
51
+ /**
52
+ * Performs AI editing operations using streaming with incremental OT operations
53
+ */
54
+ export const performAIEditing = async ({
55
+ prompt,
56
+ shareDBDoc,
57
+ llmFunction,
58
+ runCode,
59
+ }) => {
60
+ // Capture the current state of files before editing
61
+ const beforeFiles = createFilesSnapshot(
62
+ shareDBDoc.data.files,
63
+ );
64
+
65
+ const { files } = prepareFilesForPrompt(
66
+ shareDBDoc.data.files,
67
+ );
68
+ const filesContext = formatMarkdownFiles(files);
69
+
70
+ // Assemble the final prompt
71
+ const fullPrompt = assembleFullPrompt({
72
+ filesContext,
73
+ prompt,
74
+ editFormat: 'whole',
75
+ });
76
+
77
+ if (delayStart) {
78
+ await new Promise((resolve) =>
79
+ setTimeout(resolve, 1000),
80
+ );
81
+ }
82
+
83
+ // Call the LLM function which will handle streaming, incremental file updates, and Prettier formatting
84
+ const result = await llmFunction(fullPrompt);
85
+
86
+ runCode();
87
+
88
+ // 4. Capture the state of files after editing and formatting, then generate diff
89
+ const afterFiles = createFilesSnapshot(
90
+ shareDBDoc.data.files,
91
+ );
92
+ const diffData = generateFilesUnifiedDiff(
93
+ beforeFiles,
94
+ afterFiles,
95
+ );
96
+
97
+ return {
98
+ content: result.content,
99
+ generationId: result.generationId,
100
+ diffData,
101
+ };
102
+ };