1+ async function execute ( element : MacroApi . Context . IElementApi ) : Promise < void > {
2+ const providerModelsResult = await getAiProviderModels ( ) ;
3+ const settingName = "AI.ChatDrivenDomain" ;
4+
5+ // Open a dialog for the user to enter an AI prompt
6+ let promptResult = await dialogService . openForm ( {
7+ title : "Execute Prompt" ,
8+ fields : [
9+ {
10+ id : "prompt" ,
11+ fieldType : "textarea" ,
12+ label : "Enter AI prompt" ,
13+ hint : "NOTE: Intent Architect designer data will also be sent to the AI service provider."
14+ } ,
15+ ...await getAiModelSelectionFields ( providerModelsResult , settingName )
16+ ]
17+ } ) ;
18+
19+ // Check if the user cancelled or provided an empty prompt
20+ if ( ! promptResult || promptResult . prompt . trim ( ) === "" ) {
21+ return ;
22+ }
23+
24+ // Gather data about the current class model
25+ const currentClasses = lookupTypesOf ( "Class" ) . map ( clazz => {
26+ // Get all associations where this class is either source or target
27+ const associations = clazz . getAssociations ( ) . map ( assoc => {
28+ //console.log(`classId = ${clazz.id}; className = ${clazz.getName()}; assoc = {classId: ${assoc.typeReference.getTypeId()}, name: ${assoc.getName()}, isSourceEnd: ${assoc.isSourceEnd()}, isCollection: ${assoc.typeReference.isCollection}}`)
29+ // Determine if this class is the source or target of the association
30+ const isSource = assoc . isSourceEnd ( ) //assoc.getParent().id === clazz.id;
31+ const sourceEnd = isSource ? assoc : assoc . getOtherEnd ( ) ;
32+
33+ if ( isSource && ! sourceEnd . typeReference . isNavigable ) { return null ; }
34+
35+ const targetEnd = isSource ? assoc . getOtherEnd ( ) : assoc ;
36+
37+ // Get type references for both ends
38+ const sourceTypeRef = sourceEnd . typeReference ;
39+ const targetTypeRef = targetEnd . typeReference ;
40+
41+ // Build the relationship string (e.g., "1 -> *")
42+ const sourceMultiplicity = sourceTypeRef . getIsCollection ( ) ? "*" :
43+ sourceTypeRef . getIsNullable ( ) ? "0..1" : "1" ;
44+ const targetMultiplicity = targetTypeRef . getIsCollection ( ) ? "*" :
45+ targetTypeRef . getIsNullable ( ) ? "0..1" : "1" ;
46+ const relationship = `${ sourceMultiplicity } -> ${ targetMultiplicity } ` ;
47+
48+ return {
49+ id : assoc . id ,
50+ name : assoc . getName ( ) ,
51+ classId : assoc . typeReference . getTypeId ( ) ,
52+ type : relationship ,
53+ specializationEndType : isSource ? "Source End" : "Target End" ,
54+ // Add these for compatibility with the C# model
55+ relationship : relationship ,
56+ associationEndType : isSource ? "Source End" : "Target End" ,
57+ isNullable : assoc . typeReference . isNullable ,
58+ isCollection : assoc . typeReference . isCollection
59+ } ;
60+ } )
61+ . filter ( x => x != null ) ;
62+
63+ return {
64+ id : clazz . id ,
65+ name : clazz . getName ( ) ,
66+ comment : clazz . getComment ( ) ,
67+ attributes : clazz . getChildren ( "Attribute" )
68+ . filter ( attr => ! attr . hasMetadata ( "is-managed-key" ) && ! attr . hasMetadata ( "set-by-infrastructure" ) )
69+ . map ( attr => ( {
70+ id : attr . id ,
71+ name : attr . getName ( ) ,
72+ type : attr . typeReference ? attr . typeReference . getType ( ) . getName ( ) : null ,
73+ isNullable : attr . typeReference ? attr . typeReference . getIsNullable ( ) : false ,
74+ isCollection : attr . typeReference ? attr . typeReference . getIsCollection ( ) : false ,
75+ comment : attr . getComment ( )
76+ } ) ) ,
77+ associations : associations
78+ } ;
79+ } ) ;
80+
81+ const { providerId, modelId, thinkingLevel : thinkingLevel } = await collectAndPersistAiSettingsFromPromptResult (
82+ promptResult , providerModelsResult , settingName ) ;
83+
84+ const input = { prompt : promptResult . prompt , classes : currentClasses , providerId : providerId , modelId : modelId , thinkingLevel : thinkingLevel } ;
85+
86+ // Execute the AI module task
87+ let outputStr ;
88+ try {
89+ outputStr = await executeModuleTask ( "Intent.Modules.ChatDrivenDomain.Tasks.ChatCompletionTask" , JSON . stringify ( input ) ) ;
90+ } catch ( error ) {
91+ dialogService . error ( `Failed to execute AI task: ${ error . message } ` ) ;
92+ return ;
93+ }
94+
95+ // Parse the result from the AI task
96+ let updatedClasses : any ;
97+ try {
98+ updatedClasses = JSON . parse ( outputStr ) ;
99+ } catch ( error ) {
100+ dialogService . error ( `Failed to parse AI task result: ${ error . message } ` ) ;
101+ return ;
102+ }
103+
104+ // Check for errors in the AI response
105+ if ( updatedClasses . errorMessage ) {
106+ dialogService . error ( updatedClasses . errorMessage ) ;
107+ return ;
108+ }
109+
110+ // Create a lookup of type names to IDs
111+ const types = lookupTypesOf ( "Type-Definition" ) ;
112+ const typesLookup = Object . fromEntries ( types . map ( el => [ el . getName ( ) , el . id ] ) ) ;
113+
114+ // Create maps for existing classes and updated classes
115+ const existingClassesMap = new Map ( lookupTypesOf ( "Class" ) . map ( clazz => [ clazz . id , clazz ] ) ) ;
116+ const packageId = element . id ;
117+ const updatedClassesMap = new Map ( updatedClasses . map ( ( jClass : any ) => [ jClass . id , jClass ] ) ) ;
118+
119+ // Process class updates and additions
120+ updatedClasses . forEach ( ( jClass : any ) => {
121+ let clazz = existingClassesMap . get ( jClass . id ) ;
122+ if ( ! clazz ) {
123+ // Add new class
124+ clazz = createElement ( "Class" , jClass . name , packageId ) ;
125+ existingClassesMap . set ( jClass . id , clazz ) ;
126+ }
127+
128+ // Update class properties
129+ clazz . setName ( jClass . name , true ) ;
130+ clazz . setComment ( jClass . comment ) ;
131+
132+ // Process attributes - first delete all existing attributes
133+ const existingAttributes = clazz . getChildren ( "Attribute" ) ;
134+ existingAttributes . forEach ( attr => attr . delete ( ) ) ;
135+
136+ // Add all attributes from the updated model
137+ if ( jClass . attributes && jClass . attributes . length > 0 ) {
138+ jClass . attributes . forEach ( ( jAttr : any ) => {
139+ const attr = createElement ( "Attribute" , jAttr . name , clazz . id ) ;
140+ attr . setComment ( jAttr . comment || "" ) ;
141+
142+ if ( jAttr . type && typesLookup [ jAttr . type ] ) {
143+ attr . typeReference . setType ( typesLookup [ jAttr . type ] ) ;
144+ attr . typeReference . setIsCollection ( jAttr . isCollection || false ) ;
145+ attr . typeReference . setIsNullable ( jAttr . isNullable || false ) ;
146+ }
147+ } ) ;
148+ }
149+ } ) ;
150+
151+ // Delete classes not in the updated model
152+ existingClassesMap . forEach ( ( clazz , id ) => {
153+ if ( ! updatedClassesMap . has ( id ) ) {
154+ clazz . delete ( ) ;
155+ }
156+ } ) ;
157+
158+ // Clear all existing associations
159+ const existingAssociations = lookupTypesOf ( "Association" ) ;
160+ existingAssociations . forEach ( assoc => {
161+ assoc . delete ( ) ;
162+ } ) ;
163+
164+ // Create a map to track which associations we've already created
165+ // We'll use a compound key of sourceClassId + targetClassId to uniquely identify each association
166+ const createdAssociations = new Map ( ) ;
167+
168+ // For each class in our updated model
169+ updatedClasses . forEach ( ( jClass : any ) => {
170+ // Process all associations for this class
171+ jClass . associations . forEach ( ( jAssoc : any ) => {
172+ const thisClassId = jClass . id ;
173+ const otherClassId = jAssoc . classId ;
174+
175+ const thisClass = existingClassesMap . get ( thisClassId ) ;
176+ const otherClass = existingClassesMap . get ( otherClassId ) ;
177+
178+ if ( ! thisClass || ! otherClass ) return ;
179+
180+ // Determine the source and target classes based on associationEndType
181+ let sourceClassId , targetClassId ;
182+
183+ if ( jAssoc . associationEndType === "Target End" ) {
184+ // This class is the source
185+ sourceClassId = thisClassId ;
186+ targetClassId = otherClassId ;
187+ }
188+ else if ( jAssoc . associationEndType === "Source End" ) {
189+ // This class is the target
190+ sourceClassId = otherClassId ;
191+ targetClassId = thisClassId ;
192+ }
193+ else {
194+ return ; // Skip if associationEndType is not recognized
195+ }
196+
197+ // Create a compound key to identify this association
198+ const associationKey = sourceClassId + "->" + targetClassId ;
199+
200+ // Only create the association if we haven't already created it
201+ if ( ! createdAssociations . has ( associationKey ) ) {
202+ const sourceClass = existingClassesMap . get ( sourceClassId ) ;
203+ const targetClass = existingClassesMap . get ( targetClassId ) ;
204+
205+ // Create the association
206+ const association = createAssociation ( "Association" , sourceClass . id , targetClass . id ) ;
207+
208+ // Mark this association as created
209+ createdAssociations . set ( associationKey , association ) ;
210+ }
211+
212+ // Get the association we just created or previously created
213+ const association = createdAssociations . get ( associationKey ) ;
214+
215+ // Now apply the properties to the appropriate end
216+ if ( jAssoc . associationEndType === "Target End" ) {
217+ // Set properties on the target end
218+ const targetEnd = association ;
219+ targetEnd . setName ( jAssoc . name || "" ) ;
220+ targetEnd . typeReference . setIsCollection ( jAssoc . isCollection || false ) ;
221+ targetEnd . typeReference . setIsNullable ( jAssoc . isNullable || false ) ;
222+ }
223+ else if ( jAssoc . associationEndType === "Source End" ) {
224+ // Set properties on the source end
225+ // In this case, the association itself is the source end
226+ const sourceEnd = association . getOtherEnd ( ) ;
227+ sourceEnd . setName ( jAssoc . name || "" ) ;
228+ sourceEnd . typeReference . setIsCollection ( jAssoc . isCollection || false ) ;
229+ sourceEnd . typeReference . setIsNullable ( jAssoc . isNullable || false ) ;
230+ }
231+ } ) ;
232+ } ) ;
233+
234+ // Show a completion message
235+ dialogService . info ( "Domain model updated based on AI response." ) ;
236+ }
0 commit comments