mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
Merge remote-tracking branch 'upstream/v0.10.0rc' into feature/codex-cli
This commit is contained in:
@@ -10,14 +10,21 @@ import { getErrorMessage, logError } from '../common.js';
|
|||||||
export function createUpdateHandler(featureLoader: FeatureLoader) {
|
export function createUpdateHandler(featureLoader: FeatureLoader) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { projectPath, featureId, updates, descriptionHistorySource, enhancementMode } =
|
const {
|
||||||
req.body as {
|
projectPath,
|
||||||
projectPath: string;
|
featureId,
|
||||||
featureId: string;
|
updates,
|
||||||
updates: Partial<Feature>;
|
descriptionHistorySource,
|
||||||
descriptionHistorySource?: 'enhance' | 'edit';
|
enhancementMode,
|
||||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer';
|
preEnhancementDescription,
|
||||||
};
|
} = req.body as {
|
||||||
|
projectPath: string;
|
||||||
|
featureId: string;
|
||||||
|
updates: Partial<Feature>;
|
||||||
|
descriptionHistorySource?: 'enhance' | 'edit';
|
||||||
|
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer';
|
||||||
|
preEnhancementDescription?: string;
|
||||||
|
};
|
||||||
|
|
||||||
if (!projectPath || !featureId || !updates) {
|
if (!projectPath || !featureId || !updates) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
@@ -32,7 +39,8 @@ export function createUpdateHandler(featureLoader: FeatureLoader) {
|
|||||||
featureId,
|
featureId,
|
||||||
updates,
|
updates,
|
||||||
descriptionHistorySource,
|
descriptionHistorySource,
|
||||||
enhancementMode
|
enhancementMode,
|
||||||
|
preEnhancementDescription
|
||||||
);
|
);
|
||||||
res.json({ success: true, feature: updated });
|
res.json({ success: true, feature: updated });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -308,13 +308,15 @@ export class FeatureLoader {
|
|||||||
* @param updates - Partial feature updates
|
* @param updates - Partial feature updates
|
||||||
* @param descriptionHistorySource - Source of description change ('enhance' or 'edit')
|
* @param descriptionHistorySource - Source of description change ('enhance' or 'edit')
|
||||||
* @param enhancementMode - Enhancement mode if source is 'enhance'
|
* @param enhancementMode - Enhancement mode if source is 'enhance'
|
||||||
|
* @param preEnhancementDescription - Description before enhancement (for restoring original)
|
||||||
*/
|
*/
|
||||||
async update(
|
async update(
|
||||||
projectPath: string,
|
projectPath: string,
|
||||||
featureId: string,
|
featureId: string,
|
||||||
updates: Partial<Feature>,
|
updates: Partial<Feature>,
|
||||||
descriptionHistorySource?: 'enhance' | 'edit',
|
descriptionHistorySource?: 'enhance' | 'edit',
|
||||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'
|
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer',
|
||||||
|
preEnhancementDescription?: string
|
||||||
): Promise<Feature> {
|
): Promise<Feature> {
|
||||||
const feature = await this.get(projectPath, featureId);
|
const feature = await this.get(projectPath, featureId);
|
||||||
if (!feature) {
|
if (!feature) {
|
||||||
@@ -338,9 +340,31 @@ export class FeatureLoader {
|
|||||||
updates.description !== feature.description &&
|
updates.description !== feature.description &&
|
||||||
updates.description.trim()
|
updates.description.trim()
|
||||||
) {
|
) {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
|
||||||
|
// If this is an enhancement and we have the pre-enhancement description,
|
||||||
|
// add the original text to history first (so user can restore to it)
|
||||||
|
if (
|
||||||
|
descriptionHistorySource === 'enhance' &&
|
||||||
|
preEnhancementDescription &&
|
||||||
|
preEnhancementDescription.trim()
|
||||||
|
) {
|
||||||
|
// Check if this pre-enhancement text is different from the last history entry
|
||||||
|
const lastEntry = updatedHistory[updatedHistory.length - 1];
|
||||||
|
if (!lastEntry || lastEntry.description !== preEnhancementDescription) {
|
||||||
|
const preEnhanceEntry: DescriptionHistoryEntry = {
|
||||||
|
description: preEnhancementDescription,
|
||||||
|
timestamp,
|
||||||
|
source: updatedHistory.length === 0 ? 'initial' : 'edit',
|
||||||
|
};
|
||||||
|
updatedHistory = [...updatedHistory, preEnhanceEntry];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new/enhanced description to history
|
||||||
const historyEntry: DescriptionHistoryEntry = {
|
const historyEntry: DescriptionHistoryEntry = {
|
||||||
description: updates.description,
|
description: updates.description,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp,
|
||||||
source: descriptionHistorySource || 'edit',
|
source: descriptionHistorySource || 'edit',
|
||||||
...(descriptionHistorySource === 'enhance' && enhancementMode ? { enhancementMode } : {}),
|
...(descriptionHistorySource === 'enhance' && enhancementMode ? { enhancementMode } : {}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -161,12 +161,14 @@ export function BoardView() {
|
|||||||
followUpPrompt,
|
followUpPrompt,
|
||||||
followUpImagePaths,
|
followUpImagePaths,
|
||||||
followUpPreviewMap,
|
followUpPreviewMap,
|
||||||
|
followUpPromptHistory,
|
||||||
setShowFollowUpDialog,
|
setShowFollowUpDialog,
|
||||||
setFollowUpFeature,
|
setFollowUpFeature,
|
||||||
setFollowUpPrompt,
|
setFollowUpPrompt,
|
||||||
setFollowUpImagePaths,
|
setFollowUpImagePaths,
|
||||||
setFollowUpPreviewMap,
|
setFollowUpPreviewMap,
|
||||||
handleFollowUpDialogChange,
|
handleFollowUpDialogChange,
|
||||||
|
addToPromptHistory,
|
||||||
} = useFollowUpState();
|
} = useFollowUpState();
|
||||||
|
|
||||||
// Selection mode hook for mass editing
|
// Selection mode hook for mass editing
|
||||||
@@ -1422,6 +1424,8 @@ export function BoardView() {
|
|||||||
onPreviewMapChange={setFollowUpPreviewMap}
|
onPreviewMapChange={setFollowUpPreviewMap}
|
||||||
onSend={handleSendFollowUp}
|
onSend={handleSendFollowUp}
|
||||||
isMaximized={isMaximized}
|
isMaximized={isMaximized}
|
||||||
|
promptHistory={followUpPromptHistory}
|
||||||
|
onHistoryAdd={addToPromptHistory}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Backlog Plan Dialog */}
|
{/* Backlog Plan Dialog */}
|
||||||
|
|||||||
@@ -21,11 +21,9 @@ import {
|
|||||||
FeatureTextFilePath as DescriptionTextFilePath,
|
FeatureTextFilePath as DescriptionTextFilePath,
|
||||||
ImagePreviewMap,
|
ImagePreviewMap,
|
||||||
} from '@/components/ui/description-image-dropzone';
|
} from '@/components/ui/description-image-dropzone';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
import { Play, Cpu, FolderKanban } from 'lucide-react';
|
||||||
import { Sparkles, ChevronDown, ChevronRight, Play, Cpu, FolderKanban } from 'lucide-react';
|
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
|
||||||
import { modelSupportsThinking } from '@/lib/utils';
|
import { modelSupportsThinking } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
useAppStore,
|
useAppStore,
|
||||||
@@ -43,16 +41,12 @@ import {
|
|||||||
WorkModeSelector,
|
WorkModeSelector,
|
||||||
PlanningModeSelect,
|
PlanningModeSelect,
|
||||||
AncestorContextSection,
|
AncestorContextSection,
|
||||||
|
EnhanceWithAI,
|
||||||
|
EnhancementHistoryButton,
|
||||||
|
type BaseHistoryEntry,
|
||||||
} from '../shared';
|
} from '../shared';
|
||||||
import type { WorkMode } from '../shared';
|
import type { WorkMode } from '../shared';
|
||||||
import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector';
|
import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector';
|
||||||
import { ModelOverrideTrigger, useModelOverride } from '@/components/shared';
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from '@/components/ui/dropdown-menu';
|
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import {
|
import {
|
||||||
getAncestors,
|
getAncestors,
|
||||||
@@ -97,6 +91,13 @@ interface AddFeatureDialogProps {
|
|||||||
allFeatures?: Feature[];
|
allFeatures?: Feature[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single entry in the description history
|
||||||
|
*/
|
||||||
|
interface DescriptionHistoryEntry extends BaseHistoryEntry {
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
export function AddFeatureDialog({
|
export function AddFeatureDialog({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
@@ -139,11 +140,9 @@ export function AddFeatureDialog({
|
|||||||
// UI state
|
// UI state
|
||||||
const [previewMap, setPreviewMap] = useState<ImagePreviewMap>(() => new Map());
|
const [previewMap, setPreviewMap] = useState<ImagePreviewMap>(() => new Map());
|
||||||
const [descriptionError, setDescriptionError] = useState(false);
|
const [descriptionError, setDescriptionError] = useState(false);
|
||||||
const [isEnhancing, setIsEnhancing] = useState(false);
|
|
||||||
const [enhancementMode, setEnhancementMode] = useState<
|
// Description history state
|
||||||
'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'
|
const [descriptionHistory, setDescriptionHistory] = useState<DescriptionHistoryEntry[]>([]);
|
||||||
>('improve');
|
|
||||||
const [enhanceOpen, setEnhanceOpen] = useState(false);
|
|
||||||
|
|
||||||
// Spawn mode state
|
// Spawn mode state
|
||||||
const [ancestors, setAncestors] = useState<AncestorContext[]>([]);
|
const [ancestors, setAncestors] = useState<AncestorContext[]>([]);
|
||||||
@@ -152,9 +151,6 @@ export function AddFeatureDialog({
|
|||||||
// Get defaults from store
|
// Get defaults from store
|
||||||
const { defaultPlanningMode, defaultRequirePlanApproval } = useAppStore();
|
const { defaultPlanningMode, defaultRequirePlanApproval } = useAppStore();
|
||||||
|
|
||||||
// Enhancement model override
|
|
||||||
const enhancementOverride = useModelOverride({ phase: 'enhancementModel' });
|
|
||||||
|
|
||||||
// Track previous open state to detect when dialog opens
|
// Track previous open state to detect when dialog opens
|
||||||
const wasOpenRef = useRef(false);
|
const wasOpenRef = useRef(false);
|
||||||
|
|
||||||
@@ -171,6 +167,9 @@ export function AddFeatureDialog({
|
|||||||
setRequirePlanApproval(defaultRequirePlanApproval);
|
setRequirePlanApproval(defaultRequirePlanApproval);
|
||||||
setModelEntry({ model: 'opus' });
|
setModelEntry({ model: 'opus' });
|
||||||
|
|
||||||
|
// Initialize description history (empty for new feature)
|
||||||
|
setDescriptionHistory([]);
|
||||||
|
|
||||||
// Initialize ancestors for spawn mode
|
// Initialize ancestors for spawn mode
|
||||||
if (parentFeature) {
|
if (parentFeature) {
|
||||||
const ancestorList = getAncestors(parentFeature, allFeatures);
|
const ancestorList = getAncestors(parentFeature, allFeatures);
|
||||||
@@ -279,7 +278,7 @@ export function AddFeatureDialog({
|
|||||||
setRequirePlanApproval(defaultRequirePlanApproval);
|
setRequirePlanApproval(defaultRequirePlanApproval);
|
||||||
setPreviewMap(new Map());
|
setPreviewMap(new Map());
|
||||||
setDescriptionError(false);
|
setDescriptionError(false);
|
||||||
setEnhanceOpen(false);
|
setDescriptionHistory([]);
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -302,33 +301,6 @@ export function AddFeatureDialog({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEnhanceDescription = async () => {
|
|
||||||
if (!description.trim() || isEnhancing) return;
|
|
||||||
|
|
||||||
setIsEnhancing(true);
|
|
||||||
try {
|
|
||||||
const api = getElectronAPI();
|
|
||||||
const result = await api.enhancePrompt?.enhance(
|
|
||||||
description,
|
|
||||||
enhancementMode,
|
|
||||||
enhancementOverride.effectiveModel,
|
|
||||||
enhancementOverride.effectiveModelEntry.thinkingLevel
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result?.success && result.enhancedText) {
|
|
||||||
setDescription(result.enhancedText);
|
|
||||||
toast.success('Description enhanced!');
|
|
||||||
} else {
|
|
||||||
toast.error(result?.error || 'Failed to enhance description');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Enhancement failed:', error);
|
|
||||||
toast.error('Failed to enhance description');
|
|
||||||
} finally {
|
|
||||||
setIsEnhancing(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Shared card styling
|
// Shared card styling
|
||||||
const cardClass = 'rounded-lg border border-border/50 bg-muted/30 p-4 space-y-3';
|
const cardClass = 'rounded-lg border border-border/50 bg-muted/30 p-4 space-y-3';
|
||||||
const sectionHeaderClass = 'flex items-center gap-2 text-sm font-medium text-foreground';
|
const sectionHeaderClass = 'flex items-center gap-2 text-sm font-medium text-foreground';
|
||||||
@@ -380,7 +352,18 @@ export function AddFeatureDialog({
|
|||||||
{/* Task Details Section */}
|
{/* Task Details Section */}
|
||||||
<div className={cardClass}>
|
<div className={cardClass}>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="description">Description</Label>
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="description">Description</Label>
|
||||||
|
{/* Version History Button */}
|
||||||
|
<EnhancementHistoryButton
|
||||||
|
history={descriptionHistory}
|
||||||
|
currentValue={description}
|
||||||
|
onRestore={setDescription}
|
||||||
|
valueAccessor={(entry) => entry.description}
|
||||||
|
title="Version History"
|
||||||
|
restoreMessage="Description restored from history"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<DescriptionImageDropZone
|
<DescriptionImageDropZone
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
@@ -409,75 +392,35 @@ export function AddFeatureDialog({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Collapsible Enhancement Section */}
|
{/* Enhancement Section */}
|
||||||
<Collapsible open={enhanceOpen} onOpenChange={setEnhanceOpen}>
|
<EnhanceWithAI
|
||||||
<CollapsibleTrigger asChild>
|
value={description}
|
||||||
<button className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors w-full py-1">
|
onChange={setDescription}
|
||||||
{enhanceOpen ? (
|
onHistoryAdd={({ mode, originalText, enhancedText }) => {
|
||||||
<ChevronDown className="w-4 h-4" />
|
const timestamp = new Date().toISOString();
|
||||||
) : (
|
setDescriptionHistory((prev) => {
|
||||||
<ChevronRight className="w-4 h-4" />
|
const newHistory = [...prev];
|
||||||
)}
|
// Add original text first (so user can restore to pre-enhancement state)
|
||||||
<Sparkles className="w-4 h-4" />
|
// Only add if it's different from the last entry to avoid duplicates
|
||||||
<span>Enhance with AI</span>
|
const lastEntry = prev[prev.length - 1];
|
||||||
</button>
|
if (!lastEntry || lastEntry.description !== originalText) {
|
||||||
</CollapsibleTrigger>
|
newHistory.push({
|
||||||
<CollapsibleContent className="pt-3">
|
description: originalText,
|
||||||
<div className="flex flex-wrap items-center gap-2 pl-6">
|
timestamp,
|
||||||
<DropdownMenu>
|
source: prev.length === 0 ? 'initial' : 'edit',
|
||||||
<DropdownMenuTrigger asChild>
|
});
|
||||||
<Button variant="outline" size="sm" className="h-8 text-xs">
|
}
|
||||||
{enhancementMode === 'improve' && 'Improve Clarity'}
|
// Add enhanced text
|
||||||
{enhancementMode === 'technical' && 'Add Technical Details'}
|
newHistory.push({
|
||||||
{enhancementMode === 'simplify' && 'Simplify'}
|
description: enhancedText,
|
||||||
{enhancementMode === 'acceptance' && 'Add Acceptance Criteria'}
|
timestamp,
|
||||||
{enhancementMode === 'ux-reviewer' && 'User Experience'}
|
source: 'enhance',
|
||||||
<ChevronDown className="w-3 h-3 ml-1" />
|
enhancementMode: mode,
|
||||||
</Button>
|
});
|
||||||
</DropdownMenuTrigger>
|
return newHistory;
|
||||||
<DropdownMenuContent align="start">
|
});
|
||||||
<DropdownMenuItem onClick={() => setEnhancementMode('improve')}>
|
}}
|
||||||
Improve Clarity
|
/>
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setEnhancementMode('technical')}>
|
|
||||||
Add Technical Details
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setEnhancementMode('simplify')}>
|
|
||||||
Simplify
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setEnhancementMode('acceptance')}>
|
|
||||||
Add Acceptance Criteria
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setEnhancementMode('ux-reviewer')}>
|
|
||||||
User Experience
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 text-xs"
|
|
||||||
onClick={handleEnhanceDescription}
|
|
||||||
disabled={!description.trim() || isEnhancing}
|
|
||||||
loading={isEnhancing}
|
|
||||||
>
|
|
||||||
<Sparkles className="w-3 h-3 mr-1" />
|
|
||||||
Enhance
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<ModelOverrideTrigger
|
|
||||||
currentModelEntry={enhancementOverride.effectiveModelEntry}
|
|
||||||
onModelChange={enhancementOverride.setOverride}
|
|
||||||
phase="enhancementModel"
|
|
||||||
isOverridden={enhancementOverride.isOverridden}
|
|
||||||
size="sm"
|
|
||||||
variant="icon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* AI & Execution Section */}
|
{/* AI & Execution Section */}
|
||||||
|
|||||||
@@ -21,18 +21,8 @@ import {
|
|||||||
FeatureTextFilePath as DescriptionTextFilePath,
|
FeatureTextFilePath as DescriptionTextFilePath,
|
||||||
ImagePreviewMap,
|
ImagePreviewMap,
|
||||||
} from '@/components/ui/description-image-dropzone';
|
} from '@/components/ui/description-image-dropzone';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
import { GitBranch, Cpu, FolderKanban } from 'lucide-react';
|
||||||
import {
|
|
||||||
Sparkles,
|
|
||||||
ChevronDown,
|
|
||||||
ChevronRight,
|
|
||||||
GitBranch,
|
|
||||||
History,
|
|
||||||
Cpu,
|
|
||||||
FolderKanban,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
|
||||||
import { cn, modelSupportsThinking } from '@/lib/utils';
|
import { cn, modelSupportsThinking } from '@/lib/utils';
|
||||||
import { Feature, ModelAlias, ThinkingLevel, useAppStore, PlanningMode } from '@/store/app-store';
|
import { Feature, ModelAlias, ThinkingLevel, useAppStore, PlanningMode } from '@/store/app-store';
|
||||||
import type { ReasoningEffort, PhaseModelEntry, DescriptionHistoryEntry } from '@automaker/types';
|
import type { ReasoningEffort, PhaseModelEntry, DescriptionHistoryEntry } from '@automaker/types';
|
||||||
@@ -41,17 +31,12 @@ import {
|
|||||||
PrioritySelector,
|
PrioritySelector,
|
||||||
WorkModeSelector,
|
WorkModeSelector,
|
||||||
PlanningModeSelect,
|
PlanningModeSelect,
|
||||||
|
EnhanceWithAI,
|
||||||
|
EnhancementHistoryButton,
|
||||||
|
type EnhancementMode,
|
||||||
} from '../shared';
|
} from '../shared';
|
||||||
import type { WorkMode } from '../shared';
|
import type { WorkMode } from '../shared';
|
||||||
import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector';
|
import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector';
|
||||||
import { ModelOverrideTrigger, useModelOverride } from '@/components/shared';
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from '@/components/ui/dropdown-menu';
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import { DependencyTreeDialog } from './dependency-tree-dialog';
|
import { DependencyTreeDialog } from './dependency-tree-dialog';
|
||||||
import { isClaudeModel, supportsReasoningEffort } from '@automaker/types';
|
import { isClaudeModel, supportsReasoningEffort } from '@automaker/types';
|
||||||
@@ -79,7 +64,8 @@ interface EditFeatureDialogProps {
|
|||||||
requirePlanApproval: boolean;
|
requirePlanApproval: boolean;
|
||||||
},
|
},
|
||||||
descriptionHistorySource?: 'enhance' | 'edit',
|
descriptionHistorySource?: 'enhance' | 'edit',
|
||||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'
|
enhancementMode?: EnhancementMode,
|
||||||
|
preEnhancementDescription?: string
|
||||||
) => void;
|
) => void;
|
||||||
categorySuggestions: string[];
|
categorySuggestions: string[];
|
||||||
branchSuggestions: string[];
|
branchSuggestions: string[];
|
||||||
@@ -110,11 +96,6 @@ export function EditFeatureDialog({
|
|||||||
const [editFeaturePreviewMap, setEditFeaturePreviewMap] = useState<ImagePreviewMap>(
|
const [editFeaturePreviewMap, setEditFeaturePreviewMap] = useState<ImagePreviewMap>(
|
||||||
() => new Map()
|
() => new Map()
|
||||||
);
|
);
|
||||||
const [isEnhancing, setIsEnhancing] = useState(false);
|
|
||||||
const [enhancementMode, setEnhancementMode] = useState<
|
|
||||||
'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'
|
|
||||||
>('improve');
|
|
||||||
const [enhanceOpen, setEnhanceOpen] = useState(false);
|
|
||||||
const [showDependencyTree, setShowDependencyTree] = useState(false);
|
const [showDependencyTree, setShowDependencyTree] = useState(false);
|
||||||
const [planningMode, setPlanningMode] = useState<PlanningMode>(feature?.planningMode ?? 'skip');
|
const [planningMode, setPlanningMode] = useState<PlanningMode>(feature?.planningMode ?? 'skip');
|
||||||
const [requirePlanApproval, setRequirePlanApproval] = useState(
|
const [requirePlanApproval, setRequirePlanApproval] = useState(
|
||||||
@@ -133,15 +114,16 @@ export function EditFeatureDialog({
|
|||||||
|
|
||||||
// Track the source of description changes for history
|
// Track the source of description changes for history
|
||||||
const [descriptionChangeSource, setDescriptionChangeSource] = useState<
|
const [descriptionChangeSource, setDescriptionChangeSource] = useState<
|
||||||
{ source: 'enhance'; mode: 'improve' | 'technical' | 'simplify' | 'acceptance' } | 'edit' | null
|
{ source: 'enhance'; mode: EnhancementMode } | 'edit' | null
|
||||||
>(null);
|
>(null);
|
||||||
// Track the original description when the dialog opened for comparison
|
// Track the original description when the dialog opened for comparison
|
||||||
const [originalDescription, setOriginalDescription] = useState(feature?.description ?? '');
|
const [originalDescription, setOriginalDescription] = useState(feature?.description ?? '');
|
||||||
// Track if history dropdown is open
|
// Track the description before enhancement (so it can be restored)
|
||||||
const [showHistory, setShowHistory] = useState(false);
|
const [preEnhancementDescription, setPreEnhancementDescription] = useState<string | null>(null);
|
||||||
|
// Local history state for real-time display (combines persisted + session history)
|
||||||
// Enhancement model override
|
const [localHistory, setLocalHistory] = useState<DescriptionHistoryEntry[]>(
|
||||||
const enhancementOverride = useModelOverride({ phase: 'enhancementModel' });
|
feature?.descriptionHistory ?? []
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditingFeature(feature);
|
setEditingFeature(feature);
|
||||||
@@ -153,8 +135,8 @@ export function EditFeatureDialog({
|
|||||||
// Reset history tracking state
|
// Reset history tracking state
|
||||||
setOriginalDescription(feature.description ?? '');
|
setOriginalDescription(feature.description ?? '');
|
||||||
setDescriptionChangeSource(null);
|
setDescriptionChangeSource(null);
|
||||||
setShowHistory(false);
|
setPreEnhancementDescription(null);
|
||||||
setEnhanceOpen(false);
|
setLocalHistory(feature.descriptionHistory ?? []);
|
||||||
// Reset model entry
|
// Reset model entry
|
||||||
setModelEntry({
|
setModelEntry({
|
||||||
model: (feature.model as ModelAlias) || 'opus',
|
model: (feature.model as ModelAlias) || 'opus',
|
||||||
@@ -164,7 +146,8 @@ export function EditFeatureDialog({
|
|||||||
} else {
|
} else {
|
||||||
setEditFeaturePreviewMap(new Map());
|
setEditFeaturePreviewMap(new Map());
|
||||||
setDescriptionChangeSource(null);
|
setDescriptionChangeSource(null);
|
||||||
setShowHistory(false);
|
setPreEnhancementDescription(null);
|
||||||
|
setLocalHistory([]);
|
||||||
}
|
}
|
||||||
}, [feature]);
|
}, [feature]);
|
||||||
|
|
||||||
@@ -226,7 +209,13 @@ export function EditFeatureDialog({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate(editingFeature.id, updates, historySource, historyEnhancementMode);
|
onUpdate(
|
||||||
|
editingFeature.id,
|
||||||
|
updates,
|
||||||
|
historySource,
|
||||||
|
historyEnhancementMode,
|
||||||
|
preEnhancementDescription ?? undefined
|
||||||
|
);
|
||||||
setEditFeaturePreviewMap(new Map());
|
setEditFeaturePreviewMap(new Map());
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
@@ -237,36 +226,6 @@ export function EditFeatureDialog({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEnhanceDescription = async () => {
|
|
||||||
if (!editingFeature?.description.trim() || isEnhancing) return;
|
|
||||||
|
|
||||||
setIsEnhancing(true);
|
|
||||||
try {
|
|
||||||
const api = getElectronAPI();
|
|
||||||
const result = await api.enhancePrompt?.enhance(
|
|
||||||
editingFeature.description,
|
|
||||||
enhancementMode,
|
|
||||||
enhancementOverride.effectiveModel, // API accepts string, extract from PhaseModelEntry
|
|
||||||
enhancementOverride.effectiveModelEntry.thinkingLevel // Pass thinking level
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result?.success && result.enhancedText) {
|
|
||||||
const enhancedText = result.enhancedText;
|
|
||||||
setEditingFeature((prev) => (prev ? { ...prev, description: enhancedText } : prev));
|
|
||||||
// Track that this change was from enhancement
|
|
||||||
setDescriptionChangeSource({ source: 'enhance', mode: enhancementMode });
|
|
||||||
toast.success('Description enhanced!');
|
|
||||||
} else {
|
|
||||||
toast.error(result?.error || 'Failed to enhance description');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Enhancement failed:', error);
|
|
||||||
toast.error('Failed to enhance description');
|
|
||||||
} finally {
|
|
||||||
setIsEnhancing(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!editingFeature) {
|
if (!editingFeature) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -304,95 +263,18 @@ export function EditFeatureDialog({
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label htmlFor="edit-description">Description</Label>
|
<Label htmlFor="edit-description">Description</Label>
|
||||||
{/* Version History Button */}
|
{/* Version History Button - uses local history for real-time updates */}
|
||||||
{feature?.descriptionHistory && feature.descriptionHistory.length > 0 && (
|
<EnhancementHistoryButton
|
||||||
<Popover open={showHistory} onOpenChange={setShowHistory}>
|
history={localHistory}
|
||||||
<PopoverTrigger asChild>
|
currentValue={editingFeature.description}
|
||||||
<Button
|
onRestore={(description) => {
|
||||||
type="button"
|
setEditingFeature((prev) => (prev ? { ...prev, description } : prev));
|
||||||
variant="ghost"
|
setDescriptionChangeSource('edit');
|
||||||
size="sm"
|
}}
|
||||||
className="h-7 gap-1.5 text-xs text-muted-foreground"
|
valueAccessor={(entry) => entry.description}
|
||||||
>
|
title="Version History"
|
||||||
<History className="w-3.5 h-3.5" />
|
restoreMessage="Description restored from history"
|
||||||
History ({feature.descriptionHistory.length})
|
/>
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-80 p-0" align="end">
|
|
||||||
<div className="p-3 border-b">
|
|
||||||
<h4 className="font-medium text-sm">Version History</h4>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
|
||||||
Click a version to restore it
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="max-h-64 overflow-y-auto p-2 space-y-1">
|
|
||||||
{[...(feature.descriptionHistory || [])]
|
|
||||||
.reverse()
|
|
||||||
.map((entry: DescriptionHistoryEntry, index: number) => {
|
|
||||||
const isCurrentVersion =
|
|
||||||
entry.description === editingFeature.description;
|
|
||||||
const date = new Date(entry.timestamp);
|
|
||||||
const formattedDate = date.toLocaleDateString(undefined, {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
});
|
|
||||||
const getEnhancementModeLabel = (mode?: string) => {
|
|
||||||
const labels: Record<string, string> = {
|
|
||||||
improve: 'Improve Clarity',
|
|
||||||
technical: 'Add Technical Details',
|
|
||||||
simplify: 'Simplify',
|
|
||||||
acceptance: 'Add Acceptance Criteria',
|
|
||||||
'ux-reviewer': 'User Experience',
|
|
||||||
};
|
|
||||||
return labels[mode || 'improve'] || mode || 'improve';
|
|
||||||
};
|
|
||||||
const sourceLabel =
|
|
||||||
entry.source === 'initial'
|
|
||||||
? 'Original'
|
|
||||||
: entry.source === 'enhance'
|
|
||||||
? `Enhanced (${getEnhancementModeLabel(entry.enhancementMode)})`
|
|
||||||
: 'Edited';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={`${entry.timestamp}-${index}`}
|
|
||||||
onClick={() => {
|
|
||||||
setEditingFeature((prev) =>
|
|
||||||
prev ? { ...prev, description: entry.description } : prev
|
|
||||||
);
|
|
||||||
// Mark as edit since user is restoring from history
|
|
||||||
setDescriptionChangeSource('edit');
|
|
||||||
setShowHistory(false);
|
|
||||||
toast.success('Description restored from history');
|
|
||||||
}}
|
|
||||||
className={`w-full text-left p-2 rounded-md hover:bg-muted transition-colors ${
|
|
||||||
isCurrentVersion ? 'bg-muted/50 border border-primary/20' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between gap-2">
|
|
||||||
<span className="text-xs font-medium">{sourceLabel}</span>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{formattedDate}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
|
|
||||||
{entry.description.slice(0, 100)}
|
|
||||||
{entry.description.length > 100 ? '...' : ''}
|
|
||||||
</p>
|
|
||||||
{isCurrentVersion && (
|
|
||||||
<span className="text-xs text-primary font-medium mt-1 block">
|
|
||||||
Current version
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<DescriptionImageDropZone
|
<DescriptionImageDropZone
|
||||||
value={editingFeature.description}
|
value={editingFeature.description}
|
||||||
@@ -443,75 +325,40 @@ export function EditFeatureDialog({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Collapsible Enhancement Section */}
|
{/* Enhancement Section */}
|
||||||
<Collapsible open={enhanceOpen} onOpenChange={setEnhanceOpen}>
|
<EnhanceWithAI
|
||||||
<CollapsibleTrigger asChild>
|
value={editingFeature.description}
|
||||||
<button className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors w-full py-1">
|
onChange={(enhanced) =>
|
||||||
{enhanceOpen ? (
|
setEditingFeature((prev) => (prev ? { ...prev, description: enhanced } : prev))
|
||||||
<ChevronDown className="w-4 h-4" />
|
}
|
||||||
) : (
|
onHistoryAdd={({ mode, originalText, enhancedText }) => {
|
||||||
<ChevronRight className="w-4 h-4" />
|
setDescriptionChangeSource({ source: 'enhance', mode });
|
||||||
)}
|
setPreEnhancementDescription(originalText);
|
||||||
<Sparkles className="w-4 h-4" />
|
|
||||||
<span>Enhance with AI</span>
|
|
||||||
</button>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent className="pt-3">
|
|
||||||
<div className="flex flex-wrap items-center gap-2 pl-6">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="outline" size="sm" className="h-8 text-xs">
|
|
||||||
{enhancementMode === 'improve' && 'Improve Clarity'}
|
|
||||||
{enhancementMode === 'technical' && 'Add Technical Details'}
|
|
||||||
{enhancementMode === 'simplify' && 'Simplify'}
|
|
||||||
{enhancementMode === 'acceptance' && 'Add Acceptance Criteria'}
|
|
||||||
{enhancementMode === 'ux-reviewer' && 'User Experience'}
|
|
||||||
<ChevronDown className="w-3 h-3 ml-1" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="start">
|
|
||||||
<DropdownMenuItem onClick={() => setEnhancementMode('improve')}>
|
|
||||||
Improve Clarity
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setEnhancementMode('technical')}>
|
|
||||||
Add Technical Details
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setEnhancementMode('simplify')}>
|
|
||||||
Simplify
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setEnhancementMode('acceptance')}>
|
|
||||||
Add Acceptance Criteria
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setEnhancementMode('ux-reviewer')}>
|
|
||||||
User Experience
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<Button
|
// Update local history for real-time display
|
||||||
type="button"
|
const timestamp = new Date().toISOString();
|
||||||
variant="default"
|
setLocalHistory((prev) => {
|
||||||
size="sm"
|
const newHistory = [...prev];
|
||||||
className="h-8 text-xs"
|
// Add original text first (so user can restore to pre-enhancement state)
|
||||||
onClick={handleEnhanceDescription}
|
const lastEntry = prev[prev.length - 1];
|
||||||
disabled={!editingFeature.description.trim() || isEnhancing}
|
if (!lastEntry || lastEntry.description !== originalText) {
|
||||||
loading={isEnhancing}
|
newHistory.push({
|
||||||
>
|
description: originalText,
|
||||||
<Sparkles className="w-3 h-3 mr-1" />
|
timestamp,
|
||||||
Enhance
|
source: prev.length === 0 ? 'initial' : 'edit',
|
||||||
</Button>
|
});
|
||||||
|
}
|
||||||
<ModelOverrideTrigger
|
// Add enhanced text
|
||||||
currentModelEntry={enhancementOverride.effectiveModelEntry}
|
newHistory.push({
|
||||||
onModelChange={enhancementOverride.setOverride}
|
description: enhancedText,
|
||||||
phase="enhancementModel"
|
timestamp,
|
||||||
isOverridden={enhancementOverride.isOverridden}
|
source: 'enhance',
|
||||||
size="sm"
|
enhancementMode: mode,
|
||||||
variant="icon"
|
});
|
||||||
/>
|
return newHistory;
|
||||||
</div>
|
});
|
||||||
</CollapsibleContent>
|
}}
|
||||||
</Collapsible>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* AI & Execution Section */}
|
{/* AI & Execution Section */}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -17,6 +18,21 @@ import {
|
|||||||
} from '@/components/ui/description-image-dropzone';
|
} from '@/components/ui/description-image-dropzone';
|
||||||
import { MessageSquare } from 'lucide-react';
|
import { MessageSquare } from 'lucide-react';
|
||||||
import { Feature } from '@/store/app-store';
|
import { Feature } from '@/store/app-store';
|
||||||
|
import {
|
||||||
|
EnhanceWithAI,
|
||||||
|
EnhancementHistoryButton,
|
||||||
|
type EnhancementMode,
|
||||||
|
type BaseHistoryEntry,
|
||||||
|
} from '../shared';
|
||||||
|
|
||||||
|
const logger = createLogger('FollowUpDialog');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single entry in the follow-up prompt history
|
||||||
|
*/
|
||||||
|
export interface FollowUpHistoryEntry extends BaseHistoryEntry {
|
||||||
|
prompt: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface FollowUpDialogProps {
|
interface FollowUpDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -30,6 +46,10 @@ interface FollowUpDialogProps {
|
|||||||
onPreviewMapChange: (map: ImagePreviewMap) => void;
|
onPreviewMapChange: (map: ImagePreviewMap) => void;
|
||||||
onSend: () => void;
|
onSend: () => void;
|
||||||
isMaximized: boolean;
|
isMaximized: boolean;
|
||||||
|
/** History of prompt versions for restoration */
|
||||||
|
promptHistory?: FollowUpHistoryEntry[];
|
||||||
|
/** Callback to add a new entry to prompt history */
|
||||||
|
onHistoryAdd?: (entry: FollowUpHistoryEntry) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FollowUpDialog({
|
export function FollowUpDialog({
|
||||||
@@ -44,9 +64,11 @@ export function FollowUpDialog({
|
|||||||
onPreviewMapChange,
|
onPreviewMapChange,
|
||||||
onSend,
|
onSend,
|
||||||
isMaximized,
|
isMaximized,
|
||||||
|
promptHistory = [],
|
||||||
|
onHistoryAdd,
|
||||||
}: FollowUpDialogProps) {
|
}: FollowUpDialogProps) {
|
||||||
const handleClose = (open: boolean) => {
|
const handleClose = (openState: boolean) => {
|
||||||
if (!open) {
|
if (!openState) {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -77,7 +99,18 @@ export function FollowUpDialog({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4 py-4 overflow-y-auto flex-1 min-h-0">
|
<div className="space-y-4 py-4 overflow-y-auto flex-1 min-h-0">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="follow-up-prompt">Instructions</Label>
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="follow-up-prompt">Instructions</Label>
|
||||||
|
{/* Version History Button */}
|
||||||
|
<EnhancementHistoryButton
|
||||||
|
history={promptHistory}
|
||||||
|
currentValue={prompt}
|
||||||
|
onRestore={onPromptChange}
|
||||||
|
valueAccessor={(entry) => entry.prompt}
|
||||||
|
title="Prompt History"
|
||||||
|
restoreMessage="Prompt restored from history"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<DescriptionImageDropZone
|
<DescriptionImageDropZone
|
||||||
value={prompt}
|
value={prompt}
|
||||||
onChange={onPromptChange}
|
onChange={onPromptChange}
|
||||||
@@ -88,6 +121,33 @@ export function FollowUpDialog({
|
|||||||
onPreviewMapChange={onPreviewMapChange}
|
onPreviewMapChange={onPreviewMapChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Enhancement Section */}
|
||||||
|
<EnhanceWithAI
|
||||||
|
value={prompt}
|
||||||
|
onChange={onPromptChange}
|
||||||
|
onHistoryAdd={({ mode, originalText, enhancedText }) => {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
// Add original text first (so user can restore to pre-enhancement state)
|
||||||
|
// Only add if it's different from the last history entry
|
||||||
|
const lastEntry = promptHistory[promptHistory.length - 1];
|
||||||
|
if (!lastEntry || lastEntry.prompt !== originalText) {
|
||||||
|
onHistoryAdd?.({
|
||||||
|
prompt: originalText,
|
||||||
|
timestamp,
|
||||||
|
source: promptHistory.length === 0 ? 'initial' : 'edit',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Add enhanced text
|
||||||
|
onHistoryAdd?.({
|
||||||
|
prompt: enhancedText,
|
||||||
|
timestamp,
|
||||||
|
source: 'enhance',
|
||||||
|
enhancementMode: mode,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
The agent will continue from where it left off, using the existing context. You can
|
The agent will continue from where it left off, using the existing context. You can
|
||||||
attach screenshots to help explain the issue.
|
attach screenshots to help explain the issue.
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ export { CompletedFeaturesModal } from './completed-features-modal';
|
|||||||
export { ArchiveAllVerifiedDialog } from './archive-all-verified-dialog';
|
export { ArchiveAllVerifiedDialog } from './archive-all-verified-dialog';
|
||||||
export { DeleteCompletedFeatureDialog } from './delete-completed-feature-dialog';
|
export { DeleteCompletedFeatureDialog } from './delete-completed-feature-dialog';
|
||||||
export { EditFeatureDialog } from './edit-feature-dialog';
|
export { EditFeatureDialog } from './edit-feature-dialog';
|
||||||
export { FollowUpDialog } from './follow-up-dialog';
|
export { FollowUpDialog, type FollowUpHistoryEntry } from './follow-up-dialog';
|
||||||
export { PlanApprovalDialog } from './plan-approval-dialog';
|
export { PlanApprovalDialog } from './plan-approval-dialog';
|
||||||
export { MassEditDialog } from './mass-edit-dialog';
|
export { MassEditDialog } from './mass-edit-dialog';
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ interface UseBoardActionsProps {
|
|||||||
featureId: string,
|
featureId: string,
|
||||||
updates: Partial<Feature>,
|
updates: Partial<Feature>,
|
||||||
descriptionHistorySource?: 'enhance' | 'edit',
|
descriptionHistorySource?: 'enhance' | 'edit',
|
||||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'
|
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer',
|
||||||
|
preEnhancementDescription?: string
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
persistFeatureDelete: (featureId: string) => Promise<void>;
|
persistFeatureDelete: (featureId: string) => Promise<void>;
|
||||||
saveCategory: (category: string) => Promise<void>;
|
saveCategory: (category: string) => Promise<void>;
|
||||||
@@ -251,7 +252,8 @@ export function useBoardActions({
|
|||||||
workMode?: 'current' | 'auto' | 'custom';
|
workMode?: 'current' | 'auto' | 'custom';
|
||||||
},
|
},
|
||||||
descriptionHistorySource?: 'enhance' | 'edit',
|
descriptionHistorySource?: 'enhance' | 'edit',
|
||||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'
|
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer',
|
||||||
|
preEnhancementDescription?: string
|
||||||
) => {
|
) => {
|
||||||
const workMode = updates.workMode || 'current';
|
const workMode = updates.workMode || 'current';
|
||||||
|
|
||||||
@@ -308,7 +310,13 @@ export function useBoardActions({
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateFeature(featureId, finalUpdates);
|
updateFeature(featureId, finalUpdates);
|
||||||
persistFeatureUpdate(featureId, finalUpdates, descriptionHistorySource, enhancementMode);
|
persistFeatureUpdate(
|
||||||
|
featureId,
|
||||||
|
finalUpdates,
|
||||||
|
descriptionHistorySource,
|
||||||
|
enhancementMode,
|
||||||
|
preEnhancementDescription
|
||||||
|
);
|
||||||
if (updates.category) {
|
if (updates.category) {
|
||||||
saveCategory(updates.category);
|
saveCategory(updates.category);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps
|
|||||||
featureId: string,
|
featureId: string,
|
||||||
updates: Partial<Feature>,
|
updates: Partial<Feature>,
|
||||||
descriptionHistorySource?: 'enhance' | 'edit',
|
descriptionHistorySource?: 'enhance' | 'edit',
|
||||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'
|
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer',
|
||||||
|
preEnhancementDescription?: string
|
||||||
) => {
|
) => {
|
||||||
if (!currentProject) return;
|
if (!currentProject) return;
|
||||||
|
|
||||||
@@ -35,7 +36,8 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps
|
|||||||
featureId,
|
featureId,
|
||||||
updates,
|
updates,
|
||||||
descriptionHistorySource,
|
descriptionHistorySource,
|
||||||
enhancementMode
|
enhancementMode,
|
||||||
|
preEnhancementDescription
|
||||||
);
|
);
|
||||||
if (result.success && result.feature) {
|
if (result.success && result.feature) {
|
||||||
updateFeature(result.feature.id, result.feature);
|
updateFeature(result.feature.id, result.feature);
|
||||||
|
|||||||
@@ -4,13 +4,18 @@ import {
|
|||||||
FeatureImagePath as DescriptionImagePath,
|
FeatureImagePath as DescriptionImagePath,
|
||||||
ImagePreviewMap,
|
ImagePreviewMap,
|
||||||
} from '@/components/ui/description-image-dropzone';
|
} from '@/components/ui/description-image-dropzone';
|
||||||
|
import type { FollowUpHistoryEntry } from '../dialogs/follow-up-dialog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for managing follow-up dialog state including prompt history
|
||||||
|
*/
|
||||||
export function useFollowUpState() {
|
export function useFollowUpState() {
|
||||||
const [showFollowUpDialog, setShowFollowUpDialog] = useState(false);
|
const [showFollowUpDialog, setShowFollowUpDialog] = useState(false);
|
||||||
const [followUpFeature, setFollowUpFeature] = useState<Feature | null>(null);
|
const [followUpFeature, setFollowUpFeature] = useState<Feature | null>(null);
|
||||||
const [followUpPrompt, setFollowUpPrompt] = useState('');
|
const [followUpPrompt, setFollowUpPrompt] = useState('');
|
||||||
const [followUpImagePaths, setFollowUpImagePaths] = useState<DescriptionImagePath[]>([]);
|
const [followUpImagePaths, setFollowUpImagePaths] = useState<DescriptionImagePath[]>([]);
|
||||||
const [followUpPreviewMap, setFollowUpPreviewMap] = useState<ImagePreviewMap>(() => new Map());
|
const [followUpPreviewMap, setFollowUpPreviewMap] = useState<ImagePreviewMap>(() => new Map());
|
||||||
|
const [followUpPromptHistory, setFollowUpPromptHistory] = useState<FollowUpHistoryEntry[]>([]);
|
||||||
|
|
||||||
const resetFollowUpState = useCallback(() => {
|
const resetFollowUpState = useCallback(() => {
|
||||||
setShowFollowUpDialog(false);
|
setShowFollowUpDialog(false);
|
||||||
@@ -18,6 +23,7 @@ export function useFollowUpState() {
|
|||||||
setFollowUpPrompt('');
|
setFollowUpPrompt('');
|
||||||
setFollowUpImagePaths([]);
|
setFollowUpImagePaths([]);
|
||||||
setFollowUpPreviewMap(new Map());
|
setFollowUpPreviewMap(new Map());
|
||||||
|
setFollowUpPromptHistory([]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleFollowUpDialogChange = useCallback(
|
const handleFollowUpDialogChange = useCallback(
|
||||||
@@ -31,6 +37,13 @@ export function useFollowUpState() {
|
|||||||
[resetFollowUpState]
|
[resetFollowUpState]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new entry to the prompt history
|
||||||
|
*/
|
||||||
|
const addToPromptHistory = useCallback((entry: FollowUpHistoryEntry) => {
|
||||||
|
setFollowUpPromptHistory((prev) => [...prev, entry]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// State
|
// State
|
||||||
showFollowUpDialog,
|
showFollowUpDialog,
|
||||||
@@ -38,14 +51,17 @@ export function useFollowUpState() {
|
|||||||
followUpPrompt,
|
followUpPrompt,
|
||||||
followUpImagePaths,
|
followUpImagePaths,
|
||||||
followUpPreviewMap,
|
followUpPreviewMap,
|
||||||
|
followUpPromptHistory,
|
||||||
// Setters
|
// Setters
|
||||||
setShowFollowUpDialog,
|
setShowFollowUpDialog,
|
||||||
setFollowUpFeature,
|
setFollowUpFeature,
|
||||||
setFollowUpPrompt,
|
setFollowUpPrompt,
|
||||||
setFollowUpImagePaths,
|
setFollowUpImagePaths,
|
||||||
setFollowUpPreviewMap,
|
setFollowUpPreviewMap,
|
||||||
|
setFollowUpPromptHistory,
|
||||||
// Helpers
|
// Helpers
|
||||||
resetFollowUpState,
|
resetFollowUpState,
|
||||||
handleFollowUpDialogChange,
|
handleFollowUpDialogChange,
|
||||||
|
addToPromptHistory,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import { Sparkles, ChevronDown, ChevronRight } from 'lucide-react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { ModelOverrideTrigger, useModelOverride } from '@/components/shared';
|
||||||
|
import { EnhancementMode, ENHANCEMENT_MODE_LABELS } from './enhancement-constants';
|
||||||
|
|
||||||
|
const logger = createLogger('EnhanceWithAI');
|
||||||
|
|
||||||
|
interface EnhanceWithAIProps {
|
||||||
|
/** Current text value to enhance */
|
||||||
|
value: string;
|
||||||
|
/** Callback when text is enhanced */
|
||||||
|
onChange: (enhancedText: string) => void;
|
||||||
|
/** Optional callback to track enhancement in history */
|
||||||
|
onHistoryAdd?: (entry: {
|
||||||
|
mode: EnhancementMode;
|
||||||
|
originalText: string;
|
||||||
|
enhancedText: string;
|
||||||
|
}) => void;
|
||||||
|
/** Disable the enhancement feature */
|
||||||
|
disabled?: boolean;
|
||||||
|
/** Additional CSS classes */
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable "Enhance with AI" component
|
||||||
|
*
|
||||||
|
* Provides AI-powered text enhancement with multiple modes:
|
||||||
|
* - Improve Clarity
|
||||||
|
* - Add Technical Details
|
||||||
|
* - Simplify
|
||||||
|
* - Add Acceptance Criteria
|
||||||
|
* - User Experience
|
||||||
|
*
|
||||||
|
* Used in Add Feature, Edit Feature, and Follow-Up dialogs.
|
||||||
|
*/
|
||||||
|
export function EnhanceWithAI({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
onHistoryAdd,
|
||||||
|
disabled = false,
|
||||||
|
className,
|
||||||
|
}: EnhanceWithAIProps) {
|
||||||
|
const [isEnhancing, setIsEnhancing] = useState(false);
|
||||||
|
const [enhancementMode, setEnhancementMode] = useState<EnhancementMode>('improve');
|
||||||
|
const [enhanceOpen, setEnhanceOpen] = useState(false);
|
||||||
|
|
||||||
|
// Enhancement model override
|
||||||
|
const enhancementOverride = useModelOverride({ phase: 'enhancementModel' });
|
||||||
|
|
||||||
|
const handleEnhance = async () => {
|
||||||
|
if (!value.trim() || isEnhancing || disabled) return;
|
||||||
|
|
||||||
|
setIsEnhancing(true);
|
||||||
|
try {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.enhancePrompt?.enhance(
|
||||||
|
value,
|
||||||
|
enhancementMode,
|
||||||
|
enhancementOverride.effectiveModel,
|
||||||
|
enhancementOverride.effectiveModelEntry.thinkingLevel
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result?.success && result.enhancedText) {
|
||||||
|
const originalText = value;
|
||||||
|
const enhancedText = result.enhancedText;
|
||||||
|
onChange(enhancedText);
|
||||||
|
|
||||||
|
// Track in history if callback provided (includes original for restoration)
|
||||||
|
onHistoryAdd?.({ mode: enhancementMode, originalText, enhancedText });
|
||||||
|
|
||||||
|
toast.success('Enhanced successfully!');
|
||||||
|
} else {
|
||||||
|
toast.error(result?.error || 'Failed to enhance');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Enhancement failed:', error);
|
||||||
|
toast.error('Failed to enhance');
|
||||||
|
} finally {
|
||||||
|
setIsEnhancing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Collapsible open={enhanceOpen} onOpenChange={setEnhanceOpen} className={className}>
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors w-full py-1"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{enhanceOpen ? <ChevronDown className="w-4 h-4" /> : <ChevronRight className="w-4 h-4" />}
|
||||||
|
<Sparkles className="w-4 h-4" />
|
||||||
|
<span>Enhance with AI</span>
|
||||||
|
</button>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent className="pt-3">
|
||||||
|
<div className="flex flex-wrap items-center gap-2 pl-6">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" size="sm" className="h-8 text-xs" disabled={disabled}>
|
||||||
|
{ENHANCEMENT_MODE_LABELS[enhancementMode]}
|
||||||
|
<ChevronDown className="w-3 h-3 ml-1" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start">
|
||||||
|
{(Object.entries(ENHANCEMENT_MODE_LABELS) as [EnhancementMode, string][]).map(
|
||||||
|
([mode, label]) => (
|
||||||
|
<DropdownMenuItem key={mode} onClick={() => setEnhancementMode(mode)}>
|
||||||
|
{label}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 text-xs"
|
||||||
|
onClick={handleEnhance}
|
||||||
|
disabled={!value.trim() || isEnhancing || disabled}
|
||||||
|
loading={isEnhancing}
|
||||||
|
>
|
||||||
|
<Sparkles className="w-3 h-3 mr-1" />
|
||||||
|
Enhance
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ModelOverrideTrigger
|
||||||
|
currentModelEntry={enhancementOverride.effectiveModelEntry}
|
||||||
|
onModelChange={enhancementOverride.setOverride}
|
||||||
|
phase="enhancementModel"
|
||||||
|
isOverridden={enhancementOverride.isOverridden}
|
||||||
|
size="sm"
|
||||||
|
variant="icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/** Enhancement mode options for AI-powered prompt improvement */
|
||||||
|
export type EnhancementMode = 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer';
|
||||||
|
|
||||||
|
/** Labels for enhancement modes displayed in the UI */
|
||||||
|
export const ENHANCEMENT_MODE_LABELS: Record<EnhancementMode, string> = {
|
||||||
|
improve: 'Improve Clarity',
|
||||||
|
technical: 'Add Technical Details',
|
||||||
|
simplify: 'Simplify',
|
||||||
|
acceptance: 'Add Acceptance Criteria',
|
||||||
|
'ux-reviewer': 'User Experience',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Descriptions for enhancement modes (for tooltips/accessibility) */
|
||||||
|
export const ENHANCEMENT_MODE_DESCRIPTIONS: Record<EnhancementMode, string> = {
|
||||||
|
improve: 'Make the prompt clearer and more concise',
|
||||||
|
technical: 'Add implementation details and specifications',
|
||||||
|
simplify: 'Reduce complexity while keeping the core intent',
|
||||||
|
acceptance: 'Add specific acceptance criteria and test cases',
|
||||||
|
'ux-reviewer': 'Add user experience considerations and flows',
|
||||||
|
};
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import { useState, useMemo } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
|
import { History } from 'lucide-react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { EnhancementMode, ENHANCEMENT_MODE_LABELS } from './enhancement-constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base interface for history entries
|
||||||
|
*/
|
||||||
|
export interface BaseHistoryEntry {
|
||||||
|
timestamp: string;
|
||||||
|
source: 'initial' | 'enhance' | 'edit';
|
||||||
|
enhancementMode?: EnhancementMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EnhancementHistoryButtonProps<T extends BaseHistoryEntry> {
|
||||||
|
/** Array of history entries */
|
||||||
|
history: T[];
|
||||||
|
/** Current value to compare against for highlighting */
|
||||||
|
currentValue: string;
|
||||||
|
/** Callback when a history entry is restored */
|
||||||
|
onRestore: (value: string) => void;
|
||||||
|
/** Function to extract the text value from a history entry */
|
||||||
|
valueAccessor: (entry: T) => string;
|
||||||
|
/** Title for the history popover (e.g., "Version History", "Prompt History") */
|
||||||
|
title?: string;
|
||||||
|
/** Message shown when restoring an entry */
|
||||||
|
restoreMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable history button component for enhancement-related history
|
||||||
|
*
|
||||||
|
* Displays a popover with a list of historical versions that can be restored.
|
||||||
|
* Used in edit-feature-dialog and follow-up-dialog for description/prompt history.
|
||||||
|
*/
|
||||||
|
export function EnhancementHistoryButton<T extends BaseHistoryEntry>({
|
||||||
|
history,
|
||||||
|
currentValue,
|
||||||
|
onRestore,
|
||||||
|
valueAccessor,
|
||||||
|
title = 'Version History',
|
||||||
|
restoreMessage = 'Restored from history',
|
||||||
|
}: EnhancementHistoryButtonProps<T>) {
|
||||||
|
const [showHistory, setShowHistory] = useState(false);
|
||||||
|
|
||||||
|
// Memoize reversed history to avoid creating new array on every render
|
||||||
|
// NOTE: This hook MUST be called before any early returns to follow Rules of Hooks
|
||||||
|
const reversedHistory = useMemo(() => [...history].reverse(), [history]);
|
||||||
|
|
||||||
|
// Early return AFTER all hooks are called
|
||||||
|
if (history.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSourceLabel = (entry: T): string => {
|
||||||
|
if (entry.source === 'initial') {
|
||||||
|
return 'Original';
|
||||||
|
}
|
||||||
|
if (entry.source === 'enhance') {
|
||||||
|
const mode = entry.enhancementMode ?? 'improve';
|
||||||
|
const label = ENHANCEMENT_MODE_LABELS[mode as EnhancementMode] ?? mode;
|
||||||
|
return `Enhanced (${label})`;
|
||||||
|
}
|
||||||
|
return 'Edited';
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (timestamp: string): string => {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
return date.toLocaleDateString(undefined, {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={showHistory} onOpenChange={setShowHistory}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 gap-1.5 text-xs text-muted-foreground"
|
||||||
|
>
|
||||||
|
<History className="w-3.5 h-3.5" />
|
||||||
|
History ({history.length})
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-80 p-0" align="end">
|
||||||
|
<div className="p-3 border-b">
|
||||||
|
<h4 className="font-medium text-sm">{title}</h4>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">Click a version to restore it</p>
|
||||||
|
</div>
|
||||||
|
<div className="max-h-64 overflow-y-auto p-2 space-y-1">
|
||||||
|
{reversedHistory.map((entry, index) => {
|
||||||
|
const value = valueAccessor(entry);
|
||||||
|
const isCurrentVersion = value === currentValue;
|
||||||
|
const sourceLabel = getSourceLabel(entry);
|
||||||
|
const formattedDate = formatDate(entry.timestamp);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={`${entry.timestamp}-${index}`}
|
||||||
|
onClick={() => {
|
||||||
|
onRestore(value);
|
||||||
|
setShowHistory(false);
|
||||||
|
toast.success(restoreMessage);
|
||||||
|
}}
|
||||||
|
className={`w-full text-left p-2 rounded-md hover:bg-muted transition-colors ${
|
||||||
|
isCurrentVersion ? 'bg-muted/50 border border-primary/20' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<span className="text-xs font-medium">{sourceLabel}</span>
|
||||||
|
<span className="text-xs text-muted-foreground">{formattedDate}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
|
||||||
|
{value.slice(0, 100)}
|
||||||
|
{value.length > 100 ? '...' : ''}
|
||||||
|
</p>
|
||||||
|
{isCurrentVersion && (
|
||||||
|
<span className="text-xs text-primary font-medium mt-1 block">
|
||||||
|
Current version
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './enhancement-constants';
|
||||||
|
export * from './enhance-with-ai';
|
||||||
|
export * from './enhancement-history-button';
|
||||||
@@ -10,3 +10,4 @@ export * from './planning-mode-selector';
|
|||||||
export * from './planning-mode-select';
|
export * from './planning-mode-select';
|
||||||
export * from './ancestor-context-section';
|
export * from './ancestor-context-section';
|
||||||
export * from './work-mode-selector';
|
export * from './work-mode-selector';
|
||||||
|
export * from './enhancement';
|
||||||
|
|||||||
@@ -462,7 +462,8 @@ export interface FeaturesAPI {
|
|||||||
featureId: string,
|
featureId: string,
|
||||||
updates: Partial<Feature>,
|
updates: Partial<Feature>,
|
||||||
descriptionHistorySource?: 'enhance' | 'edit',
|
descriptionHistorySource?: 'enhance' | 'edit',
|
||||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'
|
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer',
|
||||||
|
preEnhancementDescription?: string
|
||||||
) => Promise<{ success: boolean; feature?: Feature; error?: string }>;
|
) => Promise<{ success: boolean; feature?: Feature; error?: string }>;
|
||||||
delete: (projectPath: string, featureId: string) => Promise<{ success: boolean; error?: string }>;
|
delete: (projectPath: string, featureId: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
getAgentOutput: (
|
getAgentOutput: (
|
||||||
@@ -612,7 +613,8 @@ export interface ElectronAPI {
|
|||||||
enhance: (
|
enhance: (
|
||||||
originalText: string,
|
originalText: string,
|
||||||
enhancementMode: string,
|
enhancementMode: string,
|
||||||
model?: string
|
model?: string,
|
||||||
|
thinkingLevel?: string
|
||||||
) => Promise<{
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
enhancedText?: string;
|
enhancedText?: string;
|
||||||
|
|||||||
@@ -1480,7 +1480,8 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
featureId: string,
|
featureId: string,
|
||||||
updates: Partial<Feature>,
|
updates: Partial<Feature>,
|
||||||
descriptionHistorySource?: 'enhance' | 'edit',
|
descriptionHistorySource?: 'enhance' | 'edit',
|
||||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'
|
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer',
|
||||||
|
preEnhancementDescription?: string
|
||||||
) =>
|
) =>
|
||||||
this.post('/api/features/update', {
|
this.post('/api/features/update', {
|
||||||
projectPath,
|
projectPath,
|
||||||
@@ -1488,6 +1489,7 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
updates,
|
updates,
|
||||||
descriptionHistorySource,
|
descriptionHistorySource,
|
||||||
enhancementMode,
|
enhancementMode,
|
||||||
|
preEnhancementDescription,
|
||||||
}),
|
}),
|
||||||
delete: (projectPath: string, featureId: string) =>
|
delete: (projectPath: string, featureId: string) =>
|
||||||
this.post('/api/features/delete', { projectPath, featureId }),
|
this.post('/api/features/delete', { projectPath, featureId }),
|
||||||
|
|||||||
Reference in New Issue
Block a user