codebakers 1.0.45 → 2.0.1

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 (82) hide show
  1. package/README.md +275 -60
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +4260 -0
  4. package/install.bat +9 -0
  5. package/package.json +71 -115
  6. package/src/channels/discord.ts +5 -0
  7. package/src/channels/slack.ts +5 -0
  8. package/src/channels/sms.ts +4 -0
  9. package/src/channels/telegram.ts +5 -0
  10. package/src/channels/whatsapp.ts +7 -0
  11. package/src/commands/check.ts +365 -0
  12. package/src/commands/code.ts +684 -0
  13. package/src/commands/connect.ts +12 -0
  14. package/src/commands/deploy.ts +414 -0
  15. package/src/commands/design.ts +298 -0
  16. package/src/commands/fix.ts +20 -0
  17. package/src/commands/gateway.ts +604 -0
  18. package/src/commands/generate.ts +178 -0
  19. package/src/commands/init.ts +574 -0
  20. package/src/commands/learn.ts +36 -0
  21. package/src/commands/security.ts +102 -0
  22. package/src/commands/setup.ts +448 -0
  23. package/src/commands/status.ts +56 -0
  24. package/src/index.ts +278 -0
  25. package/src/patterns/loader.ts +337 -0
  26. package/src/services/github.ts +61 -0
  27. package/src/services/supabase.ts +147 -0
  28. package/src/services/vercel.ts +61 -0
  29. package/src/utils/claude-md.ts +287 -0
  30. package/src/utils/config.ts +282 -0
  31. package/src/utils/updates.ts +27 -0
  32. package/tsconfig.json +17 -10
  33. package/.vscodeignore +0 -18
  34. package/LICENSE +0 -21
  35. package/codebakers-1.0.0.vsix +0 -0
  36. package/codebakers-1.0.10.vsix +0 -0
  37. package/codebakers-1.0.11.vsix +0 -0
  38. package/codebakers-1.0.12.vsix +0 -0
  39. package/codebakers-1.0.13.vsix +0 -0
  40. package/codebakers-1.0.14.vsix +0 -0
  41. package/codebakers-1.0.15.vsix +0 -0
  42. package/codebakers-1.0.16.vsix +0 -0
  43. package/codebakers-1.0.17.vsix +0 -0
  44. package/codebakers-1.0.18.vsix +0 -0
  45. package/codebakers-1.0.19.vsix +0 -0
  46. package/codebakers-1.0.20.vsix +0 -0
  47. package/codebakers-1.0.21.vsix +0 -0
  48. package/codebakers-1.0.22.vsix +0 -0
  49. package/codebakers-1.0.23.vsix +0 -0
  50. package/codebakers-1.0.24.vsix +0 -0
  51. package/codebakers-1.0.25.vsix +0 -0
  52. package/codebakers-1.0.26.vsix +0 -0
  53. package/codebakers-1.0.27.vsix +0 -0
  54. package/codebakers-1.0.28.vsix +0 -0
  55. package/codebakers-1.0.29.vsix +0 -0
  56. package/codebakers-1.0.30.vsix +0 -0
  57. package/codebakers-1.0.31.vsix +0 -0
  58. package/codebakers-1.0.32.vsix +0 -0
  59. package/codebakers-1.0.35.vsix +0 -0
  60. package/codebakers-1.0.36.vsix +0 -0
  61. package/codebakers-1.0.37.vsix +0 -0
  62. package/codebakers-1.0.38.vsix +0 -0
  63. package/codebakers-1.0.39.vsix +0 -0
  64. package/codebakers-1.0.40.vsix +0 -0
  65. package/codebakers-1.0.41.vsix +0 -0
  66. package/codebakers-1.0.42.vsix +0 -0
  67. package/codebakers-1.0.43.vsix +0 -0
  68. package/codebakers-1.0.44.vsix +0 -0
  69. package/codebakers-1.0.45.vsix +0 -0
  70. package/dist/extension.js +0 -1394
  71. package/esbuild.js +0 -63
  72. package/media/icon.png +0 -0
  73. package/media/icon.svg +0 -7
  74. package/nul +0 -1
  75. package/preview.html +0 -547
  76. package/src/ChatPanelProvider.ts +0 -1815
  77. package/src/ChatViewProvider.ts +0 -749
  78. package/src/CodeBakersClient.ts +0 -1146
  79. package/src/CodeValidator.ts +0 -645
  80. package/src/FileOperations.ts +0 -410
  81. package/src/ProjectContext.ts +0 -526
  82. package/src/extension.ts +0 -332
