ai-changelog-generator-extension 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/launch.json +41 -0
- package/.vscode/settings.json +15 -0
- package/.vscode/tasks.json +26 -0
- package/.vscodeignore +62 -0
- package/ARCHITECTURE-v0.4.0.md +418 -0
- package/CHANGELOG.md +28 -0
- package/DEVELOPMENT.md +266 -0
- package/IMPLEMENTATION_SUMMARY.md +240 -0
- package/IMPLEMENTATION_SUMMARY_v0.4.0.md +239 -0
- package/LICENSE +21 -0
- package/MIGRATION_CHECKLIST.md +200 -0
- package/QUICK-REFERENCE-v0.4.0.md +251 -0
- package/QUICK-START.md +211 -0
- package/README.md +95 -0
- package/RELEASE-NOTES-0.2.0.md +176 -0
- package/RELEASE-NOTES-0.3.0.md +275 -0
- package/RELEASE-NOTES-0.4.0.md +194 -0
- package/SETTINGS-GUIDE.md +418 -0
- package/TESTING-v0.4.0.md +263 -0
- package/TESTING.md +255 -0
- package/esbuild.js +54 -0
- package/package.json +282 -0
- package/setup.sh +58 -0
- package/src/commands/configureProvider.ts +28 -0
- package/src/commands/setupWizard.ts +333 -0
- package/src/commands/testConnection.ts +93 -0
- package/src/extension.ts +264 -0
- package/src/services/EnvFileService.ts +172 -0
- package/src/services/ExtensionConfigurationManager.ts +224 -0
- package/src/services/StatusService.ts +211 -0
- package/src/types/core.d.ts +85 -0
- package/src/views/CommitSidebarProvider.ts +572 -0
- package/src/views/OnboardingPanelProvider.ts +1366 -0
- package/src/views/ReleaseSidebarProvider.ts +370 -0
- package/src/views/SettingsPanelProvider.ts +991 -0
- package/test-package.sh +38 -0
- package/tsconfig.json +18 -0
- package/v0.4.0-COMPLETE.md +349 -0
|
@@ -0,0 +1,1366 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
import { ExtensionConfigurationManager } from '../services/ExtensionConfigurationManager'
|
|
3
|
+
import { EnvFileService } from '../services/EnvFileService'
|
|
4
|
+
import { StatusService } from '../services/StatusService'
|
|
5
|
+
|
|
6
|
+
export class OnboardingPanelProvider {
|
|
7
|
+
public static currentPanel: OnboardingPanelProvider | undefined
|
|
8
|
+
private readonly _panel: vscode.WebviewPanel
|
|
9
|
+
private _disposables: vscode.Disposable[] = []
|
|
10
|
+
|
|
11
|
+
public static createOrShow(
|
|
12
|
+
extensionUri: vscode.Uri,
|
|
13
|
+
configManager: ExtensionConfigurationManager
|
|
14
|
+
) {
|
|
15
|
+
// Open in the active editor column, or first column if none active
|
|
16
|
+
const column = vscode.window.activeTextEditor
|
|
17
|
+
? vscode.window.activeTextEditor.viewColumn
|
|
18
|
+
: vscode.ViewColumn.One
|
|
19
|
+
|
|
20
|
+
// If we already have a panel, show it
|
|
21
|
+
if (OnboardingPanelProvider.currentPanel) {
|
|
22
|
+
OnboardingPanelProvider.currentPanel._panel.reveal(column)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Otherwise, create a new panel in the main editor area
|
|
27
|
+
const panel = vscode.window.createWebviewPanel(
|
|
28
|
+
'aiChangelogOnboarding',
|
|
29
|
+
'🚀 AI Changelog Setup',
|
|
30
|
+
column || vscode.ViewColumn.One,
|
|
31
|
+
{
|
|
32
|
+
enableScripts: true,
|
|
33
|
+
localResourceRoots: [extensionUri],
|
|
34
|
+
retainContextWhenHidden: true,
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
OnboardingPanelProvider.currentPanel = new OnboardingPanelProvider(
|
|
39
|
+
panel,
|
|
40
|
+
extensionUri,
|
|
41
|
+
configManager
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private constructor(
|
|
46
|
+
panel: vscode.WebviewPanel,
|
|
47
|
+
extensionUri: vscode.Uri,
|
|
48
|
+
private readonly _configManager: ExtensionConfigurationManager
|
|
49
|
+
) {
|
|
50
|
+
this._panel = panel
|
|
51
|
+
|
|
52
|
+
// Set the webview's initial html content
|
|
53
|
+
this._panel.webview.html = this._getHtmlForWebview(this._panel.webview)
|
|
54
|
+
|
|
55
|
+
// Listen for when the panel is disposed
|
|
56
|
+
this._panel.onDidDispose(() => this.dispose(), null, this._disposables)
|
|
57
|
+
|
|
58
|
+
// Handle messages from the webview
|
|
59
|
+
this._panel.webview.onDidReceiveMessage(
|
|
60
|
+
async (message) => {
|
|
61
|
+
switch (message.type) {
|
|
62
|
+
case 'testStorageMode':
|
|
63
|
+
await this._testStorageMode(message.mode)
|
|
64
|
+
break
|
|
65
|
+
case 'testProvider':
|
|
66
|
+
await this._testProvider(message.provider)
|
|
67
|
+
break
|
|
68
|
+
case 'testApiKey':
|
|
69
|
+
await this._testApiKey(message.provider, message.apiKey)
|
|
70
|
+
break
|
|
71
|
+
case 'saveConfiguration':
|
|
72
|
+
await this._saveConfiguration(message.config)
|
|
73
|
+
break
|
|
74
|
+
case 'testFullConnection':
|
|
75
|
+
await this._testFullConnection()
|
|
76
|
+
break
|
|
77
|
+
case 'skipSetup':
|
|
78
|
+
await this._skipSetup()
|
|
79
|
+
break
|
|
80
|
+
case 'loadCurrentConfig':
|
|
81
|
+
await this._loadCurrentConfig()
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
null,
|
|
86
|
+
this._disposables
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// Load current configuration
|
|
90
|
+
this._loadCurrentConfig()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private async _testStorageMode(mode: string) {
|
|
94
|
+
StatusService.log(`[Onboarding] Testing storage mode: ${mode}`)
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
if (mode === 'env') {
|
|
98
|
+
const envVars = await EnvFileService.detectEnvironmentVariables()
|
|
99
|
+
const keyCount = Object.keys(envVars).length
|
|
100
|
+
|
|
101
|
+
if (keyCount > 0) {
|
|
102
|
+
// Send detected env vars to auto-fill the form
|
|
103
|
+
this._sendMessage({
|
|
104
|
+
type: 'validationResult',
|
|
105
|
+
step: 'storage',
|
|
106
|
+
success: true,
|
|
107
|
+
message: `✅ Found ${keyCount} configuration(s) in .env files`,
|
|
108
|
+
data: { keys: Object.keys(envVars), envVars },
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Also send a message to pre-fill the form with detected values
|
|
112
|
+
this._sendMessage({
|
|
113
|
+
type: 'envDetected',
|
|
114
|
+
envVars,
|
|
115
|
+
})
|
|
116
|
+
} else {
|
|
117
|
+
this._sendMessage({
|
|
118
|
+
type: 'validationResult',
|
|
119
|
+
step: 'storage',
|
|
120
|
+
success: false,
|
|
121
|
+
message: '⚠️ No .env file found. You can create one or choose a different storage mode.',
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
} else if (mode === 'secrets') {
|
|
125
|
+
this._sendMessage({
|
|
126
|
+
type: 'validationResult',
|
|
127
|
+
step: 'storage',
|
|
128
|
+
success: true,
|
|
129
|
+
message: '✅ Secure storage is available and ready to use',
|
|
130
|
+
})
|
|
131
|
+
} else if (mode === 'settings') {
|
|
132
|
+
this._sendMessage({
|
|
133
|
+
type: 'validationResult',
|
|
134
|
+
step: 'storage',
|
|
135
|
+
success: true,
|
|
136
|
+
message: '⚠️ Settings storage works but stores keys in plain text (not recommended)',
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
141
|
+
this._sendMessage({
|
|
142
|
+
type: 'validationResult',
|
|
143
|
+
step: 'storage',
|
|
144
|
+
success: false,
|
|
145
|
+
message: `❌ Error: ${errorMsg}`,
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private async _testProvider(provider: string) {
|
|
151
|
+
StatusService.log(`[Onboarding] Testing provider: ${provider}`)
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
if (provider === 'ollama') {
|
|
155
|
+
const config = vscode.workspace.getConfiguration('aiChangelog')
|
|
156
|
+
const host = config.get<string>('ollamaHost', 'http://localhost:11434')
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const response = await fetch(`${host}/api/tags`)
|
|
160
|
+
if (response.ok) {
|
|
161
|
+
const data = await response.json()
|
|
162
|
+
const models = data.models?.map((m: any) => m.name) || []
|
|
163
|
+
this._sendMessage({
|
|
164
|
+
type: 'validationResult',
|
|
165
|
+
step: 'provider',
|
|
166
|
+
success: true,
|
|
167
|
+
message: `✅ Ollama is running with ${models.length} model(s)`,
|
|
168
|
+
data: { models, requiresApiKey: false },
|
|
169
|
+
})
|
|
170
|
+
} else {
|
|
171
|
+
throw new Error(`HTTP ${response.status}`)
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
this._sendMessage({
|
|
175
|
+
type: 'validationResult',
|
|
176
|
+
step: 'provider',
|
|
177
|
+
success: false,
|
|
178
|
+
message: '❌ Ollama is not running. Please start Ollama first.',
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (provider === 'lmstudio') {
|
|
185
|
+
const config = vscode.workspace.getConfiguration('aiChangelog')
|
|
186
|
+
const host = config.get<string>('lmStudioHost', 'http://localhost:1234')
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const response = await fetch(`${host}/v1/models`)
|
|
190
|
+
if (response.ok) {
|
|
191
|
+
const data = await response.json()
|
|
192
|
+
const models = data.data?.map((m: any) => m.id) || []
|
|
193
|
+
this._sendMessage({
|
|
194
|
+
type: 'validationResult',
|
|
195
|
+
step: 'provider',
|
|
196
|
+
success: true,
|
|
197
|
+
message: `✅ LM Studio is running with ${models.length} model(s)`,
|
|
198
|
+
data: { models, requiresApiKey: false },
|
|
199
|
+
})
|
|
200
|
+
} else {
|
|
201
|
+
throw new Error(`HTTP ${response.status}`)
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
this._sendMessage({
|
|
205
|
+
type: 'validationResult',
|
|
206
|
+
step: 'provider',
|
|
207
|
+
success: false,
|
|
208
|
+
message: '❌ LM Studio is not running. Please start LM Studio first.',
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Cloud providers need API keys
|
|
215
|
+
this._sendMessage({
|
|
216
|
+
type: 'validationResult',
|
|
217
|
+
step: 'provider',
|
|
218
|
+
success: true,
|
|
219
|
+
message: `✅ ${this._getProviderDisplayName(provider)} selected. API key required in next step.`,
|
|
220
|
+
data: { requiresApiKey: true },
|
|
221
|
+
})
|
|
222
|
+
} catch (error) {
|
|
223
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
224
|
+
this._sendMessage({
|
|
225
|
+
type: 'validationResult',
|
|
226
|
+
step: 'provider',
|
|
227
|
+
success: false,
|
|
228
|
+
message: `❌ Error: ${errorMsg}`,
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private async _testApiKey(provider: string, apiKey: string) {
|
|
234
|
+
StatusService.log(`[Onboarding] Testing API key for ${provider}`)
|
|
235
|
+
|
|
236
|
+
if (!apiKey || apiKey.length < 10) {
|
|
237
|
+
this._sendMessage({
|
|
238
|
+
type: 'validationResult',
|
|
239
|
+
step: 'apiKey',
|
|
240
|
+
success: false,
|
|
241
|
+
message: '❌ API key is too short or empty',
|
|
242
|
+
})
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Show loading state
|
|
247
|
+
this._sendMessage({
|
|
248
|
+
type: 'validationResult',
|
|
249
|
+
step: 'apiKey',
|
|
250
|
+
success: null,
|
|
251
|
+
message: '🔄 Testing API connection...',
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
// Temporarily store the key to test it
|
|
256
|
+
await this._configManager.storeApiKey(provider, apiKey)
|
|
257
|
+
|
|
258
|
+
// Test connection using the command
|
|
259
|
+
await vscode.commands.executeCommand('ai-changelog.testConnection')
|
|
260
|
+
|
|
261
|
+
this._sendMessage({
|
|
262
|
+
type: 'validationResult',
|
|
263
|
+
step: 'apiKey',
|
|
264
|
+
success: true,
|
|
265
|
+
message: `✅ API key is valid! Connection successful.`,
|
|
266
|
+
})
|
|
267
|
+
} catch (error) {
|
|
268
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
269
|
+
this._sendMessage({
|
|
270
|
+
type: 'validationResult',
|
|
271
|
+
step: 'apiKey',
|
|
272
|
+
success: false,
|
|
273
|
+
message: `❌ Connection failed: ${errorMsg}`,
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private async _saveConfiguration(config: any) {
|
|
279
|
+
StatusService.log('[Onboarding] Saving configuration...')
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
283
|
+
|
|
284
|
+
// Save all settings
|
|
285
|
+
await wsConfig.update('storageMode', config.storageMode, vscode.ConfigurationTarget.Workspace)
|
|
286
|
+
await wsConfig.update('provider', config.provider, vscode.ConfigurationTarget.Workspace)
|
|
287
|
+
|
|
288
|
+
if (config.model) {
|
|
289
|
+
await wsConfig.update('model', config.model, vscode.ConfigurationTarget.Workspace)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (config.temperature !== undefined) {
|
|
293
|
+
await wsConfig.update('temperature', config.temperature, vscode.ConfigurationTarget.Workspace)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Provider-specific settings
|
|
297
|
+
if (config.azureEndpoint) {
|
|
298
|
+
await wsConfig.update('azureEndpoint', config.azureEndpoint, vscode.ConfigurationTarget.Workspace)
|
|
299
|
+
}
|
|
300
|
+
if (config.azureDeploymentName) {
|
|
301
|
+
await wsConfig.update('azureDeploymentName', config.azureDeploymentName, vscode.ConfigurationTarget.Workspace)
|
|
302
|
+
}
|
|
303
|
+
if (config.ollamaHost) {
|
|
304
|
+
await wsConfig.update('ollamaHost', config.ollamaHost, vscode.ConfigurationTarget.Workspace)
|
|
305
|
+
}
|
|
306
|
+
if (config.lmStudioHost) {
|
|
307
|
+
await wsConfig.update('lmStudioHost', config.lmStudioHost, vscode.ConfigurationTarget.Workspace)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Save API key if provided
|
|
311
|
+
if (config.apiKey) {
|
|
312
|
+
await this._configManager.storeApiKey(config.provider, config.apiKey)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Mark setup as complete
|
|
316
|
+
await wsConfig.update('setupCompleted', true, vscode.ConfigurationTarget.Workspace)
|
|
317
|
+
|
|
318
|
+
// Reinitialize config manager
|
|
319
|
+
await this._configManager.initialize()
|
|
320
|
+
StatusService.updateStatus(this._configManager)
|
|
321
|
+
|
|
322
|
+
this._sendMessage({
|
|
323
|
+
type: 'saveComplete',
|
|
324
|
+
success: true,
|
|
325
|
+
message: '✅ Setup complete! You can now use AI Changelog Generator.',
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
StatusService.log('[Onboarding] Configuration saved successfully')
|
|
329
|
+
|
|
330
|
+
// Show success message and close panel after short delay
|
|
331
|
+
setTimeout(() => {
|
|
332
|
+
vscode.window.showInformationMessage(
|
|
333
|
+
'🎉 AI Changelog Generator is ready to use!',
|
|
334
|
+
'Generate Commit',
|
|
335
|
+
'Generate Release'
|
|
336
|
+
).then((action) => {
|
|
337
|
+
if (action === 'Generate Commit') {
|
|
338
|
+
vscode.commands.executeCommand('ai-changelog.generateCommit')
|
|
339
|
+
} else if (action === 'Generate Release') {
|
|
340
|
+
vscode.commands.executeCommand('ai-changelog.generateRelease')
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
this._panel.dispose()
|
|
345
|
+
}, 2000)
|
|
346
|
+
} catch (error) {
|
|
347
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
348
|
+
this._sendMessage({
|
|
349
|
+
type: 'saveComplete',
|
|
350
|
+
success: false,
|
|
351
|
+
message: `❌ Failed to save: ${errorMsg}`,
|
|
352
|
+
})
|
|
353
|
+
StatusService.log(`[Onboarding] Save failed: ${errorMsg}`)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private async _testFullConnection() {
|
|
358
|
+
StatusService.log('[Onboarding] Testing full connection...')
|
|
359
|
+
|
|
360
|
+
this._sendMessage({
|
|
361
|
+
type: 'connectionTest',
|
|
362
|
+
status: 'testing',
|
|
363
|
+
message: '🔄 Testing connection to AI provider...',
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
await vscode.commands.executeCommand('ai-changelog.testConnection')
|
|
368
|
+
|
|
369
|
+
this._sendMessage({
|
|
370
|
+
type: 'connectionTest',
|
|
371
|
+
status: 'success',
|
|
372
|
+
message: '✅ Connection successful! Everything is working.',
|
|
373
|
+
})
|
|
374
|
+
} catch (error) {
|
|
375
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
376
|
+
this._sendMessage({
|
|
377
|
+
type: 'connectionTest',
|
|
378
|
+
status: 'error',
|
|
379
|
+
message: `❌ Connection failed: ${errorMsg}`,
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private async _skipSetup() {
|
|
385
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
386
|
+
await wsConfig.update('setupCompleted', true, vscode.ConfigurationTarget.Workspace)
|
|
387
|
+
|
|
388
|
+
vscode.window.showInformationMessage(
|
|
389
|
+
'Setup skipped. You can configure settings later from the Settings panel.',
|
|
390
|
+
'Open Settings'
|
|
391
|
+
).then((action) => {
|
|
392
|
+
if (action === 'Open Settings') {
|
|
393
|
+
vscode.commands.executeCommand('ai-changelog-settings-view.focus')
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
this._panel.dispose()
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private async _loadCurrentConfig() {
|
|
401
|
+
const config = vscode.workspace.getConfiguration('aiChangelog')
|
|
402
|
+
|
|
403
|
+
this._sendMessage({
|
|
404
|
+
type: 'configLoaded',
|
|
405
|
+
config: {
|
|
406
|
+
storageMode: config.get<string>('storageMode', 'secrets'),
|
|
407
|
+
provider: config.get<string>('provider', 'openai'),
|
|
408
|
+
model: config.get<string>('model', ''),
|
|
409
|
+
temperature: config.get<number>('temperature', 0.3),
|
|
410
|
+
azureEndpoint: config.get<string>('azureEndpoint', ''),
|
|
411
|
+
azureDeploymentName: config.get<string>('azureDeploymentName', ''),
|
|
412
|
+
ollamaHost: config.get<string>('ollamaHost', 'http://localhost:11434'),
|
|
413
|
+
lmStudioHost: config.get<string>('lmStudioHost', 'http://localhost:1234'),
|
|
414
|
+
},
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private _getProviderDisplayName(provider: string): string {
|
|
419
|
+
const names: { [key: string]: string } = {
|
|
420
|
+
openai: 'OpenAI',
|
|
421
|
+
anthropic: 'Anthropic (Claude)',
|
|
422
|
+
google: 'Google (Gemini)',
|
|
423
|
+
azure: 'Azure OpenAI',
|
|
424
|
+
bedrock: 'AWS Bedrock',
|
|
425
|
+
ollama: 'Ollama',
|
|
426
|
+
lmstudio: 'LM Studio',
|
|
427
|
+
}
|
|
428
|
+
return names[provider] || provider
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private _sendMessage(message: any) {
|
|
432
|
+
this._panel.webview.postMessage(message)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
public dispose() {
|
|
436
|
+
OnboardingPanelProvider.currentPanel = undefined
|
|
437
|
+
|
|
438
|
+
this._panel.dispose()
|
|
439
|
+
|
|
440
|
+
while (this._disposables.length) {
|
|
441
|
+
const disposable = this._disposables.pop()
|
|
442
|
+
if (disposable) {
|
|
443
|
+
disposable.dispose()
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private _getHtmlForWebview(webview: vscode.Webview): string {
|
|
449
|
+
return `<!DOCTYPE html>
|
|
450
|
+
<html lang="en">
|
|
451
|
+
<head>
|
|
452
|
+
<meta charset="UTF-8">
|
|
453
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
454
|
+
<title>AI Changelog Setup</title>
|
|
455
|
+
<style>
|
|
456
|
+
* {
|
|
457
|
+
margin: 0;
|
|
458
|
+
padding: 0;
|
|
459
|
+
box-sizing: border-box;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
body {
|
|
463
|
+
font-family: var(--vscode-font-family);
|
|
464
|
+
font-size: var(--vscode-font-size);
|
|
465
|
+
color: var(--vscode-foreground);
|
|
466
|
+
background: var(--vscode-editor-background);
|
|
467
|
+
padding: 0;
|
|
468
|
+
overflow-x: hidden;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.container {
|
|
472
|
+
max-width: 700px;
|
|
473
|
+
margin: 0 auto;
|
|
474
|
+
padding: 40px 20px;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.header {
|
|
478
|
+
text-align: center;
|
|
479
|
+
margin-bottom: 48px;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.header h1 {
|
|
483
|
+
font-size: 32px;
|
|
484
|
+
font-weight: 600;
|
|
485
|
+
margin-bottom: 12px;
|
|
486
|
+
background: linear-gradient(135deg, var(--vscode-textLink-foreground), var(--vscode-textLink-activeForeground));
|
|
487
|
+
-webkit-background-clip: text;
|
|
488
|
+
-webkit-text-fill-color: transparent;
|
|
489
|
+
background-clip: text;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.header p {
|
|
493
|
+
color: var(--vscode-descriptionForeground);
|
|
494
|
+
font-size: 14px;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.progress-bar {
|
|
498
|
+
display: flex;
|
|
499
|
+
justify-content: space-between;
|
|
500
|
+
margin-bottom: 48px;
|
|
501
|
+
position: relative;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.progress-bar::before {
|
|
505
|
+
content: '';
|
|
506
|
+
position: absolute;
|
|
507
|
+
top: 18px;
|
|
508
|
+
left: 0;
|
|
509
|
+
right: 0;
|
|
510
|
+
height: 2px;
|
|
511
|
+
background: var(--vscode-input-border);
|
|
512
|
+
z-index: 0;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.progress-fill {
|
|
516
|
+
position: absolute;
|
|
517
|
+
top: 18px;
|
|
518
|
+
left: 0;
|
|
519
|
+
height: 2px;
|
|
520
|
+
background: var(--vscode-textLink-foreground);
|
|
521
|
+
z-index: 1;
|
|
522
|
+
transition: width 0.3s ease;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.step-indicator {
|
|
526
|
+
width: 36px;
|
|
527
|
+
height: 36px;
|
|
528
|
+
border-radius: 50%;
|
|
529
|
+
background: var(--vscode-input-background);
|
|
530
|
+
border: 2px solid var(--vscode-input-border);
|
|
531
|
+
display: flex;
|
|
532
|
+
align-items: center;
|
|
533
|
+
justify-content: center;
|
|
534
|
+
font-weight: 600;
|
|
535
|
+
font-size: 14px;
|
|
536
|
+
z-index: 2;
|
|
537
|
+
position: relative;
|
|
538
|
+
transition: all 0.3s ease;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.step-indicator.active {
|
|
542
|
+
background: var(--vscode-textLink-foreground);
|
|
543
|
+
border-color: var(--vscode-textLink-foreground);
|
|
544
|
+
color: white;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.step-indicator.completed {
|
|
548
|
+
background: var(--vscode-testing-iconPassed);
|
|
549
|
+
border-color: var(--vscode-testing-iconPassed);
|
|
550
|
+
color: white;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.step-indicator.completed::after {
|
|
554
|
+
content: '✓';
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.step {
|
|
558
|
+
display: none;
|
|
559
|
+
animation: fadeIn 0.3s ease;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.step.active {
|
|
563
|
+
display: block;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
@keyframes fadeIn {
|
|
567
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
568
|
+
to { opacity: 1; transform: translateY(0); }
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.step-title {
|
|
572
|
+
font-size: 24px;
|
|
573
|
+
font-weight: 600;
|
|
574
|
+
margin-bottom: 8px;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.step-description {
|
|
578
|
+
color: var(--vscode-descriptionForeground);
|
|
579
|
+
margin-bottom: 32px;
|
|
580
|
+
font-size: 13px;
|
|
581
|
+
line-height: 1.6;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.option-group {
|
|
585
|
+
display: flex;
|
|
586
|
+
flex-direction: column;
|
|
587
|
+
gap: 12px;
|
|
588
|
+
margin-bottom: 24px;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.option {
|
|
592
|
+
padding: 16px;
|
|
593
|
+
border: 2px solid var(--vscode-input-border);
|
|
594
|
+
border-radius: 8px;
|
|
595
|
+
cursor: pointer;
|
|
596
|
+
transition: all 0.2s ease;
|
|
597
|
+
background: var(--vscode-input-background);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.option:hover {
|
|
601
|
+
border-color: var(--vscode-textLink-foreground);
|
|
602
|
+
background: var(--vscode-list-hoverBackground);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.option.selected {
|
|
606
|
+
border-color: var(--vscode-textLink-foreground);
|
|
607
|
+
background: var(--vscode-list-activeSelectionBackground);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.option-header {
|
|
611
|
+
display: flex;
|
|
612
|
+
align-items: center;
|
|
613
|
+
gap: 12px;
|
|
614
|
+
margin-bottom: 8px;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.option-icon {
|
|
618
|
+
font-size: 24px;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.option-title {
|
|
622
|
+
font-weight: 600;
|
|
623
|
+
font-size: 14px;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.option-badge {
|
|
627
|
+
margin-left: auto;
|
|
628
|
+
padding: 2px 8px;
|
|
629
|
+
border-radius: 12px;
|
|
630
|
+
font-size: 10px;
|
|
631
|
+
font-weight: 600;
|
|
632
|
+
text-transform: uppercase;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.option-badge.recommended {
|
|
636
|
+
background: rgba(22, 163, 74, 0.2);
|
|
637
|
+
color: var(--vscode-terminal-ansiGreen);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.option-badge.local {
|
|
641
|
+
background: rgba(59, 130, 246, 0.2);
|
|
642
|
+
color: var(--vscode-terminal-ansiBlue);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.option-description {
|
|
646
|
+
color: var(--vscode-descriptionForeground);
|
|
647
|
+
font-size: 12px;
|
|
648
|
+
line-height: 1.5;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.form-group {
|
|
652
|
+
margin-bottom: 20px;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
label {
|
|
656
|
+
display: block;
|
|
657
|
+
margin-bottom: 8px;
|
|
658
|
+
font-size: 13px;
|
|
659
|
+
font-weight: 500;
|
|
660
|
+
color: var(--vscode-foreground);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
input, select {
|
|
664
|
+
width: 100%;
|
|
665
|
+
padding: 10px 12px;
|
|
666
|
+
background: var(--vscode-input-background);
|
|
667
|
+
color: var(--vscode-input-foreground);
|
|
668
|
+
border: 1px solid var(--vscode-input-border);
|
|
669
|
+
border-radius: 4px;
|
|
670
|
+
font-family: inherit;
|
|
671
|
+
font-size: inherit;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
input[type="password"] {
|
|
675
|
+
font-family: monospace;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
input:focus, select:focus {
|
|
679
|
+
outline: 1px solid var(--vscode-focusBorder);
|
|
680
|
+
border-color: var(--vscode-focusBorder);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.input-hint {
|
|
684
|
+
margin-top: 6px;
|
|
685
|
+
font-size: 11px;
|
|
686
|
+
color: var(--vscode-descriptionForeground);
|
|
687
|
+
font-style: italic;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.validation-message {
|
|
691
|
+
margin-top: 12px;
|
|
692
|
+
padding: 12px;
|
|
693
|
+
border-radius: 4px;
|
|
694
|
+
font-size: 13px;
|
|
695
|
+
display: none;
|
|
696
|
+
animation: slideDown 0.2s ease;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
@keyframes slideDown {
|
|
700
|
+
from { opacity: 0; transform: translateY(-5px); }
|
|
701
|
+
to { opacity: 1; transform: translateY(0); }
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.validation-message.show {
|
|
705
|
+
display: block;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.validation-message.success {
|
|
709
|
+
background: rgba(22, 163, 74, 0.15);
|
|
710
|
+
color: var(--vscode-terminal-ansiGreen);
|
|
711
|
+
border: 1px solid rgba(22, 163, 74, 0.3);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.validation-message.error {
|
|
715
|
+
background: rgba(239, 68, 68, 0.15);
|
|
716
|
+
color: var(--vscode-errorForeground);
|
|
717
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.validation-message.loading {
|
|
721
|
+
background: rgba(59, 130, 246, 0.15);
|
|
722
|
+
color: var(--vscode-terminal-ansiBlue);
|
|
723
|
+
border: 1px solid rgba(59, 130, 246, 0.3);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.button-group {
|
|
727
|
+
display: flex;
|
|
728
|
+
gap: 12px;
|
|
729
|
+
margin-top: 32px;
|
|
730
|
+
padding-top: 24px;
|
|
731
|
+
border-top: 1px solid var(--vscode-panel-border);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
button {
|
|
735
|
+
flex: 1;
|
|
736
|
+
padding: 12px 24px;
|
|
737
|
+
border: none;
|
|
738
|
+
border-radius: 4px;
|
|
739
|
+
font-family: inherit;
|
|
740
|
+
font-size: 13px;
|
|
741
|
+
font-weight: 500;
|
|
742
|
+
cursor: pointer;
|
|
743
|
+
transition: all 0.2s ease;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
button:disabled {
|
|
747
|
+
opacity: 0.5;
|
|
748
|
+
cursor: not-allowed;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.btn-primary {
|
|
752
|
+
background: var(--vscode-button-background);
|
|
753
|
+
color: var(--vscode-button-foreground);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.btn-primary:hover:not(:disabled) {
|
|
757
|
+
background: var(--vscode-button-hoverBackground);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.btn-secondary {
|
|
761
|
+
background: var(--vscode-button-secondaryBackground);
|
|
762
|
+
color: var(--vscode-button-secondaryForeground);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.btn-secondary:hover:not(:disabled) {
|
|
766
|
+
background: var(--vscode-button-secondaryHoverBackground);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.btn-text {
|
|
770
|
+
background: transparent;
|
|
771
|
+
color: var(--vscode-textLink-foreground);
|
|
772
|
+
border: 1px solid var(--vscode-input-border);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.btn-text:hover:not(:disabled) {
|
|
776
|
+
background: var(--vscode-list-hoverBackground);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
.test-button {
|
|
780
|
+
margin-top: 12px;
|
|
781
|
+
width: auto;
|
|
782
|
+
padding: 8px 20px;
|
|
783
|
+
background: var(--vscode-button-secondaryBackground);
|
|
784
|
+
color: var(--vscode-button-secondaryForeground);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.summary-section {
|
|
788
|
+
background: var(--vscode-input-background);
|
|
789
|
+
border: 1px solid var(--vscode-input-border);
|
|
790
|
+
border-radius: 8px;
|
|
791
|
+
padding: 20px;
|
|
792
|
+
margin-bottom: 24px;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.summary-item {
|
|
796
|
+
display: flex;
|
|
797
|
+
justify-content: space-between;
|
|
798
|
+
padding: 12px 0;
|
|
799
|
+
border-bottom: 1px solid var(--vscode-panel-border);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.summary-item:last-child {
|
|
803
|
+
border-bottom: none;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.summary-label {
|
|
807
|
+
color: var(--vscode-descriptionForeground);
|
|
808
|
+
font-size: 13px;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
.summary-value {
|
|
812
|
+
font-weight: 500;
|
|
813
|
+
font-size: 13px;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.success-icon {
|
|
817
|
+
font-size: 64px;
|
|
818
|
+
text-align: center;
|
|
819
|
+
margin-bottom: 24px;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.azure-fields, .ollama-fields, .lmstudio-fields {
|
|
823
|
+
display: none;
|
|
824
|
+
margin-top: 20px;
|
|
825
|
+
padding-top: 20px;
|
|
826
|
+
border-top: 1px solid var(--vscode-panel-border);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.azure-fields.show, .ollama-fields.show, .lmstudio-fields.show {
|
|
830
|
+
display: block;
|
|
831
|
+
}
|
|
832
|
+
</style>
|
|
833
|
+
</head>
|
|
834
|
+
<body>
|
|
835
|
+
<div class="container">
|
|
836
|
+
<div class="header">
|
|
837
|
+
<h1>🚀 Welcome to AI Changelog Generator</h1>
|
|
838
|
+
<p>Let's get you set up in just a few steps</p>
|
|
839
|
+
</div>
|
|
840
|
+
|
|
841
|
+
<div class="progress-bar">
|
|
842
|
+
<div class="progress-fill" id="progressFill"></div>
|
|
843
|
+
<div class="step-indicator" data-step="1">1</div>
|
|
844
|
+
<div class="step-indicator" data-step="2">2</div>
|
|
845
|
+
<div class="step-indicator" data-step="3">3</div>
|
|
846
|
+
<div class="step-indicator" data-step="4">4</div>
|
|
847
|
+
</div>
|
|
848
|
+
|
|
849
|
+
<!-- Step 1: Storage Mode -->
|
|
850
|
+
<div class="step active" data-step="1">
|
|
851
|
+
<h2 class="step-title">Choose Storage Method</h2>
|
|
852
|
+
<p class="step-description">
|
|
853
|
+
Select how you want to store your AI provider API keys. We recommend secure storage for best security.
|
|
854
|
+
</p>
|
|
855
|
+
|
|
856
|
+
<div class="option-group">
|
|
857
|
+
<div class="option" data-value="secrets" onclick="selectStorage('secrets')">
|
|
858
|
+
<div class="option-header">
|
|
859
|
+
<span class="option-icon">🔒</span>
|
|
860
|
+
<span class="option-title">Secure Storage</span>
|
|
861
|
+
<span class="option-badge recommended">Recommended</span>
|
|
862
|
+
</div>
|
|
863
|
+
<p class="option-description">
|
|
864
|
+
API keys are encrypted and stored in VSCode's secure storage (keychain/credential manager)
|
|
865
|
+
</p>
|
|
866
|
+
</div>
|
|
867
|
+
|
|
868
|
+
<div class="option" data-value="env" onclick="selectStorage('env')">
|
|
869
|
+
<div class="option-header">
|
|
870
|
+
<span class="option-icon">📄</span>
|
|
871
|
+
<span class="option-title">Environment File</span>
|
|
872
|
+
</div>
|
|
873
|
+
<p class="option-description">
|
|
874
|
+
Store API keys in a .env.local file (add to .gitignore to avoid committing)
|
|
875
|
+
</p>
|
|
876
|
+
</div>
|
|
877
|
+
|
|
878
|
+
<div class="option" data-value="settings" onclick="selectStorage('settings')">
|
|
879
|
+
<div class="option-header">
|
|
880
|
+
<span class="option-icon">⚙️</span>
|
|
881
|
+
<span class="option-title">Settings File</span>
|
|
882
|
+
</div>
|
|
883
|
+
<p class="option-description">
|
|
884
|
+
Store in VSCode settings.json (not secure - keys stored in plain text)
|
|
885
|
+
</p>
|
|
886
|
+
</div>
|
|
887
|
+
</div>
|
|
888
|
+
|
|
889
|
+
<div id="storage-validation" class="validation-message"></div>
|
|
890
|
+
|
|
891
|
+
<div class="button-group">
|
|
892
|
+
<button class="btn-text" onclick="skipSetup()">Skip Setup</button>
|
|
893
|
+
<button class="btn-primary" id="storageNext" disabled onclick="nextStep()">Continue →</button>
|
|
894
|
+
</div>
|
|
895
|
+
</div>
|
|
896
|
+
|
|
897
|
+
<!-- Step 2: Provider Selection -->
|
|
898
|
+
<div class="step" data-step="2">
|
|
899
|
+
<h2 class="step-title">Select AI Provider</h2>
|
|
900
|
+
<p class="step-description">
|
|
901
|
+
Choose which AI service you want to use for analyzing your code changes.
|
|
902
|
+
</p>
|
|
903
|
+
|
|
904
|
+
<div class="option-group">
|
|
905
|
+
<div class="option" data-value="openai" onclick="selectProvider('openai')">
|
|
906
|
+
<div class="option-header">
|
|
907
|
+
<span class="option-icon">🤖</span>
|
|
908
|
+
<span class="option-title">OpenAI</span>
|
|
909
|
+
<span class="option-badge recommended">Popular</span>
|
|
910
|
+
</div>
|
|
911
|
+
<p class="option-description">GPT-4 and other OpenAI models</p>
|
|
912
|
+
</div>
|
|
913
|
+
|
|
914
|
+
<div class="option" data-value="anthropic" onclick="selectProvider('anthropic')">
|
|
915
|
+
<div class="option-header">
|
|
916
|
+
<span class="option-icon">🧠</span>
|
|
917
|
+
<span class="option-title">Anthropic Claude</span>
|
|
918
|
+
</div>
|
|
919
|
+
<p class="option-description">Claude 3 models for detailed analysis</p>
|
|
920
|
+
</div>
|
|
921
|
+
|
|
922
|
+
<div class="option" data-value="google" onclick="selectProvider('google')">
|
|
923
|
+
<div class="option-header">
|
|
924
|
+
<span class="option-icon">✨</span>
|
|
925
|
+
<span class="option-title">Google Gemini</span>
|
|
926
|
+
</div>
|
|
927
|
+
<p class="option-description">Gemini models with large context windows</p>
|
|
928
|
+
</div>
|
|
929
|
+
|
|
930
|
+
<div class="option" data-value="azure" onclick="selectProvider('azure')">
|
|
931
|
+
<div class="option-header">
|
|
932
|
+
<span class="option-icon">☁️</span>
|
|
933
|
+
<span class="option-title">Azure OpenAI</span>
|
|
934
|
+
</div>
|
|
935
|
+
<p class="option-description">Enterprise OpenAI deployment on Azure</p>
|
|
936
|
+
</div>
|
|
937
|
+
|
|
938
|
+
<div class="option" data-value="ollama" onclick="selectProvider('ollama')">
|
|
939
|
+
<div class="option-header">
|
|
940
|
+
<span class="option-icon">🦙</span>
|
|
941
|
+
<span class="option-title">Ollama</span>
|
|
942
|
+
<span class="option-badge local">Local</span>
|
|
943
|
+
</div>
|
|
944
|
+
<p class="option-description">Run models locally on your machine</p>
|
|
945
|
+
</div>
|
|
946
|
+
|
|
947
|
+
<div class="option" data-value="lmstudio" onclick="selectProvider('lmstudio')">
|
|
948
|
+
<div class="option-header">
|
|
949
|
+
<span class="option-icon">💻</span>
|
|
950
|
+
<span class="option-title">LM Studio</span>
|
|
951
|
+
<span class="option-badge local">Local</span>
|
|
952
|
+
</div>
|
|
953
|
+
<p class="option-description">Local inference with LM Studio</p>
|
|
954
|
+
</div>
|
|
955
|
+
</div>
|
|
956
|
+
|
|
957
|
+
<div id="provider-validation" class="validation-message"></div>
|
|
958
|
+
|
|
959
|
+
<div class="button-group">
|
|
960
|
+
<button class="btn-secondary" onclick="previousStep()">← Back</button>
|
|
961
|
+
<button class="btn-primary" id="providerNext" disabled onclick="nextStep()">Continue →</button>
|
|
962
|
+
</div>
|
|
963
|
+
</div>
|
|
964
|
+
|
|
965
|
+
<!-- Step 3: API Configuration -->
|
|
966
|
+
<div class="step" data-step="3">
|
|
967
|
+
<h2 class="step-title">Configure API Access</h2>
|
|
968
|
+
<p class="step-description" id="apiStepDescription">
|
|
969
|
+
Enter your API key to connect to the selected provider.
|
|
970
|
+
</p>
|
|
971
|
+
|
|
972
|
+
<div class="form-group" id="apiKeyGroup">
|
|
973
|
+
<label for="apiKey">API Key</label>
|
|
974
|
+
<input type="password" id="apiKey" placeholder="sk-..." />
|
|
975
|
+
<p class="input-hint">Your API key will be stored securely</p>
|
|
976
|
+
<button class="test-button" onclick="testApiKey()">🔌 Test Connection</button>
|
|
977
|
+
</div>
|
|
978
|
+
|
|
979
|
+
<div class="form-group">
|
|
980
|
+
<label for="model">Model (Optional)</label>
|
|
981
|
+
<input type="text" id="model" placeholder="Leave empty for provider default" />
|
|
982
|
+
<p class="input-hint">e.g., gpt-4-turbo, claude-3-5-sonnet-20241022</p>
|
|
983
|
+
</div>
|
|
984
|
+
|
|
985
|
+
<!-- Azure-specific fields -->
|
|
986
|
+
<div class="azure-fields" id="azureFields">
|
|
987
|
+
<div class="form-group">
|
|
988
|
+
<label for="azureEndpoint">Azure Endpoint *</label>
|
|
989
|
+
<input type="text" id="azureEndpoint" placeholder="https://your-resource.openai.azure.com" />
|
|
990
|
+
</div>
|
|
991
|
+
<div class="form-group">
|
|
992
|
+
<label for="azureDeployment">Deployment Name *</label>
|
|
993
|
+
<input type="text" id="azureDeployment" placeholder="gpt-4" />
|
|
994
|
+
</div>
|
|
995
|
+
</div>
|
|
996
|
+
|
|
997
|
+
<!-- Ollama-specific fields -->
|
|
998
|
+
<div class="ollama-fields" id="ollamaFields">
|
|
999
|
+
<div class="form-group">
|
|
1000
|
+
<label for="ollamaHost">Ollama Host</label>
|
|
1001
|
+
<input type="text" id="ollamaHost" value="http://localhost:11434" />
|
|
1002
|
+
</div>
|
|
1003
|
+
</div>
|
|
1004
|
+
|
|
1005
|
+
<!-- LM Studio-specific fields -->
|
|
1006
|
+
<div class="lmstudio-fields" id="lmstudioFields">
|
|
1007
|
+
<div class="form-group">
|
|
1008
|
+
<label for="lmstudioHost">LM Studio Host</label>
|
|
1009
|
+
<input type="text" id="lmstudioHost" value="http://localhost:1234" />
|
|
1010
|
+
</div>
|
|
1011
|
+
</div>
|
|
1012
|
+
|
|
1013
|
+
<div id="apiKey-validation" class="validation-message"></div>
|
|
1014
|
+
|
|
1015
|
+
<div class="button-group">
|
|
1016
|
+
<button class="btn-secondary" onclick="previousStep()">← Back</button>
|
|
1017
|
+
<button class="btn-primary" id="apiNext" onclick="nextStep()">Continue →</button>
|
|
1018
|
+
</div>
|
|
1019
|
+
</div>
|
|
1020
|
+
|
|
1021
|
+
<!-- Step 4: Review & Complete -->
|
|
1022
|
+
<div class="step" data-step="4">
|
|
1023
|
+
<h2 class="step-title">Review Configuration</h2>
|
|
1024
|
+
<p class="step-description">
|
|
1025
|
+
Everything looks good! Review your settings and complete the setup.
|
|
1026
|
+
</p>
|
|
1027
|
+
|
|
1028
|
+
<div class="summary-section">
|
|
1029
|
+
<div class="summary-item">
|
|
1030
|
+
<span class="summary-label">Storage Method:</span>
|
|
1031
|
+
<span class="summary-value" id="summaryStorage">-</span>
|
|
1032
|
+
</div>
|
|
1033
|
+
<div class="summary-item">
|
|
1034
|
+
<span class="summary-label">AI Provider:</span>
|
|
1035
|
+
<span class="summary-value" id="summaryProvider">-</span>
|
|
1036
|
+
</div>
|
|
1037
|
+
<div class="summary-item">
|
|
1038
|
+
<span class="summary-label">Model:</span>
|
|
1039
|
+
<span class="summary-value" id="summaryModel">Default</span>
|
|
1040
|
+
</div>
|
|
1041
|
+
<div class="summary-item">
|
|
1042
|
+
<span class="summary-label">API Key:</span>
|
|
1043
|
+
<span class="summary-value" id="summaryApiKey">Configured ✓</span>
|
|
1044
|
+
</div>
|
|
1045
|
+
</div>
|
|
1046
|
+
|
|
1047
|
+
<button class="test-button" onclick="testFullConnection()" style="width: 100%; margin-bottom: 20px;">
|
|
1048
|
+
🔌 Test Full Connection
|
|
1049
|
+
</button>
|
|
1050
|
+
|
|
1051
|
+
<div id="connection-validation" class="validation-message"></div>
|
|
1052
|
+
|
|
1053
|
+
<div class="button-group">
|
|
1054
|
+
<button class="btn-secondary" onclick="previousStep()">← Back</button>
|
|
1055
|
+
<button class="btn-primary" onclick="completeSetup()">🎉 Complete Setup</button>
|
|
1056
|
+
</div>
|
|
1057
|
+
</div>
|
|
1058
|
+
</div>
|
|
1059
|
+
|
|
1060
|
+
<script>
|
|
1061
|
+
const vscode = acquireVsCodeApi();
|
|
1062
|
+
|
|
1063
|
+
let currentStep = 1;
|
|
1064
|
+
let config = {
|
|
1065
|
+
storageMode: '',
|
|
1066
|
+
provider: '',
|
|
1067
|
+
apiKey: '',
|
|
1068
|
+
model: '',
|
|
1069
|
+
azureEndpoint: '',
|
|
1070
|
+
azureDeploymentName: '',
|
|
1071
|
+
ollamaHost: 'http://localhost:11434',
|
|
1072
|
+
lmStudioHost: 'http://localhost:1234',
|
|
1073
|
+
temperature: 0.3
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
// Load existing config
|
|
1077
|
+
vscode.postMessage({ type: 'loadCurrentConfig' });
|
|
1078
|
+
|
|
1079
|
+
function updateProgress() {
|
|
1080
|
+
const progress = ((currentStep - 1) / 3) * 100;
|
|
1081
|
+
document.getElementById('progressFill').style.width = progress + '%';
|
|
1082
|
+
|
|
1083
|
+
document.querySelectorAll('.step-indicator').forEach((indicator, index) => {
|
|
1084
|
+
indicator.classList.remove('active', 'completed');
|
|
1085
|
+
if (index + 1 < currentStep) {
|
|
1086
|
+
indicator.classList.add('completed');
|
|
1087
|
+
} else if (index + 1 === currentStep) {
|
|
1088
|
+
indicator.classList.add('active');
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
document.querySelectorAll('.step').forEach((step, index) => {
|
|
1093
|
+
step.classList.remove('active');
|
|
1094
|
+
if (index + 1 === currentStep) {
|
|
1095
|
+
step.classList.add('active');
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function selectStorage(mode) {
|
|
1101
|
+
config.storageMode = mode;
|
|
1102
|
+
|
|
1103
|
+
document.querySelectorAll('[data-step="1"] .option').forEach(opt => {
|
|
1104
|
+
opt.classList.remove('selected');
|
|
1105
|
+
});
|
|
1106
|
+
document.querySelector(\`[data-step="1"] .option[data-value="\${mode}"]\`).classList.add('selected');
|
|
1107
|
+
|
|
1108
|
+
document.getElementById('storageNext').disabled = false;
|
|
1109
|
+
|
|
1110
|
+
// Test the storage mode
|
|
1111
|
+
vscode.postMessage({ type: 'testStorageMode', mode });
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function selectProvider(provider) {
|
|
1115
|
+
config.provider = provider;
|
|
1116
|
+
|
|
1117
|
+
document.querySelectorAll('[data-step="2"] .option').forEach(opt => {
|
|
1118
|
+
opt.classList.remove('selected');
|
|
1119
|
+
});
|
|
1120
|
+
document.querySelector(\`[data-step="2"] .option[data-value="\${provider}"]\`).classList.add('selected');
|
|
1121
|
+
|
|
1122
|
+
document.getElementById('providerNext').disabled = false;
|
|
1123
|
+
|
|
1124
|
+
// Show/hide provider-specific fields
|
|
1125
|
+
document.getElementById('azureFields').classList.toggle('show', provider === 'azure');
|
|
1126
|
+
document.getElementById('ollamaFields').classList.toggle('show', provider === 'ollama');
|
|
1127
|
+
document.getElementById('lmstudioFields').classList.toggle('show', provider === 'lmstudio');
|
|
1128
|
+
|
|
1129
|
+
// Update API step description
|
|
1130
|
+
const needsKey = !['ollama', 'lmstudio'].includes(provider);
|
|
1131
|
+
document.getElementById('apiStepDescription').textContent = needsKey
|
|
1132
|
+
? 'Enter your API key to connect to the selected provider.'
|
|
1133
|
+
: 'Configure connection settings for your local provider.';
|
|
1134
|
+
|
|
1135
|
+
document.getElementById('apiKeyGroup').style.display = needsKey ? 'block' : 'none';
|
|
1136
|
+
|
|
1137
|
+
// Test the provider
|
|
1138
|
+
vscode.postMessage({ type: 'testProvider', provider });
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function testApiKey() {
|
|
1142
|
+
const apiKey = document.getElementById('apiKey').value;
|
|
1143
|
+
if (!apiKey) {
|
|
1144
|
+
showValidation('apiKey', 'error', '❌ Please enter an API key');
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
config.apiKey = apiKey;
|
|
1149
|
+
vscode.postMessage({
|
|
1150
|
+
type: 'testApiKey',
|
|
1151
|
+
provider: config.provider,
|
|
1152
|
+
apiKey: apiKey
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function testFullConnection() {
|
|
1157
|
+
vscode.postMessage({ type: 'testFullConnection' });
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function nextStep() {
|
|
1161
|
+
if (currentStep === 3) {
|
|
1162
|
+
// Capture all form values before moving to review
|
|
1163
|
+
config.apiKey = document.getElementById('apiKey').value;
|
|
1164
|
+
config.model = document.getElementById('model').value;
|
|
1165
|
+
|
|
1166
|
+
if (config.provider === 'azure') {
|
|
1167
|
+
config.azureEndpoint = document.getElementById('azureEndpoint').value;
|
|
1168
|
+
config.azureDeploymentName = document.getElementById('azureDeployment').value;
|
|
1169
|
+
} else if (config.provider === 'ollama') {
|
|
1170
|
+
config.ollamaHost = document.getElementById('ollamaHost').value;
|
|
1171
|
+
} else if (config.provider === 'lmstudio') {
|
|
1172
|
+
config.lmStudioHost = document.getElementById('lmstudioHost').value;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
updateSummary();
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
currentStep++;
|
|
1179
|
+
updateProgress();
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function previousStep() {
|
|
1183
|
+
currentStep--;
|
|
1184
|
+
updateProgress();
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function updateSummary() {
|
|
1188
|
+
const storageNames = {
|
|
1189
|
+
secrets: '🔒 Secure Storage',
|
|
1190
|
+
env: '📄 Environment File',
|
|
1191
|
+
settings: '⚙️ Settings File'
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
const providerNames = {
|
|
1195
|
+
openai: '🤖 OpenAI',
|
|
1196
|
+
anthropic: '🧠 Anthropic Claude',
|
|
1197
|
+
google: '✨ Google Gemini',
|
|
1198
|
+
azure: '☁️ Azure OpenAI',
|
|
1199
|
+
bedrock: '🚀 AWS Bedrock',
|
|
1200
|
+
ollama: '🦙 Ollama',
|
|
1201
|
+
lmstudio: '💻 LM Studio'
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
document.getElementById('summaryStorage').textContent = storageNames[config.storageMode] || config.storageMode;
|
|
1205
|
+
document.getElementById('summaryProvider').textContent = providerNames[config.provider] || config.provider;
|
|
1206
|
+
document.getElementById('summaryModel').textContent = config.model || 'Default';
|
|
1207
|
+
|
|
1208
|
+
const hasKey = config.apiKey || ['ollama', 'lmstudio'].includes(config.provider);
|
|
1209
|
+
document.getElementById('summaryApiKey').textContent = hasKey ? 'Configured ✓' : 'Not configured';
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function completeSetup() {
|
|
1213
|
+
vscode.postMessage({
|
|
1214
|
+
type: 'saveConfiguration',
|
|
1215
|
+
config: config
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function skipSetup() {
|
|
1220
|
+
if (confirm('Are you sure you want to skip setup? You can configure settings later.')) {
|
|
1221
|
+
vscode.postMessage({ type: 'skipSetup' });
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function showValidation(step, type, message) {
|
|
1226
|
+
const validationEl = document.getElementById(step + '-validation');
|
|
1227
|
+
if (!validationEl) return;
|
|
1228
|
+
|
|
1229
|
+
validationEl.className = 'validation-message show ' + type;
|
|
1230
|
+
validationEl.textContent = message;
|
|
1231
|
+
|
|
1232
|
+
if (type === 'success' || type === 'error') {
|
|
1233
|
+
setTimeout(() => {
|
|
1234
|
+
validationEl.classList.remove('show');
|
|
1235
|
+
}, 5000);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// Handle messages from extension
|
|
1240
|
+
window.addEventListener('message', event => {
|
|
1241
|
+
const message = event.data;
|
|
1242
|
+
|
|
1243
|
+
switch (message.type) {
|
|
1244
|
+
case 'validationResult':
|
|
1245
|
+
const feedbackType = message.success === true ? 'success' :
|
|
1246
|
+
message.success === false ? 'error' :
|
|
1247
|
+
message.success === null ? 'loading' : 'error';
|
|
1248
|
+
showValidation(message.step, feedbackType, message.message);
|
|
1249
|
+
|
|
1250
|
+
// Enable next button on successful validation
|
|
1251
|
+
if (message.success === true && message.step === 'storage') {
|
|
1252
|
+
document.getElementById('storageNext').disabled = false;
|
|
1253
|
+
}
|
|
1254
|
+
if (message.success === true && message.step === 'provider') {
|
|
1255
|
+
document.getElementById('providerNext').disabled = false;
|
|
1256
|
+
}
|
|
1257
|
+
break;
|
|
1258
|
+
|
|
1259
|
+
case 'connectionTest':
|
|
1260
|
+
if (message.status === 'testing') {
|
|
1261
|
+
showValidation('connection', 'loading', message.message);
|
|
1262
|
+
} else if (message.status === 'success') {
|
|
1263
|
+
showValidation('connection', 'success', message.message);
|
|
1264
|
+
} else {
|
|
1265
|
+
showValidation('connection', 'error', message.message);
|
|
1266
|
+
}
|
|
1267
|
+
break;
|
|
1268
|
+
|
|
1269
|
+
case 'saveComplete':
|
|
1270
|
+
if (message.success) {
|
|
1271
|
+
showValidation('connection', 'success', message.message);
|
|
1272
|
+
} else {
|
|
1273
|
+
showValidation('connection', 'error', message.message);
|
|
1274
|
+
}
|
|
1275
|
+
break;
|
|
1276
|
+
|
|
1277
|
+
case 'configLoaded':
|
|
1278
|
+
// Pre-fill form with existing config if available
|
|
1279
|
+
if (message.config) {
|
|
1280
|
+
config = { ...config, ...message.config };
|
|
1281
|
+
|
|
1282
|
+
if (config.storageMode) {
|
|
1283
|
+
const storageOption = document.querySelector(\`[data-step="1"] .option[data-value="\${config.storageMode}"]\`);
|
|
1284
|
+
if (storageOption) storageOption.classList.add('selected');
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (config.provider) {
|
|
1288
|
+
const providerOption = document.querySelector(\`[data-step="2"] .option[data-value="\${config.provider}"]\`);
|
|
1289
|
+
if (providerOption) providerOption.classList.add('selected');
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
document.getElementById('model').value = config.model || '';
|
|
1293
|
+
document.getElementById('azureEndpoint').value = config.azureEndpoint || '';
|
|
1294
|
+
document.getElementById('azureDeployment').value = config.azureDeploymentName || '';
|
|
1295
|
+
document.getElementById('ollamaHost').value = config.ollamaHost || 'http://localhost:11434';
|
|
1296
|
+
document.getElementById('lmstudioHost').value = config.lmStudioHost || 'http://localhost:1234';
|
|
1297
|
+
}
|
|
1298
|
+
break;
|
|
1299
|
+
|
|
1300
|
+
case 'envDetected':
|
|
1301
|
+
// Auto-fill form with detected .env values
|
|
1302
|
+
if (message.envVars) {
|
|
1303
|
+
const env = message.envVars;
|
|
1304
|
+
|
|
1305
|
+
// Set provider based on AI_PROVIDER env var
|
|
1306
|
+
if (env.AI_PROVIDER) {
|
|
1307
|
+
config.provider = env.AI_PROVIDER;
|
|
1308
|
+
const providerOption = document.querySelector(\`[data-step="2"] .option[data-value="\${env.AI_PROVIDER}"]\`);
|
|
1309
|
+
if (providerOption) {
|
|
1310
|
+
providerOption.classList.add('selected');
|
|
1311
|
+
document.getElementById('providerNext').disabled = false;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// Auto-fill API keys based on detected provider
|
|
1316
|
+
if (env.OPENAI_API_KEY && (config.provider === 'openai' || !config.provider)) {
|
|
1317
|
+
document.getElementById('apiKey').value = env.OPENAI_API_KEY;
|
|
1318
|
+
config.apiKey = env.OPENAI_API_KEY;
|
|
1319
|
+
}
|
|
1320
|
+
if (env.ANTHROPIC_API_KEY && config.provider === 'anthropic') {
|
|
1321
|
+
document.getElementById('apiKey').value = env.ANTHROPIC_API_KEY;
|
|
1322
|
+
config.apiKey = env.ANTHROPIC_API_KEY;
|
|
1323
|
+
}
|
|
1324
|
+
if (env.AZURE_OPENAI_KEY && config.provider === 'azure') {
|
|
1325
|
+
document.getElementById('apiKey').value = env.AZURE_OPENAI_KEY;
|
|
1326
|
+
config.apiKey = env.AZURE_OPENAI_KEY;
|
|
1327
|
+
}
|
|
1328
|
+
if (env.GOOGLE_API_KEY && config.provider === 'google') {
|
|
1329
|
+
document.getElementById('apiKey').value = env.GOOGLE_API_KEY;
|
|
1330
|
+
config.apiKey = env.GOOGLE_API_KEY;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Auto-fill Azure config
|
|
1334
|
+
if (env.AZURE_OPENAI_ENDPOINT) {
|
|
1335
|
+
document.getElementById('azureEndpoint').value = env.AZURE_OPENAI_ENDPOINT;
|
|
1336
|
+
config.azureEndpoint = env.AZURE_OPENAI_ENDPOINT;
|
|
1337
|
+
}
|
|
1338
|
+
if (env.AZURE_OPENAI_DEPLOYMENT_NAME) {
|
|
1339
|
+
document.getElementById('azureDeployment').value = env.AZURE_OPENAI_DEPLOYMENT_NAME;
|
|
1340
|
+
config.azureDeploymentName = env.AZURE_OPENAI_DEPLOYMENT_NAME;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// Auto-fill Ollama host
|
|
1344
|
+
if (env.OLLAMA_HOST) {
|
|
1345
|
+
document.getElementById('ollamaHost').value = env.OLLAMA_HOST;
|
|
1346
|
+
config.ollamaHost = env.OLLAMA_HOST;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// Auto-fill LM Studio
|
|
1350
|
+
if (env.LMSTUDIO_BASE_URL) {
|
|
1351
|
+
document.getElementById('lmstudioHost').value = env.LMSTUDIO_BASE_URL;
|
|
1352
|
+
config.lmStudioHost = env.LMSTUDIO_BASE_URL;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
showValidation('storage', 'success', '\u2705 Configuration loaded from .env.local');
|
|
1356
|
+
}
|
|
1357
|
+
break;
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
updateProgress();
|
|
1362
|
+
</script>
|
|
1363
|
+
</body>
|
|
1364
|
+
</html>`
|
|
1365
|
+
}
|
|
1366
|
+
}
|