mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-23 20:54:50 +03:00
feat: add 'generate branch name' to stack series header
This commit is contained in:
parent
ee3c1f4023
commit
1f0dca4646
@ -335,9 +335,10 @@ export class AIService {
|
||||
|
||||
const messageResult = await aiClient.evaluate(prompt, { onToken });
|
||||
if (isFailure(messageResult)) return messageResult;
|
||||
|
||||
const message = messageResult.value;
|
||||
|
||||
return ok(message.replaceAll(' ', '-').replaceAll('\n', '-'));
|
||||
return ok(message?.replaceAll(' ', '-').replaceAll('\n', '-') ?? '');
|
||||
}
|
||||
|
||||
async describePR({
|
||||
|
@ -20,7 +20,7 @@
|
||||
contextMenuEl?: ReturnType<typeof ContextMenu>;
|
||||
target?: HTMLElement;
|
||||
onCollapse: () => void;
|
||||
onGenerateBranchName: () => void;
|
||||
onGenerateBranchName?: () => void;
|
||||
}
|
||||
|
||||
let { contextMenuEl = $bindable(), target, onCollapse, onGenerateBranchName }: Props = $props();
|
||||
@ -113,14 +113,16 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<ContextMenuItem
|
||||
label="Generate branch name"
|
||||
on:click={() => {
|
||||
onGenerateBranchName();
|
||||
contextMenuEl?.close();
|
||||
}}
|
||||
disabled={!($aiGenEnabled && aiConfigurationValid) || branch.files?.length === 0}
|
||||
/>
|
||||
{#if !$stackingFeature}
|
||||
<ContextMenuItem
|
||||
label="Generate branch name"
|
||||
on:click={() => {
|
||||
onGenerateBranchName?.();
|
||||
contextMenuEl?.close();
|
||||
}}
|
||||
disabled={!($aiGenEnabled && aiConfigurationValid) || branch.files?.length === 0}
|
||||
/>
|
||||
{/if}
|
||||
</ContextMenuSection>
|
||||
|
||||
{#if !$stackingFeature}
|
||||
|
@ -2,14 +2,20 @@
|
||||
import BranchLabel from './BranchLabel.svelte';
|
||||
import StackingStatusIcon from './StackingStatusIcon.svelte';
|
||||
import { getColorFromBranchType } from './stackingUtils';
|
||||
import { PromptService } from '$lib/ai/promptService';
|
||||
import { AIService } from '$lib/ai/service';
|
||||
import { Project } from '$lib/backend/projects';
|
||||
import { BaseBranch } from '$lib/baseBranch/baseBranch';
|
||||
import StackingBranchHeaderContextMenu from '$lib/branch/StackingBranchHeaderContextMenu.svelte';
|
||||
import ContextMenu from '$lib/components/contextmenu/ContextMenu.svelte';
|
||||
import { projectAiGenEnabled } from '$lib/config/config';
|
||||
import { getGitHost } from '$lib/gitHost/interface/gitHost';
|
||||
import { getGitHostListingService } from '$lib/gitHost/interface/gitHostListingService';
|
||||
import { getGitHostPrService } from '$lib/gitHost/interface/gitHostPrService';
|
||||
import { showError } from '$lib/notifications/toasts';
|
||||
import PrDetailsModal from '$lib/pr/PrDetailsModal.svelte';
|
||||
import StackingPullRequestCard from '$lib/pr/StackingPullRequestCard.svelte';
|
||||
import { isFailure } from '$lib/result';
|
||||
import { slugify } from '$lib/utils/string';
|
||||
import { openExternalUrl } from '$lib/utils/url';
|
||||
import { BranchController } from '$lib/vbranches/branchController';
|
||||
@ -28,14 +34,19 @@
|
||||
|
||||
let descriptionVisible = $state(false);
|
||||
|
||||
const project = getContext(Project);
|
||||
const aiService = getContext(AIService);
|
||||
const promptService = getContext(PromptService);
|
||||
const branchStore = getContextStore(VirtualBranch);
|
||||
const branch = $derived($branchStore);
|
||||
|
||||
const aiGenEnabled = projectAiGenEnabled(project.id);
|
||||
const branchController = getContext(BranchController);
|
||||
const baseBranch = getContextStore(BaseBranch);
|
||||
const prService = getGitHostPrService();
|
||||
const gitHost = getGitHost();
|
||||
|
||||
const gitHostBranch = $derived(upstreamName ? $gitHost?.branch(upstreamName) : undefined);
|
||||
const branch = $derived($branchStore);
|
||||
|
||||
let contextMenu = $state<ReturnType<typeof ContextMenu>>();
|
||||
let prDetailsModal = $state<ReturnType<typeof PrDetailsModal>>();
|
||||
@ -83,6 +94,31 @@
|
||||
function addDescription() {
|
||||
descriptionVisible = true;
|
||||
}
|
||||
|
||||
async function generateBranchName() {
|
||||
if (!aiGenEnabled || !currentSeries) return;
|
||||
|
||||
const hunks = currentSeries.patches.flatMap((p) => p.files.flatMap((f) => f.hunks));
|
||||
|
||||
const prompt = promptService.selectedBranchPrompt(project.id);
|
||||
const messageResult = await aiService.summarizeBranch({
|
||||
hunks,
|
||||
branchTemplate: prompt
|
||||
});
|
||||
|
||||
if (isFailure(messageResult)) {
|
||||
console.error(messageResult.failure);
|
||||
showError('Failed to generate branch name', messageResult.failure);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const message = messageResult.value;
|
||||
|
||||
if (message && message !== currentSeries.name) {
|
||||
branchController.updateSeriesName(branch.id, currentSeries.name, slugify(message));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="branch-header">
|
||||
@ -127,6 +163,7 @@
|
||||
headName={name}
|
||||
seriesCount={branch.series?.length ?? 0}
|
||||
{addDescription}
|
||||
onGenerateBranchName={generateBranchName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { AIService } from '$lib/ai/service';
|
||||
import { Project } from '$lib/backend/projects';
|
||||
import ContextMenu from '$lib/components/contextmenu/ContextMenu.svelte';
|
||||
import ContextMenuItem from '$lib/components/contextmenu/ContextMenuItem.svelte';
|
||||
import ContextMenuSection from '$lib/components/contextmenu/ContextMenuSection.svelte';
|
||||
import { projectAiGenEnabled } from '$lib/config/config';
|
||||
import TextBox from '$lib/shared/TextBox.svelte';
|
||||
import { BranchController } from '$lib/vbranches/branchController';
|
||||
import { VirtualBranch } from '$lib/vbranches/types';
|
||||
@ -15,6 +18,7 @@
|
||||
headName: string;
|
||||
seriesCount: number;
|
||||
addDescription: () => void;
|
||||
onGenerateBranchName: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
@ -22,16 +26,29 @@
|
||||
target,
|
||||
seriesCount,
|
||||
headName,
|
||||
addDescription
|
||||
addDescription,
|
||||
onGenerateBranchName
|
||||
}: Props = $props();
|
||||
|
||||
const project = getContext(Project);
|
||||
const aiService = getContext(AIService);
|
||||
const branchStore = getContextStore(VirtualBranch);
|
||||
const branchController = getContext(BranchController);
|
||||
const aiGenEnabled = projectAiGenEnabled(project.id);
|
||||
|
||||
let deleteSeriesModal: Modal;
|
||||
let renameSeriesModal: Modal;
|
||||
let newHeadName: string = $state(headName);
|
||||
let isDeleting = $state(false);
|
||||
let aiConfigurationValid = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
setAIConfigurationValid();
|
||||
});
|
||||
|
||||
async function setAIConfigurationValid() {
|
||||
aiConfigurationValid = await aiService.validateConfiguration();
|
||||
}
|
||||
|
||||
const branch = $derived($branchStore);
|
||||
</script>
|
||||
@ -46,6 +63,14 @@
|
||||
contextMenuEl?.close();
|
||||
}}
|
||||
/>
|
||||
<ContextMenuItem
|
||||
label="Generate branch name"
|
||||
on:click={() => {
|
||||
onGenerateBranchName();
|
||||
contextMenuEl?.close();
|
||||
}}
|
||||
disabled={!($aiGenEnabled && aiConfigurationValid)}
|
||||
/>
|
||||
<ContextMenuItem
|
||||
label="Rename"
|
||||
on:click={async () => {
|
||||
|
@ -2,22 +2,17 @@
|
||||
import StackHeader from './StackHeader.svelte';
|
||||
import StackSeries from './StackSeries.svelte';
|
||||
import InfoMessage from '../shared/InfoMessage.svelte';
|
||||
import { PromptService } from '$lib/ai/promptService';
|
||||
import { AIService } from '$lib/ai/service';
|
||||
import laneNewSvg from '$lib/assets/empty-state/lane-new.svg?raw';
|
||||
import noChangesSvg from '$lib/assets/empty-state/lane-no-changes.svg?raw';
|
||||
import { Project } from '$lib/backend/projects';
|
||||
import Dropzones from '$lib/branch/Dropzones.svelte';
|
||||
import StackingNewStackCard from '$lib/branch/StackingNewStackCard.svelte';
|
||||
import CommitDialog from '$lib/commit/CommitDialog.svelte';
|
||||
import { projectAiGenEnabled } from '$lib/config/config';
|
||||
import { stackingFeatureMultipleSeries } from '$lib/config/uiFeatureFlags';
|
||||
import BranchFiles from '$lib/file/BranchFiles.svelte';
|
||||
import { getGitHostChecksMonitor } from '$lib/gitHost/interface/gitHostChecksMonitor';
|
||||
import { getGitHostListingService } from '$lib/gitHost/interface/gitHostListingService';
|
||||
import { getGitHostPrMonitor } from '$lib/gitHost/interface/gitHostPrMonitor';
|
||||
import { showError } from '$lib/notifications/toasts';
|
||||
import { isFailure } from '$lib/result';
|
||||
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
|
||||
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
||||
import Resizer from '$lib/shared/Resizer.svelte';
|
||||
@ -46,11 +41,6 @@
|
||||
|
||||
const branch = $derived($branchStore);
|
||||
|
||||
const aiGenEnabled = projectAiGenEnabled(project.id);
|
||||
|
||||
const aiService = getContext(AIService);
|
||||
const promptService = getContext(PromptService);
|
||||
|
||||
const userSettings = getContextStoreBySymbol<Settings>(SETTINGS);
|
||||
const defaultBranchWidthRem = persisted<number>(24, 'defaulBranchWidth' + project.id);
|
||||
const laneWidthKey = 'laneWidth_';
|
||||
@ -66,32 +56,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
async function generateBranchName() {
|
||||
if (!aiGenEnabled) return;
|
||||
|
||||
const hunks = branch.files.flatMap((f) => f.hunks);
|
||||
|
||||
const prompt = promptService.selectedBranchPrompt(project.id);
|
||||
const messageResult = await aiService.summarizeBranch({
|
||||
hunks,
|
||||
branchTemplate: prompt
|
||||
});
|
||||
|
||||
if (isFailure(messageResult)) {
|
||||
console.error(messageResult.failure);
|
||||
showError('Failed to generate branch name', messageResult.failure);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const message = messageResult.value;
|
||||
|
||||
if (message && message !== branch.name) {
|
||||
branch.name = message;
|
||||
branchController.updateBranchName(branch.id, branch.name);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
laneWidth = lscache.get(laneWidthKey + branch.id);
|
||||
});
|
||||
@ -141,11 +105,7 @@
|
||||
|
||||
{#if $isLaneCollapsed}
|
||||
<div class="collapsed-lane-container">
|
||||
<StackHeader
|
||||
uncommittedChanges={branch.files.length}
|
||||
onGenerateBranchName={generateBranchName}
|
||||
{isLaneCollapsed}
|
||||
/>
|
||||
<StackHeader uncommittedChanges={branch.files.length} {isLaneCollapsed} />
|
||||
<div class="collapsed-lane-divider" data-remove-from-draggable></div>
|
||||
</div>
|
||||
{:else}
|
||||
@ -165,11 +125,7 @@
|
||||
class="branch-card__contents"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<StackHeader
|
||||
{isLaneCollapsed}
|
||||
onGenerateBranchName={generateBranchName}
|
||||
stackPrs={stackPrs?.length ?? 0}
|
||||
/>
|
||||
<StackHeader {isLaneCollapsed} stackPrs={stackPrs?.length ?? 0} />
|
||||
<div class="card-stacking">
|
||||
{#if branch.files?.length > 0}
|
||||
<div class="branch-card__files card">
|
||||
|
@ -14,16 +14,10 @@
|
||||
interface Props {
|
||||
uncommittedChanges?: number;
|
||||
isLaneCollapsed: Persisted<boolean>;
|
||||
onGenerateBranchName: () => void;
|
||||
stackPrs?: number;
|
||||
}
|
||||
|
||||
const {
|
||||
uncommittedChanges = 0,
|
||||
isLaneCollapsed,
|
||||
onGenerateBranchName,
|
||||
stackPrs = 0
|
||||
}: Props = $props();
|
||||
const { uncommittedChanges = 0, isLaneCollapsed, stackPrs = 0 }: Props = $props();
|
||||
|
||||
const branchController = getContext(BranchController);
|
||||
const branchStore = getContextStore(VirtualBranch);
|
||||
@ -130,7 +124,6 @@
|
||||
bind:contextMenuEl={contextMenu}
|
||||
target={meatballButtonEl}
|
||||
onCollapse={collapseLane}
|
||||
{onGenerateBranchName}
|
||||
/>
|
||||
</div>
|
||||
<div class="header__info-row">
|
||||
|
Loading…
Reference in New Issue
Block a user