Remove local on-device model support and related IPC commands, consolidating commit-generation to cloud providers and simplifying the AI crate surface. Local-specific types, generation profiles, caching, and the local prompt builder were removed while message sanitization and diff-echo detection were preserved. Also harden repository handling and runtime: unborn HEADs are handled gracefully so empty repos still report files, Git LFS sync is skipped for repositories without commits, and tokio runtime features were enabled. - Remove local model engine, load/status commands, and local profile code - Handle unborn HEAD and skip LFS sync for repos without commits - Enable tokio runtime features and route AI generation to cloud only
160 lines
5.2 KiB
Svelte
160 lines
5.2 KiB
Svelte
<script lang="ts">
|
|
import { Check, GitCommitHorizontal, LoaderCircle, RotateCcw, Settings, ShieldCheck, Sparkles } from "@lucide/svelte";
|
|
|
|
interface Props {
|
|
commitMessage: string;
|
|
canCommit: boolean;
|
|
commitBlockReason: string;
|
|
hasRepository: boolean;
|
|
isBusy: boolean;
|
|
operation: string;
|
|
stagedCount: number;
|
|
commitAiGenerating: boolean;
|
|
commitAiReviewing: boolean;
|
|
commitAiSplitting: boolean;
|
|
canAmend: boolean;
|
|
amendMode: boolean;
|
|
onCommit: () => void;
|
|
onCommitMessageChange: (msg: string) => void;
|
|
onGenerateCommitMessage: () => void;
|
|
onReviewStaged: () => void;
|
|
onSplitStaged: () => void;
|
|
onOpenAiSettings: () => void;
|
|
onToggleAmend: (checked: boolean) => void;
|
|
onUndoLastCommit: () => void;
|
|
}
|
|
|
|
let {
|
|
commitMessage = "",
|
|
canCommit = false,
|
|
commitBlockReason = "",
|
|
hasRepository = false,
|
|
isBusy = false,
|
|
operation = "",
|
|
stagedCount = 0,
|
|
commitAiGenerating = false,
|
|
commitAiReviewing = false,
|
|
commitAiSplitting = false,
|
|
canAmend = false,
|
|
amendMode = false,
|
|
onCommit = () => {},
|
|
onCommitMessageChange = () => {},
|
|
onGenerateCommitMessage = () => {},
|
|
onReviewStaged = () => {},
|
|
onSplitStaged = () => {},
|
|
onOpenAiSettings = () => {},
|
|
onToggleAmend = () => {},
|
|
onUndoLastCommit = () => {},
|
|
}: Props = $props();
|
|
|
|
function handleSubmit(event: SubmitEvent) {
|
|
event.preventDefault();
|
|
onCommit();
|
|
}
|
|
|
|
function aiButtonTitle(staged: number): string {
|
|
if (staged === 0) return "Stage changes first";
|
|
return "Generate commit message with AI from the staged diff";
|
|
}
|
|
|
|
let canGenerate = $derived(
|
|
hasRepository &&
|
|
!isBusy &&
|
|
!commitAiGenerating &&
|
|
!commitAiReviewing &&
|
|
stagedCount > 0,
|
|
);
|
|
let canReview = $derived(canGenerate);
|
|
let canSplit = $derived(canReview && stagedCount > 1 && !commitAiSplitting);
|
|
</script>
|
|
|
|
<section class="panel commit-panel" aria-label="Commit">
|
|
<div class="section-head">
|
|
<div>
|
|
<span class="eyebrow">Create revision</span>
|
|
<h2 class="mt-0.5 text-ink text-base font-bold leading-tight">Commit</h2>
|
|
</div>
|
|
<div class="commit-head-actions">
|
|
<span class="pill pill-count">{stagedCount} staged</span>
|
|
<button
|
|
class="commit-review-button"
|
|
type="button"
|
|
onclick={onSplitStaged}
|
|
disabled={!canSplit}
|
|
title="Suggest logical commits for the staged files"
|
|
>
|
|
{#if commitAiSplitting}<LoaderCircle class="spin" size={14} aria-hidden="true" />{:else}<GitCommitHorizontal size={14} aria-hidden="true" />{/if}
|
|
Split
|
|
</button>
|
|
<button
|
|
class="commit-review-button"
|
|
type="button"
|
|
onclick={onReviewStaged}
|
|
disabled={!canReview}
|
|
title={stagedCount === 0 ? "Stage changes first" : "Review staged changes for bugs and risks"}
|
|
>
|
|
{#if commitAiReviewing}<LoaderCircle class="spin" size={14} aria-hidden="true" />{:else}<ShieldCheck size={14} aria-hidden="true" />{/if}
|
|
Review
|
|
</button>
|
|
<button
|
|
class="commit-generate-button"
|
|
type="button"
|
|
onclick={onGenerateCommitMessage}
|
|
disabled={!canGenerate}
|
|
title={aiButtonTitle(stagedCount)}
|
|
>
|
|
{#if commitAiGenerating}<LoaderCircle class="spin" size={14} aria-hidden="true" />{:else}<Sparkles size={14} aria-hidden="true" />{/if}
|
|
Generate
|
|
</button>
|
|
<button class="commit-settings-button" type="button" onclick={onOpenAiSettings} disabled={isBusy} title="AI settings" aria-label="AI settings">
|
|
<Settings size={14} aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<form class="commit-form" onsubmit={handleSubmit}>
|
|
<textarea
|
|
value={commitMessage}
|
|
oninput={(e) => onCommitMessageChange((e.target as HTMLTextAreaElement).value)}
|
|
placeholder="Commit message"
|
|
disabled={!hasRepository || isBusy}
|
|
></textarea>
|
|
{#if commitBlockReason}
|
|
<p class="commit-block-reason">{commitBlockReason}</p>
|
|
{/if}
|
|
{#if canAmend}
|
|
<div class="commit-amend-row">
|
|
<label class="commit-amend-toggle">
|
|
<input
|
|
type="checkbox"
|
|
checked={amendMode}
|
|
disabled={isBusy}
|
|
onchange={(e) => onToggleAmend((e.target as HTMLInputElement).checked)}
|
|
/>
|
|
Amend last commit
|
|
</label>
|
|
<button
|
|
class="btn-secondary commit-undo-button"
|
|
type="button"
|
|
onclick={onUndoLastCommit}
|
|
disabled={isBusy}
|
|
title="Undo the last commit — its changes come back as uncommitted changes (not pushed yet, so safe)"
|
|
>
|
|
<RotateCcw size={13} aria-hidden="true" />
|
|
Undo last commit
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
<div class="commit-actions-row flex-shrink-0">
|
|
<button class="btn-primary flex-1" type="submit" disabled={!canCommit} title={commitBlockReason || (amendMode ? "Amend the last commit" : "Commit staged changes")}>
|
|
{#if operation === "Committing" || operation === "Amending"}
|
|
<LoaderCircle class="spin" size={16} aria-hidden="true" />
|
|
{:else}
|
|
<Check size={16} aria-hidden="true" />
|
|
{/if}
|
|
{amendMode ? "Amend" : "Commit"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</section>
|