vzcode 2.21.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 (40) hide show
  1. package/README.md +359 -0
  2. package/dist/assets/{index-D6-he0oi.js → index-CfDs-dAu.js} +99 -99
  3. package/dist/assets/{index-fYq6iaqF.css → index-fSroLVgi.css} +1 -1
  4. package/dist/index.html +2 -2
  5. package/dist/llm-streaming-server/aiEditing.js +58 -0
  6. package/dist/llm-streaming-server/chatOperations.js +494 -0
  7. package/dist/llm-streaming-server/errorHandling.js +49 -0
  8. package/dist/llm-streaming-server/index.js +20 -0
  9. package/dist/llm-streaming-server/llmStreaming.js +265 -0
  10. package/dist/llm-streaming-server/validation.js +19 -0
  11. package/dist/server/aiChatHandler/index.js +5 -5
  12. package/package.json +35 -35
  13. package/src/client/CodeEditor/getOrCreateEditor.tsx +20 -13
  14. package/src/client/CodeEditor/index.tsx +3 -3
  15. package/src/client/VZSidebar/AIChat/styles.scss +38 -0
  16. package/src/client/VZSidebar/FileTypeIcon.tsx +1 -0
  17. package/src/client/VZSidebar/index.tsx +1 -1
  18. package/src/llm-streaming-server/README.md +71 -0
  19. package/src/llm-streaming-server/aiEditing.ts +102 -0
  20. package/src/llm-streaming-server/chatOperations.ts +655 -0
  21. package/src/llm-streaming-server/errorHandling.ts +66 -0
  22. package/src/llm-streaming-server/index.ts +51 -0
  23. package/src/llm-streaming-server/llmStreaming.ts +403 -0
  24. package/src/llm-streaming-server/validation.ts +24 -0
  25. package/src/llm-streaming-ui/README.md +103 -0
  26. package/src/llm-streaming-ui/components/ChatInput.tsx +237 -0
  27. package/src/llm-streaming-ui/components/DiffView.scss +173 -0
  28. package/src/llm-streaming-ui/components/DiffView.tsx +236 -0
  29. package/src/llm-streaming-ui/components/FileEditingIndicator.tsx +89 -0
  30. package/src/llm-streaming-ui/components/IndividualFileDiff.tsx +86 -0
  31. package/src/llm-streaming-ui/components/JumpToLatestButton.tsx +64 -0
  32. package/src/llm-streaming-ui/components/Message.tsx +145 -0
  33. package/src/llm-streaming-ui/components/MessageList.tsx +241 -0
  34. package/src/llm-streaming-ui/components/ThinkingScratchpad.tsx +42 -0
  35. package/src/llm-streaming-ui/components/TypingIndicator.tsx +19 -0
  36. package/src/llm-streaming-ui/components/index.tsx +388 -0
  37. package/src/llm-streaming-ui/components/styles.scss +831 -0
  38. package/src/llm-streaming-ui/components/useSpeechRecognition.ts +116 -0
  39. package/src/llm-streaming-ui/index.ts +34 -0
  40. package/src/server/aiChatHandler/index.ts +5 -5
