@actuate-media/cms-admin 0.35.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -93,12 +93,32 @@ export interface AiUsageView {
93
93
  runCount: number
94
94
  }
95
95
 
96
+ /** Editable brand voice fields (the buffered Save-bar portion). */
97
+ export interface BrandVoiceForm {
98
+ enabled: boolean
99
+ name: string
100
+ tone: string
101
+ audience: string
102
+ guidelines: string
103
+ sampleText: string
104
+ }
105
+
106
+ const EMPTY_BRAND_VOICE: BrandVoiceForm = {
107
+ enabled: false,
108
+ name: '',
109
+ tone: '',
110
+ audience: '',
111
+ guidelines: '',
112
+ sampleText: '',
113
+ }
114
+
96
115
  /** The buffered (Save-bar) portion of AI settings. */
97
116
  export interface AiSettingsForm {
98
117
  defaultProviderId: string
99
118
  defaultModelId: string
100
119
  features: AiFeatureView[]
101
120
  monthlyTokenLimit: string
121
+ brandVoice: BrandVoiceForm
102
122
  }
103
123
 
104
124
  interface AiSettingsResponse {
@@ -107,6 +127,14 @@ interface AiSettingsResponse {
107
127
  defaultModelId?: string
108
128
  features: AiFeatureView[]
109
129
  budget: { monthlyTokenLimit?: number }
130
+ brandVoice?: {
131
+ enabled?: boolean
132
+ name?: string
133
+ tone?: string
134
+ audience?: string
135
+ guidelines?: string
136
+ sampleText?: string
137
+ }
110
138
  }
111
139
  providers: AiProviderView[]
112
140
  defaultModel: AiModelView | null
@@ -127,6 +155,14 @@ function formFromResponse(res: AiSettingsResponse): AiSettingsForm {
127
155
  typeof res.settings.budget.monthlyTokenLimit === 'number'
128
156
  ? String(res.settings.budget.monthlyTokenLimit)
129
157
  : '',
158
+ brandVoice: {
159
+ enabled: res.settings.brandVoice?.enabled ?? false,
160
+ name: res.settings.brandVoice?.name ?? '',
161
+ tone: res.settings.brandVoice?.tone ?? '',
162
+ audience: res.settings.brandVoice?.audience ?? '',
163
+ guidelines: res.settings.brandVoice?.guidelines ?? '',
164
+ sampleText: res.settings.brandVoice?.sampleText ?? '',
165
+ },
130
166
  }
131
167
  }
132
168
 
@@ -135,6 +171,19 @@ const EMPTY_FORM: AiSettingsForm = {
135
171
  defaultModelId: '',
136
172
  features: [],
137
173
  monthlyTokenLimit: '',
174
+ brandVoice: { ...EMPTY_BRAND_VOICE },
175
+ }
176
+
177
+ /** A brand voice with no content and disabled clears the stored profile (send null). */
178
+ function brandVoiceIsEmpty(bv: BrandVoiceForm): boolean {
179
+ return (
180
+ !bv.enabled &&
181
+ !bv.name.trim() &&
182
+ !bv.tone.trim() &&
183
+ !bv.audience.trim() &&
184
+ !bv.guidelines.trim() &&
185
+ !bv.sampleText.trim()
186
+ )
138
187
  }
139
188
 
140
189
  export interface ProviderInput {
@@ -162,6 +211,8 @@ export interface UseAiSettings {
162
211
  toggleFeature: (key: string, enabled: boolean) => void
163
212
  /** Set a per-feature model override. Empty string reverts to the default model. */
164
213
  setFeatureModel: (key: string, modelId: string) => void
214
+ /** Patch one or more brand voice fields. */
215
+ setBrandVoice: (patch: Partial<BrandVoiceForm>) => void
165
216
  dirty: boolean
166
217
  budgetError: string | null
167
218
  saveState: SaveState
@@ -273,6 +324,11 @@ export function useAiSettings(canEdit: boolean): UseAiSettings {
273
324
  setSaveState('idle')
274
325
  }, [])
275
326
 
327
+ const setBrandVoice = useCallback((patch: Partial<BrandVoiceForm>) => {
328
+ setForm((prev) => ({ ...prev, brandVoice: { ...prev.brandVoice, ...patch } }))
329
+ setSaveState('idle')
330
+ }, [])
331
+
276
332
  const budgetError = useMemo(() => {
277
333
  if (form.monthlyTokenLimit === '') return null
278
334
  const n = Number(form.monthlyTokenLimit)
@@ -285,6 +341,15 @@ export function useAiSettings(canEdit: boolean): UseAiSettings {
285
341
  if (form.defaultModelId !== baseline.defaultModelId) return true
286
342
  if (form.monthlyTokenLimit !== baseline.monthlyTokenLimit) return true
287
343
  if (form.features.length !== baseline.features.length) return true
344
+ const bvKeys: (keyof BrandVoiceForm)[] = [
345
+ 'enabled',
346
+ 'name',
347
+ 'tone',
348
+ 'audience',
349
+ 'guidelines',
350
+ 'sampleText',
351
+ ]
352
+ if (bvKeys.some((k) => form.brandVoice[k] !== baseline.brandVoice[k])) return true
288
353
  return form.features.some((f, i) => {
289
354
  const base = baseline.features[i]
290
355
  return f.enabled !== base?.enabled || (f.modelId ?? '') !== (base?.modelId ?? '')
@@ -310,6 +375,16 @@ export function useAiSettings(canEdit: boolean): UseAiSettings {
310
375
  monthlyTokenLimit:
311
376
  form.monthlyTokenLimit === '' ? null : Number(form.monthlyTokenLimit),
312
377
  },
378
+ brandVoice: brandVoiceIsEmpty(form.brandVoice)
379
+ ? null
380
+ : {
381
+ enabled: form.brandVoice.enabled,
382
+ name: form.brandVoice.name,
383
+ tone: form.brandVoice.tone,
384
+ audience: form.brandVoice.audience,
385
+ guidelines: form.brandVoice.guidelines,
386
+ sampleText: form.brandVoice.sampleText,
387
+ },
313
388
  }),
314
389
  })
315
390
  if (res.error) throw new Error(res.error)
@@ -459,6 +534,7 @@ export function useAiSettings(canEdit: boolean): UseAiSettings {
459
534
  setMonthlyTokenLimit,
460
535
  toggleFeature,
461
536
  setFeatureModel,
537
+ setBrandVoice,
462
538
  dirty,
463
539
  budgetError,
464
540
  saveState,