Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 41 additions & 275 deletions src/components/Draft/DraftTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import { useDraftFirstResponse } from "./useDraftFirstResponse";
import { useDraftGeneration } from "./useDraftGeneration";
import { useDraftIntake } from "./useDraftIntake";
import { useGuidedRunbook } from "./useGuidedRunbook";
import { useWorkspaceClipboardPacks } from "./useWorkspaceClipboardPacks";
import { ConversationInput } from "./ConversationInput";
import { WorkspaceDialogs } from "./WorkspaceDialogs";
import { WorkspaceModeShell } from "./WorkspaceModeShell";
Expand Down Expand Up @@ -43,9 +45,6 @@
buildResolutionKitFromWorkspace,
buildSimilarCases,
compactLines,
formatEvidencePackForClipboard,
formatHandoffPackForClipboard,
formatKbDraftForClipboard,
parseCaseIntake,
} from "../../features/workspace/workspaceAssistant";
import {
Expand Down Expand Up @@ -337,7 +336,6 @@
const [similarCases, setSimilarCases] = useState<SimilarCase[]>([]);
const [similarCasesLoading, setSimilarCasesLoading] = useState(false);
const [compareCase, setCompareCase] = useState<SimilarCase | null>(null);
const [guidedRunbookNote, setGuidedRunbookNote] = useState("");
const [workspaceRunbookScopeKey, setWorkspaceRunbookScopeKey] =
useState<string>(createWorkspaceRunbookScopeKey);
const [autosaveDraftId, setAutosaveDraftId] = useState<string | null>(null);
Expand Down Expand Up @@ -488,6 +486,31 @@
setWorkspacePersonalization,
});

const {
guidedRunbookNote,
setGuidedRunbookNote,
handleStartGuidedRunbook,
handleAdvanceGuidedRunbook,
handleCopyRunbookProgressToNotes,
handleGuidedRunbookNoteChange,
} = useGuidedRunbook({
runbookTemplates,
guidedRunbookSession,
workspaceRunbookScopeKey,
currentTicketId,
startRunbookSession,
addRunbookStepEvidence,
advanceRunbookSession,
refreshWorkspaceCatalog,
logEvent,
setDiagnosticNotes,
setPanelDensityMode,
setRunbookSessionSourceScopeKey,
setRunbookSessionTouched,
onShowSuccess: showSuccess,
onShowError: showError,
});

const handleResponseLengthChange = useCallback((length: ResponseLength) => {
setResponseLength(length);
setWorkspacePersonalization((prev) => ({
Expand Down Expand Up @@ -515,7 +538,7 @@
return next;
});
},
[savedDraftId],

Check warning on line 541 in src/components/Draft/DraftTab.tsx

View workflow job for this annotation

GitHub Actions / quality-gates

React Hook useCallback has a missing dependency: 'setCaseIntake'. Either include it or remove the dependency array
);

const handleRefreshSimilarCases = useCallback(async () => {
Expand Down Expand Up @@ -688,7 +711,7 @@
setPendingSimilarCaseOpen(null);
setWorkspaceRunbookScopeKey(createWorkspaceRunbookScopeKey());
resetGeneration();
}, [workspacePersonalization.preferred_note_audience, resetGeneration]);

Check warning on line 714 in src/components/Draft/DraftTab.tsx

View workflow job for this annotation

GitHub Actions / quality-gates

React Hook useCallback has missing dependencies: 'resetApproval', 'resetChecklist', 'resetFirstResponse', 'setCaseIntake', 'setGuidedRunbookNote', 'setGuidedRunbookSession', 'setPendingSimilarCaseOpen', 'setRunbookSessionSourceScopeKey', and 'setRunbookSessionTouched'. Either include them or remove the dependency array

const handleResponseChange = useCallback(
(text: string) => {
Expand Down Expand Up @@ -773,7 +796,7 @@
setGenerating(false);
}
},
[modelLoaded, responseLength, generateStreaming, clearStreamingText],

Check warning on line 799 in src/components/Draft/DraftTab.tsx

View workflow job for this annotation

GitHub Actions / quality-gates

React Hook useCallback has a missing dependency: 'setGenerating'. Either include it or remove the dependency array
);