@@ -0,0 +1,265 @@
1
+ import OpenAI from 'openai';
2
+ import { parseMarkdownFiles, StreamingMarkdownParser, } from 'llm-code-format';
3
+ import { mergeFileChanges } from 'editcodewithai';
4
+ import { updateFiles, updateAIScratchpad, createStreamingAIMessage, addStreamingEvent, updateStreamingStatus, finalizeStreamingMessage, } from './chatOperations.js';
5
+ import { formatFiles } from '../server/prettier.js';
6
+ // Verbose logs
7
+ const DEBUG = false;
8
+ // Useful for testing/debugging the streaming behavior
9
+ const slowMode = false;
10
+ // If the `EMIT_FIXTURES` variable is true,
11
+ // then an output file in the `test/fixtures` folder
12
+ // with the before and after file states for testing purposes.
13
+ // This feeds into tests in codemirror-ot.
14
+ const EMIT_FIXTURES = false;
15
+ /**
16
+ * Creates and configures the LLM function for streaming with reasoning tokens
17
+ */
18
+ export const createLLMFunction = ({ shareDBDoc, chatId,
19
+ // Feature flag to enable/disable reasoning tokens.
20
+ // When false, reasoning tokens are not requested from the API
21
+ // and reasoning content is not processed in the streaming response.
22
+ enableReasoningTokens = false, model, aiRequestOptions, }) => {
23
+ return async (fullPrompt) => {
24
+ // Create OpenRouter client for reasoning token support
25
+ const openRouterClient = new OpenAI({
26
+ apiKey: process.env.VZCODE_EDIT_WITH_AI_API_KEY,
27
+ baseURL: process.env.VZCODE_EDIT_WITH_AI_BASE_URL ||
28
+ 'https://openrouter.ai/api/v1',
29
+ defaultHeaders: {
30
+ 'HTTP-Referer': 'https://vizhub.com',
31
+ 'X-Title': 'VizHub',
32
+ },
33
+ });
34
+ let fullContent = '';
35
+ let generationId = '';
36
+ let currentEditingFileName = null;
37
+ let accumulatedTextChunk = '';
38
+ let currentFileContent = '';
39
+ // Create streaming AI message
40
+ createStreamingAIMessage(shareDBDoc, chatId);
41
+ // Set initial content generation status
42
+ updateStreamingStatus(shareDBDoc, chatId, 'Formulating a plan...');
43
+ // Helper to get original file content
44
+ const getOriginalFileContent = (fileName) => {
45
+ const files = shareDBDoc.data.files;
46
+ for (const file of Object.values(files)) {
47
+ if (file.name === fileName) {
48
+ return file.text || '';
49
+ }
50
+ }
51
+ return '';
52
+ };
53
+ // Helper to emit text chunk when accumulated
54
+ const emitTextChunk = async () => {
55
+ if (accumulatedTextChunk.trim()) {
56
+ DEBUG &&
57
+ console.log('LLMStreaming: Emitting text chunk:', accumulatedTextChunk.substring(0, 100) + '...');
58
+ await addStreamingEvent(shareDBDoc, chatId, {
59
+ type: 'text_chunk',
60
+ content: accumulatedTextChunk,
61
+ timestamp: Date.now(),
62
+ });
63
+ accumulatedTextChunk = '';
64
+ }
65
+ };
66
+ // Helper to complete file editing
67
+ const completeFileEditing = async (fileName) => {
68
+ if (fileName) {
69
+ DEBUG &&
70
+ console.log(`LLMStreaming: Completing file editing for ${fileName}`);
71
+ await addStreamingEvent(shareDBDoc, chatId, {
72
+ type: 'file_complete',
73
+ fileName,
74
+ beforeContent: getOriginalFileContent(fileName),
75
+ afterContent: currentFileContent,
76
+ timestamp: Date.now(),
77
+ });
78
+ currentFileContent = '';
79
+ }
80
+ };
81
+ // Define callbacks for streaming parser
82
+ const callbacks = {
83
+ onFileNameChange: async (fileName, format) => {
84
+ DEBUG &&
85
+ console.log(`LLMStreaming: File changed to: ${fileName} (${format})`);
86
+ // Emit any accumulated text chunk first
87
+ await emitTextChunk();
88
+ // Complete previous file if any
89
+ if (currentEditingFileName) {
90
+ await completeFileEditing(currentEditingFileName);
91
+ }
92
+ // Start new file
93
+ currentEditingFileName = fileName;
94
+ currentFileContent = '';
95
+ // Emit file start event
96
+ await addStreamingEvent(shareDBDoc, chatId, {
97
+ type: 'file_start',
98
+ fileName,
99
+ timestamp: Date.now(),
100
+ });
101
+ // Update status
102
+ updateStreamingStatus(shareDBDoc, chatId, `Editing ${fileName}...`);
103
+ },
104
+ onCodeLine: async (line) => {
105
+ DEBUG && console.log(`Code line: ${line}`);
106
+ // Accumulate code content for the current file
107
+ currentFileContent += line + '\n';
108
+ },
109
+ onNonCodeLine: async (line) => {
110
+ DEBUG && console.log(`Non-code line: ${line}`);
111
+ // Accumulate non-code content as text chunk
112
+ if (line.trim() !== '') {
113
+ accumulatedTextChunk += line + '\n';
114
+ // Update status for subsequent non-code chunks
115
+ if (firstNonCodeChunkProcessed) {
116
+ updateStreamingStatus(shareDBDoc, chatId, 'Describing changes...');
117
+ }
118
+ else {
119
+ firstNonCodeChunkProcessed = true;
120
+ }
121
+ }
122
+ },
123
+ onFileDelete: async (fileName) => {
124
+ DEBUG &&
125
+ console.log(`LLMStreaming: File marked for deletion: ${fileName}`);
126
+ // Emit any accumulated text chunk first
127
+ await emitTextChunk();
128
+ // Complete previous file if any
129
+ if (currentEditingFileName) {
130
+ await completeFileEditing(currentEditingFileName);
131
+ }
132
+ // Reset current editing state
133
+ currentEditingFileName = null;
134
+ currentFileContent = '';
135
+ // Emit file delete event
136
+ await addStreamingEvent(shareDBDoc, chatId, {
137
+ type: 'file_delete',
138
+ fileName,
139
+ timestamp: Date.now(),
140
+ });
141
+ // Update status
142
+ updateStreamingStatus(shareDBDoc, chatId, `Deleting ${fileName}...`);
143
+ },
144
+ };
145
+ const parser = new StreamingMarkdownParser(callbacks);
146
+ const chunks = [];
147
+ let reasoningContent = '';
148
+ // Stream the response with reasoning tokens
149
+ const modelName = model ||
150
+ process.env.VZCODE_EDIT_WITH_AI_MODEL_NAME ||
151
+ 'anthropic/claude-3.5-sonnet';
152
+ // Configure reasoning tokens based on enableReasoningTokens flag
153
+ const requestConfig = {
154
+ model: modelName,
155
+ messages: [{ role: 'user', content: fullPrompt }],
156
+ usage: { include: true },
157
+ stream: true,
158
+ ...aiRequestOptions,
159
+ };
160
+ // Only include reasoning configuration if reasoning tokens are enabled
161
+ if (enableReasoningTokens) {
162
+ requestConfig.reasoning = {
163
+ effort: 'low',
164
+ exclude: false,
165
+ };
166
+ }
167
+ const stream = await openRouterClient.chat.completions.create(requestConfig);
168
+ let reasoningStarted = false;
169
+ let contentStarted = false;
170
+ let firstNonCodeChunkProcessed = false;
171
+ for await (const chunk of stream) {
172
+ if (slowMode) {
173
+ await new Promise((resolve) => setTimeout(resolve, 500));
174
+ }
175
+ const delta = chunk.choices[0]?.delta; // Type assertion for OpenRouter-specific reasoning fields
176
+ if (delta?.reasoning && enableReasoningTokens) {
177
+ // Handle reasoning tokens (thinking) - only if enabled
178
+ if (!reasoningStarted) {
179
+ reasoningStarted = true;
180
+ updateStreamingStatus(shareDBDoc, chatId, 'Thinking...');
181
+ }
182
+ reasoningContent += delta.reasoning;
183
+ updateAIScratchpad(shareDBDoc, chatId, reasoningContent);
184
+ }
185
+ else if (delta?.content) {
186
+ // Handle regular content tokens
187
+ if (!contentStarted) {
188
+ contentStarted = true;
189
+ if (reasoningStarted) {
190
+ // Clear reasoning when content starts
191
+ updateAIScratchpad(shareDBDoc, chatId, '');
192
+ }
193
+ // // Set initial content generation status
194
+ // updateStreamingStatus(
195
+ // shareDBDoc,
196
+ // chatId,
197
+ // 'Formulating a plan...',
198
+ // );
199
+ }
200
+ const chunkContent = delta.content;
201
+ chunks.push(chunkContent);
202
+ await parser.processChunk(chunkContent);
203
+ fullContent += chunkContent;
204
+ }
205
+ else if (chunk.usage) {
206
+ // Handle usage information
207
+ DEBUG && console.log('Usage:', chunk.usage);
208
+ }
209
+ if (!generationId && chunk.id) {
210
+ generationId = chunk.id;
211
+ }
212
+ }
213
+ await parser.flushRemaining();
214
+ // Emit any remaining text chunk
215
+ await emitTextChunk();
216
+ // Complete final file if any
217
+ if (currentEditingFileName) {
218
+ await completeFileEditing(currentEditingFileName);
219
+ }
220
+ // // Capture the current state of files before applying changes
221
+ // const beforeFiles = createFilesSnapshot(
222
+ // shareDBDoc.data.files,
223
+ // );
224
+ // Parse the full content to extract file changes
225
+ // export type FileCollection = Record<string, string>;
226
+ const newFilesUnformatted = parseMarkdownFiles(fullContent, 'bold').files;
227
+ // Run Prettier on `newFiles` before applying them,
228
+ // preserving empty files as empty
229
+ // since that is the cue to delete a file.
230
+ const newFilesFormatted = await formatFiles(newFilesUnformatted);
231
+ // Capture the current state of files before applying changes
232
+ let vizFilesBefore;
233
+ if (EMIT_FIXTURES) {
234
+ vizFilesBefore = JSON.parse(JSON.stringify(shareDBDoc.data.files));
235
+ }
236
+ // Apply all the edits at once
237
+ const vizFilesAfter = mergeFileChanges(shareDBDoc.data.files, newFilesFormatted);
238
+ const filesOp = updateFiles(shareDBDoc, vizFilesAfter);
239
+ if (EMIT_FIXTURES) {
240
+ const fs = await import('fs');
241
+ const path = await import('path');
242
+ const testCasesDir = path.resolve(process.cwd(), '../', 'fixtures');
243
+ if (!fs.existsSync(testCasesDir)) {
244
+ fs.mkdirSync(testCasesDir, { recursive: true });
245
+ }
246
+ const timestamp = new Date()
247
+ .toISOString()
248
+ .replace(/[:.]/g, '-');
249
+ const testCasePath = path.join(testCasesDir, `ai-chat-${timestamp}.json`);
250
+ const testCaseData = {
251
+ vizFilesBefore,
252
+ vizFilesAfter,
253
+ filesOp,
254
+ };
255
+ fs.writeFileSync(testCasePath, JSON.stringify(testCaseData, null, 2));
256
+ console.log(`AI chat test case written to ${testCasePath}`);
257
+ }
258
+ // Finalize streaming message
259
+ finalizeStreamingMessage(shareDBDoc, chatId);
260
+ return {
261
+ content: fullContent,
262
+ generationId: generationId,
263
+ };
264
+ };
265
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Validates incoming request data for AI chat messages
3
+ */
4
+ export const validateRequest = (req, res) => {
5
+ const { content, chatId } = req.body;
6
+ if (!content || typeof content !== 'string') {
7
+ res.status(400).json({
8
+ error: 'Invalid request: content is required and must be a string',
9
+ });
10
+ return false;
11
+ }
12
+ if (!chatId || typeof chatId !== 'string') {
13
+ res.status(400).json({
14
+ error: 'Invalid request: chatId is required and must be a string',
15
+ });
16
+ return false;
17
+ }
18
+ return true;
19
+ };
@@ -1,8 +1,8 @@
1
- import { validateRequest } from './validation.js';
2
- import { ensureChatsExist, ensureChatExists, addUserMessage, setAIStatus, } from './chatOperations.js';
3
- import { createLLMFunction } from './llmStreaming.js';
4
- import { performAIEditing } from './aiEditing.js';
5
- import { handleError, handleBackgroundError, } from './errorHandling.js';
1
+ import { validateRequest } from '../../llm-streaming-server/validation.js';
2
+ import { ensureChatsExist, ensureChatExists, addUserMessage, setAIStatus, } from '../../llm-streaming-server/chatOperations.js';
3
+ import { createLLMFunction } from '../../llm-streaming-server/llmStreaming.js';
4
+ import { performAIEditing } from '../../llm-streaming-server/aiEditing.js';
5
+ import { handleError, handleBackgroundError, } from '../../llm-streaming-server/errorHandling.js';
6
6
  import { createRunCodeFunction } from '../../runCode.js';
7
7
  import { createSubmitOperation } from '../../submitOperation.js';
8
8
  import { getGenerationMetadata } from 'editcodewithai';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "2.21.0",
3
+ "version": "2.22.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -110,35 +110,35 @@
110
110
  },
