66 useImperativeHandle ,
77 useMemo ,
88} from "react" ;
9- import { invoke } from "@tauri-apps/api/core" ;
109import { DraftResponsePanel } from "./DraftResponsePanel" ;
1110import { InputPanel } from "./InputPanel" ;
1211import { DiagnosisPanel , TreeResult } from "./DiagnosisPanel" ;
@@ -18,6 +17,7 @@ import { useDraftGeneration } from "./useDraftGeneration";
1817import { useDraftIntake } from "./useDraftIntake" ;
1918import { useDraftPersistence } from "./useDraftPersistence" ;
2019import { useGuidedRunbook } from "./useGuidedRunbook" ;
20+ import { useResponseActions } from "./useResponseActions" ;
2121import { useWorkspaceArtifacts } from "./useWorkspaceArtifacts" ;
2222import { useWorkspaceClipboardPacks } from "./useWorkspaceClipboardPacks" ;
2323import { ConversationInput } from "./ConversationInput" ;
@@ -46,10 +46,7 @@ import {
4646 compactLines ,
4747 parseCaseIntake ,
4848} from "../../features/workspace/workspaceAssistant" ;
49- import {
50- calculateEditRatio ,
51- countWords ,
52- } from "../../features/analytics/qualityMetrics" ;
49+ import { countWords } from "../../features/analytics/qualityMetrics" ;
5350import type { JiraTicket } from "../../hooks/useJira" ;
5451import type {
5552 ConfidenceAssessment ,
@@ -339,10 +336,6 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
339336 } = useAlternatives ( ) ;
340337 const { suggestions, findSimilar, saveAsTemplate, incrementUsage } =
341338 useSavedResponses ( ) ;
342- const [ showTemplateModal , setShowTemplateModal ] = useState ( false ) ;
343- const [ templateModalRating , setTemplateModalRating ] = useState <
344- number | undefined
345- > ( undefined ) ;
346339 const [ suggestionsDismissed , setSuggestionsDismissed ] = useState ( false ) ;
347340
348341 const {
@@ -531,43 +524,37 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
531524 [ savedDraftId ] ,
532525 ) ;
533526
534- const handleApplyTemplate = useCallback ( ( content : string ) => {
535- setResponse ( content ) ;
536- } , [ ] ) ;
537-
538- const handleSaveAsTemplate = useCallback ( ( rating : number ) => {
539- setTemplateModalRating ( rating ) ;
540- setShowTemplateModal ( true ) ;
541- } , [ ] ) ;
542-
543- const handleTemplateModalSave = useCallback (
544- async (
545- name : string ,
546- category : string | null ,
547- content : string ,
548- variablesJson : string | null ,
549- ) : Promise < boolean > => {
550- const id = await saveAsTemplate ( name , content , {
551- sourceDraftId : savedDraftId ?? undefined ,
552- sourceRating : templateModalRating ,
553- category : category ?? undefined ,
554- variablesJson : variablesJson ?? undefined ,
555- } ) ;
556- if ( id ) {
557- showSuccess ( "Response saved as template" ) ;
558- return true ;
559- }
560- showError ( "Failed to save template" ) ;
561- return false ;
562- } ,
563- [
564- saveAsTemplate ,
565- savedDraftId ,
566- templateModalRating ,
567- showSuccess ,
568- showError ,
569- ] ,
570- ) ;
527+ const {
528+ showTemplateModal,
529+ setShowTemplateModal,
530+ templateModalRating,
531+ handleApplyTemplate,
532+ handleSaveAsTemplate,
533+ handleTemplateModalSave,
534+ handleResponseChange,
535+ handleCancel,
536+ handleCopyResponse,
537+ handleExportResponse,
538+ resetResponseActions,
539+ } = useResponseActions ( {
540+ response,
541+ originalResponse,
542+ isResponseEdited,
543+ confidence,
544+ sources,
545+ savedDraftId,
546+ streamingText,
547+ cancelGeneration,
548+ saveAsTemplate,
549+ logEvent,
550+ setResponse,
551+ setOriginalResponse,
552+ setIsResponseEdited,
553+ setGenerating,
554+ setHandoffTouched,
555+ onShowSuccess : showSuccess ,
556+ onShowError : showError ,
557+ } ) ;
571558
572559 const handleSuggestionApply = useCallback (
573560 ( content : string , templateId : string ) => {
@@ -620,8 +607,7 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
620607 setSavedDraftCreatedAt ( null ) ;
621608 setConversationEntries ( [ ] ) ;
622609 setHandoffTouched ( false ) ;
623- setShowTemplateModal ( false ) ;
624- setTemplateModalRating ( undefined ) ;
610+ resetResponseActions ( ) ;
625611 setSuggestionsDismissed ( false ) ;
626612 setCaseIntake ( {
627613 ...parseCaseIntake ( null ) ,
@@ -638,14 +624,6 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
638624 resetGeneration ( ) ;
639625 } , [ workspacePersonalization . preferred_note_audience , resetGeneration ] ) ;
640626
641- const handleResponseChange = useCallback (
642- ( text : string ) => {
643- setResponse ( text ) ;
644- setIsResponseEdited ( text !== originalResponse ) ;
645- } ,
646- [ originalResponse ] ,
647- ) ;
648-
649627 const handleTreeComplete = useCallback ( ( result : TreeResult ) => {
650628 setTreeResult ( result ) ;
651629 } , [ ] ) ;
@@ -724,17 +702,6 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
724702 [ modelLoaded , responseLength , generateStreaming , clearStreamingText ] ,
725703 ) ;
726704
727- const handleCancel = useCallback ( async ( ) => {
728- await cancelGeneration ( ) ;
729- setGenerating ( false ) ;
730- // Keep the streaming text that was generated so far
731- if ( streamingText ) {
732- setResponse ( streamingText ) ;
733- setOriginalResponse ( streamingText ) ;
734- setIsResponseEdited ( false ) ;
735- }
736- } , [ cancelGeneration , streamingText ] ) ;
737-
738705 useEffect ( ( ) => {
739706 if ( viewMode !== "panels" ) {
740707 return ;
@@ -1191,73 +1158,6 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
11911158 loadTemplates ( ) ;
11921159 } , [ loadTemplates ] ) ;
11931160
1194- const handleCopyResponse = useCallback ( async ( ) => {
1195- if ( ! response ) return ;
1196- try {
1197- const mode = confidence ?. mode ?? "answer" ;
1198- const hasCitations = sources . length > 0 ;
1199- const copyAllowed = mode === "answer" && hasCitations ;
1200-
1201- if ( ! copyAllowed ) {
1202- const reason = window . prompt (
1203- "Copy override required. This response is missing citations or is not in answer mode.\n\nEnter a reason to proceed (will be logged locally):" ,
1204- ) ;
1205- if ( ! reason || ! reason . trim ( ) ) {
1206- showError ( "Copy cancelled (reason required)." ) ;
1207- return ;
1208- }
1209- await invoke ( "audit_response_copy_override" , {
1210- reason : reason . trim ( ) ,
1211- confidenceMode : confidence ?. mode ?? null ,
1212- sourcesCount : sources . length ,
1213- } ) ;
1214- }
1215- await navigator . clipboard . writeText ( response ) ;
1216- setHandoffTouched ( true ) ;
1217- logEvent ( "response_copied" , {
1218- draft_id : savedDraftId ,
1219- word_count : countWords ( response ) ,
1220- is_edited : isResponseEdited ,
1221- edit_ratio : Number (
1222- calculateEditRatio ( originalResponse , response ) . toFixed ( 3 ) ,
1223- ) ,
1224- } ) ;
1225- showSuccess ( "Response copied to clipboard" ) ;
1226- } catch {
1227- showError ( "Failed to copy response" ) ;
1228- }
1229- } , [
1230- response ,
1231- confidence ?. mode ,
1232- sources . length ,
1233- showSuccess ,
1234- showError ,
1235- logEvent ,
1236- savedDraftId ,
1237- isResponseEdited ,
1238- originalResponse ,
1239- setHandoffTouched ,
1240- ] ) ;
1241-
1242- const handleExportResponse = useCallback ( async ( ) => {
1243- if ( ! response ) {
1244- showError ( "No response to export" ) ;
1245- return ;
1246- }
1247- try {
1248- const saved = await invoke < boolean > ( "export_draft" , {
1249- responseText : response ,
1250- format : "Markdown" ,
1251- } ) ;
1252- if ( saved ) {
1253- setHandoffTouched ( true ) ;
1254- showSuccess ( "Response exported successfully" ) ;
1255- }
1256- } catch ( e ) {
1257- showError ( `Export failed: ${ e } ` ) ;
1258- }
1259- } , [ response , showSuccess , showError , setHandoffTouched ] ) ;
1260-
12611161 // Expose functions to parent via ref
12621162 useImperativeHandle (
12631163 ref ,
0 commit comments