feat: add 'generate branch name' to stack series header

This commit is contained in:
ndom91 2024-10-15 17:33:27 +02:00 committed by Nico Domino
parent ee3c1f4023
commit 1f0dca4646
6 changed files with 80 additions and 66 deletions

View File

@ -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({

View File

@ -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}

View File

@ -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>

View File

@ -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 () => {

View File

@ -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">

View File

@ -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">