@@ -61,6 +61,25 @@ const QUESTION_SCHEMA = Joi.object({
6161 importTime : Joi . string ( ) . isoDate ( ) . optional ( ) ,
6262} ) ;
6363
64+ // AI Topics configuration schemas
65+ const AI_PROMPT_SCHEMA = Joi . object ( {
66+ prompt : Joi . string ( ) . min ( 1 ) . required ( ) ,
67+ regions : Joi . array ( ) . items ( Joi . string ( ) . pattern ( / ^ [ a - z ] { 2 } $ / ) ) . required ( ) ,
68+ origin : Joi . string ( ) . allow ( 'human' , 'ai' , '' ) . optional ( ) ,
69+ source : Joi . string ( ) . optional ( ) , // Allows any source. Active: 'flow' (created by flow). Schema-defined: 'config', 'api'. Tests: 'manual', 'ahrefs'
70+ } ) . unknown ( true ) ;
71+
72+ const AI_TOPIC_SCHEMA = Joi . object ( {
73+ name : Joi . string ( ) . min ( 1 ) . required ( ) ,
74+ prompts : Joi . array ( ) . items ( AI_PROMPT_SCHEMA ) . min ( 1 ) . required ( ) ,
75+ category : Joi . string ( ) . min ( 1 ) . optional ( ) ,
76+ } ) . unknown ( true ) ;
77+
78+ const AI_TOPICS_CONFIG_SCHEMA = Joi . object ( ) . pattern (
79+ Joi . string ( ) . uuid ( ) ,
80+ AI_TOPIC_SCHEMA ,
81+ ) ;
82+
6483const LLMO_URL_PATTERN_SCHEMA = {
6584 urlPattern : Joi . string ( ) . uri ( ) . required ( ) ,
6685 tags : Joi . array ( ) . items ( LLMO_TAG ) . optional ( ) ,
@@ -311,6 +330,7 @@ export const configSchema = Joi.object({
311330 contentAiConfig : Joi . object ( {
312331 index : Joi . string ( ) . optional ( ) ,
313332 } ) . optional ( ) ,
333+ aiTopics : AI_TOPICS_CONFIG_SCHEMA . optional ( ) ,
314334 handlers : Joi . object ( ) . pattern ( Joi . string ( ) , Joi . object ( {
315335 mentions : Joi . object ( ) . pattern ( Joi . string ( ) , Joi . array ( ) . items ( Joi . string ( ) ) ) ,
316336 excludedURLs : Joi . array ( ) . items ( Joi . string ( ) ) ,
@@ -342,6 +362,13 @@ export const DEFAULT_CONFIG = {
342362 handlers : { } ,
343363} ;
344364
365+ // Export AI Topics schemas for external validation
366+ export const AI_TOPICS_SCHEMAS = {
367+ AI_PROMPT_SCHEMA ,
368+ AI_TOPIC_SCHEMA ,
369+ AI_TOPICS_CONFIG_SCHEMA ,
370+ } ;
371+
345372// Function to validate incoming configuration
346373export function validateConfiguration ( config ) {
347374 const { error, value } = configSchema . validate ( config ) ;
@@ -390,6 +417,7 @@ export const Config = (data = {}) => {
390417 self . getSlackMentions = ( type ) => state ?. handlers ?. [ type ] ?. mentions ?. slack ;
391418 self . getHandlerConfig = ( type ) => state ?. handlers ?. [ type ] ;
392419 self . getContentAiConfig = ( ) => state ?. contentAiConfig ;
420+ self . getAiTopics = ( ) => state ?. aiTopics ;
393421 self . getHandlers = ( ) => state . handlers ;
394422 self . getImports = ( ) => state . imports ;
395423 self . getExcludedURLs = ( type ) => state ?. handlers ?. [ type ] ?. excludedURLs ;
@@ -661,6 +689,47 @@ export const Config = (data = {}) => {
661689 state . cdnLogsConfig = cdnLogsConfig ;
662690 } ;
663691
692+ self . updateAiTopics = ( aiTopics ) => {
693+ state . aiTopics = aiTopics ;
694+ validateConfiguration ( state ) ;
695+ } ;
696+
697+ self . addAiTopic = ( topicId , topic ) => {
698+ // Validate the topic
699+ const { error } = AI_TOPIC_SCHEMA . validate ( topic ) ;
700+ if ( error ) {
701+ throw new Error ( `Invalid AI topic: ${ error . message } ` ) ;
702+ }
703+
704+ state . aiTopics = state . aiTopics || { } ;
705+ state . aiTopics [ topicId ] = topic ;
706+ validateConfiguration ( state ) ;
707+ } ;
708+
709+ self . removeAiTopic = ( topicId ) => {
710+ if ( ! state . aiTopics ) return ;
711+
712+ delete state . aiTopics [ topicId ] ;
713+ validateConfiguration ( state ) ;
714+ } ;
715+
716+ self . updateAiTopic = ( topicId , topicUpdate ) => {
717+ if ( ! state . aiTopics || ! state . aiTopics [ topicId ] ) {
718+ throw new Error ( `AI topic ${ topicId } not found` ) ;
719+ }
720+
721+ const updatedTopic = { ...state . aiTopics [ topicId ] , ...topicUpdate } ;
722+
723+ // Validate the updated topic
724+ const { error } = AI_TOPIC_SCHEMA . validate ( updatedTopic ) ;
725+ if ( error ) {
726+ throw new Error ( `Invalid AI topic update: ${ error . message } ` ) ;
727+ }
728+
729+ state . aiTopics [ topicId ] = updatedTopic ;
730+ validateConfiguration ( state ) ;
731+ } ;
732+
664733 self . updateTokowakaConfig = ( tokowakaConfig ) => {
665734 state . tokowakaConfig = tokowakaConfig ;
666735 } ;
@@ -674,6 +743,7 @@ Config.toDynamoItem = (config) => ({
674743 slack : config . getSlackConfig ( ) ,
675744 handlers : config . getHandlers ( ) ,
676745 contentAiConfig : config . getContentAiConfig ( ) ,
746+ aiTopics : config . getAiTopics ( ) ,
677747 imports : config . getImports ( ) ,
678748 fetchConfig : config . getFetchConfig ( ) ,
679749 brandConfig : config . getBrandConfig ( ) ,
0 commit comments