const handleCancel = useCallback(async () => {
Expand All @@ -785,7 +808,7 @@
setOriginalResponse(streamingText);
setIsResponseEdited(false);
}
}, [cancelGeneration, streamingText]);

Check warning on line 811 in src/components/Draft/DraftTab.tsx

View workflow job for this annotation

GitHub Actions / quality-gates

React Hook useCallback has a missing dependency: 'setGenerating'. Either include it or remove the dependency array

useEffect(() => {
if (viewMode !== "panels") {
Expand Down Expand Up @@ -1026,116 +1049,20 @@
setOcrText,
});

const handleCopyHandoffPack = useCallback(async () => {
try {
await navigator.clipboard.writeText(
formatHandoffPackForClipboard(handoffPack),
);
setHandoffTouched(true);
if (savedDraftId) {
await saveCaseOutcome({
draft_id: savedDraftId,
status: "handoff-ready",
outcome_summary: handoffPack.summary,
handoff_pack_json: JSON.stringify(handoffPack),
kb_draft_json: JSON.stringify(kbDraft),
evidence_pack_json: JSON.stringify(evidencePack),
tags_json: JSON.stringify(
[caseIntake.likely_category].filter(Boolean),
),
});
}
void logEvent("workspace_handoff_pack_copied", {
ticket_id: currentTicketId,
note_audience: caseIntake.note_audience,
});
showSuccess("Handoff pack copied");
} catch {
showError("Failed to copy handoff pack");
}
}, [
handoffPack,
savedDraftId,
saveCaseOutcome,
kbDraft,
evidencePack,
caseIntake.likely_category,
logEvent,
currentTicketId,
caseIntake.note_audience,
showSuccess,
showError,
]);

const handleCopyEvidencePack = useCallback(async () => {
try {
await navigator.clipboard.writeText(
formatEvidencePackForClipboard(evidencePack),
);
if (savedDraftId) {
await saveCaseOutcome({
draft_id: savedDraftId,
status: "evidence-ready",
outcome_summary: evidencePack.summary,
handoff_pack_json: JSON.stringify(handoffPack),
kb_draft_json: JSON.stringify(kbDraft),
evidence_pack_json: JSON.stringify(evidencePack),
tags_json: JSON.stringify(kbDraft.tags),
});
}
void logEvent("workspace_evidence_pack_copied", {
ticket_id: currentTicketId,
});
showSuccess("Evidence pack copied");
} catch {
showError("Failed to copy evidence pack");
}
}, [
evidencePack,
savedDraftId,
saveCaseOutcome,
handoffPack,
kbDraft,
logEvent,
currentTicketId,
showSuccess,
showError,
]);

const handleCopyKbDraft = useCallback(async () => {
try {
await navigator.clipboard.writeText(formatKbDraftForClipboard(kbDraft));
if (savedDraftId) {
await saveCaseOutcome({
draft_id: savedDraftId,
status: "kb-promoted",
outcome_summary: kbDraft.summary,
handoff_pack_json: JSON.stringify(handoffPack),
kb_draft_json: JSON.stringify(kbDraft),
evidence_pack_json: JSON.stringify(evidencePack),
tags_json: JSON.stringify(kbDraft.tags),
});
}
void logEvent("workspace_kb_draft_copied", {
ticket_id: currentTicketId,
category: caseIntake.likely_category,
});
showSuccess("KB draft copied");
} catch {
showError("Failed to copy KB draft");
}
}, [
kbDraft,
saveCaseOutcome,
savedDraftId,
handoffPack,
evidencePack,
logEvent,
currentTicketId,
caseIntake.likely_category,
showSuccess,
showError,
]);
const { handleCopyHandoffPack, handleCopyEvidencePack, handleCopyKbDraft } =
useWorkspaceClipboardPacks({
handoffPack,
evidencePack,
kbDraft,
caseIntake,
savedDraftId,
currentTicketId,
saveCaseOutcome,
logEvent,
onHandoffCopied: () => setHandoffTouched(true),
onShowSuccess: showSuccess,
onShowError: showError,
});

const handleSaveCurrentResolutionKit = useCallback(async () => {
try {
Expand Down Expand Up @@ -1198,7 +1125,7 @@
});
showSuccess(`Applied ${kit.name}`);
},
[input, response, caseIntake, logEvent, currentTicketId, showSuccess],

Check warning on line 1128 in src/components/Draft/DraftTab.tsx

View workflow job for this annotation

GitHub Actions / quality-gates

React Hook useCallback has a missing dependency: 'setCaseIntake'. Either include it or remove the dependency array
);

