feat: add scriptGeneratorStore Zustand store

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-13 02:01:02 -04:00
parent b1c39f52be
commit 9d10aa90ad

View File

@@ -0,0 +1,180 @@
import { create } from 'zustand'
import { scriptsApi } from '@/api'
import type {
ScriptCategoryResponse,
ScriptTemplateListItem,
ScriptTemplateDetail,
ScriptParametersSchema,
} from '@/types'
interface ScriptGeneratorState {
// Template browsing
categories: ScriptCategoryResponse[]
templates: ScriptTemplateListItem[]
selectedTemplate: ScriptTemplateDetail | null
searchQuery: string
activeCategoryId: string | null // null = "All"
isLoadingTemplates: boolean // drives skeleton in ScriptTemplateList
isLoadingDetail: boolean // drives spinner in ScriptGeneratorPanel
// Form
paramValues: Record<string, string> // keyed by ScriptParameter.key; booleans as 'true'/'false'
formErrors: Record<string, string> // keyed by ScriptParameter.key
// Output
generatedScript: string | null
generationId: string | null
generationWarnings: string[]
isGenerating: boolean
generateError: string | null
// Actions
loadCategories: () => Promise<void>
loadTemplates: () => Promise<void>
selectTemplate: (id: string) => Promise<void>
setCategory: (id: string | null) => void
setSearch: (query: string) => void
setParamValue: (key: string, value: string) => void
validate: () => boolean
generate: (sessionId?: string) => Promise<void>
clearOutput: () => void
reset: () => void
}
export const useScriptGeneratorStore = create<ScriptGeneratorState>()((set, get) => ({
// Initial state
categories: [],
templates: [],
selectedTemplate: null,
searchQuery: '',
activeCategoryId: null,
isLoadingTemplates: false,
isLoadingDetail: false,
paramValues: {},
formErrors: {},
generatedScript: null,
generationId: null,
generationWarnings: [],
isGenerating: false,
generateError: null,
loadCategories: async () => {
const categories = await scriptsApi.getCategories()
set({ categories })
},
loadTemplates: async () => {
set({ isLoadingTemplates: true })
const { activeCategoryId, categories, searchQuery } = get()
const category = categories.find(c => c.id === activeCategoryId)
const params: { category_slug?: string; search?: string } = {}
if (category) params.category_slug = category.slug
if (searchQuery) params.search = searchQuery
const templates = await scriptsApi.getTemplates(params)
set({ templates, isLoadingTemplates: false })
},
selectTemplate: async (id: string) => {
set({ isLoadingDetail: true })
const detail = await scriptsApi.getTemplateDetail(id)
// Populate paramValues from parameter defaults
const schema = detail.parameters_schema as ScriptParametersSchema
const parameters = schema?.parameters ?? []
const paramValues: Record<string, string> = {}
for (const param of parameters) {
const d = param.default
if (d === null || d === undefined) paramValues[param.key] = ''
else if (typeof d === 'boolean') paramValues[param.key] = d ? 'true' : 'false'
else paramValues[param.key] = String(d)
}
set({
selectedTemplate: detail,
paramValues,
formErrors: {},
generatedScript: null,
generationId: null,
generationWarnings: [],
generateError: null,
isLoadingDetail: false,
})
},
setCategory: (id: string | null) => {
set({ activeCategoryId: id })
get().loadTemplates()
},
setSearch: (query: string) => {
set({ searchQuery: query })
get().loadTemplates()
},
setParamValue: (key: string, value: string) => {
set(state => ({
paramValues: { ...state.paramValues, [key]: value },
formErrors: { ...state.formErrors, [key]: '' }, // clear error on change
}))
},
validate: () => {
const { selectedTemplate, paramValues } = get()
if (!selectedTemplate) return true
const schema = selectedTemplate.parameters_schema as ScriptParametersSchema
const parameters = schema?.parameters ?? []
const errors: Record<string, string> = {}
for (const param of parameters) {
if (param.required && !paramValues[param.key]) {
errors[param.key] = `${param.label} is required`
}
}
set({ formErrors: errors })
return Object.keys(errors).length === 0
},
generate: async (sessionId?: string) => {
const { selectedTemplate, paramValues } = get()
if (!selectedTemplate) return
if (!get().validate()) return
set({ isGenerating: true, generateError: null })
try {
const response = await scriptsApi.generate({
template_id: selectedTemplate.id,
parameters: paramValues,
...(sessionId ? { session_id: sessionId } : {}),
})
set({
generatedScript: response.script,
generationId: response.id,
generationWarnings: response.warnings,
isGenerating: false,
})
} catch (error: unknown) {
const axiosErr = error as { response?: { data?: { detail?: string } } }
const message = axiosErr.response?.data?.detail ?? 'Failed to generate script'
set({ generateError: message, isGenerating: false })
}
},
clearOutput: () => {
set({
generatedScript: null,
generationId: null,
generationWarnings: [],
generateError: null,
})
},
// Exposed for Phase 3 callers (session execution context).
// Does NOT clear selectedTemplate, categories, templates, or browsing state.
reset: () => {
set({
paramValues: {},
formErrors: {},
generatedScript: null,
generationId: null,
generationWarnings: [],
generateError: null,
})
},
}))