@myrialabs/clopen 0.2.3 → 0.2.4
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/backend/engine/adapters/claude/stream.ts +107 -0
- package/backend/engine/adapters/opencode/stream.ts +81 -1
- package/backend/engine/types.ts +17 -0
- package/backend/git/git-service.ts +2 -1
- package/backend/ws/git/commit-message.ts +108 -0
- package/backend/ws/git/index.ts +3 -1
- package/backend/ws/system/index.ts +7 -1
- package/backend/ws/system/operations.ts +28 -2
- package/frontend/App.svelte +3 -0
- package/frontend/components/auth/SetupPage.svelte +2 -2
- package/frontend/components/chat/input/ChatInput.svelte +1 -1
- package/frontend/components/chat/message/ChatMessage.svelte +64 -16
- package/frontend/components/checkpoint/ConflictResolutionModal.svelte +189 -0
- package/frontend/components/checkpoint/TimelineModal.svelte +7 -162
- package/frontend/components/common/feedback/RestartRequiredModal.svelte +53 -0
- package/frontend/components/common/feedback/UpdateBanner.svelte +17 -6
- package/frontend/components/git/BranchManager.svelte +143 -155
- package/frontend/components/git/CommitForm.svelte +61 -11
- package/frontend/components/settings/SettingsModal.svelte +1 -1
- package/frontend/components/settings/SettingsView.svelte +1 -1
- package/frontend/components/settings/engines/AIEnginesSettings.svelte +2 -2
- package/frontend/components/settings/general/UpdateSettings.svelte +10 -3
- package/frontend/components/settings/git/GitSettings.svelte +392 -0
- package/frontend/components/settings/model/EngineModelPicker.svelte +275 -0
- package/frontend/components/settings/model/ModelSettings.svelte +172 -289
- package/frontend/components/workspace/PanelHeader.svelte +1 -3
- package/frontend/components/workspace/WorkspaceLayout.svelte +11 -1
- package/frontend/components/workspace/panels/GitPanel.svelte +12 -5
- package/frontend/main.ts +4 -0
- package/frontend/stores/features/settings.svelte.ts +13 -2
- package/frontend/stores/ui/settings-modal.svelte.ts +9 -9
- package/frontend/stores/ui/update.svelte.ts +45 -4
- package/package.json +1 -1
- package/shared/types/git.ts +15 -0
- package/shared/types/stores/settings.ts +12 -0
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
let showAddRemoteForm = $state(false);
|
|
36
36
|
let newRemoteName = $state('origin');
|
|
37
37
|
let newRemoteUrl = $state('');
|
|
38
|
-
let showRemoteSection = $state(false);
|
|
39
38
|
|
|
40
39
|
// Confirm dialog
|
|
41
40
|
let showConfirmDialog = $state(false);
|
|
@@ -168,49 +167,6 @@
|
|
|
168
167
|
{/snippet}
|
|
169
168
|
|
|
170
169
|
{#snippet children()}
|
|
171
|
-
<!-- Create branch form -->
|
|
172
|
-
{#if showCreateForm}
|
|
173
|
-
<div class="mb-4 p-3 bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-lg space-y-2">
|
|
174
|
-
<input
|
|
175
|
-
type="text"
|
|
176
|
-
bind:value={newBranchName}
|
|
177
|
-
placeholder="New branch name..."
|
|
178
|
-
class="w-full px-3 py-2 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 outline-none focus:border-violet-500/40"
|
|
179
|
-
onkeydown={(e) => e.key === 'Enter' && handleCreate()}
|
|
180
|
-
autofocus
|
|
181
|
-
/>
|
|
182
|
-
<div class="flex gap-2">
|
|
183
|
-
<button
|
|
184
|
-
type="button"
|
|
185
|
-
class="flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors cursor-pointer border-none
|
|
186
|
-
{newBranchName.trim()
|
|
187
|
-
? 'bg-violet-600 text-white hover:bg-violet-700'
|
|
188
|
-
: 'bg-slate-200 dark:bg-slate-700 text-slate-400 dark:text-slate-500 cursor-not-allowed'}"
|
|
189
|
-
onclick={handleCreate}
|
|
190
|
-
disabled={!newBranchName.trim()}
|
|
191
|
-
>
|
|
192
|
-
Create Branch
|
|
193
|
-
</button>
|
|
194
|
-
<button
|
|
195
|
-
type="button"
|
|
196
|
-
class="px-3 py-2 text-sm font-medium bg-transparent border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors cursor-pointer"
|
|
197
|
-
onclick={() => { showCreateForm = false; newBranchName = ''; }}
|
|
198
|
-
>
|
|
199
|
-
Cancel
|
|
200
|
-
</button>
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
{:else}
|
|
204
|
-
<button
|
|
205
|
-
type="button"
|
|
206
|
-
class="flex items-center justify-center gap-2 w-full mb-4 py-2.5 px-3 border border-dashed border-slate-300 dark:border-slate-600 rounded-lg text-sm text-slate-500 hover:text-violet-600 hover:border-violet-400 transition-colors cursor-pointer bg-transparent"
|
|
207
|
-
onclick={() => showCreateForm = true}
|
|
208
|
-
>
|
|
209
|
-
<Icon name="lucide:plus" class="w-4 h-4" />
|
|
210
|
-
<span>Create New Branch</span>
|
|
211
|
-
</button>
|
|
212
|
-
{/if}
|
|
213
|
-
|
|
214
170
|
<!-- Search -->
|
|
215
171
|
<div class="mb-4">
|
|
216
172
|
<div class="flex items-center gap-2 py-2.5 px-3.5 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-lg">
|
|
@@ -242,7 +198,7 @@
|
|
|
242
198
|
{activeTab === 'local'
|
|
243
199
|
? 'bg-violet-500/10 text-violet-600'
|
|
244
200
|
: 'bg-transparent text-slate-500 hover:text-slate-700 dark:hover:text-slate-300'}"
|
|
245
|
-
onclick={() => activeTab = 'local'}
|
|
201
|
+
onclick={() => { activeTab = 'local'; showCreateForm = false; newBranchName = ''; }}
|
|
246
202
|
>
|
|
247
203
|
Local ({filteredLocal.length})
|
|
248
204
|
</button>
|
|
@@ -261,6 +217,49 @@
|
|
|
261
217
|
<!-- Branch list -->
|
|
262
218
|
<div class="space-y-1.5 max-h-80 overflow-y-auto">
|
|
263
219
|
{#if activeTab === 'local'}
|
|
220
|
+
<!-- Create branch form (Local tab only) -->
|
|
221
|
+
{#if showCreateForm}
|
|
222
|
+
<div class="mb-2 p-3 bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-lg space-y-2">
|
|
223
|
+
<input
|
|
224
|
+
type="text"
|
|
225
|
+
bind:value={newBranchName}
|
|
226
|
+
placeholder="New branch name..."
|
|
227
|
+
class="w-full px-3 py-2 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 outline-none focus:border-violet-500/40"
|
|
228
|
+
onkeydown={(e) => e.key === 'Enter' && handleCreate()}
|
|
229
|
+
autofocus
|
|
230
|
+
/>
|
|
231
|
+
<div class="flex gap-2">
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
class="flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors cursor-pointer border-none
|
|
235
|
+
{newBranchName.trim()
|
|
236
|
+
? 'bg-violet-600 text-white hover:bg-violet-700'
|
|
237
|
+
: 'bg-slate-200 dark:bg-slate-700 text-slate-400 dark:text-slate-500 cursor-not-allowed'}"
|
|
238
|
+
onclick={handleCreate}
|
|
239
|
+
disabled={!newBranchName.trim()}
|
|
240
|
+
>
|
|
241
|
+
Create Branch
|
|
242
|
+
</button>
|
|
243
|
+
<button
|
|
244
|
+
type="button"
|
|
245
|
+
class="px-3 py-2 text-sm font-medium bg-transparent border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors cursor-pointer"
|
|
246
|
+
onclick={() => { showCreateForm = false; newBranchName = ''; }}
|
|
247
|
+
>
|
|
248
|
+
Cancel
|
|
249
|
+
</button>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
{:else}
|
|
253
|
+
<button
|
|
254
|
+
type="button"
|
|
255
|
+
class="flex items-center justify-center gap-2 w-full mb-2 py-2.5 px-3 border border-dashed border-slate-300 dark:border-slate-600 rounded-lg text-sm text-slate-500 hover:text-violet-600 hover:border-violet-400 transition-colors cursor-pointer bg-transparent"
|
|
256
|
+
onclick={() => showCreateForm = true}
|
|
257
|
+
>
|
|
258
|
+
<Icon name="lucide:plus" class="w-4 h-4" />
|
|
259
|
+
<span>Create New Branch</span>
|
|
260
|
+
</button>
|
|
261
|
+
{/if}
|
|
262
|
+
|
|
264
263
|
{#each filteredLocal as branch (branch.name)}
|
|
265
264
|
<div
|
|
266
265
|
class="group flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors
|
|
@@ -320,129 +319,118 @@
|
|
|
320
319
|
</div>
|
|
321
320
|
{/each}
|
|
322
321
|
{:else if activeTab === 'remote'}
|
|
323
|
-
{#
|
|
324
|
-
<div
|
|
325
|
-
class="
|
|
326
|
-
>
|
|
327
|
-
<div class="flex items-center gap-2.5 flex-1 min-w-0">
|
|
328
|
-
<Icon name="lucide:cloud" class="w-4 h-4 text-slate-400 shrink-0" />
|
|
329
|
-
<span class="text-sm text-slate-900 dark:text-slate-100 truncate">{branch.name}</span>
|
|
330
|
-
</div>
|
|
331
|
-
|
|
332
|
-
<div class="items-center gap-1 shrink-0 hidden group-hover:flex">
|
|
333
|
-
<button
|
|
334
|
-
type="button"
|
|
335
|
-
class="flex items-center justify-center w-8 h-8 rounded-lg text-slate-500 hover:bg-violet-500/10 hover:text-violet-600 transition-colors cursor-pointer bg-transparent border-none"
|
|
336
|
-
onclick={() => handleSwitchRemote(branch.name)}
|
|
337
|
-
title="Checkout remote branch"
|
|
338
|
-
>
|
|
339
|
-
<Icon name="lucide:arrow-right" class="w-4 h-4" />
|
|
340
|
-
</button>
|
|
341
|
-
</div>
|
|
322
|
+
{#if isLoadingRemotes}
|
|
323
|
+
<div class="flex items-center justify-center py-8">
|
|
324
|
+
<div class="w-4 h-4 border-2 border-slate-200 dark:border-slate-700 border-t-violet-600 rounded-full animate-spin"></div>
|
|
342
325
|
</div>
|
|
343
|
-
{:else}
|
|
326
|
+
{:else if remotes.length === 0}
|
|
344
327
|
<div class="flex flex-col items-center gap-2 py-8 text-slate-500 dark:text-slate-400 text-sm">
|
|
345
|
-
<Icon name="lucide:
|
|
346
|
-
<p class="font-medium">No remote
|
|
328
|
+
<Icon name="lucide:server-off" class="w-10 h-10 opacity-40" />
|
|
329
|
+
<p class="font-medium">No remote connections</p>
|
|
330
|
+
<p class="text-xs text-center opacity-70">Add a remote below to track remote branches</p>
|
|
347
331
|
</div>
|
|
348
|
-
{
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
>
|
|
359
|
-
<Icon name={showRemoteSection ? 'lucide:chevron-down' : 'lucide:chevron-right'} class="w-3.5 h-3.5 text-slate-400" />
|
|
360
|
-
<Icon name="lucide:server" class="w-3.5 h-3.5 text-slate-500" />
|
|
361
|
-
<span class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide">
|
|
362
|
-
Remote Servers ({remotes.length})
|
|
363
|
-
</span>
|
|
364
|
-
</button>
|
|
365
|
-
|
|
366
|
-
{#if showRemoteSection}
|
|
367
|
-
<div class="mt-2 space-y-2">
|
|
368
|
-
{#if isLoadingRemotes}
|
|
369
|
-
<div class="flex items-center justify-center py-4">
|
|
370
|
-
<div class="w-4 h-4 border-2 border-slate-200 dark:border-slate-700 border-t-violet-600 rounded-full animate-spin"></div>
|
|
371
|
-
</div>
|
|
372
|
-
{:else}
|
|
373
|
-
<!-- Existing remotes -->
|
|
374
|
-
{#each remotes as remote (remote.name)}
|
|
375
|
-
<div class="group flex items-center gap-2.5 px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-800/30">
|
|
376
|
-
<div class="flex items-center justify-center w-6 h-6 rounded-md bg-slate-100 dark:bg-slate-800">
|
|
377
|
-
<Icon name="lucide:globe" class="w-3.5 h-3.5 text-slate-500" />
|
|
378
|
-
</div>
|
|
379
|
-
<div class="flex-1 min-w-0">
|
|
380
|
-
<div class="text-sm font-medium text-slate-900 dark:text-slate-100">{remote.name}</div>
|
|
381
|
-
<div class="text-xs text-slate-500 dark:text-slate-400 truncate font-mono">{remote.fetchUrl}</div>
|
|
382
|
-
</div>
|
|
383
|
-
<button
|
|
384
|
-
type="button"
|
|
385
|
-
class="flex items-center justify-center w-7 h-7 rounded-md text-slate-400 hover:bg-red-500/10 hover:text-red-500 transition-colors cursor-pointer bg-transparent border-none shrink-0 opacity-0 group-hover:opacity-100"
|
|
386
|
-
onclick={() => handleRemoveRemote(remote.name)}
|
|
387
|
-
title="Disconnect remote"
|
|
388
|
-
>
|
|
389
|
-
<Icon name="lucide:unlink" class="w-3.5 h-3.5" />
|
|
390
|
-
</button>
|
|
391
|
-
</div>
|
|
392
|
-
{/each}
|
|
393
|
-
|
|
394
|
-
<!-- Add remote form -->
|
|
395
|
-
{#if showAddRemoteForm}
|
|
396
|
-
<div class="p-3 bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-lg space-y-2">
|
|
397
|
-
<input
|
|
398
|
-
type="text"
|
|
399
|
-
bind:value={newRemoteName}
|
|
400
|
-
placeholder="Name (e.g. origin)"
|
|
401
|
-
class="w-full px-3 py-2 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 outline-none focus:border-violet-500/40"
|
|
402
|
-
/>
|
|
403
|
-
<input
|
|
404
|
-
type="text"
|
|
405
|
-
bind:value={newRemoteUrl}
|
|
406
|
-
placeholder="URL (e.g. https://github.com/user/repo.git)"
|
|
407
|
-
class="w-full px-3 py-2 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 outline-none focus:border-violet-500/40 font-mono"
|
|
408
|
-
onkeydown={(e) => e.key === 'Enter' && handleAddRemote()}
|
|
409
|
-
autofocus
|
|
410
|
-
/>
|
|
411
|
-
<div class="flex gap-2">
|
|
412
|
-
<button
|
|
413
|
-
type="button"
|
|
414
|
-
class="flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors cursor-pointer border-none
|
|
415
|
-
{newRemoteName.trim() && newRemoteUrl.trim()
|
|
416
|
-
? 'bg-violet-600 text-white hover:bg-violet-700'
|
|
417
|
-
: 'bg-slate-200 dark:bg-slate-700 text-slate-400 dark:text-slate-500 cursor-not-allowed'}"
|
|
418
|
-
onclick={handleAddRemote}
|
|
419
|
-
disabled={!newRemoteName.trim() || !newRemoteUrl.trim()}
|
|
420
|
-
>
|
|
421
|
-
Connect
|
|
422
|
-
</button>
|
|
332
|
+
{:else}
|
|
333
|
+
{#each remotes as remote (remote.name)}
|
|
334
|
+
{@const remoteBranches = filteredRemote.filter(b => b.name.startsWith(remote.name + '/'))}
|
|
335
|
+
{#if !searchQuery || remoteBranches.length > 0}
|
|
336
|
+
<div class="space-y-1">
|
|
337
|
+
<!-- Remote group header -->
|
|
338
|
+
<div class="group flex items-center gap-2 px-2 py-1.5 rounded-lg">
|
|
339
|
+
<Icon name="lucide:server" class="w-3.5 h-3.5 text-slate-400 shrink-0" />
|
|
340
|
+
<span class="text-xs font-semibold text-slate-600 dark:text-slate-300">{remote.name}</span>
|
|
341
|
+
<span class="text-xs text-slate-400 dark:text-slate-500 font-mono truncate flex-1">{remote.fetchUrl}</span>
|
|
423
342
|
<button
|
|
424
343
|
type="button"
|
|
425
|
-
class="
|
|
426
|
-
onclick={() =>
|
|
344
|
+
class="flex items-center justify-center w-6 h-6 rounded-md text-slate-400 hover:bg-red-500/10 hover:text-red-500 transition-colors cursor-pointer bg-transparent border-none shrink-0 opacity-0 group-hover:opacity-100"
|
|
345
|
+
onclick={() => handleRemoveRemote(remote.name)}
|
|
346
|
+
title="Disconnect remote"
|
|
427
347
|
>
|
|
428
|
-
|
|
348
|
+
<Icon name="lucide:unlink" class="w-3.5 h-3.5" />
|
|
429
349
|
</button>
|
|
430
350
|
</div>
|
|
351
|
+
|
|
352
|
+
<!-- Branches under this remote -->
|
|
353
|
+
{#if remoteBranches.length > 0}
|
|
354
|
+
<div class="ml-5 space-y-1">
|
|
355
|
+
{#each remoteBranches as branch (branch.name)}
|
|
356
|
+
<div class="group flex items-center gap-2.5 px-3 py-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800/50 transition-colors border border-slate-200 dark:border-slate-700">
|
|
357
|
+
<Icon name="lucide:git-branch" class="w-3.5 h-3.5 text-slate-400 shrink-0" />
|
|
358
|
+
<span class="text-sm text-slate-900 dark:text-slate-100 truncate flex-1">
|
|
359
|
+
{branch.name.substring(remote.name.length + 1)}
|
|
360
|
+
</span>
|
|
361
|
+
<button
|
|
362
|
+
type="button"
|
|
363
|
+
class="flex items-center justify-center w-7 h-7 rounded-lg text-slate-400 hover:bg-violet-500/10 hover:text-violet-600 transition-colors cursor-pointer bg-transparent border-none shrink-0 opacity-0 group-hover:opacity-100"
|
|
364
|
+
onclick={() => handleSwitchRemote(branch.name)}
|
|
365
|
+
title="Checkout remote branch locally"
|
|
366
|
+
>
|
|
367
|
+
<Icon name="lucide:arrow-right" class="w-4 h-4" />
|
|
368
|
+
</button>
|
|
369
|
+
</div>
|
|
370
|
+
{/each}
|
|
371
|
+
</div>
|
|
372
|
+
{:else if !searchQuery}
|
|
373
|
+
<p class="ml-7 text-xs text-slate-400 dark:text-slate-500 py-1">No branches — try Fetch</p>
|
|
374
|
+
{/if}
|
|
431
375
|
</div>
|
|
432
|
-
{
|
|
376
|
+
{/if}
|
|
377
|
+
{/each}
|
|
378
|
+
{/if}
|
|
379
|
+
{/if}
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<!-- Add remote (only shown in Remote tab) -->
|
|
383
|
+
{#if activeTab === 'remote'}
|
|
384
|
+
<div class="mt-3 pt-3 border-t border-slate-200 dark:border-slate-700">
|
|
385
|
+
{#if showAddRemoteForm}
|
|
386
|
+
<div class="p-3 bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-lg space-y-2">
|
|
387
|
+
<input
|
|
388
|
+
type="text"
|
|
389
|
+
bind:value={newRemoteName}
|
|
390
|
+
placeholder="Name (e.g. origin)"
|
|
391
|
+
class="w-full px-3 py-2 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 outline-none focus:border-violet-500/40"
|
|
392
|
+
/>
|
|
393
|
+
<input
|
|
394
|
+
type="text"
|
|
395
|
+
bind:value={newRemoteUrl}
|
|
396
|
+
placeholder="URL (e.g. https://github.com/user/repo.git)"
|
|
397
|
+
class="w-full px-3 py-2 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 outline-none focus:border-violet-500/40 font-mono"
|
|
398
|
+
onkeydown={(e) => e.key === 'Enter' && handleAddRemote()}
|
|
399
|
+
autofocus
|
|
400
|
+
/>
|
|
401
|
+
<div class="flex gap-2">
|
|
433
402
|
<button
|
|
434
403
|
type="button"
|
|
435
|
-
class="flex
|
|
436
|
-
|
|
404
|
+
class="flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors cursor-pointer border-none
|
|
405
|
+
{newRemoteName.trim() && newRemoteUrl.trim()
|
|
406
|
+
? 'bg-violet-600 text-white hover:bg-violet-700'
|
|
407
|
+
: 'bg-slate-200 dark:bg-slate-700 text-slate-400 dark:text-slate-500 cursor-not-allowed'}"
|
|
408
|
+
onclick={handleAddRemote}
|
|
409
|
+
disabled={!newRemoteName.trim() || !newRemoteUrl.trim()}
|
|
437
410
|
>
|
|
438
|
-
|
|
439
|
-
<span>Add Remote Server</span>
|
|
411
|
+
Connect
|
|
440
412
|
</button>
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
413
|
+
<button
|
|
414
|
+
type="button"
|
|
415
|
+
class="px-3 py-2 text-sm font-medium bg-transparent border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors cursor-pointer"
|
|
416
|
+
onclick={() => { showAddRemoteForm = false; newRemoteName = 'origin'; newRemoteUrl = ''; }}
|
|
417
|
+
>
|
|
418
|
+
Cancel
|
|
419
|
+
</button>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
{:else}
|
|
423
|
+
<button
|
|
424
|
+
type="button"
|
|
425
|
+
class="flex items-center gap-2 w-full px-3 py-2 text-xs text-slate-500 hover:text-violet-600 hover:bg-violet-500/5 rounded-lg transition-colors cursor-pointer bg-transparent border-none"
|
|
426
|
+
onclick={() => showAddRemoteForm = true}
|
|
427
|
+
>
|
|
428
|
+
<Icon name="lucide:plus" class="w-3.5 h-3.5" />
|
|
429
|
+
<span>Add Remote</span>
|
|
430
|
+
</button>
|
|
431
|
+
{/if}
|
|
432
|
+
</div>
|
|
433
|
+
{/if}
|
|
446
434
|
{/snippet}
|
|
447
435
|
</Modal>
|
|
448
436
|
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
2
3
|
import Icon from '$frontend/components/common/display/Icon.svelte';
|
|
4
|
+
import { settings } from '$frontend/stores/features/settings.svelte';
|
|
5
|
+
import { projectState } from '$frontend/stores/core/projects.svelte';
|
|
6
|
+
import { showError } from '$frontend/stores/ui/notification.svelte';
|
|
7
|
+
import ws from '$frontend/utils/ws';
|
|
3
8
|
|
|
4
9
|
interface Props {
|
|
5
10
|
stagedCount: number;
|
|
@@ -11,6 +16,7 @@
|
|
|
11
16
|
|
|
12
17
|
let commitMessage = $state('');
|
|
13
18
|
let textareaEl = $state<HTMLTextAreaElement | null>(null);
|
|
19
|
+
let isGenerating = $state(false);
|
|
14
20
|
|
|
15
21
|
function handleCommit() {
|
|
16
22
|
if (!commitMessage.trim() || stagedCount === 0) return;
|
|
@@ -42,21 +48,65 @@
|
|
|
42
48
|
function handleInput() {
|
|
43
49
|
autoResize();
|
|
44
50
|
}
|
|
51
|
+
|
|
52
|
+
async function generateCommitMessage() {
|
|
53
|
+
const projectId = projectState.currentProject?.id;
|
|
54
|
+
if (!projectId || stagedCount === 0 || isGenerating) return;
|
|
55
|
+
|
|
56
|
+
isGenerating = true;
|
|
57
|
+
try {
|
|
58
|
+
const { useCustomModel, engine, model, format } = settings.commitGenerator;
|
|
59
|
+
const resolvedEngine = useCustomModel ? engine : settings.selectedEngine;
|
|
60
|
+
const resolvedModel = useCustomModel ? model : settings.selectedModel;
|
|
61
|
+
const result = await ws.http('git:generate-commit-message', {
|
|
62
|
+
projectId,
|
|
63
|
+
engine: resolvedEngine,
|
|
64
|
+
model: resolvedModel,
|
|
65
|
+
format
|
|
66
|
+
});
|
|
67
|
+
commitMessage = result.message;
|
|
68
|
+
await tick();
|
|
69
|
+
autoResize();
|
|
70
|
+
} catch (err) {
|
|
71
|
+
showError('Generate Failed', err instanceof Error ? err.message : 'Failed to generate commit message');
|
|
72
|
+
} finally {
|
|
73
|
+
isGenerating = false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
45
76
|
</script>
|
|
46
77
|
|
|
47
78
|
<div class="px-2 py-2">
|
|
48
79
|
<div class="flex flex-col gap-1.5">
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
80
|
+
<div class="relative">
|
|
81
|
+
<textarea
|
|
82
|
+
bind:this={textareaEl}
|
|
83
|
+
bind:value={commitMessage}
|
|
84
|
+
placeholder="Commit message..."
|
|
85
|
+
class="w-full px-2.5 py-2 pr-8 text-sm bg-white dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 rounded-md text-slate-900 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-600 resize-none outline-none focus:ring-1 focus:ring-violet-500 transition-colors"
|
|
86
|
+
rows="1"
|
|
87
|
+
style="overflow-y: hidden;"
|
|
88
|
+
onkeydown={handleKeydown}
|
|
89
|
+
oninput={handleInput}
|
|
90
|
+
disabled={isCommitting || isGenerating}
|
|
91
|
+
></textarea>
|
|
92
|
+
<!-- AI generate button -->
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
class="absolute top-1.5 right-1.5 flex items-center justify-center w-6 h-6 rounded transition-all duration-150
|
|
96
|
+
{stagedCount > 0 && !isGenerating && !isCommitting
|
|
97
|
+
? 'text-slate-400 hover:text-violet-500 hover:bg-violet-500/10 cursor-pointer'
|
|
98
|
+
: 'text-slate-300 dark:text-slate-700 cursor-not-allowed'}"
|
|
99
|
+
onclick={generateCommitMessage}
|
|
100
|
+
disabled={stagedCount === 0 || isGenerating || isCommitting}
|
|
101
|
+
title="Generate commit message with AI"
|
|
102
|
+
>
|
|
103
|
+
{#if isGenerating}
|
|
104
|
+
<div class="w-3.5 h-3.5 border-2 border-violet-400/30 border-t-violet-500 rounded-full animate-spin"></div>
|
|
105
|
+
{:else}
|
|
106
|
+
<Icon name="lucide:sparkles" class="w-3.5 h-3.5" />
|
|
107
|
+
{/if}
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
60
110
|
<button
|
|
61
111
|
type="button"
|
|
62
112
|
class="flex items-center justify-center gap-1.5 w-full py-1.5 px-3 rounded-md text-xs font-medium transition-all duration-150
|
|
@@ -219,7 +219,7 @@
|
|
|
219
219
|
<!-- Content Area -->
|
|
220
220
|
<main class="flex-1 flex flex-col min-w-0 overflow-hidden">
|
|
221
221
|
<div class="flex-1 overflow-y-auto p-4 md:p-5">
|
|
222
|
-
{#if activeSection === '
|
|
222
|
+
{#if activeSection === 'models'}
|
|
223
223
|
<div in:fly={{ x: 20, duration: 200 }}>
|
|
224
224
|
<ModelSettings />
|
|
225
225
|
</div>
|
|
@@ -383,9 +383,9 @@
|
|
|
383
383
|
|
|
384
384
|
<div class="space-y-6">
|
|
385
385
|
<!-- Header -->
|
|
386
|
-
<h3 class="text-base font-bold text-slate-900 dark:text-slate-100 mb-1.5">
|
|
386
|
+
<h3 class="text-base font-bold text-slate-900 dark:text-slate-100 mb-1.5">Engines</h3>
|
|
387
387
|
<p class="text-sm text-slate-600 dark:text-slate-500 mb-4">
|
|
388
|
-
Manage
|
|
388
|
+
Manage engine installations and accounts
|
|
389
389
|
</p>
|
|
390
390
|
|
|
391
391
|
<!-- Claude Code Card -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { systemSettings, updateSystemSettings } from '$frontend/stores/features/settings.svelte';
|
|
3
|
-
import { updateState, checkForUpdate, runUpdate } from '$frontend/stores/ui/update.svelte';
|
|
3
|
+
import { updateState, checkForUpdate, runUpdate, showRestartModal } from '$frontend/stores/ui/update.svelte';
|
|
4
4
|
import Icon from '../../common/display/Icon.svelte';
|
|
5
5
|
|
|
6
6
|
function toggleAutoUpdate() {
|
|
@@ -88,10 +88,17 @@
|
|
|
88
88
|
</div>
|
|
89
89
|
{/if}
|
|
90
90
|
|
|
91
|
-
{#if updateState.updateSuccess}
|
|
91
|
+
{#if updateState.updateSuccess || updateState.pendingRestart}
|
|
92
92
|
<div class="mt-3 flex items-center gap-2 px-3 py-2 bg-emerald-500/10 border border-emerald-500/20 rounded-lg">
|
|
93
93
|
<Icon name="lucide:circle-check" class="w-4 h-4 text-emerald-500 shrink-0" />
|
|
94
|
-
<span class="text-xs text-emerald-600 dark:text-emerald-400">Updated
|
|
94
|
+
<span class="text-xs text-emerald-600 dark:text-emerald-400">Updated to v{updateState.latestVersion} — restart required</span>
|
|
95
|
+
<button
|
|
96
|
+
type="button"
|
|
97
|
+
onclick={() => showRestartModal()}
|
|
98
|
+
class="text-xs font-semibold text-emerald-600 dark:text-emerald-400 underline underline-offset-2 hover:text-emerald-700 dark:hover:text-emerald-300 transition-colors"
|
|
99
|
+
>
|
|
100
|
+
How to restart
|
|
101
|
+
</button>
|
|
95
102
|
</div>
|
|
96
103
|
{/if}
|
|
97
104
|
</div>
|