const handleToggleWorkspaceFavorite = useCallback(
Expand Down Expand Up @@ -1247,167 +1174,6 @@
],
);

const handleStartGuidedRunbook = useCallback(
async (templateId: string) => {
const template = runbookTemplates.find(
(item) => item.id === templateId,
);
if (!template) {
showError("Choose a guided runbook template first");
return;
}
if (
guidedRunbookSession &&
guidedRunbookSession.status !== "completed"
) {
showError(
"Finish the current guided runbook before starting another one",
);
return;
}

try {
await startRunbookSession(
template.scenario,
template.steps,
workspaceRunbookScopeKey,
);
setGuidedRunbookNote("");
setRunbookSessionSourceScopeKey(workspaceRunbookScopeKey);
setRunbookSessionTouched(true);
await refreshWorkspaceCatalog();
setPanelDensityMode("focus-intake");
void logEvent("workspace_guided_runbook_started", {
ticket_id: currentTicketId,
template_id: template.id,
scenario: template.scenario,
});
showSuccess(`Started ${template.name}`);
} catch {
showError("Failed to start guided runbook");
}
},
[
runbookTemplates,
startRunbookSession,
refreshWorkspaceCatalog,
workspaceRunbookScopeKey,
guidedRunbookSession,
logEvent,
currentTicketId,
showSuccess,
showError,
],
);

const handleAdvanceGuidedRunbook = useCallback(
async (status: "completed" | "skipped" | "failed") => {
if (!guidedRunbookSession) {
showError("Start a guided runbook before updating a step");
return;
}

const currentStep = guidedRunbookSession.current_step;
const stepLabel =
guidedRunbookSession.steps[currentStep] ?? `Step ${currentStep + 1}`;
const noteText = guidedRunbookNote.trim();
const evidenceText = noteText || `${status} · ${stepLabel}`;
const skipReason =
status === "skipped"
? noteText || "Skipped from workspace"
: undefined;
const nextStep =
status === "failed"
? currentStep
: Math.min(
currentStep + 1,
Math.max(guidedRunbookSession.steps.length - 1, 0),
);
const nextStatus =
status === "failed"
? "paused"
: currentStep >= guidedRunbookSession.steps.length - 1
? "completed"
: "active";

try {
await addRunbookStepEvidence(
guidedRunbookSession.id,
currentStep,
status,
evidenceText,
skipReason,
);
await advanceRunbookSession(
guidedRunbookSession.id,
nextStep,
nextStatus,
);
setRunbookSessionTouched(true);
if (noteText) {
setDiagnosticNotes((prev) =>
compactLines([prev, `Runbook ${stepLabel}: ${noteText}`]),
);
}
setGuidedRunbookNote("");
await refreshWorkspaceCatalog();
void logEvent("workspace_guided_runbook_step_recorded", {
ticket_id: currentTicketId,
session_id: guidedRunbookSession.id,
step_index: currentStep,
status,
});
showSuccess(
status === "failed"
? `Paused the runbook at ${stepLabel}`
: nextStatus === "completed"
? "Guided runbook completed"
: `Recorded ${stepLabel}`,
);
} catch {
showError("Failed to update guided runbook progress");
}
},
[
guidedRunbookSession,
guidedRunbookNote,
addRunbookStepEvidence,
advanceRunbookSession,
refreshWorkspaceCatalog,
currentTicketId,
logEvent,
showSuccess,
showError,
],
);

