1717package ai.tock.bot.admin
1818
1919import ai.tock.bot.admin.FaqAdminService.FAQ_CATEGORY
20+ import ai.tock.bot.admin.annotation.*
2021import ai.tock.bot.admin.answer.AnswerConfiguration
2122import ai.tock.bot.admin.answer.AnswerConfigurationType.builtin
2223import ai.tock.bot.admin.answer.AnswerConfigurationType.script
@@ -46,6 +47,7 @@ import ai.tock.bot.admin.story.dump.*
4647import ai.tock.bot.admin.user.UserReportDAO
4748import ai.tock.bot.connector.ConnectorType
4849import ai.tock.bot.definition.IntentWithoutNamespace
50+ import ai.tock.bot.engine.action.Action
4951import ai.tock.bot.engine.config.SatisfactionIntent
5052import ai.tock.bot.engine.dialog.Dialog
5153import ai.tock.bot.engine.dialog.DialogFlowDAO
@@ -62,6 +64,7 @@ import ai.tock.nlp.front.shared.config.*
6264import ai.tock.nlp.front.shared.config.ClassifiedSentenceStatus.model
6365import ai.tock.nlp.front.shared.config.ClassifiedSentenceStatus.validated
6466import ai.tock.shared.*
67+ import ai.tock.shared.exception.rest.NotFoundException
6568import ai.tock.shared.security.UserLogin
6669import ai.tock.shared.security.key.HasSecretKey
6770import ai.tock.shared.security.key.SecretKey
@@ -70,6 +73,7 @@ import ai.tock.translator.*
7073import com.github.salomonbrys.kodein.instance
7174import mu.KotlinLogging
7275import org.litote.kmongo.Id
76+ import org.litote.kmongo.newId
7377import org.litote.kmongo.toId
7478import java.time.Instant
7579import java.util.*
@@ -149,6 +153,208 @@ object BotAdminService {
149153 }
150154 }
151155
156+ fun saveAnnotation (
157+ dialogId : String ,
158+ actionId : String ,
159+ annotationDTO : BotAnnotationDTO ,
160+ user : String
161+ ): BotAnnotation {
162+ return if (dialogReportDAO.annotationExists(dialogId, actionId)) {
163+ updateAnnotation(dialogId, actionId, annotationDTO, user)
164+ } else {
165+ createAnnotation(dialogId, actionId, annotationDTO, user)
166+ }
167+ }
168+
169+ private fun createAnnotation (
170+ dialogId : String ,
171+ actionId : String ,
172+ annotationDTO : BotAnnotationDTO ,
173+ user : String
174+ ): BotAnnotation {
175+ val annotation = BotAnnotation (
176+ state = annotationDTO.state,
177+ reason = annotationDTO.reason,
178+ description = annotationDTO.description,
179+ groundTruth = annotationDTO.groundTruth,
180+ events = mutableListOf (
181+ BotAnnotationEventState (
182+ eventId = newId(),
183+ creationDate = Instant .now(),
184+ lastUpdateDate = Instant .now(),
185+ user = user,
186+ before = null ,
187+ after = annotationDTO.state.name
188+ )
189+ ),
190+ lastUpdateDate = Instant .now()
191+ )
192+
193+ dialogReportDAO.insertAnnotation(dialogId, actionId, annotation)
194+ return annotation
195+ }
196+
197+ fun addCommentToAnnotation (
198+ dialogId : String ,
199+ actionId : String ,
200+ eventDTO : BotAnnotationEventDTO ,
201+ user : String
202+ ): BotAnnotationEvent {
203+ if (eventDTO.type != BotAnnotationEventType .COMMENT ) {
204+ throw IllegalArgumentException (" Only COMMENT events are allowed" )
205+ }
206+
207+ require(! eventDTO.comment.isNullOrBlank()) { " Comment is required and cannot be blank for COMMENT event type" }
208+
209+ val annotation = dialogReportDAO.findAnnotation(dialogId, actionId)
210+ ? : throw IllegalStateException (" Annotation not found" )
211+
212+ val event = BotAnnotationEventComment (
213+ eventId = newId(),
214+ creationDate = Instant .now(),
215+ lastUpdateDate = Instant .now(),
216+ user = user,
217+ comment = eventDTO.comment ? : throw IllegalArgumentException (" Comment required" )
218+ )
219+
220+ dialogReportDAO.addAnnotationEvent(dialogId, actionId, event)
221+
222+ return event.copy(canEdit = true )
223+ }
224+
225+ fun updateAnnotationEvent (
226+ dialogId : String ,
227+ actionId : String ,
228+ eventId : String ,
229+ eventDTO : BotAnnotationEventDTO ,
230+ user : String
231+ ): BotAnnotationEvent {
232+ val existingEvent = dialogReportDAO.getAnnotationEvent(dialogId, actionId, eventId)
233+ ? : throw IllegalArgumentException (" Event not found" )
234+
235+ if (existingEvent.type != BotAnnotationEventType .COMMENT ) {
236+ throw IllegalArgumentException (" Only comment events can be updated" )
237+ }
238+
239+ if (eventDTO.type != BotAnnotationEventType .COMMENT ) {
240+ throw IllegalArgumentException (" Event type must be COMMENT" )
241+ }
242+
243+ require(eventDTO.comment != null ) { " Comment must be provided" }
244+
245+ val annotation = dialogReportDAO.findAnnotation(dialogId, actionId)
246+ ? : throw IllegalStateException (" Annotation not found" )
247+
248+ val existingCommentEvent = existingEvent as BotAnnotationEventComment
249+ val updatedEvent = existingCommentEvent.copy(
250+ comment = eventDTO.comment!! ,
251+ lastUpdateDate = Instant .now()
252+ )
253+
254+ dialogReportDAO.updateAnnotationEvent(dialogId, actionId, eventId, updatedEvent)
255+
256+ return updatedEvent.copy(canEdit = updatedEvent.user == user)
257+ }
258+
259+ fun deleteAnnotationEvent (
260+ dialogId : String ,
261+ actionId : String ,
262+ eventId : String ,
263+ user : String
264+ ) {
265+ val existingEvent = dialogReportDAO.getAnnotationEvent(dialogId, actionId, eventId)
266+ ? : throw IllegalArgumentException (" Event not found" )
267+
268+ if (existingEvent.type != BotAnnotationEventType .COMMENT ) {
269+ throw IllegalArgumentException (" Only comment events can be deleted" )
270+ }
271+
272+ dialogReportDAO.deleteAnnotationEvent(dialogId, actionId, eventId)
273+ }
274+
275+ fun updateAnnotation (
276+ dialogId : String ,
277+ actionId : String ,
278+ annotationDTO : BotAnnotationDTO ,
279+ user : String
280+ ): BotAnnotation {
281+ val existingAnnotation = dialogReportDAO.findAnnotation(dialogId, actionId)
282+ ? : throw IllegalStateException (" Annotation not found" )
283+
284+ val events = mutableListOf<BotAnnotationEvent >()
285+
286+ if (existingAnnotation.state != annotationDTO.state) {
287+ events.add(
288+ BotAnnotationEventState (
289+ eventId = newId(),
290+ creationDate = Instant .now(),
291+ lastUpdateDate = Instant .now(),
292+ user = user,
293+ before = existingAnnotation.state.name,
294+ after = annotationDTO.state.name
295+ )
296+ )
297+ existingAnnotation.state = annotationDTO.state
298+ }
299+
300+ if (existingAnnotation.reason != annotationDTO.reason) {
301+ events.add(
302+ BotAnnotationEventReason (
303+ eventId = newId(),
304+ creationDate = Instant .now(),
305+ lastUpdateDate = Instant .now(),
306+ user = user,
307+ before = existingAnnotation.reason?.name,
308+ after = annotationDTO.reason?.name
309+ )
310+ )
311+ existingAnnotation.reason = annotationDTO.reason
312+ }
313+
314+ if (existingAnnotation.groundTruth != annotationDTO.groundTruth) {
315+ events.add(
316+ BotAnnotationEventGroundTruth (
317+ eventId = newId(),
318+ creationDate = Instant .now(),
319+ lastUpdateDate = Instant .now(),
320+ user = user,
321+ before = existingAnnotation.groundTruth,
322+ after = annotationDTO.groundTruth
323+ )
324+ )
325+ existingAnnotation.groundTruth = annotationDTO.groundTruth
326+ }
327+
328+ if (existingAnnotation.description != annotationDTO.description) {
329+ events.add(
330+ BotAnnotationEventDescription (
331+ eventId = newId(),
332+ creationDate = Instant .now(),
333+ lastUpdateDate = Instant .now(),
334+ user = user,
335+ before = existingAnnotation.description,
336+ after = annotationDTO.description
337+ )
338+ )
339+ existingAnnotation.description = annotationDTO.description
340+ }
341+
342+ existingAnnotation.lastUpdateDate = Instant .now()
343+ existingAnnotation.events.addAll(events)
344+
345+ dialogReportDAO.insertAnnotation(dialogId, actionId, existingAnnotation)
346+
347+ return existingAnnotation.copy(
348+ events = existingAnnotation.events.map { event ->
349+ if (event is BotAnnotationEventComment ) {
350+ event.copy(canEdit = event.user == user)
351+ } else {
352+ event
353+ }
354+ }.toMutableList()
355+ )
356+ }
357+
152358 fun createOrGetIntent (
153359 namespace : String ,
154360 intentName : String ,
@@ -210,15 +416,47 @@ object BotAdminService {
210416 )
211417 }
212418 )
213- }
214-
215- // Add nlp stats
216- searchResult.copy(
419+ }.copy(
217420 nlpStats = dialogReportDAO.getNlpStats(searchResult.dialogs.map { it.id }, query.namespace)
218421 )
219422 }
220423 }
221424
425+ fun searchWithCommentRights (query : DialogsSearchQuery , userLogin : String ): DialogReportQueryResult {
426+ val result = search(query)
427+ return result.copy(
428+ dialogs = result.dialogs.map { dialog ->
429+ processAnnotationsForUser(dialog, userLogin)
430+ }
431+ )
432+ }
433+
434+ fun getDialogWithCommentRights (id : Id <Dialog >, userLogin : String ): DialogReport ? {
435+ return dialogReportDAO.getDialog(id)?.let { dialog ->
436+ processAnnotationsForUser(dialog, userLogin)
437+ }
438+ }
439+
440+ private fun processAnnotationsForUser (dialog : DialogReport , userLogin : String ): DialogReport {
441+ return dialog.copy(
442+ actions = dialog.actions.map { action ->
443+ action.copy(
444+ annotation = action.annotation?.let { annotation ->
445+ annotation.copy(
446+ events = annotation.events.map { event ->
447+ if (event is BotAnnotationEventComment ) {
448+ event.copy(canEdit = event.user == userLogin)
449+ } else {
450+ event
451+ }
452+ }.toMutableList()
453+ )
454+ }
455+ )
456+ }
457+ )
458+ }
459+
222460 fun getIntentsInDialogs (namespace : String ,nlpModel : String ) : Set <String >{
223461 return dialogReportDAO.intents(namespace,nlpModel)
224462 }
0 commit comments