@@ -43,6 +43,7 @@ import { SessionSidePanel } from "@/pages/session/session-side-panel"
4343import { TerminalPanel } from "@/pages/session/terminal-panel"
4444import { useSessionCommands } from "@/pages/session/use-session-commands"
4545import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll"
46+ import { extractPromptFromParts } from "@/utils/prompt"
4647import { same } from "@/utils/same"
4748import { formatServerError } from "@/utils/server-errors"
4849
@@ -286,6 +287,7 @@ export default function Page() {
286287 const [ ui , setUi ] = createStore ( {
287288 git : false ,
288289 pendingMessage : undefined as string | undefined ,
290+ restoring : undefined as string | undefined ,
289291 reviewSnap : false ,
290292 scrollGesture : 0 ,
291293 scroll : {
@@ -1179,6 +1181,110 @@ export default function Page() {
11791181 scroller : ( ) => scroller ,
11801182 } )
11811183
1184+ const draft = ( id : string ) =>
1185+ extractPromptFromParts ( sync . data . part [ id ] ?? [ ] , {
1186+ directory : sdk . directory ,
1187+ attachmentName : language . t ( "common.attachment" ) ,
1188+ } )
1189+
1190+ const line = ( id : string ) => {
1191+ const text = draft ( id )
1192+ . map ( ( part ) => ( part . type === "image" ? `[image:${ part . filename } ]` : part . content ) )
1193+ . join ( "" )
1194+ . replace ( / \s + / g, " " )
1195+ . trim ( )
1196+ if ( text ) return text
1197+ return `[${ language . t ( "common.attachment" ) } ]`
1198+ }
1199+
1200+ const fail = ( err : unknown ) => {
1201+ showToast ( {
1202+ variant : "error" ,
1203+ title : language . t ( "common.requestFailed" ) ,
1204+ description : formatServerError ( err , language . t ) ,
1205+ } )
1206+ }
1207+
1208+ const busy = ( sessionID : string ) => {
1209+ if ( sync . data . session_status [ sessionID ] ?. type !== "idle" ) return true
1210+ return ( sync . data . message [ sessionID ] ?? [ ] ) . some (
1211+ ( item ) => item . role === "assistant" && typeof item . time . completed !== "number" ,
1212+ )
1213+ }
1214+
1215+ const halt = ( sessionID : string ) =>
1216+ busy ( sessionID ) ? sdk . client . session . abort ( { sessionID } ) . catch ( ( ) => { } ) : Promise . resolve ( )
1217+
1218+ const fork = ( input : { sessionID : string ; messageID : string } ) => {
1219+ const value = draft ( input . messageID )
1220+ return sdk . client . session
1221+ . fork ( input )
1222+ . then ( ( result ) => {
1223+ const next = result . data
1224+ if ( ! next ) {
1225+ showToast ( {
1226+ variant : "error" ,
1227+ title : language . t ( "common.requestFailed" ) ,
1228+ } )
1229+ return
1230+ }
1231+ navigate ( `/${ base64Encode ( sdk . directory ) } /session/${ next . id } ` )
1232+ requestAnimationFrame ( ( ) => {
1233+ prompt . set ( value )
1234+ } )
1235+ } )
1236+ . catch ( fail )
1237+ }
1238+
1239+ const revert = ( input : { sessionID : string ; messageID : string } ) => {
1240+ const value = draft ( input . messageID )
1241+ return halt ( input . sessionID )
1242+ . then ( ( ) => sdk . client . session . revert ( input ) )
1243+ . then ( ( ) => {
1244+ prompt . set ( value )
1245+ } )
1246+ . catch ( fail )
1247+ }
1248+
1249+ const restore = ( id : string ) => {
1250+ const sessionID = params . id
1251+ if ( ! sessionID || ui . restoring ) return
1252+
1253+ const next = userMessages ( ) . find ( ( item ) => item . id > id )
1254+ setUi ( "restoring" , id )
1255+
1256+ const task = ! next
1257+ ? halt ( sessionID )
1258+ . then ( ( ) => sdk . client . session . unrevert ( { sessionID } ) )
1259+ . then ( ( ) => {
1260+ prompt . reset ( )
1261+ } )
1262+ : halt ( sessionID )
1263+ . then ( ( ) =>
1264+ sdk . client . session . revert ( {
1265+ sessionID,
1266+ messageID : next . id ,
1267+ } ) ,
1268+ )
1269+ . then ( ( ) => {
1270+ prompt . set ( draft ( next . id ) )
1271+ } )
1272+
1273+ return task . catch ( fail ) . finally ( ( ) => {
1274+ setUi ( "restoring" , ( value ) => ( value === id ? undefined : value ) )
1275+ } )
1276+ }
1277+
1278+ const rolled = createMemo ( ( ) => {
1279+ const id = revertMessageID ( )
1280+ if ( ! id ) return [ ]
1281+ return userMessages ( )
1282+ . filter ( ( item ) => item . id >= id )
1283+ . map ( ( item ) => ( { id : item . id , text : line ( item . id ) } ) )
1284+ } )
1285+
1286+ const actions = { fork, revert }
1287+
11821288 createResizeObserver (
11831289 ( ) => promptDock ,
11841290 ( { height } ) => {
@@ -1268,6 +1374,7 @@ export default function Page() {
12681374 loadingClass : "px-4 py-4 text-text-weak" ,
12691375 emptyClass : "h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6" ,
12701376 } ) }
1377+ actions = { actions }
12711378 scroll = { ui . scroll }
12721379 onResumeScroll = { resumeScroll }
12731380 setScrollRef = { setScrollRef }
@@ -1333,6 +1440,15 @@ export default function Page() {
13331440 resumeScroll ( )
13341441 } }
13351442 onResponseSubmit = { resumeScroll }
1443+ revert = {
1444+ rolled ( ) . length > 0
1445+ ? {
1446+ items : rolled ( ) ,
1447+ restoring : ui . restoring ,
1448+ onRestore : restore ,
1449+ }
1450+ : undefined
1451+ }
13361452 setPromptDockRef = { ( el ) => {
13371453 promptDock = el
13381454 } }
0 commit comments