diff --git a/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialog.test.ts b/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialog.test.ts index eacdd3e5994..f17eaea0845 100644 --- a/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialog.test.ts +++ b/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialog.test.ts @@ -1,11 +1,43 @@ -import { describe, expect, it } from 'vitest' +import React, { type ReactNode } from 'react' +import { renderToStaticMarkup } from 'react-dom/server' +import { describe, expect, it, vi } from 'vitest' import { buildCommitMessageGenerationParams } from './SourceControlTextGenerationDialog' -import { getDefaultSourceControlTextGenerationSaveTargetKey } from './SourceControlTextGenerationDialogForm' +import { + getDefaultSourceControlTextGenerationSaveTargetKey, + SourceControlTextGenerationDialogForm +} from './SourceControlTextGenerationDialogForm' import { applyCommitMessageGenerationDefaults, applySourceControlTextGenerationDefaults } from './SourceControlTextGenerationDefaults' +vi.mock('../source-control/SourceControlActionVariableChips', () => ({ + SourceControlActionVariableChips: ({ + variablePreviews + }: { + variablePreviews?: Partial> + }) => + React.createElement('div', { + 'data-variable-previews': JSON.stringify(variablePreviews ?? {}) + }) +})) + +vi.mock('@/components/ui/dialog', () => ({ + DialogFooter: ({ children }: { children?: ReactNode }) => + React.createElement('div', null, children) +})) + +vi.mock('@/components/ui/select', () => ({ + Select: ({ children }: { children?: ReactNode }) => React.createElement('div', null, children), + SelectContent: ({ children }: { children?: ReactNode }) => + React.createElement('div', null, children), + SelectItem: ({ children }: { children?: ReactNode }) => + React.createElement('div', null, children), + SelectTrigger: ({ children }: { children?: ReactNode }) => + React.createElement('button', null, children), + SelectValue: () => React.createElement('span') +})) + describe('buildCommitMessageGenerationParams', () => { it('defaults saved text-generation recipes to the global target when repo and global are available', () => { expect( @@ -24,6 +56,29 @@ describe('buildCommitMessageGenerationParams', () => { ).toBe('global') }) + it('passes the base prompt preview to variable chips in text generation dialogs', () => { + const markup = renderToStaticMarkup( + React.createElement(SourceControlTextGenerationDialogForm, { + actionId: 'commitMessage', + generateLabel: 'Generate', + settings: null, + repo: null, + baseParams: { + agentId: 'codex', + model: 'gpt-5.4-mini', + commandInputTemplate: '{basePrompt}' + }, + basePromptPreview: 'You are generating a single git commit message.', + saveTargets: [], + onGenerate: () => {}, + onOpenChange: () => {}, + onSaveDefaults: () => {} + }) + ) + + expect(markup).toContain('You are generating a single git commit message.') + }) + it('preserves the resolved model and thinking level for the selected agent', () => { expect( buildCommitMessageGenerationParams({ diff --git a/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialog.tsx b/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialog.tsx index dc33a97d24a..7ef7ab9c82f 100644 --- a/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialog.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialog.tsx @@ -14,6 +14,9 @@ import { import type { SourceControlTextActionId } from '../../../../shared/source-control-ai-actions' import type { GlobalSettings, Repo } from '../../../../shared/types' import type { SourceControlAiWriteTarget } from '../../../../shared/source-control-ai-recipe-save' +import { buildBranchNamePrompt } from '../../../../shared/branch-name-from-work' +import { buildCommitMessagePrompt } from '../../../../shared/commit-message-generation' +import { buildPullRequestFieldsPrompt } from '../../../../shared/pull-request-generation' import { SourceControlTextGenerationDialogForm, type SourceControlTextGenerationSaveTarget @@ -42,6 +45,40 @@ type SourceControlTextGenerationDialogProps = SourceControlTextGenerationBaseDia generateLabel: string } +function buildBasePromptPreview(actionId: SourceControlTextActionId): string { + switch (actionId) { + case 'commitMessage': + return buildCommitMessagePrompt( + { + branch: 'feature/example', + stagedSummary: 'M src/example.ts', + stagedPatch: 'diff --git a/src/example.ts b/src/example.ts\n+addSourceControlAiPreview()' + }, + '' + ) + case 'pullRequest': + return buildPullRequestFieldsPrompt( + { + branch: 'feature/example', + base: 'main', + branchChangedByPreparation: false, + currentTitle: 'Draft title', + currentBody: 'Draft description', + currentDraft: false, + commitSummary: 'a1b2c3d Add Source Control AI prompt previews', + changeSummary: 'src/example.ts | 12 ++++++++++--', + patch: 'diff --git a/src/example.ts b/src/example.ts\n+addSourceControlAiPreview()' + }, + '' + ) + case 'branchName': + return buildBranchNamePrompt({ + firstPrompt: 'Add source-control AI prompt previews', + assistantMessage: 'I will update the generation dialog variable chip preview.' + }) + } +} + export function SourceControlTextGenerationDialog({ actionId, title, @@ -126,6 +163,7 @@ export function SourceControlTextGenerationDialog({ settings={settings} repo={repo ?? null} baseParams={baseParams} + basePromptPreview={buildBasePromptPreview(actionId)} saveTargets={saveTargets} onGenerate={onGenerate} onOpenChange={onOpenChange} diff --git a/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialogForm.tsx b/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialogForm.tsx index d003bc79e3a..7315ba66207 100644 --- a/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialogForm.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialogForm.tsx @@ -45,6 +45,7 @@ type SourceControlTextGenerationDialogFormProps = { settings: GlobalSettings | null repo: Pick | null baseParams: ResolvedSourceControlAiGenerationParams | null + basePromptPreview?: string saveTargets: SourceControlTextGenerationSaveTarget[] onGenerate: (params: ResolvedSourceControlAiGenerationParams) => void onOpenChange: (open: boolean) => void @@ -78,6 +79,7 @@ export function SourceControlTextGenerationDialogForm({ settings, repo, baseParams, + basePromptPreview, saveTargets, onGenerate, onOpenChange, @@ -285,6 +287,7 @@ export function SourceControlTextGenerationDialogForm({ /> { const separator = commandTemplate.endsWith('\n') || commandTemplate.length === 0 ? '' : ' ' diff --git a/src/renderer/src/components/source-control/SourceControlActionVariableChips.test.tsx b/src/renderer/src/components/source-control/SourceControlActionVariableChips.test.tsx new file mode 100644 index 00000000000..13e13eb50bf --- /dev/null +++ b/src/renderer/src/components/source-control/SourceControlActionVariableChips.test.tsx @@ -0,0 +1,35 @@ +import type { ReactNode } from 'react' +import { renderToStaticMarkup } from 'react-dom/server' +import { describe, expect, it, vi } from 'vitest' +import { SourceControlActionVariableChips } from './SourceControlActionVariableChips' + +vi.mock('../ui/hover-card', () => ({ + HoverCard: ({ children }: { children: ReactNode }) => ( +
{children}
+ ), + HoverCardContent: ({ children, className }: { children: ReactNode; className?: string }) => ( +
+ {children} +
+ ), + HoverCardTrigger: ({ children }: { children: ReactNode }) => ( +
{children}
+ ) +})) + +describe('SourceControlActionVariableChips', () => { + it('renders variable details in a scrollable hover card', () => { + const markup = renderToStaticMarkup( + {}} + /> + ) + + expect(markup).toContain('data-slot="hover-card-content"') + expect(markup).toContain('scrollbar-sleek') + expect(markup).toContain('overflow-y-auto') + expect(markup).toContain('Generate a commit message.') + }) +}) diff --git a/src/renderer/src/components/source-control/SourceControlActionVariableChips.tsx b/src/renderer/src/components/source-control/SourceControlActionVariableChips.tsx index 93ea4ad673e..b6ee822ed37 100644 --- a/src/renderer/src/components/source-control/SourceControlActionVariableChips.tsx +++ b/src/renderer/src/components/source-control/SourceControlActionVariableChips.tsx @@ -6,7 +6,7 @@ import { type SourceControlActionId } from '../../../../shared/source-control-ai-actions' import { Button } from '../ui/button' -import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip' +import { HoverCard, HoverCardContent, HoverCardTrigger } from '../ui/hover-card' import { translate } from '@/i18n/i18n' type SourceControlActionVariableChipsProps = { @@ -28,7 +28,7 @@ function hasVariablePreview( ) } -function SourceControlVariableTooltip({ +function SourceControlVariableDetails({ variable, preview }: { @@ -38,17 +38,25 @@ function SourceControlVariableTooltip({ if (preview !== undefined) { if (variable === 'basePrompt') { return ( -
-          {preview || translate("auto.components.source.control.SourceControlActionVariableChips.4bf6d88039", "(empty)")}
+        
+          {preview ||
+            translate(
+              'auto.components.source.control.SourceControlActionVariableChips.4bf6d88039',
+              '(empty)'
+            )}
         
) } return (
-
{`{${variable}}`}
-
-          {preview || translate("auto.components.source.control.SourceControlActionVariableChips.4bf6d88039", "(empty)")}
+        
{`{${variable}}`}
+
+          {preview ||
+            translate(
+              'auto.components.source.control.SourceControlActionVariableChips.4bf6d88039',
+              '(empty)'
+            )}
         
) @@ -59,12 +67,16 @@ function SourceControlVariableTooltip({
{`{${variable}}`}
-
{info.description}
+
{info.description}
-
- {translate("auto.components.source.control.SourceControlActionVariableChips.6b921a0ac2", "Example")}
-
+        
+ {translate( + 'auto.components.source.control.SourceControlActionVariableChips.6b921a0ac2', + 'Example' + )} +
+
           {info.example}
         
@@ -82,14 +94,18 @@ export function SourceControlActionVariableChips({
- {translate("auto.components.source.control.SourceControlActionVariableChips.1b77798d5f", "Variables")} + {translate( + 'auto.components.source.control.SourceControlActionVariableChips.1b77798d5f', + 'Variables' + )} + {SOURCE_CONTROL_ACTION_VARIABLES[actionId].map((variable) => { const preview = hasVariablePreview(variablePreviews, variable) ? variablePreviews?.[variable] : undefined return ( - - + +