feat(pilot): Phase 6 — post-resolve templatize prompt + draft accept/reject
All checks were successful
Mirror to GitHub / mirror (push) Successful in 11s
All checks were successful
Mirror to GitHub / mirror (push) Successful in 11s
Closes the loop on the Phase 5 "Run now, templatize after resolve" path.
After a session resolves, drafts queued by the three-option dialog surface
as a modal that lets the engineer review the AI-proposed parameterization
and either save as a reusable team template or skip. A "don't ask again"
toggle writes to account_settings.preferences so the next resolve won't
pop the modal.
Backend:
- /api/v1/draft-templates:
* GET — list account drafts (pending_only default true; pass false for
audit view including accepted/rejected)
* GET /{id} — single draft
* POST /{id}/accept — promotes to a new script_templates row with
source_session_id / source_user_id / source_ticket_ref populated
(drives the Script Library "generated from CW #X · resolved by Y"
provenance chip). Draft flips to status=accepted,
promoted_template_id set, resolved_at stamped. 409 on re-accept /
already-rejected. 400 on unknown category_id.
* POST /{id}/reject — flips to status=rejected. 409 on re-reject.
- /api/v1/accounts/me/preferences (GET/PATCH) — thin wrapper over
AccountSettings.get_setting/set_setting. PATCH merges keys into the
JSONB column, preserving existing keys the client didn't touch.
Used by the "Don't ask again for this team" checkbox
(templatize_prompt_enabled=false) and, forward-looking, by
cw_resolved_status_id / cw_escalated_status_id from Phase 4.
- 13 tests: list filter, accept with/without edited_body, provenance
copy-through, reject, 409 on re-accept / re-reject, 400 on unknown
category, prefs round-trip with merge semantics.
Frontend:
- src/components/pilot/script/TemplatizePrompt.tsx — modal showing the
drafted script with proposed parameters in the Phase 5
ParameterizationPreview, editable name/category/description, an
individual-parameter remove button, and the "don't ask again" opt-out.
Accept posts to /draft-templates/{id}/accept + optionally PATCHes
preferences. Skip posts /reject.
- src/api/draftTemplates.ts — typed client plus accountPreferencesApi.
- AssistantChatPage: after a successful Resolve (external OR local),
fetches preferences + pending drafts for the session and queues the
modal one draft at a time. Escalate does not trigger this flow.
- Sidebar: Scripts nav shows the pending-draft count as a badge. Fetched
independently of the main sidebar stats so endpoint flakes don't
break the rest of the sidebar.
Verified live 2026-04-22: seed two drafts → GET sees both pending →
accept draft A (template created, provenance CW #99123 populated) →
reject draft B → pending count drops → PATCH opt-out → GET confirms
persistence.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
98
frontend/src/api/draftTemplates.ts
Normal file
98
frontend/src/api/draftTemplates.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Draft templates API — Phase 6 post-resolve templatization flow.
|
||||
*
|
||||
* A draft is produced when the engineer picks "Run now, templatize after
|
||||
* resolve" on the three-option dialog. After Resolve, the TemplatizePrompt
|
||||
* modal lists pending drafts and lets the engineer accept (→ real
|
||||
* script_templates row) or reject.
|
||||
*
|
||||
* Mirrors backend endpoints under /api/v1/draft-templates.
|
||||
*/
|
||||
import apiClient from './client'
|
||||
|
||||
export type DraftStatus = 'pending' | 'accepted' | 'rejected'
|
||||
|
||||
export interface DraftTemplate {
|
||||
id: string
|
||||
account_id: string
|
||||
source_session_id: string
|
||||
source_user_id: string
|
||||
script_body: string
|
||||
proposed_parameters: { parameters?: Array<Record<string, unknown>> } | Record<string, unknown>
|
||||
proposed_name: string | null
|
||||
proposed_category_id: string | null
|
||||
status: DraftStatus
|
||||
resolved_at: string | null
|
||||
promoted_template_id: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface DraftAcceptRequest {
|
||||
name: string
|
||||
category_id: string
|
||||
description?: string | null
|
||||
parameters_schema: { parameters: Array<Record<string, unknown>> } | Record<string, unknown>
|
||||
edited_body?: string | null
|
||||
}
|
||||
|
||||
export interface DraftAcceptResponse {
|
||||
draft_id: string
|
||||
promoted_template_id: string
|
||||
template_slug: string
|
||||
}
|
||||
|
||||
export interface DraftRejectResponse {
|
||||
draft_id: string
|
||||
status: 'rejected'
|
||||
}
|
||||
|
||||
export const draftTemplatesApi = {
|
||||
async list(pendingOnly = true): Promise<DraftTemplate[]> {
|
||||
const r = await apiClient.get<{ drafts: DraftTemplate[] }>('/draft-templates', {
|
||||
params: { pending_only: pendingOnly },
|
||||
})
|
||||
return r.data.drafts
|
||||
},
|
||||
|
||||
async get(id: string): Promise<DraftTemplate> {
|
||||
const r = await apiClient.get<DraftTemplate>(`/draft-templates/${id}`)
|
||||
return r.data
|
||||
},
|
||||
|
||||
async accept(id: string, data: DraftAcceptRequest): Promise<DraftAcceptResponse> {
|
||||
const r = await apiClient.post<DraftAcceptResponse>(
|
||||
`/draft-templates/${id}/accept`,
|
||||
data,
|
||||
)
|
||||
return r.data
|
||||
},
|
||||
|
||||
async reject(id: string): Promise<DraftRejectResponse> {
|
||||
const r = await apiClient.post<DraftRejectResponse>(
|
||||
`/draft-templates/${id}/reject`,
|
||||
)
|
||||
return r.data
|
||||
},
|
||||
}
|
||||
|
||||
// ── Account preferences (used by the "don't ask again" opt-out) ────────────
|
||||
|
||||
export interface AccountPreferences {
|
||||
preferences: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const accountPreferencesApi = {
|
||||
async get(): Promise<AccountPreferences> {
|
||||
const r = await apiClient.get<AccountPreferences>('/accounts/me/preferences')
|
||||
return r.data
|
||||
},
|
||||
|
||||
async update(patch: Record<string, unknown>): Promise<AccountPreferences> {
|
||||
const r = await apiClient.patch<AccountPreferences>('/accounts/me/preferences', {
|
||||
preferences: patch,
|
||||
})
|
||||
return r.data
|
||||
},
|
||||
}
|
||||
|
||||
export default draftTemplatesApi
|
||||
Reference in New Issue
Block a user