const handleCopyRunbookProgressToNotes = useCallback(() => {
if (!guidedRunbookSession || guidedRunbookSession.evidence.length === 0) {
showError("No guided runbook progress to copy yet");
return;
}

const progressText = compactLines([
`Guided runbook: ${guidedRunbookSession.scenario}`,
...guidedRunbookSession.evidence.map((item) => {
const stepLabel =
guidedRunbookSession.steps[item.step_index] ??
`Step ${item.step_index + 1}`;
return `- ${stepLabel}: ${item.status}${item.evidence_text ? ` · ${item.evidence_text}` : ""}`;
}),
]);

setDiagnosticNotes((prev) => compactLines([prev, progressText]));
showSuccess("Copied guided runbook progress into the notes");
}, [guidedRunbookSession, showError, showSuccess]);

const handleGuidedRunbookNoteChange = useCallback((value: string) => {
setGuidedRunbookNote(value);
if (value.trim()) {
setRunbookSessionTouched(true);
}
}, []);

const loadSimilarCaseIntoWorkspace = useCallback(
async (similarCase: SimilarCase) => {
const fullDraft = await getDraft(similarCase.draft_id);
Expand Down Expand Up @@ -1438,7 +1204,7 @@
showError("Failed to open similar case");
}
},
[

Check warning on line 1207 in src/components/Draft/DraftTab.tsx

View workflow job for this annotation

GitHub Actions / quality-gates

React Hook useCallback has a missing dependency: 'setPendingSimilarCaseOpen'. Either include it or remove the dependency array
loadSimilarCaseIntoWorkspace,
logEvent,
currentTicketId,
Expand Down Expand Up @@ -1532,7 +1298,7 @@

void handleCopyKbDraft();
},
[

Check warning on line 1301 in src/components/Draft/DraftTab.tsx

View workflow job for this annotation

GitHub Actions / quality-gates

React Hook useCallback has missing dependencies: 'setApprovalQuery' and 'setCaseIntake'. Either include them or remove the dependency array
logEvent,
currentTicketId,
handleGenerate,
Expand Down Expand Up @@ -1644,7 +1410,7 @@
return draftId;
}
return null;
}, [

Check warning on line 1413 in src/components/Draft/DraftTab.tsx

View workflow job for this annotation

GitHub Actions / quality-gates

React Hook useCallback has a missing dependency: 'setRunbookSessionSourceScopeKey'. Either include it or remove the dependency array
activeWorkspaceDraft.updated_at,
buildDiagnosisJson,
currentTicket?.summary,
Expand Down Expand Up @@ -1710,7 +1476,7 @@
showError("Failed to open the saved case");
}
},
[

Check warning on line 1479 in src/components/Draft/DraftTab.tsx

View workflow job for this annotation

GitHub Actions / quality-gates

React Hook useCallback has a missing dependency: 'setPendingSimilarCaseOpen'. Either include it or remove the dependency array
currentTicketId,
handleSaveDraft,
loadSimilarCaseIntoWorkspace,
Expand Down Expand Up @@ -1746,7 +1512,7 @@
showError("Failed to open the selected draft");
}
},
[

Check warning on line 1515 in src/components/Draft/DraftTab.tsx

View workflow job for this annotation

GitHub Actions / quality-gates

React Hook useCallback has a missing dependency: 'setPendingDraftOpen'. Either include it or remove the dependency array
applyLoadedDraft,
handleSaveDraft,
pendingDraftOpen,
Expand Down
Loading
Loading