1- import { useState , useEffect , useCallback } from 'react' ;
1+ import { useState , useEffect , useCallback , useMemo } from 'react' ;
22import { useParams } from 'react-router-dom' ;
33import { usePromptManagement } from '../../hooks/usePromptManagement' ;
44import { PromptInputArea } from './components/PromptInputArea' ;
@@ -8,6 +8,7 @@ import { useToast } from '../../context/toastContextHelpers';
88import SearchBar from '../../components/ui/SearchBar' ;
99import LoadingSpinner from '../../components/ui/LoadingSpinner' ;
1010import { exportSinglePrompt } from '../../utils/exportImport' ;
11+ import { PromptVersion } from '../../types/prompt' ;
1112
1213/**
1314 * PromptEditorPage provides a comprehensive prompt editing experience with integrated testing.
@@ -26,6 +27,9 @@ export function PromptEditorPage() {
2627 const [ isSaving , setIsSaving ] = useState ( false ) ;
2728 // Current content being edited (may differ from saved activePrompt.content)
2829 const [ currentContent , setCurrentContent ] = useState < string > ( '' ) ;
30+ const [ workingVersions , setWorkingVersions ] = useState < PromptVersion [ ] > ( [ ] ) ;
31+ const [ currentVersion , setCurrentVersion ] = useState < string > ( '' ) ;
32+ const [ diffTarget , setDiffTarget ] = useState < PromptVersion | null > ( null ) ;
2933 // Search state
3034 const [ searchQuery , setSearchQuery ] = useState < string > ( '' ) ;
3135
@@ -36,6 +40,9 @@ export function PromptEditorPage() {
3640 useEffect ( ( ) => {
3741 if ( activePrompt ) {
3842 setCurrentContent ( activePrompt . content ) ;
43+ setWorkingVersions ( activePrompt . versions ) ;
44+ setCurrentVersion ( activePrompt . version ) ;
45+ setDiffTarget ( null ) ;
3946 }
4047 } , [ activePrompt ] ) ;
4148
@@ -49,13 +56,33 @@ export function PromptEditorPage() {
4956 // Simulate API call delay
5057 await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ;
5158 console . log ( 'Saving content:' , newContent ) ;
59+ const bumpPatch = ( version : string ) => {
60+ const parts = version . split ( '.' ) . map ( ( part ) => Number . parseInt ( part , 10 ) ) ;
61+ if ( parts . length === 3 && parts . every ( ( n ) => Number . isInteger ( n ) ) ) {
62+ const [ major , minor , patch ] = parts ;
63+ return `${ major } .${ minor } .${ patch + 1 } ` ;
64+ }
65+ return `${ version } .1` ;
66+ } ;
67+ const nextVersion = bumpPatch ( currentVersion || activePrompt . version || '1.0.0' ) ;
68+ const newVersion : PromptVersion = {
69+ id : `v-${ Date . now ( ) } ` ,
70+ promptId : activePrompt . id ,
71+ version : nextVersion ,
72+ content : newContent ,
73+ createdAt : new Date ( ) . toISOString ( ) ,
74+ metadata : { rationale : 'Manual save from editor' } ,
75+ } ;
76+ setWorkingVersions ( ( prev ) => [ newVersion , ...prev ] ) ;
77+ setCurrentVersion ( nextVersion ) ;
78+ setDiffTarget ( newVersion ) ;
5279 showToast ( 'Prompt saved successfully!' , 'success' ) ;
5380 } catch ( error ) {
5481 showToast ( 'Failed to save prompt' , 'error' ) ;
5582 } finally {
5683 setIsSaving ( false ) ;
5784 }
58- } , [ activePrompt , showToast ] ) ;
85+ } , [ activePrompt , showToast , currentVersion ] ) ;
5986
6087 /**
6188 * Updates the current content state when user edits the prompt.
@@ -65,6 +92,23 @@ export function PromptEditorPage() {
6592 setCurrentContent ( content ) ;
6693 } ;
6794
95+ /**
96+ * Roll back to a previous version in-memory with human confirmation.
97+ */
98+ const handleRollback = ( version : string ) => {
99+ const targetVersion = workingVersions . find ( ( v ) => v . version === version ) ;
100+ if ( ! targetVersion ) {
101+ showToast ( 'Version not found' , 'error' ) ;
102+ return ;
103+ }
104+ if ( window . confirm ( `Rollback to version ${ version } ? Unsaved changes will be replaced.` ) ) {
105+ setCurrentContent ( targetVersion . content ) ;
106+ setCurrentVersion ( version ) ;
107+ setDiffTarget ( targetVersion ) ;
108+ showToast ( `Rolled back to version ${ version } ` , 'info' ) ;
109+ }
110+ } ;
111+
68112 /**
69113 * Handle export functionality
70114 */
@@ -89,6 +133,19 @@ export function PromptEditorPage() {
89133 }
90134 } ;
91135
136+ const diffRows = useMemo ( ( ) => {
137+ if ( ! diffTarget ) return [ ] ;
138+ const currentLines = currentContent . split ( '\n' ) ;
139+ const targetLines = diffTarget . content . split ( '\n' ) ;
140+ const length = Math . max ( currentLines . length , targetLines . length ) ;
141+ return Array . from ( { length } ) . map ( ( _ , idx ) => ( {
142+ line : idx + 1 ,
143+ current : currentLines [ idx ] ?? '' ,
144+ target : targetLines [ idx ] ?? '' ,
145+ changed : ( currentLines [ idx ] ?? '' ) !== ( targetLines [ idx ] ?? '' ) ,
146+ } ) ) ;
147+ } , [ currentContent , diffTarget ] ) ;
148+
92149 /**
93150 * Keyboard shortcuts handler
94151 */
@@ -206,14 +263,47 @@ export function PromptEditorPage() {
206263 { /* Version Management Panel */ }
207264 < div >
208265 < PromptVersioningPanel
209- currentVersion = { activePrompt . version }
210- versionHistory = { activePrompt . versions . map ( v => ( {
266+ currentVersion = { currentVersion || activePrompt . version }
267+ versionHistory = { workingVersions . map ( v => ( {
211268 version : v . version ,
212269 date : v . createdAt ,
213- rationale : v . metadata ?. rationale as string || 'No rationale provided'
270+ rationale : ( v . metadata ?. rationale as string ) || 'No rationale provided'
214271 } ) ) }
215- onRollback = { ( version ) => console . log ( 'Rollback to version:' , version ) }
272+ onRollback = { handleRollback }
273+ onViewDiff = { ( version ) => {
274+ const target = workingVersions . find ( ( v ) => v . version === version ) ?? null ;
275+ setDiffTarget ( target ) ;
276+ } }
216277 />
278+ { diffTarget && (
279+ < div style = { { marginTop : '15px' , padding : '12px' , border : '1px solid #e0e0e0' , borderRadius : '8px' , background : '#fafafa' } } >
280+ < h4 style = { { marginTop : 0 } } > Diff vs. version { diffTarget . version } </ h4 >
281+ < div style = { { display : 'grid' , gridTemplateColumns : '1fr 1fr' , gap : '8px' , fontFamily : 'monospace' , fontSize : '0.85em' } } >
282+ < div >
283+ < strong > Current</ strong >
284+ < div style = { { border : '1px solid #ddd' , borderRadius : '6px' , padding : '8px' , maxHeight : '220px' , overflowY : 'auto' } } >
285+ { diffRows . map ( ( row ) => (
286+ < div key = { `curr-${ row . line } ` } style = { { background : row . changed ? '#fff8e1' : 'transparent' } } >
287+ < span style = { { color : '#999' } } > { row . line . toString ( ) . padStart ( 3 , ' ' ) } </ span >
288+ < span > { row . current } </ span >
289+ </ div >
290+ ) ) }
291+ </ div >
292+ </ div >
293+ < div >
294+ < strong > Selected ({ diffTarget . version } )</ strong >
295+ < div style = { { border : '1px solid #ddd' , borderRadius : '6px' , padding : '8px' , maxHeight : '220px' , overflowY : 'auto' } } >
296+ { diffRows . map ( ( row ) => (
297+ < div key = { `old-${ row . line } ` } style = { { background : row . changed ? '#ffeceb' : 'transparent' } } >
298+ < span style = { { color : '#999' } } > { row . line . toString ( ) . padStart ( 3 , ' ' ) } </ span >
299+ < span > { row . target } </ span >
300+ </ div >
301+ ) ) }
302+ </ div >
303+ </ div >
304+ </ div >
305+ </ div >
306+ ) }
217307 </ div >
218308 </ div >
219309
0 commit comments