@@ -1,1815 +0,0 @@
1
- import * as vscode from 'vscode';
2
- import { CodeBakersClient, FileOperation, CommandToRun } from './CodeBakersClient';
3
- import { ProjectContext } from './ProjectContext';
4
- import { FileOperations } from './FileOperations';
5
- import { CodeValidator } from './CodeValidator';
6
-
7
- interface Message {
8
- role: 'user' | 'assistant';
9
- content: string;
10
- thinking?: string;
11
- timestamp: Date;
12
- }
13
-
14
- interface PendingChange {
15
- id: string;
16
- operation: FileOperation;
17
- status: 'pending' | 'applied' | 'rejected';
18
- }
19
-
20
- interface PendingCommand {
21
- id: string;
22
- command: CommandToRun;
23
- status: 'pending' | 'running' | 'done';
24
- }
25
-
26
- export class ChatPanelProvider {
27
- private static _instance: ChatPanelProvider | undefined;
28
- private _panel: vscode.WebviewPanel | undefined;
29
- private _messages: Message[] = [];
30
- private _conversationSummary: string = '';
31
- private readonly fileOps: FileOperations;
32
- private _abortController: AbortController | null = null;
33
-
34
- // Separate tracking for pending operations (Claude Code style)
35
- private _pendingChanges: PendingChange[] = [];
36
- private _pendingCommands: PendingCommand[] = [];
37
-
38
- // Throttling for streaming updates
39
- private _streamBuffer: string = '';
40
- private _thinkingBuffer: string = '';
41
- private _streamThrottleTimer: NodeJS.Timeout | null = null;
42
- private _lastStreamUpdate: number = 0;
43
- private readonly STREAM_THROTTLE_MS = 50; // Update UI every 50ms max
44
-
45
- private constructor(
46
- private readonly context: vscode.ExtensionContext,
47
- private readonly client: CodeBakersClient,
48
- private readonly projectContext: ProjectContext
49
- ) {
50
- this.fileOps = new FileOperations();
51
- }
52
-
53
- public static getInstance(
54
- context: vscode.ExtensionContext,
55
- client: CodeBakersClient,
56
- projectContext: ProjectContext
57
- ): ChatPanelProvider {
58
- if (!ChatPanelProvider._instance) {
59
- ChatPanelProvider._instance = new ChatPanelProvider(context, client, projectContext);
60
- }
61
- return ChatPanelProvider._instance;
62
- }
63
-
64
- public show() {
65
- if (this._panel) {
66
- this._panel.reveal(vscode.ViewColumn.Beside);
67
- return;
68
- }
69
-
70
- this._panel = vscode.window.createWebviewPanel(
71
- 'codebakers.chat',
72
- 'CodeBakers',
73
- {
74
- viewColumn: vscode.ViewColumn.Beside,
75
- preserveFocus: true
76
- },
77
- {
78
- enableScripts: true,
79
- retainContextWhenHidden: true,
80
- localResourceRoots: [this.context.extensionUri]
81
- }
82
- );
83
-
84
- this._panel.iconPath = vscode.Uri.joinPath(this.context.extensionUri, 'media', 'icon.svg');
85
- this._panel.webview.html = this._getHtmlForWebview(this._panel.webview);
86
-
87
- this._panel.webview.onDidReceiveMessage(async (data) => {
88
- switch (data.type) {
89
- case 'sendMessage':
90
- await this.sendMessage(data.message);
91
- break;
92
- case 'clearChat':
93
- this._messages = [];
94
- this._conversationSummary = '';
95
- this._pendingChanges = [];
96
- this._pendingCommands = [];
97
- this._updateWebview();
98
- break;
99
- case 'runTool':
100
- await this._executeTool(data.tool);
101
- break;
102
- case 'login':
103
- await this.client.login();
104
- break;
105
- case 'applyFile':
106
- await this._applyFileOperation(data.id);
107
- break;
108
- case 'applyAllFiles':
109
- await this._applyAllPendingChanges();
110
- break;
111
- case 'rejectFile':
112
- this._rejectFileOperation(data.id);
113
- break;
114
- case 'rejectAllFiles':
115
- this._rejectAllPendingChanges();
116
- break;
117
- case 'runCommand':
118
- await this._runCommand(data.id);
119
- break;
120
- case 'showDiff':
121
- await this._showDiff(data.id);
122
- break;
123
- case 'undoFile':
124
- await this._undoFileOperation(data.id);
125
- break;
126
- case 'cancelRequest':
127
- this._cancelCurrentRequest();
128
- break;
129
- }
130
- });
131
-
132
- this._panel.onDidDispose(() => {
133
- this._panel = undefined;
134
- });
135
-
136
- this._initializeStatus();
137
- }
138
-
139
- public refresh() {
140
- if (this._panel) {
141
- this._initializeStatus();
142
- this._updateWebview();
143
- }
144
- }
145
-
146
- private async _initializeStatus() {
147
- if (!this._panel) return;
148
-
149
- try {
150
- const planInfo = this.client.getPlanInfo();
151
- this._panel.webview.postMessage({
152
- type: 'updatePlan',
153
- plan: planInfo.plan
154
- });
155
-
156
- if (!this.client.hasSessionToken()) {
157
- this._panel.webview.postMessage({
158
- type: 'updateHealth',
159
- health: 0,
160
- score: 0
161
- });
162
- this._panel.webview.postMessage({
163
- type: 'showLogin'
164
- });
165
- return;
166
- }
167
-
168
- try {
169
- const health = await this.client.guardianStatus();
170
- this._panel.webview.postMessage({
171
- type: 'updateHealth',
172
- health: health.data?.health || 85,
173
- score: health.data?.health || 85
174
- });
175
- } catch (healthError) {
176
- console.warn('Health check failed:', healthError);
177
- this._panel.webview.postMessage({
178
- type: 'updateHealth',
179
- health: 85,
180
- score: 85
181
- });
182
- }
183
- } catch (error) {
184
- console.error('Failed to initialize status:', error);
185
- }
186
- }
187
-
188
- private async _executeTool(toolName: string) {
189
- if (!this._panel) return;
190
-
191
- try {
192
- this._panel.webview.postMessage({ type: 'typing', isTyping: true });
193
- const result = await this.client.executeTool(toolName, {});
194
-
195
- // Add tool result as a message
196
- this._messages.push({
197
- role: 'assistant',
198
- content: `**Tool: ${toolName}**\n\`\`\`json\n${JSON.stringify(result.data || result, null, 2)}\n\`\`\``,
199
- timestamp: new Date()
200
- });
201
-
202
- if (toolName === 'guardian_status' && result.data?.health) {
203
- this._panel.webview.postMessage({
204
- type: 'updateHealth',
205
- health: result.data.health,
206
- score: result.data.health
207
- });
208
- }
209
- } catch (error) {
210
- this._messages.push({
211
- role: 'assistant',
212
- content: `**Tool Error: ${toolName}**\n${error instanceof Error ? error.message : 'Tool execution failed'}`,
213
- timestamp: new Date()
214
- });
215
- } finally {
216
- this._panel?.webview.postMessage({ type: 'typing', isTyping: false });
217
- this._updateWebview();
218
- }
219
- }
220
-
221
- private async _applyFileOperation(id: string) {
222
- if (!this._panel) return;
223
-
224
- const change = this._pendingChanges.find(c => c.id === id);
225
- if (!change || change.status !== 'pending') return;
226
-
227
- try {
228
- const success = await this.fileOps.applyChange({
229
- path: change.operation.path,
230
- action: change.operation.action,
231
- content: change.operation.content,
232
- description: change.operation.description
233
- });
234
-
235
- if (success) {
236
- change.status = 'applied';
237
- vscode.window.showInformationMessage(`✅ ${change.operation.action}: ${change.operation.path}`);
238
-
239
- // Open the file if not a delete
240
- if (change.operation.action !== 'delete') {
241
- await this.fileOps.openFile(change.operation.path);
242
- }
243
- } else {
244
- throw new Error('Operation failed');
245
- }
246
- } catch (error) {
247
- vscode.window.showErrorMessage(`❌ Failed: ${change.operation.path} - ${error}`);
248
- }
249
-
250
- this._updatePendingChanges();
251
- }
252
-
253
- private async _applyAllPendingChanges() {
254
- if (!this._panel) return;
255
-
256
- const pending = this._pendingChanges.filter(c => c.status === 'pending');
257
- if (pending.length === 0) {
258
- vscode.window.showInformationMessage('No pending changes to apply');
259
- return;
260
- }
261
-
262
- // Show progress in webview
263
- const total = pending.length;
264
- this._panel?.webview.postMessage({
265
- type: 'showProgress',
266
- text: `Applying 0/${total} files...`,
267
- current: 0,
268
- total,
269
- show: true
270
- });
271
-
272
- // Apply files in parallel batches of 5 for speed
273
- const BATCH_SIZE = 5;
274
- let success = 0;
275
- let failed = 0;
276
-
277
- for (let i = 0; i < pending.length; i += BATCH_SIZE) {
278
- const batch = pending.slice(i, i + BATCH_SIZE);
279
-
280
- const results = await Promise.allSettled(
281
- batch.map(async (change) => {
282
- try {
283
- const result = await this.fileOps.applyChange({
284
- path: change.operation.path,
285
- action: change.operation.action,
286
- content: change.operation.content,
287
- description: change.operation.description
288
- });
289
-
290
- if (result) {
291
- change.status = 'applied';
292
- return true;
293
- } else {
294
- return false;
295
- }
296
- } catch (error) {
297
- console.error(`Failed to apply ${change.operation.path}:`, error);
298
- return false;
299
- }
300
- })
301
- );
302
-
303
- // Count results
304
- results.forEach((result) => {
305
- if (result.status === 'fulfilled' && result.value) {
306
- success++;
307
- } else {
308
- failed++;
309
- }
310
- });
311
-
312
- // Update progress indicator
313
- const done = success + failed;
314
- this._panel?.webview.postMessage({
315
- type: 'showProgress',
316
- text: `Applying ${done}/${total} files...`,
317
- current: done,
318
- total,
319
- show: true
320
- });
321
-
322
- // Update UI after each batch
323
- this._updatePendingChanges();
324
- }
325
-
326
- this._panel?.webview.postMessage({ type: 'showProgress', show: false });
327
- vscode.window.showInformationMessage(
328
- `✅ Applied ${success} file(s)${failed > 0 ? `, ${failed} failed` : ''}`
329
- );
330
-
331
- // Auto-run TypeScript check after applying files
332
- if (success > 0) {
333
- await this._runTscCheck();
334
- }
335
- }
336
-
337
- private async _runTscCheck(autoRetry: boolean = true) {
338
- // Check if project has TypeScript
339
- const tsconfigExists = await this.fileOps.fileExists('tsconfig.json');
340
- if (!tsconfigExists) {
341
- return; // No TypeScript in project
342
- }
343
-
344
- this._panel?.webview.postMessage({
345
- type: 'showStatus',
346
- text: 'Checking TypeScript...',
347
- show: true
348
- });
349
-
350
- try {
351
- const validator = new CodeValidator();
352
- const result = await validator.runTypeScriptCheck();
353
-
354
- this._panel?.webview.postMessage({ type: 'showStatus', show: false });
355
-
356
- if (!result.passed && result.errors.length > 0) {
357
- const action = await vscode.window.showWarningMessage(
358
- `⚠️ TypeScript errors found (${result.errorCount})`,
359
- 'Auto-Fix with AI',
360
- 'Show Errors',
361
- 'Ignore'
362
- );
363
-
364
- if (action === 'Auto-Fix with AI' && autoRetry) {
365
- // Send errors to AI for auto-fix
366
- const errorMessage = `Please fix these TypeScript errors:\n\n${result.errors.map((e: any) =>
367
- `${e.file}:${e.line}: ${e.message}`
368
- ).join('\n')}`;
369
-
370
- this._panel?.webview.postMessage({
371
- type: 'showStatus',
372
- text: 'AI fixing TypeScript errors...',
373
- show: true
374
- });
375
-
376
- // Trigger a new AI request with the errors
377
- await this.sendMessage(errorMessage);
378
- } else if (action === 'Show Errors') {
379
- // Show errors in output channel
380
- const outputChannel = vscode.window.createOutputChannel('CodeBakers TSC');
381
- outputChannel.clear();
382
- outputChannel.appendLine('TypeScript Errors:');
383
- outputChannel.appendLine('=================\n');
384
- result.errors.forEach((e: any) => {
385
- outputChannel.appendLine(`${e.file}:${e.line}:${e.column}`);
386
- outputChannel.appendLine(` ${e.message}\n`);
387
- });
388
- outputChannel.show();
389
- }
390
- } else {
391
- vscode.window.showInformationMessage('✅ TypeScript check passed!');
392
- }
393
- } catch (error) {
394
- this._panel?.webview.postMessage({ type: 'showStatus', show: false });
395
- console.error('TSC check failed:', error);
396
- }
397
- }
398
-
399
- private _rejectFileOperation(id: string) {
400
- const change = this._pendingChanges.find(c => c.id === id);
401
- if (change && change.status === 'pending') {
402
- change.status = 'rejected';
403
- this._updatePendingChanges();
404
- }
405
- }
406
-
407
- private _rejectAllPendingChanges() {
408
- for (const change of this._pendingChanges) {
409
- if (change.status === 'pending') {
410
- change.status = 'rejected';
411
- }
412
- }
413
- this._updatePendingChanges();
414
- }
415
-
416
- private async _runCommand(id: string) {
417
- if (!this._panel) return;
418
-
419
- const cmd = this._pendingCommands.find(c => c.id === id);
420
- if (!cmd || cmd.status !== 'pending') return;
421
-
422
- try {
423
- cmd.status = 'running';
424
- this._updatePendingChanges();
425
-
426
- await this.fileOps.runCommand(cmd.command.command, cmd.command.description || 'CodeBakers');
427
- cmd.status = 'done';
428
- vscode.window.showInformationMessage(`🚀 Running: ${cmd.command.command}`);
429
- } catch (error) {
430
- cmd.status = 'pending'; // Reset to allow retry
431
- vscode.window.showErrorMessage(`❌ Failed to run command: ${error}`);
432
- }
433
-
434
- this._updatePendingChanges();
435
- }
436
-
437
- private async _showDiff(id: string) {
438
- const change = this._pendingChanges.find(c => c.id === id);
439
- if (!change || !change.operation.content) return;
440
-
441
- try {
442
- await this.fileOps.showDiff(
443
- change.operation.path,
444
- change.operation.content,
445
- `CodeBakers: ${change.operation.path}`
446
- );
447
- } catch (error) {
448
- vscode.window.showErrorMessage(`❌ Failed to show diff: ${error}`);
449
- }
450
- }
451
-
452
- private async _undoFileOperation(id: string) {
453
- const change = this._pendingChanges.find(c => c.id === id);
454
- if (!change || change.status !== 'applied') {
455
- vscode.window.showWarningMessage('Cannot undo - change was not applied');
456
- return;
457
- }
458
-
459
- try {
460
- const success = await this.fileOps.restoreFromBackup(change.operation.path);
461
- if (success) {
462
- // Mark as pending again so user can re-apply if desired
463
- change.status = 'pending';
464
- this._updatePendingChanges();
465
- }
466
- } catch (error) {
467
- vscode.window.showErrorMessage(`❌ Failed to undo: ${error}`);
468
- }
469
- }
470
-
471
- private _cancelCurrentRequest() {
472
- if (this._abortController) {
473
- this._abortController.abort();
474
- this._abortController = null;
475
- this._panel?.webview.postMessage({ type: 'requestCancelled' });
476
- vscode.window.showInformationMessage('Request cancelled');
477
- }
478
- }
479
-
480
- // Throttled streaming update
481
- private _throttledStreamUpdate() {
482
- const now = Date.now();
483
-
484
- if (now - this._lastStreamUpdate < this.STREAM_THROTTLE_MS) {
485
- // Schedule update if not already scheduled
486
- if (!this._streamThrottleTimer) {
487
- this._streamThrottleTimer = setTimeout(() => {
488
- this._streamThrottleTimer = null;
489
- this._sendStreamUpdate();
490
- }, this.STREAM_THROTTLE_MS);
491
- }
492
- return;
493
- }
494
-
495
- this._sendStreamUpdate();
496
- }
497
-
498
- private _sendStreamUpdate() {
499
- this._lastStreamUpdate = Date.now();
500
-
501
- if (this._thinkingBuffer) {
502
- this._panel?.webview.postMessage({
503
- type: 'streamThinking',
504
- thinking: this._thinkingBuffer
505
- });
506
- }
507
-
508
- if (this._streamBuffer) {
509
- this._panel?.webview.postMessage({
510
- type: 'streamContent',
511
- content: this._streamBuffer
512
- });
513
- }
514
- }
515
-
516
- async sendMessage(userMessage: string) {
517
- if (!this._panel) return;
518
-
519
- // Check if user is logged in
520
- if (!this.client.hasSessionToken()) {
521
- this._panel.webview.postMessage({ type: 'showLogin' });
522
- vscode.window.showWarningMessage(
523
- 'Please sign in to CodeBakers first',
524
- 'Sign In with GitHub'
525
- ).then(selection => {
526
- if (selection === 'Sign In with GitHub') {
527
- this.client.login();
528
- }
529
- });
530
- return;
531
- }
532
-
533
- this._messages.push({
534
- role: 'user',
535
- content: userMessage,
536
- timestamp: new Date()
537
- });
538
- this._updateWebview();
539
-
540
- // Reset stream buffers
541
- this._streamBuffer = '';
542
- this._thinkingBuffer = '';
543
-
544
- try {
545
- this._abortController = new AbortController();
546
- this._panel.webview.postMessage({ type: 'typing', isTyping: true });
547
-
548
- const projectState = await this.projectContext.getProjectState();
549
- const contextualizedMessages = await this._buildContextualizedMessages(userMessage, projectState);
550
-
551
- const response = await this.client.chat(contextualizedMessages, projectState, {
552
- onThinking: (thinking) => {
553
- this._thinkingBuffer = thinking;
554
- this._throttledStreamUpdate();
555
- },
556
- onContent: (content) => {
557
- this._streamBuffer = content;
558
- this._throttledStreamUpdate();
559
- },
560
- onDone: () => {
561
- this._panel?.webview.postMessage({ type: 'validating' });
562
- },
563
- onError: (error) => {
564
- this._panel?.webview.postMessage({
565
- type: 'streamError',
566
- error: error.message
567
- });
568
- },
569
- abortSignal: this._abortController.signal
570
- });
571
-
572
- // Add assistant message (content only, no file ops embedded)
573
- this._messages.push({
574
- role: 'assistant',
575
- content: response.content,
576
- thinking: response.thinking,
577
- timestamp: new Date()
578
- });
579
-
580
- // Add file operations to pending changes panel
581
- if (response.fileOperations && response.fileOperations.length > 0) {
582
- for (const op of response.fileOperations) {
583
- this._pendingChanges.push({
584
- id: `file-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
585
- operation: op,
586
- status: 'pending'
587
- });
588
- }
589
- }
590
-
591
- // Add commands to pending commands
592
- if (response.commands && response.commands.length > 0) {
593
- for (const cmd of response.commands) {
594
- this._pendingCommands.push({
595
- id: `cmd-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
596
- command: cmd,
597
- status: 'pending'
598
- });
599
- }
600
- }
601
-
602
- if (response.projectUpdates) {
603
- await this.projectContext.applyUpdates(response.projectUpdates);
604
- }
605
-
606
- if (this._messages.length > 20) {
607
- await this._summarizeConversation();
608
- }
609
- } catch (error) {
610
- if ((error as Error).message !== 'Request was cancelled') {
611
- this._messages.push({
612
- role: 'assistant',
613
- content: `**Error:** ${error instanceof Error ? error.message : 'Unknown error'}`,
614
- timestamp: new Date()
615
- });
616
- }
617
- } finally {
618
- // Clear throttle timer
619
- if (this._streamThrottleTimer) {
620
- clearTimeout(this._streamThrottleTimer);
621
- this._streamThrottleTimer = null;
622
- }
623
-
624
- this._abortController = null;
625
- this._panel?.webview.postMessage({ type: 'typing', isTyping: false });
626
- this._updateWebview();
627
- }
628
- }
629
-
630
- private async _buildContextualizedMessages(userMessage: string, projectState: any): Promise<any[]> {
631
- const messages: any[] = [];
632
-
633
- if (this._conversationSummary) {
634
- messages.push({
635
- role: 'system',
636
- content: `Previous conversation summary:\n${this._conversationSummary}`
637
- });
638
- }
639
-
640
- if (projectState) {
641
- messages.push({
642
- role: 'system',
643
- content: `Current project state:\n${JSON.stringify(projectState, null, 2)}`
644
- });
645
- }
646
-
647
- const recentMessages = this._messages.slice(-10);
648
- for (const msg of recentMessages) {
649
- messages.push({
650
- role: msg.role,
651
- content: msg.content
652
- });
653
- }
654
-
655
- return messages;
656
- }
657
-
658
- private async _summarizeConversation() {
659
- const oldMessages = this._messages.slice(0, -10);
660
- if (oldMessages.length === 0) return;
661
-
662
- const summaryPrompt = `Summarize these conversation messages, keeping key decisions and context:\n${
663
- oldMessages.map(m => `${m.role}: ${m.content}`).join('\n')
664
- }`;
665
-
666
- try {
667
- const summary = await this.client.summarize(summaryPrompt);
668
- this._conversationSummary = summary;
669
- this._messages = this._messages.slice(-10);
670
- } catch (error) {
671
- console.error('Failed to summarize conversation:', error);
672
- }
673
- }
674
-
675
- private _updateWebview() {
676
- if (!this._panel) return;
677
-
678
- this._panel.webview.postMessage({
679
- type: 'updateMessages',
680
- messages: this._messages.map(m => ({
681
- role: m.role,
682
- content: m.content,
683
- thinking: m.thinking,
684
- timestamp: m.timestamp.toISOString()
685
- }))
686
- });
687
-
688
- this._updatePendingChanges();
689
- }
690
-
691
- private _updatePendingChanges() {
692
- if (!this._panel) return;
693
-
694
- const pendingFileChanges = this._pendingChanges.filter(c => c.status === 'pending');
695
- const pendingCmds = this._pendingCommands.filter(c => c.status === 'pending');
696
-
697
- this._panel.webview.postMessage({
698
- type: 'updatePendingChanges',
699
- changes: this._pendingChanges.map(c => ({
700
- id: c.id,
701
- path: c.operation.path,
702
- action: c.operation.action,
703
- description: c.operation.description,
704
- status: c.status,
705
- hasContent: !!c.operation.content
706
- })),
707
- commands: this._pendingCommands.map(c => ({
708
- id: c.id,
709
- command: c.command.command,
710
- description: c.command.description,
711
- status: c.status
712
- })),
713
- pendingCount: pendingFileChanges.length + pendingCmds.length
714
- });
715
- }
716
-
717
- private _getHtmlForWebview(webview: vscode.Webview): string {
718
- return `<!DOCTYPE html>
719
- <html lang="en">
720
- <head>
721
- <meta charset="UTF-8">
722
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
723
- <title>CodeBakers</title>
724
- <style>
725
- * {
726
- box-sizing: border-box;
727
- margin: 0;
728
- padding: 0;
729
- }
730
-
731
- body {
732
- font-family: var(--vscode-font-family);
733
- font-size: var(--vscode-font-size);
734
- color: var(--vscode-foreground);
735
- background: var(--vscode-editor-background);
736
- height: 100vh;
737
- display: flex;
738
- flex-direction: column;
739
- }
740
-
741
- .header {
742
- padding: 12px 16px;
743
- border-bottom: 1px solid var(--vscode-panel-border);
744
- display: flex;
745
- align-items: center;
746
- gap: 10px;
747
- flex-shrink: 0;
748
- }
749
-
750
- .header-icon {
751
- width: 20px;
752
- height: 20px;
753
- }
754
-
755
- .header-title {
756
- font-weight: 600;
757
- font-size: 13px;
758
- flex: 1;
759
- }
760
-
761
- .header-btn {
762
- background: transparent;
763
- border: none;
764
- color: var(--vscode-foreground);
765
- cursor: pointer;
766
- padding: 4px 8px;
767
- border-radius: 4px;
768
- font-size: 11px;
769
- opacity: 0.7;
770
- }
771
-
772
- .header-btn:hover {
773
- background: var(--vscode-toolbar-hoverBackground);
774
- opacity: 1;
775
- }
776
-
777
- .plan-badge {
778
- font-size: 10px;
779
- padding: 2px 8px;
780
- background: var(--vscode-button-background);
781
- color: var(--vscode-button-foreground);
782
- border-radius: 10px;
783
- }
784
-
785
- .plan-badge.trial {
786
- background: #f0a030;
787
- }
788
-
789
- /* Main content area */
790
- .main-content {
791
- flex: 1;
792
- display: flex;
793
- flex-direction: column;
794
- min-height: 0;
795
- }
796
-
797
- /* Messages area */
798
- .messages {
799
- flex: 1;
800
- overflow-y: auto;
801
- padding: 16px;
802
- display: flex;
803
- flex-direction: column;
804
- gap: 12px;
805
- }
806
-
807
- .message {
808
- max-width: 90%;
809
- padding: 10px 14px;
810
- border-radius: 10px;
811
- line-height: 1.5;
812
- font-size: 13px;
813
- }
814
-
815
- .message.user {
816
- background: var(--vscode-button-background);
817
- color: var(--vscode-button-foreground);
818
- align-self: flex-end;
819
- border-bottom-right-radius: 4px;
820
- }
821
-
822
- .message.assistant {
823
- background: var(--vscode-editor-inactiveSelectionBackground);
824
- align-self: flex-start;
825
- border-bottom-left-radius: 4px;
826
- }
827
-
828
- .message pre {
829
- background: var(--vscode-textCodeBlock-background);
830
- padding: 8px;
831
- border-radius: 4px;
832
- overflow-x: auto;
833
- margin: 8px 0;
834
- font-size: 12px;
835
- }
836
-
837
- .message code {
838
- font-family: var(--vscode-editor-font-family);
839
- font-size: 12px;
840
- }
841
-
842
- .message h1, .message h2, .message h3, .message h4 {
843
- margin: 10px 0 6px 0;
844
- font-weight: 600;
845
- }
846
-
847
- .message h1 { font-size: 1.3em; }
848
- .message h2 { font-size: 1.2em; }
849
- .message h3 { font-size: 1.1em; }
850
-
851
- .message ul, .message ol {
852
- margin: 6px 0;
853
- padding-left: 18px;
854
- }
855
-
856
- .message li { margin: 3px 0; }
857
-
858
- .message hr {
859
- border: none;
860
- border-top: 1px solid var(--vscode-panel-border);
861
- margin: 10px 0;
862
- }
863
-
864
- .message p { margin: 6px 0; }
865
- .message p:first-child { margin-top: 0; }
866
- .message p:last-child { margin-bottom: 0; }
867
-
868
- .thinking-toggle {
869
- display: flex;
870
- align-items: center;
871
- gap: 6px;
872
- font-size: 11px;
873
- color: var(--vscode-textLink-foreground);
874
- cursor: pointer;
875
- margin-bottom: 8px;
876
- padding: 4px 0;
877
- }
878
-
879
- .thinking-content {
880
- background: var(--vscode-textBlockQuote-background);
881
- border-left: 2px solid var(--vscode-textLink-foreground);
882
- padding: 8px;
883
- font-size: 11px;
884
- color: var(--vscode-descriptionForeground);
885
- margin-bottom: 8px;
886
- display: none;
887
- max-height: 200px;
888
- overflow-y: auto;
889
- }
890
-
891
- .thinking-content.show { display: block; }
892
-
893
- .streaming-indicator {
894
- align-self: flex-start;
895
- display: none;
896
- padding: 10px 14px;
897
- background: var(--vscode-editor-inactiveSelectionBackground);
898
- border-radius: 10px;
899
- border-bottom-left-radius: 4px;
900
- max-width: 90%;
901
- }
902
-
903
- .streaming-indicator.show { display: block; }
904
-
905
- .typing-dots {
906
- display: flex;
907
- gap: 4px;
908
- padding: 8px 0;
909
- }
910
-
911
- .typing-dot {
912
- width: 6px;
913
- height: 6px;
914
- background: var(--vscode-foreground);
915
- border-radius: 50%;
916
- animation: bounce 1.4s infinite ease-in-out;
917
- }
918
-
919
- .typing-dot:nth-child(2) { animation-delay: 0.2s; }
920
- .typing-dot:nth-child(3) { animation-delay: 0.4s; }
921
-
922
- @keyframes bounce {
923
- 0%, 80%, 100% { transform: translateY(0); }
924
- 40% { transform: translateY(-6px); }
925
- }
926
-
927
- /* Pending Changes Panel - Claude Code Style */
928
- .pending-panel {
929
- border-top: 1px solid var(--vscode-panel-border);
930
- background: var(--vscode-sideBar-background);
931
- max-height: 40vh;
932
- overflow-y: auto;
933
- display: none;
934
- flex-shrink: 0;
935
- }
936
-
937
- .pending-panel.show { display: block; }
938
-
939
- .pending-header {
940
- display: flex;
941
- align-items: center;
942
- justify-content: space-between;
943
- padding: 10px 14px;
944
- background: var(--vscode-editor-inactiveSelectionBackground);
945
- position: sticky;
946
- top: 0;
947
- z-index: 10;
948
- }
949
-
950
- .pending-title {
951
- font-weight: 600;
952
- font-size: 12px;
953
- display: flex;
954
- align-items: center;
955
- gap: 8px;
956
- }
957
-
958
- .pending-count {
959
- background: var(--vscode-badge-background);
960
- color: var(--vscode-badge-foreground);
961
- padding: 2px 6px;
962
- border-radius: 10px;
963
- font-size: 10px;
964
- }
965
-
966
- .pending-actions {
967
- display: flex;
968
- gap: 8px;
969
- }
970
-
971
- .accept-all-btn {
972
- background: #28a745;
973
- color: white;
974
- border: none;
975
- border-radius: 4px;
976
- padding: 5px 12px;
977
- font-size: 11px;
978
- font-weight: 500;
979
- cursor: pointer;
980
- }
981
-
982
- .accept-all-btn:hover { background: #218838; }
983
-
984
- .reject-all-btn {
985
- background: transparent;
986
- color: var(--vscode-foreground);
987
- border: 1px solid var(--vscode-panel-border);
988
- border-radius: 4px;
989
- padding: 5px 12px;
990
- font-size: 11px;
991
- cursor: pointer;
992
- }
993
-
994
- .reject-all-btn:hover {
995
- background: var(--vscode-toolbar-hoverBackground);
996
- }
997
-
998
- .pending-list {
999
- padding: 0;
1000
- }
1001
-
1002
- .pending-item {
1003
- display: flex;
1004
- align-items: center;
1005
- gap: 10px;
1006
- padding: 8px 14px;
1007
- border-bottom: 1px solid var(--vscode-panel-border);
1008
- }
1009
-
1010
- .pending-item:last-child { border-bottom: none; }
1011
-
1012
- .pending-item.applied {
1013
- opacity: 0.5;
1014
- background: rgba(40, 167, 69, 0.1);
1015
- }
1016
-
1017
- .pending-item.rejected {
1018
- opacity: 0.5;
1019
- text-decoration: line-through;
1020
- }
1021
-
1022
- .pending-icon {
1023
- width: 18px;
1024
- text-align: center;
1025
- font-size: 12px;
1026
- }
1027
-
1028
- .pending-info {
1029
- flex: 1;
1030
- min-width: 0;
1031
- }
1032
-
1033
- .pending-path {
1034
- font-family: var(--vscode-editor-font-family);
1035
- font-size: 12px;
1036
- color: var(--vscode-textLink-foreground);
1037
- white-space: nowrap;
1038
- overflow: hidden;
1039
- text-overflow: ellipsis;
1040
- }
1041
-
1042
- .pending-desc {
1043
- font-size: 10px;
1044
- color: var(--vscode-descriptionForeground);
1045
- margin-top: 2px;
1046
- }
1047
-
1048
- .pending-item-actions {
1049
- display: flex;
1050
- gap: 4px;
1051
- }
1052
-
1053
- .item-btn {
1054
- background: transparent;
1055
- border: 1px solid var(--vscode-panel-border);
1056
- border-radius: 3px;
1057
- padding: 3px 8px;
1058
- font-size: 10px;
1059
- cursor: pointer;
1060
- color: var(--vscode-foreground);
1061
- }
1062
-
1063
- .item-btn:hover {
1064
- background: var(--vscode-toolbar-hoverBackground);
1065
- }
1066
-
1067
- .item-btn.accept {
1068
- background: #28a745;
1069
- border-color: #28a745;
1070
- color: white;
1071
- }
1072
-
1073
- .item-btn.accept:hover { background: #218838; }
1074
-
1075
- .item-btn.reject {
1076
- color: #dc3545;
1077
- border-color: #dc3545;
1078
- }
1079
-
1080
- .item-btn.reject:hover {
1081
- background: rgba(220, 53, 69, 0.1);
1082
- }
1083
-
1084
- .command-item {
1085
- background: var(--vscode-textCodeBlock-background);
1086
- }
1087
-
1088
- .command-text {
1089
- font-family: var(--vscode-editor-font-family);
1090
- font-size: 11px;
1091
- }
1092
-
1093
- /* Input area */
1094
- .input-area {
1095
- padding: 12px 16px;
1096
- border-top: 1px solid var(--vscode-panel-border);
1097
- display: flex;
1098
- gap: 8px;
1099
- flex-shrink: 0;
1100
- }
1101
-
1102
- .input-area textarea {
1103
- flex: 1;
1104
- background: var(--vscode-input-background);
1105
- color: var(--vscode-input-foreground);
1106
- border: 1px solid var(--vscode-input-border);
1107
- border-radius: 6px;
1108
- padding: 10px 12px;
1109
- font-family: inherit;
1110
- font-size: 13px;
1111
- resize: none;
1112
- min-height: 40px;
1113
- max-height: 120px;
1114
- }
1115
-
1116
- .input-area textarea:focus {
1117
- outline: none;
1118
- border-color: var(--vscode-focusBorder);
1119
- }
1120
-
1121
- .input-area textarea:disabled {
1122
- opacity: 0.6;
1123
- }
1124
-
1125
- .send-btn {
1126
- background: var(--vscode-button-background);
1127
- color: var(--vscode-button-foreground);
1128
- border: none;
1129
- border-radius: 6px;
1130
- padding: 0 16px;
1131
- cursor: pointer;
1132
- font-weight: 500;
1133
- font-size: 12px;
1134
- }
1135
-
1136
- .send-btn:hover {
1137
- background: var(--vscode-button-hoverBackground);
1138
- }
1139
-
1140
- .send-btn:disabled {
1141
- opacity: 0.5;
1142
- cursor: not-allowed;
1143
- }
1144
-
1145
- .cancel-btn {
1146
- background: #dc3545;
1147
- color: white;
1148
- border: none;
1149
- border-radius: 6px;
1150
- padding: 0 16px;
1151
- cursor: pointer;
1152
- font-weight: 500;
1153
- font-size: 12px;
1154
- display: none;
1155
- }
1156
-
1157
- .cancel-btn.show { display: block; }
1158
- .cancel-btn:hover { background: #c82333; }
1159
-
1160
- /* Welcome screen */
1161
- .welcome {
1162
- flex: 1;
1163
- display: flex;
1164
- flex-direction: column;
1165
- align-items: center;
1166
- justify-content: center;
1167
- padding: 30px;
1168
- text-align: center;
1169
- }
1170
-
1171
- .welcome-icon { font-size: 48px; margin-bottom: 16px; }
1172
- .welcome-title { font-size: 18px; font-weight: 600; margin-bottom: 8px; }
1173
- .welcome-text { color: var(--vscode-descriptionForeground); margin-bottom: 20px; max-width: 350px; font-size: 13px; }
1174
-
1175
- .quick-actions {
1176
- display: flex;
1177
- flex-wrap: wrap;
1178
- gap: 8px;
1179
- justify-content: center;
1180
- }
1181
-
1182
- .quick-action {
1183
- background: var(--vscode-button-secondaryBackground);
1184
- color: var(--vscode-button-secondaryForeground);
1185
- border: none;
1186
- border-radius: 16px;
1187
- padding: 6px 14px;
1188
- cursor: pointer;
1189
- font-size: 12px;
1190
- }
1191
-
1192
- .quick-action:hover {
1193
- background: var(--vscode-button-secondaryHoverBackground);
1194
- }
1195
-
1196
- /* Login prompt */
1197
- .login-prompt {
1198
- flex: 1;
1199
- display: none;
1200
- flex-direction: column;
1201
- align-items: center;
1202
- justify-content: center;
1203
- padding: 30px;
1204
- text-align: center;
1205
- }
1206
-
1207
- .login-prompt.show { display: flex; }
1208
-
1209
- .login-btn {
1210
- background: var(--vscode-button-background);
1211
- color: var(--vscode-button-foreground);
1212
- border: none;
1213
- border-radius: 6px;
1214
- padding: 10px 20px;
1215
- cursor: pointer;
1216
- font-size: 13px;
1217
- font-weight: 500;
1218
- margin-top: 16px;
1219
- }
1220
-
1221
- .login-btn:hover {
1222
- background: var(--vscode-button-hoverBackground);
1223
- }
1224
-
1225
- /* Status indicator */
1226
- .status-indicator {
1227
- display: none;
1228
- align-items: center;
1229
- gap: 8px;
1230
- padding: 8px 14px;
1231
- background: var(--vscode-editor-inactiveSelectionBackground);
1232
- font-size: 12px;
1233
- border-top: 1px solid var(--vscode-panel-border);
1234
- }
1235
-
1236
- .status-indicator.show { display: flex; }
1237
-
1238
- .status-spinner {
1239
- width: 14px;
1240
- height: 14px;
1241
- border: 2px solid var(--vscode-foreground);
1242
- border-top-color: transparent;
1243
- border-radius: 50%;
1244
- animation: spin 1s linear infinite;
1245
- }
1246
-
1247
- @keyframes spin {
1248
- to { transform: rotate(360deg); }
1249
- }
1250
-
1251
- /* Progress bar */
1252
- .progress-bar {
1253
- width: 100%;
1254
- height: 4px;
1255
- background: var(--vscode-progressBar-background, #333);
1256
- border-radius: 2px;
1257
- margin-top: 6px;
1258
- overflow: hidden;
1259
- }
1260
-
1261
- .progress-fill {
1262
- height: 100%;
1263
- background: var(--vscode-progressBar-foreground, #0e7ad3);
1264
- border-radius: 2px;
1265
- transition: width 0.2s ease;
1266
- }
1267
- </style>
1268
- </head>
1269
- <body>
1270
- <div class="header">
1271
- <svg class="header-icon" viewBox="0 0 24 24" fill="currentColor">
1272
- <path d="M4 4 L10 12 L4 20 L8 20 L12 14 L16 20 L20 20 L14 12 L20 4 L16 4 L12 10 L8 4 Z"/>
1273
- </svg>
1274
- <span class="header-title">CodeBakers</span>
1275
- <span class="plan-badge" id="planBadge">Pro</span>
1276
- <button class="header-btn" onclick="clearChat()">Clear</button>
1277
- </div>
1278
-
1279
- <div class="login-prompt" id="loginPrompt">
1280
- <div class="welcome-icon">🔐</div>
1281
- <div class="welcome-title">Sign in to CodeBakers</div>
1282
- <div class="welcome-text">Connect with GitHub to start your free trial.</div>
1283
- <button class="login-btn" onclick="login()">Sign in with GitHub</button>
1284
- </div>
1285
-
1286
- <div class="main-content" id="mainContent">
1287
- <div class="messages" id="messages">
1288
- <div class="welcome" id="welcome">
1289
- <div class="welcome-icon">🍪</div>
1290
- <div class="welcome-title">CodeBakers AI</div>
1291
- <div class="welcome-text">Production-ready code with AI. Ask me to build features, edit files, or audit your code.</div>
1292
- <div class="quick-actions">
1293
- <button class="quick-action" onclick="quickAction('/build')">Build Project</button>
1294
- <button class="quick-action" onclick="quickAction('/feature')">Add Feature</button>
1295
- <button class="quick-action" onclick="quickAction('/audit')">Audit Code</button>
1296
- </div>
1297
- </div>
1298
-
1299
- <div class="streaming-indicator" id="streaming">
1300
- <div class="typing-dots">
1301
- <div class="typing-dot"></div>
1302
- <div class="typing-dot"></div>
1303
- <div class="typing-dot"></div>
1304
- </div>
1305
- <div id="streamingContent" style="margin-top: 8px; display: none;"></div>
1306
- </div>
1307
- </div>
1308
-
1309
- <!-- Claude Code Style Pending Changes Panel -->
1310
- <div class="pending-panel" id="pendingPanel">
1311
- <div class="pending-header">
1312
- <div class="pending-title">
1313
- <span>Pending Changes</span>
1314
- <span class="pending-count" id="pendingCount">0</span>
1315
- </div>
1316
- <div class="pending-actions">
1317
- <button class="reject-all-btn" onclick="rejectAll()">Reject All</button>
1318
- <button class="accept-all-btn" onclick="acceptAll()">Accept All</button>
1319
- </div>
1320
- </div>
1321
- <div class="pending-list" id="pendingList"></div>
1322
- </div>
1323
-
1324
- <div class="status-indicator" id="statusIndicator">
1325
- <div class="status-spinner"></div>
1326
- <span id="statusText">Processing...</span>
1327
- </div>
1328
- </div>
1329
-
1330
- <div class="input-area">
1331
- <textarea
1332
- id="input"
1333
- placeholder="Ask CodeBakers anything..."
1334
- rows="1"
1335
- onkeydown="handleKeydown(event)"
1336
- oninput="autoResize(this)"
1337
- ></textarea>
1338
- <button class="send-btn" id="sendBtn" onclick="sendMessage()">Send</button>
1339
- <button class="cancel-btn" id="cancelBtn" onclick="cancelRequest()">Cancel</button>
1340
- </div>
1341
-
1342
- <script>
1343
- const vscode = acquireVsCodeApi();
1344
- const messagesEl = document.getElementById('messages');
1345
- const welcomeEl = document.getElementById('welcome');
1346
- const loginPromptEl = document.getElementById('loginPrompt');
1347
- const mainContentEl = document.getElementById('mainContent');
1348
- const inputEl = document.getElementById('input');
1349
- const sendBtn = document.getElementById('sendBtn');
1350
- const cancelBtn = document.getElementById('cancelBtn');
1351
- const streamingEl = document.getElementById('streaming');
1352
- const streamingContentEl = document.getElementById('streamingContent');
1353
- const pendingPanel = document.getElementById('pendingPanel');
1354
- const pendingList = document.getElementById('pendingList');
1355
- const pendingCount = document.getElementById('pendingCount');
1356
- const statusIndicator = document.getElementById('statusIndicator');
1357
- const statusText = document.getElementById('statusText');
1358
-
1359
- let currentMessages = [];
1360
- let currentChanges = [];
1361
- let currentCommands = [];
1362
- let isStreaming = false;
1363
-
1364
- // Command history for up/down navigation
1365
- let commandHistory = JSON.parse(localStorage.getItem('codebakers-history') || '[]');
1366
- let historyIndex = -1;
1367
- let tempInput = ''; // Store current input when navigating
1368
-
1369
- function sendMessage() {
1370
- const message = inputEl.value.trim();
1371
- if (message) {
1372
- // Add to history (avoid duplicates of last command)
1373
- if (commandHistory.length === 0 || commandHistory[commandHistory.length - 1] !== message) {
1374
- commandHistory.push(message);
1375
- // Keep only last 50 commands
1376
- if (commandHistory.length > 50) {
1377
- commandHistory = commandHistory.slice(-50);
1378
- }
1379
- localStorage.setItem('codebakers-history', JSON.stringify(commandHistory));
1380
- }
1381
- historyIndex = -1;
1382
- tempInput = '';
1383
- }
1384
- if (!message || isStreaming) return;
1385
-
1386
- vscode.postMessage({ type: 'sendMessage', message });
1387
- inputEl.value = '';
1388
- inputEl.style.height = 'auto';
1389
- setStreamingState(true);
1390
- }
1391
-
1392
- function cancelRequest() {
1393
- vscode.postMessage({ type: 'cancelRequest' });
1394
- }
1395
-
1396
- function setStreamingState(streaming) {
1397
- isStreaming = streaming;
1398
- sendBtn.style.display = streaming ? 'none' : 'block';
1399
- cancelBtn.classList.toggle('show', streaming);
1400
- inputEl.disabled = streaming;
1401
- streamingEl.classList.toggle('show', streaming);
1402
-
1403
- if (streaming) {
1404
- streamingContentEl.style.display = 'none';
1405
- streamingContentEl.innerHTML = '';
1406
- }
1407
- }
1408
-
1409
- function quickAction(command) {
1410
- inputEl.value = command + ' ';
1411
- inputEl.focus();
1412
- }
1413
-
1414
- function clearChat() {
1415
- vscode.postMessage({ type: 'clearChat' });
1416
- }
1417
-
1418
- function login() {
1419
- vscode.postMessage({ type: 'login' });
1420
- }
1421
-
1422
- function acceptAll() {
1423
- vscode.postMessage({ type: 'applyAllFiles' });
1424
- }
1425
-
1426
- function rejectAll() {
1427
- vscode.postMessage({ type: 'rejectAllFiles' });
1428
- }
1429
-
1430
- function acceptFile(id) {
1431
- vscode.postMessage({ type: 'applyFile', id });
1432
- }
1433
-
1434
- function rejectFile(id) {
1435
- vscode.postMessage({ type: 'rejectFile', id });
1436
- }
1437
-
1438
- function showDiff(id) {
1439
- vscode.postMessage({ type: 'showDiff', id });
1440
- }
1441
-
1442
- function undoFile(id) {
1443
- vscode.postMessage({ type: 'undoFile', id });
1444
- }
1445
-
1446
- function runCommand(id) {
1447
- vscode.postMessage({ type: 'runCommand', id });
1448
- }
1449
-
1450
- function handleKeydown(e) {
1451
- // Enter (without Shift) or Ctrl+Enter to send
1452
- if (e.key === 'Enter' && !e.shiftKey) {
1453
- e.preventDefault();
1454
- sendMessage();
1455
- return;
1456
- }
1457
-
1458
- // Up arrow: Previous command in history
1459
- if (e.key === 'ArrowUp' && commandHistory.length > 0) {
1460
- e.preventDefault();
1461
- if (historyIndex === -1) {
1462
- tempInput = inputEl.value; // Save current input
1463
- historyIndex = commandHistory.length - 1;
1464
- } else if (historyIndex > 0) {
1465
- historyIndex--;
1466
- }
1467
- inputEl.value = commandHistory[historyIndex];
1468
- autoResize(inputEl);
1469
- return;
1470
- }
1471
-
1472
- // Down arrow: Next command in history
1473
- if (e.key === 'ArrowDown' && historyIndex !== -1) {
1474
- e.preventDefault();
1475
- if (historyIndex < commandHistory.length - 1) {
1476
- historyIndex++;
1477
- inputEl.value = commandHistory[historyIndex];
1478
- } else {
1479
- historyIndex = -1;
1480
- inputEl.value = tempInput; // Restore saved input
1481
- }
1482
- autoResize(inputEl);
1483
- return;
1484
- }
1485
- }
1486
-
1487
- // Global keyboard shortcuts
1488
- document.addEventListener('keydown', function(e) {
1489
- // Ctrl+Shift+A: Accept all pending changes
1490
- if (e.ctrlKey && e.shiftKey && e.key === 'A') {
1491
- e.preventDefault();
1492
- acceptAll();
1493
- return;
1494
- }
1495
-
1496
- // Escape: Cancel current request
1497
- if (e.key === 'Escape') {
1498
- e.preventDefault();
1499
- cancelRequest();
1500
- return;
1501
- }
1502
-
1503
- // Ctrl+Enter anywhere: Focus input and send if has content
1504
- if (e.ctrlKey && e.key === 'Enter') {
1505
- e.preventDefault();
1506
- inputEl.focus();
1507
- if (inputEl.value.trim()) {
1508
- sendMessage();
1509
- }
1510
- return;
1511
- }
1512
-
1513
- // Ctrl+/ : Focus input
1514
- if (e.ctrlKey && e.key === '/') {
1515
- e.preventDefault();
1516
- inputEl.focus();
1517
- return;
1518
- }
1519
- });
1520
-
1521
- function autoResize(el) {
1522
- el.style.height = 'auto';
1523
- el.style.height = Math.min(el.scrollHeight, 120) + 'px';
1524
- }
1525
-
1526
- function escapeHtml(text) {
1527
- const div = document.createElement('div');
1528
- div.textContent = text;
1529
- return div.innerHTML;
1530
- }
1531
-
1532
- function formatContent(content) {
1533
- if (!content) return '';
1534
-
1535
- let text = content;
1536
- const bt = String.fromCharCode(96); // backtick
1537
- const bt3 = bt + bt + bt;
1538
-
1539
- // Step 1: Extract and protect code blocks
1540
- const codeBlocks = [];
1541
- const codeBlockPattern = bt3 + '([a-zA-Z]*)' + String.fromCharCode(10) + '([\\s\\S]*?)' + bt3;
1542
- text = text.replace(new RegExp(codeBlockPattern, 'g'), function(match, lang, code) {
1543
- const idx = codeBlocks.length;
1544
- codeBlocks.push('<pre class="code-block"><code>' + escapeHtml(code.trim()) + '</code></pre>');
1545
- return '%%CODE' + idx + '%%';
1546
- });
1547
-
1548
- // Step 2: Extract and protect inline code
1549
- const inlineCodes = [];
1550
- const inlinePattern = bt + '([^' + bt + ']+)' + bt;
1551
- text = text.replace(new RegExp(inlinePattern, 'g'), function(match, code) {
1552
- const idx = inlineCodes.length;
1553
- inlineCodes.push('<code class="inline-code">' + escapeHtml(code) + '</code>');
1554
- return '%%INLINE' + idx + '%%';
1555
- });
1556
-
1557
- // Step 3: Strip markdown to plain text (not converting to HTML tags)
1558
- // Remove headers markers but keep text
1559
- text = text.replace(/^#{1,6} /gm, '');
1560
-
1561
- // Remove bold markers but keep text: **text** -> text
1562
- text = text.replace(/\*\*([^*]+)\*\*/g, '$1');
1563
-
1564
- // Remove italic markers but keep text: *text* -> text
1565
- text = text.replace(/\*([^*]+)\*/g, '$1');
1566
-
1567
- // Remove horizontal rules
1568
- text = text.replace(/^-{3,}$/gm, '');
1569
- text = text.replace(/^_{3,}$/gm, '');
1570
- text = text.replace(/^\*{3,}$/gm, '');
1571
-
1572
- // Clean up list markers but keep text
1573
- text = text.replace(/^[\-\*] /gm, '• ');
1574
- text = text.replace(/^\d+\. /gm, '• ');
1575
-
1576
- // Remove link syntax but show text: [text](url) -> text
1577
- text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
1578
-
1579
- // Step 4: Escape HTML in text (but not in code placeholders)
1580
- // Split by placeholders, escape non-placeholder parts, rejoin
1581
- const parts = text.split(/(%%CODE\d+%%|%%INLINE\d+%%)/g);
1582
- text = parts.map(part => {
1583
- if (part.match(/^%%CODE\d+%%$/) || part.match(/^%%INLINE\d+%%$/)) {
1584
- return part; // Keep placeholder as-is
1585
- }
1586
- return escapeHtml(part);
1587
- }).join('');
1588
-
1589
- // Step 5: Restore code blocks and inline code
1590
- for (let i = 0; i < codeBlocks.length; i++) {
1591
- text = text.replace('%%CODE' + i + '%%', codeBlocks[i]);
1592
- }
1593
- for (let i = 0; i < inlineCodes.length; i++) {
1594
- text = text.replace('%%INLINE' + i + '%%', inlineCodes[i]);
1595
- }
1596
-
1597
- // Step 6: Handle paragraphs and line breaks
1598
- const paragraphs = text.split(/\n\n+/);
1599
- const formatted = paragraphs.map(p => {
1600
- const trimmed = p.trim();
1601
- if (!trimmed) return '';
1602
- if (trimmed.startsWith('<pre') || trimmed.startsWith('<code')) return trimmed;
1603
- return '<p>' + trimmed.replace(/\n/g, '<br>') + '</p>';
1604
- }).filter(p => p).join('');
1605
-
1606
- return formatted || '<p></p>';
1607
- }
1608
-
1609
- function renderMessage(msg, index) {
1610
- const div = document.createElement('div');
1611
- div.className = 'message ' + msg.role;
1612
-
1613
- let html = '';
1614
-
1615
- // Thinking toggle
1616
- if (msg.role === 'assistant' && msg.thinking) {
1617
- html += '<div class="thinking-toggle" onclick="toggleThinking(this)">▶ Show reasoning</div>';
1618
- html += '<div class="thinking-content">' + escapeHtml(msg.thinking) + '</div>';
1619
- }
1620
-
1621
- html += formatContent(msg.content);
1622
-
1623
- div.innerHTML = html;
1624
- return div;
1625
- }
1626
-
1627
- function toggleThinking(el) {
1628
- const content = el.nextElementSibling;
1629
- const isShown = content.classList.toggle('show');
1630
- el.textContent = isShown ? '▼ Hide reasoning' : '▶ Show reasoning';
1631
- }
1632
-
1633
- function renderPendingChanges() {
1634
- const pending = currentChanges.filter(c => c.status === 'pending');
1635
- const pendingCmds = currentCommands.filter(c => c.status === 'pending');
1636
- const total = pending.length + pendingCmds.length;
1637
-
1638
- pendingCount.textContent = total;
1639
- pendingPanel.classList.toggle('show', currentChanges.length > 0 || currentCommands.length > 0);
1640
-
1641
- let html = '';
1642
-
1643
- // File changes
1644
- for (const change of currentChanges) {
1645
- const icon = change.action === 'create' ? '➕' : change.action === 'edit' ? '✏️' : '🗑️';
1646
- const statusClass = change.status !== 'pending' ? change.status : '';
1647
-
1648
- html += '<div class="pending-item ' + statusClass + '">';
1649
- html += '<span class="pending-icon">' + icon + '</span>';
1650
- html += '<div class="pending-info">';
1651
- html += '<div class="pending-path">' + escapeHtml(change.path) + '</div>';
1652
- if (change.description) {
1653
- html += '<div class="pending-desc">' + escapeHtml(change.description) + '</div>';
1654
- }
1655
- html += '</div>';
1656
-
1657
- if (change.status === 'pending') {
1658
- html += '<div class="pending-item-actions">';
1659
- if (change.hasContent && change.action !== 'delete') {
1660
- html += '<button class="item-btn" onclick="showDiff(\'' + change.id + '\')">Diff</button>';
1661
- }
1662
- html += '<button class="item-btn reject" onclick="rejectFile(\'' + change.id + '\')">✕</button>';
1663
- html += '<button class="item-btn accept" onclick="acceptFile(\'' + change.id + '\')">✓</button>';
1664
- html += '</div>';
1665
- } else if (change.status === 'applied') {
1666
- html += '<div class="pending-item-actions">';
1667
- html += '<span style="font-size: 10px; color: #28a745; margin-right: 6px;">✓ applied</span>';
1668
- html += '<button class="item-btn" onclick="undoFile(\'' + change.id + '\')" title="Undo this change">↩</button>';
1669
- html += '</div>';
1670
- } else {
1671
- html += '<span style="font-size: 10px; opacity: 0.7;">' + change.status + '</span>';
1672
- }
1673
-
1674
- html += '</div>';
1675
- }
1676
-
1677
- // Commands
1678
- for (const cmd of currentCommands) {
1679
- const statusClass = cmd.status !== 'pending' ? cmd.status : '';
1680
-
1681
- html += '<div class="pending-item command-item ' + statusClass + '">';
1682
- html += '<span class="pending-icon">▶</span>';
1683
- html += '<div class="pending-info">';
1684
- html += '<div class="command-text">' + escapeHtml(cmd.command) + '</div>';
1685
- if (cmd.description) {
1686
- html += '<div class="pending-desc">' + escapeHtml(cmd.description) + '</div>';
1687
- }
1688
- html += '</div>';
1689
-
1690
- if (cmd.status === 'pending') {
1691
- html += '<div class="pending-item-actions">';
1692
- html += '<button class="item-btn accept" onclick="runCommand(\'' + cmd.id + '\')">Run</button>';
1693
- html += '</div>';
1694
- } else {
1695
- html += '<span style="font-size: 10px; opacity: 0.7;">' + cmd.status + '</span>';
1696
- }
1697
-
1698
- html += '</div>';
1699
- }
1700
-
1701
- pendingList.innerHTML = html;
1702
- }
1703
-
1704
- window.addEventListener('message', event => {
1705
- const data = event.data;
1706
-
1707
- switch (data.type) {
1708
- case 'updateMessages':
1709
- currentMessages = data.messages;
1710
- welcomeEl.style.display = data.messages.length > 0 ? 'none' : 'flex';
1711
-
1712
- // Clear and re-render messages
1713
- const existing = messagesEl.querySelectorAll('.message');
1714
- existing.forEach(el => el.remove());
1715
-
1716
- data.messages.forEach((msg, i) => {
1717
- messagesEl.insertBefore(renderMessage(msg, i), streamingEl);
1718
- });
1719
-
1720
- messagesEl.scrollTop = messagesEl.scrollHeight;
1721
- setStreamingState(false);
1722
- break;
1723
-
1724
- case 'updatePendingChanges':
1725
- currentChanges = data.changes || [];
1726
- currentCommands = data.commands || [];
1727
- renderPendingChanges();
1728
- break;
1729
-
1730
- case 'typing':
1731
- if (data.isTyping) {
1732
- welcomeEl.style.display = 'none';
1733
- }
1734
- streamingEl.classList.toggle('show', data.isTyping);
1735
- messagesEl.scrollTop = messagesEl.scrollHeight;
1736
- break;
1737
-
1738
- case 'streamThinking':
1739
- // Could show thinking indicator if desired
1740
- break;
1741
-
1742
- case 'streamContent':
1743
- streamingContentEl.style.display = 'block';
1744
- streamingContentEl.innerHTML = formatContent(data.content);
1745
- messagesEl.scrollTop = messagesEl.scrollHeight;
1746
- break;
1747
-
1748
- case 'validating':
1749
- statusText.textContent = 'Validating TypeScript...';
1750
- statusIndicator.classList.add('show');
1751
- break;
1752
-
1753
- case 'streamError':
1754
- statusIndicator.classList.remove('show');
1755
- setStreamingState(false);
1756
- alert('Error: ' + (data.error || 'Unknown error'));
1757
- break;
1758
-
1759
- case 'requestCancelled':
1760
- statusIndicator.classList.remove('show');
1761
- setStreamingState(false);
1762
- break;
1763
-
1764
- case 'updatePlan':
1765
- const badge = document.getElementById('planBadge');
1766
- badge.textContent = data.plan.charAt(0).toUpperCase() + data.plan.slice(1);
1767
- badge.className = 'plan-badge' + (data.plan === 'trial' ? ' trial' : '');
1768
- break;
1769
-
1770
- case 'showStatus':
1771
- if (data.show) {
1772
- statusText.textContent = data.text || 'Processing...';
1773
- statusIndicator.classList.add('show');
1774
- } else {
1775
- statusIndicator.classList.remove('show');
1776
- }
1777
- break;
1778
-
1779
- case 'showProgress':
1780
- if (data.show) {
1781
- const pct = data.total > 0 ? Math.round((data.current / data.total) * 100) : 0;
1782
- statusText.innerHTML = data.text + '<div class="progress-bar"><div class="progress-fill" style="width:' + pct + '%"></div></div>';
1783
- statusIndicator.classList.add('show');
1784
- } else {
1785
- statusIndicator.classList.remove('show');
1786
- }
1787
- break;
1788
-
1789
- case 'showLogin':
1790
- loginPromptEl.classList.add('show');
1791
- mainContentEl.style.display = 'none';
1792
- break;
1793
-
1794
- case 'hideLogin':
1795
- loginPromptEl.classList.remove('show');
1796
- mainContentEl.style.display = 'flex';
1797
- break;
1798
-
1799
- case 'updateHealth':
1800
- // Could show health indicator
1801
- break;
1802
- }
1803
- });
1804
-
1805
- // Hide status after a delay
1806
- setInterval(() => {
1807
- if (!isStreaming) {
1808
- statusIndicator.classList.remove('show');
1809
- }
1810
- }, 3000);
1811
- </script>
1812
- </body>
1813
- </html>`;
1814
- }
1815
- }