111
111
  "homepage": "https://github.com/vizhub-core/vzcode#readme",
112
112
  "dependencies": {
113
- "@codemirror/autocomplete": "^6.19.0",
113
+ "@codemirror/autocomplete": "^6.19.1",
114
114
  "@codemirror/lang-css": "^6.3.1",
115
115
  "@codemirror/lang-html": "^6.4.11",
116
116
  "@codemirror/lang-javascript": "^6.2.4",
117
117
  "@codemirror/lang-json": "^6.0.2",
118
- "@codemirror/lang-markdown": "^6.4.0",
119
- "@codemirror/lint": "^6.9.0",
118
+ "@codemirror/lang-markdown": "^6.5.0",
119
+ "@codemirror/lint": "^6.9.2",
120
120
  "@codemirror/state": "^6.5.2",
121
121
  "@codemirror/theme-one-dark": "^6.1.3",
122
- "@codemirror/view": "^6.38.5",
123
- "@langchain/core": "^0.3.78",
124
- "@langchain/openai": "^0.6.14",
125
- "@lezer/highlight": "^1.2.1",
122
+ "@codemirror/view": "^6.38.6",
123
+ "@langchain/core": "^1.0.3",
124
+ "@langchain/openai": "^1.0.0",
125
+ "@lezer/highlight": "^1.2.3",
126
126
  "@livekit/components-react": "^2.9.15",
127
127
  "@replit/codemirror-indentation-markers": "^6.5.3",
128
128
  "@replit/codemirror-interact": "^6.3.1",
129
129
  "@replit/codemirror-lang-svelte": "^6.0.0",
130
130
  "@replit/codemirror-vscode-keymap": "^6.0.2",
131
131
  "@teamwork/websocket-json-stream": "^2.0.0",
132
- "@typescript/vfs": "^1.6.1",
133
- "@uiw/codemirror-theme-abcdef": "^4.25.2",
134
- "@uiw/codemirror-theme-dracula": "^4.25.2",
135
- "@uiw/codemirror-theme-eclipse": "^4.25.2",
136
- "@uiw/codemirror-theme-github": "^4.25.2",
137
- "@uiw/codemirror-theme-material": "^4.25.2",
138
- "@uiw/codemirror-theme-nord": "^4.25.2",
139
- "@uiw/codemirror-theme-okaidia": "^4.25.2",
140
- "@uiw/codemirror-theme-xcode": "^4.25.2",
141
- "@uiw/codemirror-themes": "^4.25.2",
132
+ "@typescript/vfs": "^1.6.2",
133
+ "@uiw/codemirror-theme-abcdef": "^4.25.3",
134
+ "@uiw/codemirror-theme-dracula": "^4.25.3",
135
+ "@uiw/codemirror-theme-eclipse": "^4.25.3",
136
+ "@uiw/codemirror-theme-github": "^4.25.3",
137
+ "@uiw/codemirror-theme-material": "^4.25.3",
138
+ "@uiw/codemirror-theme-nord": "^4.25.3",
139
+ "@uiw/codemirror-theme-okaidia": "^4.25.3",
140
+ "@uiw/codemirror-theme-xcode": "^4.25.3",
141
+ "@uiw/codemirror-themes": "^4.25.3",
142
142
  "@valtown/codemirror-ts": "^2.3.1",
143
143
  "@vizhub/runtime": "^4.5.0",
144
144
  "@vizhub/viz-types": "^0.5.0",
@@ -157,14 +157,14 @@
157
157
  "diff2html": "^3.4.52",
158
158
  "dotenv": "^17.2.3",
159
159
  "editcodewithai": "^2.4.0",
160
- "eslint-linter-browserify": "^9.37.0",
160
+ "eslint-linter-browserify": "^9.39.1",
161
161
  "express": "^5.1.0",
162
162
  "ignore": "^7.0.5",
163
163
  "json0-ot-diff": "^1.1.2",
164
164
  "jszip": "^3.10.1",
165
165
  "livekit-server-sdk": "^2.14.0",
166
166
  "llm-code-format": "^3.1.0",
167
- "lucide-react": "^0.545.0",
167
+ "lucide-react": "^0.553.0",
168
168
  "npm": "^11.6.2",
169
169
  "open": "^10.2.0",
170
170
  "prettier-plugin-svelte": "^3.4.0",
@@ -172,7 +172,7 @@
172
172
  "react-bootstrap": "^2.10.10",
173
173
  "react-dom": "^18",
174
174
  "react-markdown": "^10.1.0",
175
- "react-router-dom": "^7.9.4",
175
+ "react-router-dom": "^7.9.5",
176
176
  "remark-gfm": "^4.0.1",
177
177
  "sharedb": "^5.2.2",
178
178
  "sharedb-client-browser": "^5.2.2",
@@ -181,31 +181,31 @@
181
181
  "ws": "^8.18.3"
182
182
  },
