@@ -16,6 +16,7 @@ import { useDraftChecklist } from "./useDraftChecklist";
1616import { useDraftFirstResponse } from "./useDraftFirstResponse" ;
1717import { useDraftGeneration } from "./useDraftGeneration" ;
1818import { useDraftIntake } from "./useDraftIntake" ;
19+ import { useDraftPersistence } from "./useDraftPersistence" ;
1920import { useGuidedRunbook } from "./useGuidedRunbook" ;
2021import { useWorkspaceClipboardPacks } from "./useWorkspaceClipboardPacks" ;
2122import { ConversationInput } from "./ConversationInput" ;
@@ -47,10 +48,6 @@ import {
4748 compactLines ,
4849 parseCaseIntake ,
4950} from "../../features/workspace/workspaceAssistant" ;
50- import {
51- shouldMigrateVisibleRunbookSession ,
52- shouldProceedAfterSaveAttempt ,
53- } from "../../features/workspace/workspaceDraftSession" ;
5451import {
5552 calculateEditRatio ,
5653 countWords ,
@@ -1324,202 +1321,50 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
13241321 onCompareLastResolution : handleCompareLastResolution ,
13251322 } ) ;
13261323
1327- const handleSaveDraft = useCallback ( async ( ) => {
1328- if ( ! hasSaveableWorkspaceContent ) {
1329- showError ( "Cannot save empty draft" ) ;
1330- return null ;
1331- }
1332-
1333- const diagnosisData = buildDiagnosisJson ( ) ;
1334- const currentCreatedAt = savedDraftCreatedAt ?? new Date ( ) . toISOString ( ) ;
1335- const draftPayload = {
1336- input_text : input ,
1337- summary_text : currentTicket ?. summary ?? null ,
1338- diagnosis_json : diagnosisData ,
1339- response_text : response || null ,
1340- ticket_id : currentTicketId ,
1341- kb_sources_json : sources . length > 0 ? JSON . stringify ( sources ) : null ,
1342- is_autosave : false ,
1343- model_name : loadedModelName ,
1344- case_intake_json : serializedCaseIntake ,
1345- handoff_summary : handoffPack . summary ,
1346- status : "draft" as const ,
1347- } ;
1348-
1349- const draftId = savedDraftId
1350- ? await updateDraft ( {
1351- id : savedDraftId ,
1352- created_at : currentCreatedAt ,
1353- updated_at : activeWorkspaceDraft . updated_at ,
1354- finalized_at : null ,
1355- finalized_by : null ,
1356- ...draftPayload ,
1357- } )
1358- : await saveDraft ( draftPayload ) ;
1359-
1360- if ( draftId ) {
1361- const nextScopeKey = `draft:${ draftId } ` ;
1362- let runbookScopeLinked = true ;
1363- if ( workspaceRunbookScopeKey !== nextScopeKey ) {
1364- try {
1365- const shouldMigrateActiveRunbookSession = guidedRunbookSession
1366- ? shouldMigrateVisibleRunbookSession ( {
1367- hasGuidedRunbookSession : true ,
1368- runbookSessionTouched,
1369- runbookSessionSourceScopeKey,
1370- workspaceRunbookScopeKey,
1371- } )
1372- : false ;
1373-
1374- const activeRunbookSessionId = guidedRunbookSession ?. id ?? null ;
1375- if ( shouldMigrateActiveRunbookSession && activeRunbookSessionId ) {
1376- await reassignRunbookSessionById (
1377- activeRunbookSessionId ,
1378- nextScopeKey ,
1379- ) ;
1380- } else {
1381- await reassignRunbookSessionScope (
1382- workspaceRunbookScopeKey ,
1383- nextScopeKey ,
1384- ) ;
1385- }
1386- setWorkspaceRunbookScopeKey ( nextScopeKey ) ;
1387- setRunbookSessionSourceScopeKey ( nextScopeKey ) ;
1388- } catch {
1389- runbookScopeLinked = false ;
1390- }
1391- }
1392- setAutosaveDraftId ( null ) ;
1393- setSavedDraftId ( draftId ) ;
1394- setSavedDraftCreatedAt ( currentCreatedAt ) ;
1395- const responseWordCount = countWords ( response ) ;
1396- const editRatio = calculateEditRatio ( originalResponse , response ) ;
1397- logEvent ( "response_saved" , {
1398- draft_id : draftId ,
1399- word_count : responseWordCount ,
1400- is_edited : isResponseEdited ,
1401- edit_ratio : Number ( editRatio . toFixed ( 3 ) ) ,
1402- } ) ;
1403- if ( runbookScopeLinked ) {
1404- showSuccess ( "Draft saved" ) ;
1405- } else {
1406- showError (
1407- "Draft saved, but guided runbook progress stayed attached to the previous workspace state" ,
1408- ) ;
1409- }
1410- return draftId ;
1411- }
1412- return null ;
1413- } , [
1414- activeWorkspaceDraft . updated_at ,
1415- buildDiagnosisJson ,
1416- currentTicket ?. summary ,
1417- currentTicketId ,
1418- guidedRunbookSession ,
1419- handoffPack . summary ,
1420- hasSaveableWorkspaceContent ,
1324+ const {
1325+ handleSaveDraft,
1326+ handleConfirmOpenDraft,
1327+ handleConfirmOpenSimilarCase,
1328+ } = useDraftPersistence ( {
14211329 input,
1422- isResponseEdited ,
1423- loadedModelName ,
1424- logEvent ,
1425- originalResponse ,
1426- reassignRunbookSessionById ,
1427- reassignRunbookSessionScope ,
14281330 response,
1331+ sources,
1332+ currentTicket,
1333+ currentTicketId,
1334+ savedDraftId,
14291335 savedDraftCreatedAt,
1430- runbookSessionSourceScopeKey ,
1336+ loadedModelName,
1337+ handoffPack,
1338+ serializedCaseIntake,
1339+ isResponseEdited,
1340+ originalResponse,
1341+ hasSaveableWorkspaceContent,
1342+ activeWorkspaceDraft,
1343+ workspaceRunbookScopeKey,
1344+ guidedRunbookSession,
14311345 runbookSessionTouched,
1432- savedDraftId ,
1346+ runbookSessionSourceScopeKey,
1347+ buildDiagnosisJson,
14331348 saveDraft,
1434- serializedCaseIntake ,
1435- showError ,
1436- showSuccess ,
1437- sources ,
14381349 updateDraft,
1439- workspaceRunbookScopeKey ,
1440- ] ) ;
1441-
1442- const handleConfirmOpenSimilarCase = useCallback (
1443- async ( mode : "replace" | "save-and-open" | "compare" ) => {
1444- if ( ! pendingSimilarCaseOpen ) {
1445- return ;
1446- }
1447-
1448- if ( mode === "compare" ) {
1449- setCompareCase ( pendingSimilarCaseOpen ) ;
1450- setPendingSimilarCaseOpen ( null ) ;
1451- return ;
1452- }
1453-
1454- try {
1455- if ( mode === "save-and-open" ) {
1456- const savedId = await handleSaveDraft ( ) ;
1457- if ( ! shouldProceedAfterSaveAttempt ( mode , savedId ) ) {
1458- return ;
1459- }
1460- }
1461-
1462- await loadSimilarCaseIntoWorkspace ( pendingSimilarCaseOpen ) ;
1463- setPendingSimilarCaseOpen ( null ) ;
1464- void logEvent ( "workspace_similar_case_opened" , {
1465- ticket_id : currentTicketId ,
1466- similar_case_id : pendingSimilarCaseOpen . draft_id ,
1467- similar_case_ticket : pendingSimilarCaseOpen . ticket_id ,
1468- open_mode : mode ,
1469- } ) ;
1470- showSuccess (
1471- mode === "save-and-open"
1472- ? "Saved the current workspace and opened the saved case"
1473- : "Opened the saved case in the workspace" ,
1474- ) ;
1475- } catch {
1476- showError ( "Failed to open the saved case" ) ;
1477- }
1478- } ,
1479- [
1480- currentTicketId ,
1481- handleSaveDraft ,
1482- loadSimilarCaseIntoWorkspace ,
1483- logEvent ,
1484- pendingSimilarCaseOpen ,
1485- showError ,
1486- showSuccess ,
1487- ] ,
1488- ) ;
1489-
1490- const handleConfirmOpenDraft = useCallback (
1491- async ( mode : "replace" | "save-and-open" ) => {
1492- if ( ! pendingDraftOpen ) {
1493- return ;
1494- }
1495-
1496- try {
1497- if ( mode === "save-and-open" ) {
1498- const savedId = await handleSaveDraft ( ) ;
1499- if ( ! shouldProceedAfterSaveAttempt ( mode , savedId ) ) {
1500- return ;
1501- }
1502- }
1503-
1504- applyLoadedDraft ( pendingDraftOpen ) ;
1505- setPendingDraftOpen ( null ) ;
1506- showSuccess (
1507- mode === "save-and-open"
1508- ? "Saved the current workspace and opened the selected draft"
1509- : "Opened the selected draft in the workspace" ,
1510- ) ;
1511- } catch {
1512- showError ( "Failed to open the selected draft" ) ;
1513- }
1514- } ,
1515- [
1516- applyLoadedDraft ,
1517- handleSaveDraft ,
1518- pendingDraftOpen ,
1519- showError ,
1520- showSuccess ,
1521- ] ,
1522- ) ;
1350+ reassignRunbookSessionById,
1351+ reassignRunbookSessionScope,
1352+ logEvent,
1353+ setWorkspaceRunbookScopeKey,
1354+ setRunbookSessionSourceScopeKey,
1355+ setAutosaveDraftId,
1356+ setSavedDraftId,
1357+ setSavedDraftCreatedAt,
1358+ pendingDraftOpen,
1359+ setPendingDraftOpen,
1360+ applyLoadedDraft,
1361+ pendingSimilarCaseOpen,
1362+ setPendingSimilarCaseOpen,
1363+ loadSimilarCaseIntoWorkspace,
1364+ setCompareCase,
1365+ onShowSuccess : showSuccess ,
1366+ onShowError : showError ,
1367+ } ) ;
15231368
15241369 // Load initial draft if provided
15251370 useEffect ( ( ) => {
0 commit comments