183
183
  "devDependencies": {
184
- "@eslint/js": "^9.37.0",
185
- "@tauri-apps/cli": "^2.8.4",
184
+ "@eslint/js": "^9.39.1",
185
+ "@tauri-apps/cli": "^2.9.3",
186
186
  "@types/d3-color": "^3.1.3",
187
187
  "@types/react": "^18",
188
188
  "@types/react-dom": "^18",
189
- "@typescript-eslint/eslint-plugin": "^8.46.0",
190
- "@typescript-eslint/parser": "^8.46.0",
191
- "@vitejs/plugin-react": "^5.0.4",
189
+ "@typescript-eslint/eslint-plugin": "^8.46.3",
190
+ "@typescript-eslint/parser": "^8.46.3",
191
+ "@vitejs/plugin-react": "^5.1.0",
192
192
  "concurrently": "^9.2.1",
193
193
  "cross-env": "^10.1.0",
194
- "eslint": "^9.37.0",
194
+ "eslint": "^9.39.1",
195
195
  "eslint-plugin-jsx-a11y": "^6.10.2",
196
196
  "eslint-plugin-react": "^7.37.5",
197
- "eslint-plugin-react-hooks": "^7.0.0",
198
- "globals": "^16.4.0",
199
- "npm-check-updates": "^19.0.0",
197
+ "eslint-plugin-react-hooks": "^7.0.1",
198
+ "globals": "^16.5.0",
199
+ "npm-check-updates": "^19.1.2",
200
200
  "prettier": "^3.6.2",
201
- "sass": "^1.93.2",
201
+ "sass": "^1.93.3",
202
202
  "ts-node": "^10.9.2",
203
203
  "typescript": "^5.9.3",
204
- "vite": "^7.1.9",
205
- "vitest": "^3.2.4"
204
+ "vite": "^7.2.2",
205
+ "vitest": "^4.0.8"
206
206
  },
207
207
  "optionalDependencies": {
208
- "@rollup/rollup-darwin-arm64": "^4.52.4",
209
- "@rollup/rollup-win32-x64-msvc": "^4.52.4"
208
+ "@rollup/rollup-darwin-arm64": "^4.53.1",
209
+ "@rollup/rollup-win32-x64-msvc": "^4.53.1"
210
210
  }
211
211
  }
@@ -70,7 +70,7 @@ import { SparklesSVG } from '../Icons/SparklesSVG';
70
70
  import { MINIMAL_EXTENSIONS } from '../featureFlags';
71
71
 
72
72
  const DEBUG = false;
73
- const enableToDoPlugin = false;
73
+ const enableToDoPlugin = true;
74
74
 
75
75
  // Define a StateField to store the file name.
76
76
  // This should be defined at the module level if it's to be imported by other modules.
@@ -138,6 +138,7 @@ const languageExtensions = {
138
138
  html: () => html(htmlConfig),
139
139
  css,
140
140
  md: markdown,
141
+ prompt: markdown,
141
142
  svelte,
142
143
  };
143
144
 
@@ -188,7 +189,7 @@ export const getOrCreateEditor = async ({
188
189
  esLintSource,
189
190
  rainbowBracketsEnabled = true,
190
191
  setIsAIChatOpen,
191
- handleSendMessage,
192
+ setAIChatMessage,
192
193
  }: {
193
194
  // TODO pass this in from the outside
194
195
  paneId?: PaneId;
@@ -225,7 +226,7 @@ export const getOrCreateEditor = async ({
225
226
  ) => Promise<readonly Diagnostic[]>;
226
227
  rainbowBracketsEnabled?: boolean; // New parameter type
227
228
  setIsAIChatOpen: (isAIChatOpen: boolean) => void;
228
- handleSendMessage: any; // TODO fix types
229
+ setAIChatMessage: (message: string) => void;
229
230
  }): Promise<ExtendedEditorCacheValue> => {
230
231
  // Cache hit
231
232
 
@@ -356,7 +357,10 @@ export const getOrCreateEditor = async ({
356
357
  );
357
358
 
358
359
  // Enable line wrapping for Markdown files
359
- if (fileExtension === 'md') {
360
+ if (
361
+ fileExtension === 'md' ||
362
+ fileExtension === 'prompt'
363
+ ) {
360
364
  extensions.push(EditorView.lineWrapping);
361
365
  }
362
366
 
@@ -485,22 +489,25 @@ export const getOrCreateEditor = async ({
485
489
  wrap.style.alignItems = 'center';
486
490
  wrap.style.justifyContent = 'center';
487
491
  wrap.className = 'icon-button icon-button-dark';
492
+ wrap.style.cursor = 'pointer';
493
+
494
+ // Handle click directly on the wrapper element
495
+ wrap.addEventListener('click', (e) => {
496
+ e.preventDefault();
497
+ e.stopPropagation();
498
+ setAIChatMessage('Implement the TODO');
499
+ setIsAIChatOpen(true);
500
+ });
501
+
488
502
  const reactContainer =
489
503
  document.createElement('div');
490
504
  wrap.appendChild(reactContainer);
491
505
 
492
506
  const root = createRoot(reactContainer);
493
507
  root.render(
494
- <div
495
- onClick={() => {
496
- setIsAIChatOpen(true);
497
- // setAIChatMessage('Implement the TODO');
498
- handleSendMessage('Implement the TODO');
499
- }}
500
- >
501
- <SparklesSVG width={14} height={14} />
502
- </div>,
508
+ <SparklesSVG width={14} height={14} />,
503
509
  );
510
+
504
511
  return wrap;
505
512
  }
506
513
 
@@ -43,7 +43,7 @@ export const CodeEditor = ({
43
43
  codeEditorRef,
44
44
  enableAutoFollow,
45
45
  setIsAIChatOpen,
46
- handleSendMessage,
46
+ setAIChatMessage,
47
47
  } = useContext(VZCodeContext);
48
48
 
49
49
  // Set `doc.data.isInteracting` to `true` when the user is interacting
@@ -117,7 +117,7 @@ export const CodeEditor = ({
117
117
  aiCopilotEndpoint,
118
118
  esLintSource,
119
119
  setIsAIChatOpen,
120
- handleSendMessage,
120
+ setAIChatMessage,
121
121
  });
122
122
 
123
123
  if (isMounted) {
@@ -142,7 +142,7 @@ export const CodeEditor = ({
142
142
  aiCopilotEndpoint,
143
143
  esLintSource,
144
144
  setIsAIChatOpen,
145
- handleSendMessage,
145
+ setAIChatMessage,
146
146
  ]);
147
147
 
148
148
  // Every time the active file switches from one file to another,
@@ -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';