From 8df09b71607bd301f60e6b9cc5d53ba7ebe0a587 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Wed, 9 Jul 2025 19:13:49 +0200
Subject: [PATCH 001/114] Add toxic to config class and implement getter for it
---
.../config/corpusConfig/CorpusAnnotationConfig.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/corpusConfig/CorpusAnnotationConfig.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/corpusConfig/CorpusAnnotationConfig.java
index ec588bd3..dc5ed7b0 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/corpusConfig/CorpusAnnotationConfig.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/corpusConfig/CorpusAnnotationConfig.java
@@ -24,6 +24,7 @@ public class CorpusAnnotationConfig {
private boolean scope;
private boolean xscope;
private boolean unifiedTopic;
+ private boolean toxic;
public boolean isGeoNames() {
return geoNames;
@@ -203,4 +204,7 @@ public boolean isUnifiedTopic() {
return unifiedTopic;
}
+ public boolean isToxic(){
+ return toxic;
+ }
}
From 01baec2f83c1943a6679f6f6b39ec82cd4a70175 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Wed, 9 Jul 2025 19:27:17 +0200
Subject: [PATCH 002/114] create toxic class
---
.../texttechnologylab/models/toxic/Toxic.java | 44 +++++++++++++++++++
1 file changed, 44 insertions(+)
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
new file mode 100644
index 00000000..fe30ef63
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
@@ -0,0 +1,44 @@
+package org.texttechnologylab.models.toxic;
+
+import org.texttechnologylab.models.UIMAAnnotation;
+import org.texttechnologylab.models.WikiModel;
+import org.texttechnologylab.models.corpus.Document;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "toxic")
+public class Toxic extends UIMAAnnotation implements WikiModel {
+
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
+ @JoinColumn(name = "document_id", nullable = false)
+ private Document document;
+
+ public Toxic() {
+ super(-1, -1);
+ }
+
+ public Toxic(int begin, int end) {
+ super(begin, end);
+ }
+
+ public Toxic(int begin, int end, String coveredText) {
+ super(begin, end);
+ setCoveredText(coveredText);
+ }
+
+ public Document getDocument() {
+ return document;
+ }
+
+ public void setDocument(Document document) {
+ this.document = document;
+ }
+
+ @Override
+ public String getWikiId() {
+ return "UT" + "-" + this.getId();
+ }
+
+}
From 9e9c0a2baa028d0f6f0a5cca823c5b1d8aef68ac Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Wed, 9 Jul 2025 19:38:27 +0200
Subject: [PATCH 003/114] add toxic and nonToxic properties as well as getters
and setters
---
.../texttechnologylab/models/toxic/Toxic.java | 22 +++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
index fe30ef63..868f1041 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
@@ -9,7 +9,10 @@
@Entity
@Table(name = "toxic")
public class Toxic extends UIMAAnnotation implements WikiModel {
-
+ @Column(name = "toxic", nullable = false)
+ private double toxic;
+ @Column(name = "non_toxic", nullable = false)
+ private double nonToxic;
@ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "document_id", nullable = false)
@@ -28,6 +31,22 @@ public Toxic(int begin, int end, String coveredText) {
setCoveredText(coveredText);
}
+ public double getToxic() {
+ return toxic;
+ }
+
+ public void setToxic(double newToxic) {
+ this.toxic = newToxic;
+ }
+
+ public double getNonToxic() {
+ return nonToxic;
+ }
+
+ public void setNonToxic(double nonToxic) {
+ this.nonToxic = nonToxic;
+ }
+
public Document getDocument() {
return document;
}
@@ -40,5 +59,4 @@ public void setDocument(Document document) {
public String getWikiId() {
return "UT" + "-" + this.getId();
}
-
}
From 1ebdb2a1afbd72dbce94f9c04be7abd9d9c64d40 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Wed, 9 Jul 2025 19:42:04 +0200
Subject: [PATCH 004/114] typesystem toxic
---
.../main/java/org/texttechnologylab/models/toxic/Toxic.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
index 868f1041..eca25722 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
@@ -1,5 +1,6 @@
package org.texttechnologylab.models.toxic;
+import org.texttechnologylab.annotations.Typesystem;
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
@@ -8,6 +9,7 @@
@Entity
@Table(name = "toxic")
+@Typesystem(types = {org.texttechnologylab.annotation.Toxic.class})
public class Toxic extends UIMAAnnotation implements WikiModel {
@Column(name = "toxic", nullable = false)
private double toxic;
@@ -34,7 +36,6 @@ public Toxic(int begin, int end, String coveredText) {
public double getToxic() {
return toxic;
}
-
public void setToxic(double newToxic) {
this.toxic = newToxic;
}
From 22e0028901810e1be0d8e4d41b7da39742ebb45b Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Wed, 9 Jul 2025 20:12:14 +0200
Subject: [PATCH 005/114] Add toxic annotation handling method in Importer
---
.../src/main/java/org/texttechnologylab/Importer.java | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index 8a20f607..baad46f1 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -566,6 +566,11 @@ public Document XMIToDocument(JCas jCas, Corpus corpus, String filePath) {
() -> setUnifiedTopic(document, jCas),
(ex) -> logImportWarn("This file should have contained UnifiedTopic annotations, but selecting them caused an error.", ex, filePath));
+ if (corpusConfig.getAnnotations().isToxic())
+ ExceptionUtils.tryCatchLog(
+ () -> setToxic(document, jCas),
+ (ex) -> logImportWarn("This file should have contained Toxic annotations, but selecting them caused an error.", ex, filePath));
+
// Keep this at the end of the annotation setting, as they might require previous annotations. Order matter here!
if (corpusConfig.getAnnotations().isLogicalLinks())
ExceptionUtils.tryCatchLog(
@@ -1410,6 +1415,12 @@ private void setUnifiedTopic(Document document, JCas jCas) {
document.setUnifiedTopics(unifiedTopics);
}
+ /**
+ * Selects and sets the toxicities to a document.
+ */
+ private void setToxic(Document document, JCas jCas) {
+ }
+
/**
* Set the cleaned full text. That is the sum of all tokens except of all anomalies
* Update: OBSOLETE for now, as this takes forever and makes the OCR worse.
From ae5bd3880c4209ead4858f635cb2e413dc5278e8 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Wed, 9 Jul 2025 20:18:33 +0200
Subject: [PATCH 006/114] Implement toxic selection and mapping in setToxic
method
---
.../src/main/java/org/texttechnologylab/Importer.java | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index baad46f1..4be826cc 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -49,6 +49,7 @@
import org.texttechnologylab.models.topic.TopicValueBaseWithScore;
import org.texttechnologylab.models.topic.TopicWord;
import org.texttechnologylab.models.topic.UnifiedTopic;
+import org.texttechnologylab.models.toxic.Toxic;
import org.texttechnologylab.services.*;
import org.texttechnologylab.utils.*;
import org.texttechnologylab.models.negation.CompleteNegation;
@@ -1419,6 +1420,16 @@ private void setUnifiedTopic(Document document, JCas jCas) {
* Selects and sets the toxicities to a document.
*/
private void setToxic(Document document, JCas jCas) {
+ List toxics = new ArrayList<>();
+
+ JCasUtil.select(jCas, org.texttechnologylab.annotation.Toxic.class).forEach(t -> {
+ Toxic toxic = new Toxic(t.getBegin(), t.getEnd());
+ toxic.setDocument(document);
+ toxic.setCoveredText(t.getCoveredText());
+ toxic.setToxic(t.getToxic());
+ toxic.setNonToxic(t.getNonToxic());
+ toxics.add(toxic);
+ });
}
/**
From b2ec3381325b61d9397643a369d194295c2290e7 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Wed, 9 Jul 2025 20:20:46 +0200
Subject: [PATCH 007/114] Add toxics property to Document class with
appropriate annotations
---
.../java/org/texttechnologylab/models/corpus/Document.java | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
index d9d898b4..6d828e39 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
@@ -26,6 +26,7 @@
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicValueBaseWithScore;
import org.texttechnologylab.models.topic.UnifiedTopic;
+import org.texttechnologylab.models.toxic.Toxic;
import org.texttechnologylab.utils.FreemarkerUtils;
import org.texttechnologylab.utils.StringUtils;
@@ -218,6 +219,12 @@ public long getPrimaryDbIdentifier() {
@Fetch(value = FetchMode.SUBSELECT)
private List unifiedTopics;
+ @Setter
+ @Getter
+ @OneToMany(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+ @Fetch(value = FetchMode.SUBSELECT)
+ private List toxics;
+
public Document() {
metadataTitleInfo = new MetadataTitleInfo();
}
From 5540c7c5ac4ff7093b1f6f3b03dbab290c475a13 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Wed, 9 Jul 2025 20:21:07 +0200
Subject: [PATCH 008/114] Set toxics in Document after processing import
---
.../src/main/java/org/texttechnologylab/Importer.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index 4be826cc..f6eb8ce5 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -1430,6 +1430,8 @@ private void setToxic(Document document, JCas jCas) {
toxic.setNonToxic(t.getNonToxic());
toxics.add(toxic);
});
+
+ document.setToxics(toxics);
}
/**
From d4e0cfdca27fa81e953cc0dd1ab422e61e023402 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Wed, 9 Jul 2025 20:37:43 +0200
Subject: [PATCH 009/114] Add Toxic class to Hibernate metadata sources
---
.../main/java/org/texttechnologylab/config/HibernateConf.java | 3 +++
1 file changed, 3 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
index 1f1da164..905962ea 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
@@ -21,6 +21,7 @@
import org.texttechnologylab.models.topic.TopicValueBaseWithScore;
import org.texttechnologylab.models.topic.TopicWord;
import org.texttechnologylab.models.topic.UnifiedTopic;
+import org.texttechnologylab.models.toxic.Toxic;
import java.util.HashMap;
@@ -80,6 +81,8 @@ public static SessionFactory buildSessionFactory() {
metadataSources.addAnnotatedClass(TopicWord.class);
metadataSources.addAnnotatedClass(TopicValueBase.class);
metadataSources.addAnnotatedClass(TopicValueBaseWithScore.class);
+ // toxic
+ metadataSources.addAnnotatedClass(Toxic.class);
metadataSources.addAnnotatedClass(DocumentTopThreeTopics.class);
var metadata = metadataSources.buildMetadata();
From cc2dda6b46c7fe0838f6448a1a11ecb929398a9e Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 20:57:14 +0200
Subject: [PATCH 010/114] Add OffensiveSpeech entity for handling offensive
speech annotations
---
.../offensiveSpeech/OffensiveSpeech.java | 45 +++++++++++++++++++
1 file changed, 45 insertions(+)
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
new file mode 100644
index 00000000..2f7176b1
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
@@ -0,0 +1,45 @@
+package org.texttechnologylab.models.offensiveSpeech;
+
+import org.texttechnologylab.models.UIMAAnnotation;
+import org.texttechnologylab.models.WikiModel;
+import org.texttechnologylab.models.corpus.Document;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "offensive-speech")
+public class OffensiveSpeech extends UIMAAnnotation implements WikiModel {
+
+
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
+ @JoinColumn(name = "document_id", nullable = false)
+ private Document document;
+
+ public OffensiveSpeech() {
+ super(-1, -1);
+ }
+
+ public OffensiveSpeech(int begin, int end) {
+ super(begin, end);
+ }
+
+ public OffensiveSpeech(int begin, int end, String coveredText) {
+ super(begin, end);
+ setCoveredText(coveredText);
+ }
+
+ public Document getDocument() {
+ return document;
+ }
+
+ public void setDocument(Document document) {
+ this.document = document;
+ }
+
+ @Override
+ public String getWikiId() {
+ return "UT" + "-" + this.getId();
+ }
+
+}
From f60d1a24c5a3aa757c4375dbc7a7636593b4dac6 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 20:57:32 +0200
Subject: [PATCH 011/114] Add Typesystem annotation to OffensiveSpeech entity
---
.../models/offensiveSpeech/OffensiveSpeech.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
index 2f7176b1..f41f9ee2 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
@@ -1,5 +1,6 @@
package org.texttechnologylab.models.offensiveSpeech;
+import org.texttechnologylab.annotations.Typesystem;
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
@@ -8,6 +9,7 @@
@Entity
@Table(name = "offensive-speech")
+@Typesystem(types = {org.texttechnologylab.annotation.OffensiveSpeech.class})
public class OffensiveSpeech extends UIMAAnnotation implements WikiModel {
From 8ad779f9321c4539ebea961c2aa0504fdd03fd14 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 21:06:32 +0200
Subject: [PATCH 012/114] Add offensive and non-offensive flags to
OffensiveSpeech entity
---
.../models/offensiveSpeech/OffensiveSpeech.java | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
index f41f9ee2..50bb8be5 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
@@ -1,18 +1,31 @@
package org.texttechnologylab.models.offensiveSpeech;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.Fetch;
+import org.hibernate.annotations.FetchMode;
import org.texttechnologylab.annotations.Typesystem;
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
import javax.persistence.*;
+import java.util.List;
@Entity
@Table(name = "offensive-speech")
@Typesystem(types = {org.texttechnologylab.annotation.OffensiveSpeech.class})
public class OffensiveSpeech extends UIMAAnnotation implements WikiModel {
+ @Getter
+ @Setter
+ @Column(name = "offensive", nullable = false)
+ private boolean offensive;
+ @Getter
+ @Setter
+ @Column(name = "non_offensive", nullable = false)
+ private boolean nonOffensive;
@ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "document_id", nullable = false)
From 6be01b325a8dffba702dc907b8182c321f15f1da Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 21:07:43 +0200
Subject: [PATCH 013/114] add to HibernateConf
---
.../main/java/org/texttechnologylab/config/HibernateConf.java | 3 +++
1 file changed, 3 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
index 1f1da164..e331f1aa 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
@@ -17,6 +17,7 @@
import org.texttechnologylab.models.imp.ImportLog;
import org.texttechnologylab.models.imp.UCEImport;
import org.texttechnologylab.models.negation.*;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicValueBaseWithScore;
import org.texttechnologylab.models.topic.TopicWord;
@@ -80,6 +81,8 @@ public static SessionFactory buildSessionFactory() {
metadataSources.addAnnotatedClass(TopicWord.class);
metadataSources.addAnnotatedClass(TopicValueBase.class);
metadataSources.addAnnotatedClass(TopicValueBaseWithScore.class);
+ // offensive speech
+ metadataSources.addAnnotatedClass(OffensiveSpeech.class);
metadataSources.addAnnotatedClass(DocumentTopThreeTopics.class);
var metadata = metadataSources.buildMetadata();
From ffb920b12a54ffc7dedabfdda3925950e53f78c3 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 21:09:15 +0200
Subject: [PATCH 014/114] add to CorpusAnnotationConfig
---
.../config/corpusConfig/CorpusAnnotationConfig.java | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/corpusConfig/CorpusAnnotationConfig.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/corpusConfig/CorpusAnnotationConfig.java
index ec588bd3..4a3300bc 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/corpusConfig/CorpusAnnotationConfig.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/corpusConfig/CorpusAnnotationConfig.java
@@ -24,6 +24,7 @@ public class CorpusAnnotationConfig {
private boolean scope;
private boolean xscope;
private boolean unifiedTopic;
+ private boolean offensiveSpeech;
public boolean isGeoNames() {
return geoNames;
@@ -203,4 +204,8 @@ public boolean isUnifiedTopic() {
return unifiedTopic;
}
+ public boolean isOffensiveSpeech() {
+ return offensiveSpeech;
+ }
+
}
From bd4744f375c0c0e8b2c4b5758458fb82d25f112d Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 21:11:11 +0200
Subject: [PATCH 015/114] Add support for importing offensive speech
annotations
---
.../src/main/java/org/texttechnologylab/Importer.java | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index 8a20f607..571ad4ea 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -566,6 +566,11 @@ public Document XMIToDocument(JCas jCas, Corpus corpus, String filePath) {
() -> setUnifiedTopic(document, jCas),
(ex) -> logImportWarn("This file should have contained UnifiedTopic annotations, but selecting them caused an error.", ex, filePath));
+ if (corpusConfig.getAnnotations().isOffensiveSpeech())
+ ExceptionUtils.tryCatchLog(
+ () -> setOffensiveSpeech(document, jCas),
+ (ex) -> logImportWarn("This file should have contained OffensiveSpeech annotations, but selecting them caused an error.", ex, filePath));
+
// Keep this at the end of the annotation setting, as they might require previous annotations. Order matter here!
if (corpusConfig.getAnnotations().isLogicalLinks())
ExceptionUtils.tryCatchLog(
@@ -1410,6 +1415,10 @@ private void setUnifiedTopic(Document document, JCas jCas) {
document.setUnifiedTopics(unifiedTopics);
}
+ private void setOffensiveSpeech(Document document, JCas jCas) {
+
+ }
+
/**
* Set the cleaned full text. That is the sum of all tokens except of all anomalies
* Update: OBSOLETE for now, as this takes forever and makes the OCR worse.
From 905e75f44c77b1595524584e453a418e2a8706c7 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 21:20:54 +0200
Subject: [PATCH 016/114] fix data type
---
.../models/offensiveSpeech/OffensiveSpeech.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
index 50bb8be5..f3f94f3e 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
@@ -20,12 +20,12 @@ public class OffensiveSpeech extends UIMAAnnotation implements WikiModel {
@Getter
@Setter
@Column(name = "offensive", nullable = false)
- private boolean offensive;
+ private double offensive;
@Getter
@Setter
@Column(name = "non_offensive", nullable = false)
- private boolean nonOffensive;
+ private double nonOffensive;
@ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "document_id", nullable = false)
From 2e3df8406ba08ce73c983959fbae76b3392b1b1d Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 21:21:27 +0200
Subject: [PATCH 017/114] Add method to set offensive speech annotations with
score validation
---
.../java/org/texttechnologylab/Importer.java | 58 +++++++++++++++----
1 file changed, 47 insertions(+), 11 deletions(-)
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index 571ad4ea..6c997be8 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -19,6 +19,7 @@
import org.apache.uima.util.CasIOUtils;
import org.apache.uima.util.CasLoadMode;
import org.springframework.context.ApplicationContext;
+import org.texttechnologylab.annotation.AnnotationComment;
import org.texttechnologylab.annotation.DocumentAnnotation;
import org.texttechnologylab.annotation.geonames.GeoNamesEntity;
import org.texttechnologylab.annotation.link.*;
@@ -43,6 +44,7 @@
import org.texttechnologylab.models.imp.ImportStatus;
import org.texttechnologylab.models.imp.LogStatus;
import org.texttechnologylab.models.negation.*;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
import org.texttechnologylab.models.rag.DocumentChunkEmbedding;
import org.texttechnologylab.models.rag.DocumentSentenceEmbedding;
import org.texttechnologylab.models.topic.TopicValueBase;
@@ -140,13 +142,13 @@ public int getXMICountInPath() {
public void start(int numThreads) throws DatabaseOperationException {
logger.info(
"\n _ _ _____ _____ _____ _ \n" +
- "| | | / __ \\| ___| |_ _| | | \n" +
- "| | | | / \\/| |__ | | _ __ ___ _ __ ___ _ __| |_ \n" +
- "| | | | | | __| | || '_ ` _ \\| '_ \\ / _ \\| '__| __|\n" +
- "| |_| | \\__/\\| |___ _| || | | | | | |_) | (_) | | | |_ \n" +
- " \\___/ \\____/\\____/ \\___/_| |_| |_| .__/ \\___/|_| \\__|\n" +
- " | | \n" +
- " |_|"
+ "| | | / __ \\| ___| |_ _| | | \n" +
+ "| | | | / \\/| |__ | | _ __ ___ _ __ ___ _ __| |_ \n" +
+ "| | | | | | __| | || '_ ` _ \\| '_ \\ / _ \\| '__| __|\n" +
+ "| |_| | \\__/\\| |___ _| || | | | | | |_) | (_) | | | |_ \n" +
+ " \\___/ \\____/\\____/ \\___/_| |_| |_| .__/ \\___/|_| \\__|\n" +
+ " | | \n" +
+ " |_|"
);
logger.info("===========> Global Import Id: " + importId);
logger.info("===========> Importer Number: " + importerNumber);
@@ -208,7 +210,7 @@ public void storeCorpusFromFolderAsync(String folderName, int numThreads) throws
final var corpusConfig1 = corpusConfig; // This sucks so hard - why doesn't java just do this itself if needed?
var existingCorpus = ExceptionUtils.tryCatchLog(() -> db.getCorpusByName(corpusConfig1.getName()),
(ex) -> logger.error("Error getting an existing corpus by name. The corpus config should probably be changed " +
- "to not add to existing corpus then.", ex));
+ "to not add to existing corpus then.", ex));
if (existingCorpus != null) { // If we have the corpus, use that. Else store the new corpus.
corpus = existingCorpus;
@@ -468,7 +470,7 @@ public Document XMIToDocument(JCas jCas, Corpus corpus, String filePath) {
var exists = db.documentExists(corpus.getId(), document.getDocumentId());
if (exists) {
logger.info("Document with id " + document.getDocumentId()
- + " already exists in the corpus " + corpus.getId() + ".");
+ + " already exists in the corpus " + corpus.getId() + ".");
logger.info("Checking if that document was also post-processed yet...");
var existingDoc = db.getDocumentByCorpusAndDocumentId(corpus.getId(), document.getDocumentId());
if (!existingDoc.isPostProcessed()) {
@@ -824,8 +826,8 @@ private void setMetadataTitleInfo(Document document, JCas jCas, CorpusConfig cor
if (documentAnnotation != null) {
try {
metadataTitleInfo.setPublished(documentAnnotation.getDateDay() + "."
- + documentAnnotation.getDateMonth() + "."
- + documentAnnotation.getDateYear());
+ + documentAnnotation.getDateMonth() + "."
+ + documentAnnotation.getDateYear());
metadataTitleInfo.setAuthor(documentAnnotation.getAuthor());
} catch (Exception ex) {
logger.warn("Tried extracting DocumentAnnotation type, it caused an error. Import will be continued as usual.");
@@ -1416,7 +1418,41 @@ private void setUnifiedTopic(Document document, JCas jCas) {
}
private void setOffensiveSpeech(Document document, JCas jCas) {
+ List offensiveSpeeches = new ArrayList<>();
+ JCasUtil.select(jCas, org.texttechnologylab.annotation.OffensiveSpeech.class).forEach(os -> {
+ OffensiveSpeech offensiveSpeech = new OffensiveSpeech(os.getBegin(), os.getEnd());
+ offensiveSpeech.setDocument(document);
+ offensiveSpeech.setCoveredText(os.getCoveredText());
+
+ FSArray offensives = os.getOffensives();
+ if (offensives == null) {
+ logger.warn("OffensiveSpeech annotation without offensives found. Skipping this annotation.");
+ return;
+ }
+ if (offensives.size() != 2) {
+ logger.warn("OffensiveSpeech annotation with " + offensives.size() + " offensives found. Expected 2. Skipping this annotation.");
+ return;
+ }
+ final String offensiveKey = "Offensive";
+ final String nonOffensiveKey = "Not Offensive";
+ OptionalDouble offensiveScore = offensives.stream()
+ .filter(oc -> oc.getKey().equals(offensiveKey))
+ .map(AnnotationComment::getValue)
+ .mapToDouble(Double::parseDouble)
+ .findFirst();
+ OptionalDouble nonOffensiveScore = offensives.stream()
+ .filter(oc -> oc.getKey().equals(nonOffensiveKey))
+ .map(AnnotationComment::getValue)
+ .mapToDouble(Double::parseDouble).findFirst();
+ if (offensiveScore.isEmpty() || nonOffensiveScore.isEmpty()) {
+ logger.warn("OffensiveSpeech annotation without offensive or non-offensive score found. Skipping this annotation.");
+ return;
+ }
+ offensiveSpeech.setOffensive(offensiveScore.getAsDouble());
+ offensiveSpeech.setNonOffensive(nonOffensiveScore.getAsDouble());
+ offensiveSpeeches.add(offensiveSpeech);
+ });
}
/**
From f81da7a7ed0fc336a6c75105c11306f3183e4cf0 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 21:22:41 +0200
Subject: [PATCH 018/114] add offensive speech to document entity
---
.../java/org/texttechnologylab/models/corpus/Document.java | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
index d9d898b4..d6225e33 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
@@ -21,6 +21,7 @@
import org.texttechnologylab.models.corpus.links.DocumentToAnnotationLink;
import org.texttechnologylab.models.corpus.links.Link;
import org.texttechnologylab.models.negation.*;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
import org.texttechnologylab.models.search.AnnotationSearchResult;
import org.texttechnologylab.models.search.PageSnippet;
import org.texttechnologylab.models.topic.TopicValueBase;
@@ -218,6 +219,12 @@ public long getPrimaryDbIdentifier() {
@Fetch(value = FetchMode.SUBSELECT)
private List unifiedTopics;
+ @Setter
+ @Getter
+ @OneToMany(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+ @Fetch(value = FetchMode.SUBSELECT)
+ private List offensiveSpeeches;
+
public Document() {
metadataTitleInfo = new MetadataTitleInfo();
}
From 3cad3e3201bc51ffbffc0c688035bbecd13af7f7 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 21:23:02 +0200
Subject: [PATCH 019/114] set offensive speeches in document after import
---
.../src/main/java/org/texttechnologylab/Importer.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index 6c997be8..2ce83cc9 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -1453,6 +1453,8 @@ private void setOffensiveSpeech(Document document, JCas jCas) {
offensiveSpeech.setNonOffensive(nonOffensiveScore.getAsDouble());
offensiveSpeeches.add(offensiveSpeech);
});
+
+ document.setOffensiveSpeeches(offensiveSpeeches);
}
/**
From 50eea9c2779e8502789479acc6948f7fc47a1ac4 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Wed, 9 Jul 2025 21:24:08 +0200
Subject: [PATCH 020/114] fix table name
---
.../models/offensiveSpeech/OffensiveSpeech.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
index f3f94f3e..d3f3be4b 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
@@ -13,7 +13,7 @@
import java.util.List;
@Entity
-@Table(name = "offensive-speech")
+@Table(name = "offensive_speech")
@Typesystem(types = {org.texttechnologylab.annotation.OffensiveSpeech.class})
public class OffensiveSpeech extends UIMAAnnotation implements WikiModel {
From fe4099076736164bb253852aaf927940bf9ba42d Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Thu, 10 Jul 2025 14:45:42 +0200
Subject: [PATCH 021/114] Add Emotion entity for emotion annotations in
documents
---
.../models/emotion/Emotion.java | 43 +++++++++++++++++++
1 file changed, 43 insertions(+)
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java
new file mode 100644
index 00000000..1c64d887
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java
@@ -0,0 +1,43 @@
+package org.texttechnologylab.models.emotion;
+
+import org.texttechnologylab.models.UIMAAnnotation;
+import org.texttechnologylab.models.WikiModel;
+import org.texttechnologylab.models.corpus.Document;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "emotion")
+public class Emotion extends UIMAAnnotation implements WikiModel {
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
+ @JoinColumn(name = "document_id", nullable = false)
+ private Document document;
+
+ public Emotion() {
+ super(-1, -1);
+ }
+
+ public Emotion(int begin, int end) {
+ super(begin, end);
+ }
+
+ public Emotion(int begin, int end, String coveredText) {
+ super(begin, end);
+ setCoveredText(coveredText);
+ }
+
+ public Document getDocument() {
+ return document;
+ }
+
+ public void setDocument(Document document) {
+ this.document = document;
+ }
+
+ @Override
+ public String getWikiId() {
+ return "UT" + "-" + this.getId();
+ }
+
+}
From 6eee6f418c3aa34c1c628085f3757954e33f1074 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Thu, 10 Jul 2025 14:46:05 +0200
Subject: [PATCH 022/114] Add Typesystem annotation to Emotion entity
---
.../main/java/org/texttechnologylab/models/emotion/Emotion.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java
index 1c64d887..598493eb 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java
@@ -1,5 +1,6 @@
package org.texttechnologylab.models.emotion;
+import org.texttechnologylab.annotations.Typesystem;
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
@@ -8,6 +9,7 @@
@Entity
@Table(name = "emotion")
+@Typesystem(types = {org.texttechnologylab.annotation.Emotion.class})
public class Emotion extends UIMAAnnotation implements WikiModel {
@ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
From b80fe97fec3082df57fdb9c26270939433f29830 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Thu, 10 Jul 2025 14:53:02 +0200
Subject: [PATCH 023/114] Add EmotionType entity for managing emotion types
---
.../models/emotion/EmotionType.java | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionType.java
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionType.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionType.java
new file mode 100644
index 00000000..038fa5e6
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionType.java
@@ -0,0 +1,26 @@
+package org.texttechnologylab.models.emotion;
+
+import lombok.Getter;
+import org.texttechnologylab.models.ModelBase;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "emotion_type")
+public class EmotionType extends ModelBase {
+
+ @Getter
+ @Column(name = "name", nullable = false, unique = true)
+ private String name;
+
+ public EmotionType() {
+ // Default constructor for JPA
+ }
+
+ public EmotionType(String name) {
+ this.name = name;
+ }
+
+}
From 66556784c79efc2e8c8a5e4d0f5ee2d51dfb3a67 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Thu, 10 Jul 2025 14:57:22 +0200
Subject: [PATCH 024/114] Add EmotionValue entity for storing emotion values
---
.../models/emotion/EmotionValue.java | 38 +++++++++++++++++++
1 file changed, 38 insertions(+)
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionValue.java
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionValue.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionValue.java
new file mode 100644
index 00000000..549257e3
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionValue.java
@@ -0,0 +1,38 @@
+package org.texttechnologylab.models.emotion;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.texttechnologylab.models.ModelBase;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "emotion_value")
+public class EmotionValue extends ModelBase {
+
+ @Getter
+ @Setter
+ @ManyToOne
+ @JoinColumn(name = "emotion_type_id", nullable = false)
+ private EmotionType emotionType;
+
+ @Getter
+ @Setter
+ @ManyToOne
+ @JoinColumn(name = "emotion_id", nullable = false)
+ private Emotion emotion;
+
+ @Getter
+ @Setter
+ @Column(name = "value", nullable = false)
+ private double value;
+
+ public EmotionValue() {
+ }
+
+ public EmotionValue(EmotionType emotionType, Emotion emotion, double value) {
+ this.emotionType = emotionType;
+ this.emotion = emotion;
+ this.value = value;
+ }
+}
From 1159672883f82566d487c01bac22d6af2e91afdd Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Thu, 10 Jul 2025 14:59:29 +0200
Subject: [PATCH 025/114] Add emotionValues field to Emotion entity for
managing associated EmotionValue instances
---
.../org/texttechnologylab/models/emotion/Emotion.java | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java
index 598493eb..c6c29b82 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/Emotion.java
@@ -1,17 +1,28 @@
package org.texttechnologylab.models.emotion;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.Fetch;
+import org.hibernate.annotations.FetchMode;
import org.texttechnologylab.annotations.Typesystem;
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
import javax.persistence.*;
+import java.util.List;
@Entity
@Table(name = "emotion")
@Typesystem(types = {org.texttechnologylab.annotation.Emotion.class})
public class Emotion extends UIMAAnnotation implements WikiModel {
+ @Getter
+ @Setter
+ @OneToMany(mappedBy = "emotion", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Fetch(value = FetchMode.SUBSELECT)
+ private List emotionValues;
+
@ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "document_id", nullable = false)
private Document document;
From 1d4b317d02fd5e674ac50746005835b6ae6420fc Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Thu, 10 Jul 2025 14:59:37 +0200
Subject: [PATCH 026/114] Add setter for name field in EmotionType entity
---
.../java/org/texttechnologylab/models/emotion/EmotionType.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionType.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionType.java
index 038fa5e6..f3cc3061 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionType.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/emotion/EmotionType.java
@@ -1,6 +1,7 @@
package org.texttechnologylab.models.emotion;
import lombok.Getter;
+import lombok.Setter;
import org.texttechnologylab.models.ModelBase;
import javax.persistence.Column;
@@ -12,6 +13,7 @@
public class EmotionType extends ModelBase {
@Getter
+ @Setter
@Column(name = "name", nullable = false, unique = true)
private String name;
From 865e2ff209573c0a4a9d655677e05d13494587b6 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Thu, 10 Jul 2025 15:00:31 +0200
Subject: [PATCH 027/114] Add Emotion, EmotionValue, and EmotionType classes to
Hibernate configuration
---
.../java/org/texttechnologylab/config/HibernateConf.java | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
index 1f1da164..fbb7548b 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
@@ -13,6 +13,9 @@
import org.texttechnologylab.models.corpus.links.AnnotationToDocumentLink;
import org.texttechnologylab.models.corpus.links.DocumentLink;
import org.texttechnologylab.models.corpus.links.DocumentToAnnotationLink;
+import org.texttechnologylab.models.emotion.Emotion;
+import org.texttechnologylab.models.emotion.EmotionType;
+import org.texttechnologylab.models.emotion.EmotionValue;
import org.texttechnologylab.models.gbif.GbifOccurrence;
import org.texttechnologylab.models.imp.ImportLog;
import org.texttechnologylab.models.imp.UCEImport;
@@ -80,6 +83,10 @@ public static SessionFactory buildSessionFactory() {
metadataSources.addAnnotatedClass(TopicWord.class);
metadataSources.addAnnotatedClass(TopicValueBase.class);
metadataSources.addAnnotatedClass(TopicValueBaseWithScore.class);
+ // emotion
+ metadataSources.addAnnotatedClass(Emotion.class);
+ metadataSources.addAnnotatedClass(EmotionValue.class);
+ metadataSources.addAnnotatedClass(EmotionType.class);
metadataSources.addAnnotatedClass(DocumentTopThreeTopics.class);
var metadata = metadataSources.buildMetadata();
From 316dfea13328d97eedc9cac817c33289ea8aa146 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Thu, 10 Jul 2025 15:01:06 +0200
Subject: [PATCH 028/114] Add emotions field to Document entity for managing
associated Emotion instances
---
.../org/texttechnologylab/models/corpus/Document.java | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
index d9d898b4..9b42c6cd 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
@@ -19,10 +19,8 @@
import org.texttechnologylab.models.corpus.links.AnnotationToDocumentLink;
import org.texttechnologylab.models.corpus.links.DocumentLink;
import org.texttechnologylab.models.corpus.links.DocumentToAnnotationLink;
-import org.texttechnologylab.models.corpus.links.Link;
+import org.texttechnologylab.models.emotion.Emotion;
import org.texttechnologylab.models.negation.*;
-import org.texttechnologylab.models.search.AnnotationSearchResult;
-import org.texttechnologylab.models.search.PageSnippet;
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicValueBaseWithScore;
import org.texttechnologylab.models.topic.UnifiedTopic;
@@ -218,6 +216,12 @@ public long getPrimaryDbIdentifier() {
@Fetch(value = FetchMode.SUBSELECT)
private List unifiedTopics;
+ @Setter
+ @Getter
+ @OneToMany(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+ @Fetch(value = FetchMode.SUBSELECT)
+ private List emotions;
+
public Document() {
metadataTitleInfo = new MetadataTitleInfo();
}
From 9071db656399ae9c9242ca7ffc3a6b2c16e60ed0 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Thu, 10 Jul 2025 15:04:07 +0200
Subject: [PATCH 029/114] Add methods for retrieving and creating EmotionType
instances in PostgresqlDataInterface_Impl
---
.../PostgresqlDataInterface_Impl.java | 156 +++++++++++-------
1 file changed, 96 insertions(+), 60 deletions(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index dc9b4541..79d522ef 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -25,6 +25,8 @@
import org.texttechnologylab.models.dto.UCEMetadataFilterDto;
import org.texttechnologylab.models.dto.map.MapClusterDto;
import org.texttechnologylab.models.dto.map.PointDto;
+import org.texttechnologylab.models.emotion.Emotion;
+import org.texttechnologylab.models.emotion.EmotionType;
import org.texttechnologylab.models.gbif.GbifOccurrence;
import org.texttechnologylab.models.globe.GlobeTaxon;
import org.texttechnologylab.models.imp.ImportLog;
@@ -423,12 +425,13 @@ public List getGlobeDataForDocument(long documentId) throws Database
() -> getAllLinksOfLinkable(taxon.getId(), taxon.getClass(), List.of(AnnotationLink.class))
.stream()
.filter(l -> l.getLinkId().equals("context") && l.getToAnnotationType().equals(GeoName.class.getName())).toList(),
- (ex) -> { });
+ (ex) -> {
+ });
// Foreach taxa, fetch a possible geoname link.
if (links != null)
for (var link : links) {
var geoname = doc.getGeoNames().stream().filter(g -> g.getId() == link.getToId()).findFirst();
- if(geoname.isEmpty()) continue;
+ if (geoname.isEmpty()) continue;
var globeTaxon = new GlobeTaxon();
globeTaxon.setLongitude(geoname.get().getLongitude());
globeTaxon.setLatitude(geoname.get().getLatitude());
@@ -970,7 +973,7 @@ public DocumentSearchResult defaultSearchForDocuments(int skip,
return executeOperationSafely((session) -> session.doReturningWork((connection) -> {
DocumentSearchResult search = null;
try (var storedProcedure = connection.prepareCall("{call uce_search_layer_" + layer.name().toLowerCase() +
- "(?::bigint, ?::text[], ?::text, ?::integer, ?::integer, ?::boolean, ?::text, ?::text, ?::jsonb, ?::boolean, ?::text, ?::text)}")) {
+ "(?::bigint, ?::text[], ?::text, ?::integer, ?::integer, ?::boolean, ?::text, ?::text, ?::jsonb, ?::boolean, ?::text, ?::text)}")) {
storedProcedure.setInt(1, (int) corpusId);
storedProcedure.setArray(2, connection.createArrayOf("text", searchTokens.stream().map(this::escapeSql).toArray()));
storedProcedure.setString(3, ogSearchQuery);
@@ -1190,9 +1193,9 @@ public List getDistinctTimesByCondition(String condition, long corpusId,
return executeOperationSafely((session) -> {
// Construct HQL dynamically (THIS IS UNSAFE BECAUSE OF THE CONDITION INSERTION)
String hql = "SELECT DISTINCT t.coveredText " +
- "FROM Time t " +
- "JOIN Document d ON t.documentId = d.id " +
- "WHERE " + condition + " AND d.corpusId = :corpusId";
+ "FROM Time t " +
+ "JOIN Document d ON t.documentId = d.id " +
+ "WHERE " + condition + " AND d.corpusId = :corpusId";
var query = session.createQuery(hql, String.class);
query.setParameter("corpusId", corpusId);
@@ -1349,13 +1352,46 @@ public UnifiedTopic getInitializedUnifiedTopicById(long id) throws DatabaseOpera
return executeOperationSafely((session) -> {
var topic = session.get(UnifiedTopic.class, id);
Hibernate.initialize(topic.getTopics());
- for(var t:topic.getTopics()){
+ for (var t : topic.getTopics()) {
Hibernate.initialize(t.getWords());
}
return topic;
});
}
+ public EmotionType getEmotionTypeById(long id) throws DatabaseOperationException {
+ return executeOperationSafely((session) -> session.get(EmotionType.class, id));
+ }
+
+ public EmotionType getEmotionTypeByName(String name) throws DatabaseOperationException {
+ return executeOperationSafely((session) -> {
+ var criteriaBuilder = session.getCriteriaBuilder();
+ var criteriaQuery = criteriaBuilder.createQuery(EmotionType.class);
+ var root = criteriaQuery.from(EmotionType.class);
+ criteriaQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name));
+ return session.createQuery(criteriaQuery).uniqueResult();
+ });
+ }
+
+ public EmotionType getOrCreateEmotionTypeByName(String name) throws DatabaseOperationException {
+ return executeOperationSafely((session) -> {
+ var criteriaBuilder = session.getCriteriaBuilder();
+ var criteriaQuery = criteriaBuilder.createQuery(EmotionType.class);
+ var root = criteriaQuery.from(EmotionType.class);
+ criteriaQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name));
+
+ EmotionType emotionType;
+ try {
+ emotionType = session.createQuery(criteriaQuery).getSingleResult();
+ } catch (NoResultException e) {
+ // If not found, create a new one
+ emotionType = new EmotionType(name);
+ session.save(emotionType);
+ }
+ return emotionType;
+ });
+ }
+
public List getKeywordDistributionsByString(Class clazz, String topic, int limit) throws DatabaseOperationException {
return executeOperationSafely((session) -> {
var builder = session.getCriteriaBuilder();
@@ -1597,9 +1633,9 @@ public List
+
+
+ <#if modelCategories?has_content && modelCategories?size gt 0>
+
+
${languageResource.get("modelSelection")}
+
+ <#list modelCategories as category>
+
+
+
+
+ #list>
+
+
+ #if>
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
index bf6ff836..7775ac26 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
@@ -20,6 +20,7 @@
import org.texttechnologylab.models.corpus.links.DocumentLink;
import org.texttechnologylab.models.corpus.links.DocumentToAnnotationLink;
import org.texttechnologylab.models.emotion.Emotion;
+import org.texttechnologylab.models.modelInfo.ModelNameHelper;
import org.texttechnologylab.models.negation.*;
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicValueBaseWithScore;
@@ -347,7 +348,7 @@ public List getAllTaxa(){
*
* @return
*/
- public List getAllAnnotations(int pagesSkip, int pagesTake) {
+ public List getAllAnnotations(int pagesSkip, int pagesTake, Map modelSelection) {
var pagesBegin = getPages().stream().skip(pagesSkip).limit(1).findFirst().get().getBegin();
var pagesEnd = getPages().stream().skip(Math.min(pagesSkip + pagesTake, getPages().size() - 1)).limit(1).findFirst().get().getEnd();
@@ -369,7 +370,8 @@ public List getAllAnnotations(int pagesSkip, int pagesTake) {
// unifiedTopics
annotations.addAll(unifiedTopics.stream().filter(a -> a.getBegin() >= pagesBegin && a.getEnd() <= pagesEnd).toList());
// emotions
- annotations.addAll(emotions.stream().filter(a -> a.getBegin() >= pagesBegin && a.getEnd() <= pagesEnd).toList());
+ int emotionModelId = modelSelection.getOrDefault(ModelNameHelper.getModelName(Emotion.class), -1);
+ annotations.addAll(emotions.stream().filter(a -> a.getBegin() >= pagesBegin && a.getEnd() <= pagesEnd).filter(e -> emotionModelId == -1 || e.getModelVersion().getModel().getId() == emotionModelId).toList());
annotations.sort(Comparator.comparingInt(UIMAAnnotation::getBegin));
return annotations;
@@ -434,4 +436,12 @@ public List getDocumentUnifiedTopicDistribution(Integer topN) {
.collect(Collectors.toList());
}
+ public List getModelCategories() {
+ List categories = new ArrayList<>();
+ if (!emotions.isEmpty()) {
+ categories.add(ModelNameHelper.getModelName(Emotion.class));
+ }
+ return categories;
+ }
+
}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/modelInfo/ModelNameHelper.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/modelInfo/ModelNameHelper.java
new file mode 100644
index 00000000..10d47da6
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/modelInfo/ModelNameHelper.java
@@ -0,0 +1,23 @@
+package org.texttechnologylab.models.modelInfo;
+
+import org.jetbrains.annotations.NotNull;
+
+public class ModelNameHelper {
+
+ private ModelNameHelper() {
+ // Utility class, no instantiation allowed
+ }
+
+ @NotNull
+ public static String getModelName(@NotNull Class> modelClass) {
+ NamedModel namedModel = modelClass.getAnnotation(NamedModel.class);
+ if (namedModel == null) {
+ throw new IllegalArgumentException("Class " + modelClass.getName() + " is not annotated with @NamedModel");
+ }
+ String modelName = namedModel.name();
+ if (modelName.isEmpty()) {
+ throw new IllegalArgumentException("Model name cannot be empty for class " + modelClass.getName());
+ }
+ return modelName;
+ }
+}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index da1fff14..d8c7589f 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -2047,7 +2047,7 @@ public List getLemmaByPage(long documentId) throws DatabaseOperationEx
});
}
- public List getDocumentEmotionsOrdered(long documentId) throws DatabaseOperationException {
+ public List getDocumentEmotionsOrdered(long documentId, int modelId) throws DatabaseOperationException {
class EmotionEntry {
long emotionId;
long emotionType;
@@ -2060,11 +2060,14 @@ class EmotionEntry {
ev.value AS emotionValue
FROM emotion e
JOIN emotion_value ev ON e.id = ev.emotion_id
+ JOIN model_version mv ON e.model_version_id = mv.id
WHERE e.document_id = :documentId
+ AND mv.model_id = :modelId
ORDER BY e.beginn, e.endd
""";
var query = session.createNativeQuery(sql)
- .setParameter("documentId", documentId);
+ .setParameter("documentId", documentId)
+ .setParameter("modelId", modelId);
var result = query.getResultList();
List emotionIds = new ArrayList<>();
@@ -2114,6 +2117,33 @@ public List getEmotionTypes() throws DatabaseOperationException {
});
}
+ public List getEmotionTypesForDocument(long documentId, long modelId) throws DatabaseOperationException {
+ return executeOperationSafely((session) -> {
+ String sql = """
+ SELECT DISTINCT et.id AS emotionTypeId, et.name AS emotionTypeName
+ FROM emotion e
+ JOIN emotion_value ev ON e.id = ev.emotion_id
+ JOIN emotion_type et ON ev.emotion_type_id = et.id
+ JOIN model_version mv ON e.model_version_id = mv.id
+ WHERE e.document_id = :documentId
+ AND mv.model_id = :modelId
+ """;
+ var query = session.createNativeQuery(sql)
+ .setParameter("documentId", documentId)
+ .setParameter("modelId", modelId);
+ //noinspection unchecked
+ return query.getResultList().stream()
+ .map(result -> {
+ Object[] row = (Object[]) result;
+ return Map.of(
+ "id", ((Number) row[0]).longValue(),
+ "name", (String) row[1]
+ );
+ })
+ .toList();
+ });
+ }
+
public List getGeonameByPage(long documentId) throws DatabaseOperationException {
return executeOperationSafely((session) -> {
String sql = "SELECT gn.page_id, gn.coveredtext AS geoname_value " +
@@ -2330,6 +2360,8 @@ private Document initializeCompleteDocument(Document doc, int skipPages, int pag
for (var emotion : doc.getEmotions()) {
Hibernate.initialize(emotion.getEmotionValues());
+ Hibernate.initialize(emotion.getModelVersion());
+ Hibernate.initialize(emotion.getModelVersion().getModel());
for (var ev : emotion.getEmotionValues()) {
Hibernate.initialize(ev.getEmotionType());
}
diff --git a/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/DocumentApi.java b/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/DocumentApi.java
index 09051178..9c5586b3 100644
--- a/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/DocumentApi.java
+++ b/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/DocumentApi.java
@@ -2,6 +2,8 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
import freemarker.template.Configuration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -13,6 +15,7 @@
import org.texttechnologylab.config.CorpusConfig;
import org.texttechnologylab.exceptions.ExceptionUtils;
import org.texttechnologylab.models.corpus.UCEMetadataValueType;
+import org.texttechnologylab.models.modelInfo.ModelCategory;
import org.texttechnologylab.models.search.SearchType;
import org.texttechnologylab.services.PostgresqlDataInterface_Impl;
import org.texttechnologylab.services.S3StorageService;
@@ -186,6 +189,14 @@ public DocumentApi(ApplicationContext serviceContext, Configuration freemarkerCo
model.put("searchTokens", String.join("[TOKEN]", activeSearchState.getSearchTokens()));
}
}
+
+ List modelCategories = new ArrayList<>();
+ List modelCategoryNames = doc.getModelCategories();
+ for (String categoryName : modelCategoryNames) {
+ ModelCategory modelCategory = db.getOrCreateModelCategory(categoryName);
+ modelCategories.add(modelCategory);
+ }
+ model.put("modelCategories", modelCategories);
} catch (Exception ex) {
logger.error("Error creating the document reader view for document with id: " + id, ex);
return new CustomFreeMarkerEngine(this.freemarkerConfig).render(new ModelAndView(null, "defaultError.ftl"));
@@ -203,10 +214,24 @@ public DocumentApi(ApplicationContext serviceContext, Configuration freemarkerCo
if (id == null)
return new CustomFreeMarkerEngine(this.freemarkerConfig).render(new ModelAndView(null, "defaultError.ftl"));
+ String modelSelectionString = request.queryParams("modelSelection");
+ Map modelSelection = new HashMap<>();
+ if (modelSelectionString != null && !modelSelectionString.isEmpty()) {
+ try {
+ JsonObject jsonObject = JsonParser.parseString(modelSelectionString).getAsJsonObject();
+ for (var entry : jsonObject.entrySet()) {
+ modelSelection.put(entry.getKey(), entry.getValue().getAsInt());
+ }
+ } catch (Exception ex) {
+ logger.error("Error parsing model selection JSON: " + modelSelectionString, ex);
+ }
+ }
+
+
try {
var skip = Integer.parseInt(request.queryParams("skip"));
var doc = db.getCompleteDocumentById(Long.parseLong(id), skip, 10);
- var annotations = doc.getAllAnnotations(skip, 10);
+ var annotations = doc.getAllAnnotations(skip, 10, modelSelection);
model.put("documentAnnotations", annotations);
model.put("documentText", doc.getFullText());
model.put("documentPages", doc.getPages(10, skip));
@@ -518,6 +543,8 @@ public DocumentApi(ApplicationContext serviceContext, Configuration freemarkerCo
public Route getDocumentEmotionDevelopment = (request, response) -> {
var documentId = ExceptionUtils.tryCatchLog(() -> Long.parseLong(request.queryParams("documentId")),
(ex) -> logger.error("Error: couldn't determine the documentId for emotion development.", ex));
+ String modelIdString = request.queryParams("modelId");
+ int modelId = modelIdString == null ? -1 : Integer.parseInt(modelIdString);
if (documentId == null) {
response.status(400);
@@ -526,8 +553,8 @@ public DocumentApi(ApplicationContext serviceContext, Configuration freemarkerCo
}
try {
- var emotionData = db.getDocumentEmotionsOrdered(documentId);
- var emotionTypes = db.getEmotionTypes();
+ var emotionData = db.getDocumentEmotionsOrdered(documentId, modelId);
+ var emotionTypes = db.getEmotionTypesForDocument(documentId, modelId);
Map emotionDevelopment = new HashMap<>();
emotionDevelopment.put("emotionTypes", emotionTypes);
emotionDevelopment.put("emotionData", emotionData);
diff --git a/uce.portal/uce.web/src/main/resources/languageTranslations.json b/uce.portal/uce.web/src/main/resources/languageTranslations.json
index fe109024..9549e573 100644
--- a/uce.portal/uce.web/src/main/resources/languageTranslations.json
+++ b/uce.portal/uce.web/src/main/resources/languageTranslations.json
@@ -451,6 +451,10 @@
"de-DE": "Wichtige Themen",
"en-EN": "Key Topics"
},
+ "modelSelection": {
+ "de-DE": "Modellauswahl",
+ "en-EN": "Model Selection"
+ },
"corpusWords": {
"de-DE": "Top Wörter aus dem Korpus",
"en-EN": "Top words from Corpus"
diff --git a/uce.portal/uce.web/src/main/resources/public/js/visualization/echarts.js b/uce.portal/uce.web/src/main/resources/public/js/visualization/echarts.js
index 446bdd74..787a6074 100644
--- a/uce.portal/uce.web/src/main/resources/public/js/visualization/echarts.js
+++ b/uce.portal/uce.web/src/main/resources/public/js/visualization/echarts.js
@@ -23,6 +23,14 @@ class ECharts {
this.option = newOption;
this.render();
}
+
+ setChartId(id) {
+ this.chartId = id;
+ }
+
+ getChartId() {
+ return this.chartId;
+ }
}
-export { ECharts };
+export {ECharts};
From cd92c8d1b5939994328643c8cc627f692040b5b1 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Wed, 23 Jul 2025 13:49:26 +0200
Subject: [PATCH 083/114] fix events
---
.../resources/templates/js/documentReader.js | 63 +++++++++++++++----
uce.portal/resources/templates/js/graphViz.js | 10 +--
2 files changed, 53 insertions(+), 20 deletions(-)
diff --git a/uce.portal/resources/templates/js/documentReader.js b/uce.portal/resources/templates/js/documentReader.js
index 0e991504..754acb88 100644
--- a/uce.portal/resources/templates/js/documentReader.js
+++ b/uce.portal/resources/templates/js/documentReader.js
@@ -1497,6 +1497,43 @@ function renderEmotionDevelopment(containerId) {
chartConfig,
tooltipFormatter
);
+ const chart = window.graphVizHandler.getChartById(chartId);
+ if (!chart) {
+ console.error('Chart not found for ID:', chartId);
+ return;
+ }
+ // remove old event listeners
+ chart.getInstance().off('mouseover');
+ chart.getInstance().off('mouseout');
+ chart.getInstance().off('click');
+
+ // add new event listeners
+ chart.getInstance().on('mouseover', function (params) {
+ // remove previous highlights to be safe
+ $('.emotion-covered.highlight').removeClass('highlight');
+ const index = params.dataIndex;
+ const elementId = 'emot-E-' + emotionData.emotionData[index][0].emotionId;
+ const element = $('#' + elementId);
+ element.addClass('highlight');
+ });
+ chart.getInstance().on('mouseout', function (params) {
+ const index = params.dataIndex;
+ const elementId = 'emot-E-' + emotionData.emotionData[index][0].emotionId;
+ const element = $('#' + elementId);
+ element.removeClass('highlight');
+ });
+ chart.getInstance().on('click', function (params) {
+ const index = params.dataIndex;
+ const emotionId = emotionData.emotionData[index][0].emotionId;
+ const elementId = 'emot-E-' + emotionId;
+ const element = document.getElementById(elementId);
+ if (element) {
+ element.scrollIntoView({behavior: 'smooth', block: 'center'});
+ clearTopicColoring();
+ hideTopicNavButtons();
+ $('.scrollbar-minimap').hide();
+ }
+ });
container.classList.remove('dirty');
}
else {
@@ -1504,19 +1541,7 @@ function renderEmotionDevelopment(containerId) {
containerId,
'',
chartConfig,
- tooltipFormatter,
- function (params) {
- const index = params.dataIndex;
- const emotionId = emotionData.emotionData[index][0].emotionId;
- const elementId = 'emot-E-' + emotionId;
- const element = document.getElementById(elementId);
- if (element) {
- element.scrollIntoView({behavior: 'smooth', block: 'center'});
- clearTopicColoring();
- hideTopicNavButtons();
- $('.scrollbar-minimap').hide();
- }
- }
+ tooltipFormatter
).then(chart => {
chart.getInstance().on('mouseover', function (params) {
// remove previous highlights to be safe
@@ -1532,6 +1557,18 @@ function renderEmotionDevelopment(containerId) {
const element = $('#' + elementId);
element.removeClass('highlight');
});
+ chart.getInstance().on('click', function (params) {
+ const index = params.dataIndex;
+ const emotionId = emotionData.emotionData[index][0].emotionId;
+ const elementId = 'emot-E-' + emotionId;
+ const element = document.getElementById(elementId);
+ if (element) {
+ element.scrollIntoView({behavior: 'smooth', block: 'center'});
+ clearTopicColoring();
+ hideTopicNavButtons();
+ $('.scrollbar-minimap').hide();
+ }
+ });
$(container).on('mouseout', function () {
$('.emotion-covered.highlight').removeClass('highlight');
});
diff --git a/uce.portal/resources/templates/js/graphViz.js b/uce.portal/resources/templates/js/graphViz.js
index c08d70a6..f7401364 100644
--- a/uce.portal/resources/templates/js/graphViz.js
+++ b/uce.portal/resources/templates/js/graphViz.js
@@ -295,8 +295,7 @@ var GraphVizHandler = (function () {
target,
title,
config,
- tooltipFormatter,
- onClick = null
+ tooltipFormatter
) {
const chartId = generateUUID();
@@ -448,10 +447,6 @@ var GraphVizHandler = (function () {
this.activeCharts[chartId] = echart;
echart.setChartId(chartId);
- if (onClick && typeof onClick === 'function') {
- echart.getInstance().on('click', onClick);
- }
-
return echart;
};
@@ -459,7 +454,8 @@ var GraphVizHandler = (function () {
chartId,
title,
config,
- tooltipFormatter) {
+ tooltipFormatter
+ ) {
const echart = this.getChartById(chartId);
if (!echart) {
console.warn('No chart found with ID:', chartId);
From c828de065e962d28ce877eea490f0af7fe87682c Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Fri, 25 Jul 2025 15:18:23 +0200
Subject: [PATCH 084/114] Refactor OffensiveSpeech entity and add
OffensiveSpeechType and OffensiveSpeechValue classes
---
.../offensiveSpeech/OffensiveSpeech.java | 10 ++----
.../offensiveSpeech/OffensiveSpeechType.java | 27 +++++++++++++++
.../offensiveSpeech/OffensiveSpeechValue.java | 33 +++++++++++++++++++
3 files changed, 63 insertions(+), 7 deletions(-)
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechType.java
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechValue.java
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
index 60243c6e..2a9023a0 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
@@ -19,13 +19,9 @@ public class OffensiveSpeech extends UIMAAnnotation implements WikiModel {
@Getter
@Setter
- @Column(name = "offensive", nullable = false)
- private double offensive;
-
- @Getter
- @Setter
- @Column(name = "non_offensive", nullable = false)
- private double nonOffensive;
+ @OneToMany(mappedBy = "offensiveSpeech", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Fetch(FetchMode.SUBSELECT)
+ private List offensiveSpeechValues;
@ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "document_id", nullable = false)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechType.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechType.java
new file mode 100644
index 00000000..de77b8bd
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechType.java
@@ -0,0 +1,27 @@
+package org.texttechnologylab.models.offensiveSpeech;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.texttechnologylab.models.ModelBase;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "offensive_speech_type")
+public class OffensiveSpeechType extends ModelBase {
+
+ @Getter
+ @Setter
+ @Column(name = "name", nullable = false, unique = true)
+ private String name;
+
+ public OffensiveSpeechType() {
+ }
+
+ public OffensiveSpeechType(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechValue.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechValue.java
new file mode 100644
index 00000000..64bd0bd4
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechValue.java
@@ -0,0 +1,33 @@
+package org.texttechnologylab.models.offensiveSpeech;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.texttechnologylab.models.ModelBase;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "offensive_speech_value")
+public class OffensiveSpeechValue extends ModelBase {
+
+ @Getter
+ @Setter
+ @ManyToOne
+ @JoinColumn(name = "offensive_speech_type_id", nullable = false)
+ private OffensiveSpeechType offensiveSpeechType;
+
+ @Getter
+ @Setter
+ @ManyToOne
+ @JoinColumn(name = "offensive_speech_id", nullable = false)
+ private OffensiveSpeech offensiveSpeech;
+
+ @Getter
+ @Setter
+ @Column(name = "value", nullable = false)
+ private double value;
+
+ public OffensiveSpeechValue() {
+ }
+
+}
From 52a902e4c6e0a91faee07f2dd3cc47dfd8ae323b Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Fri, 25 Jul 2025 15:39:32 +0200
Subject: [PATCH 085/114] Add OffensiveSpeechType and OffensiveSpeechValue
handling in Hibernate configuration and importer
---
.../config/HibernateConf.java | 4 ++
.../PostgresqlDataInterface_Impl.java | 21 +++++++++
.../java/org/texttechnologylab/Importer.java | 47 +++++++++++--------
3 files changed, 52 insertions(+), 20 deletions(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
index e331f1aa..490cec79 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
@@ -18,6 +18,8 @@
import org.texttechnologylab.models.imp.UCEImport;
import org.texttechnologylab.models.negation.*;
import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeechType;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeechValue;
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicValueBaseWithScore;
import org.texttechnologylab.models.topic.TopicWord;
@@ -83,6 +85,8 @@ public static SessionFactory buildSessionFactory() {
metadataSources.addAnnotatedClass(TopicValueBaseWithScore.class);
// offensive speech
metadataSources.addAnnotatedClass(OffensiveSpeech.class);
+ metadataSources.addAnnotatedClass(OffensiveSpeechValue.class);
+ metadataSources.addAnnotatedClass(OffensiveSpeechType.class);
metadataSources.addAnnotatedClass(DocumentTopThreeTopics.class);
var metadata = metadataSources.buildMetadata();
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index dc9b4541..03ef50ed 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -30,6 +30,7 @@
import org.texttechnologylab.models.imp.ImportLog;
import org.texttechnologylab.models.imp.UCEImport;
import org.texttechnologylab.models.negation.CompleteNegation;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeechType;
import org.texttechnologylab.models.search.*;
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicWord;
@@ -1356,6 +1357,26 @@ public UnifiedTopic getInitializedUnifiedTopicById(long id) throws DatabaseOpera
});
}
+ public synchronized OffensiveSpeechType getOrCreateOffensiveSpeechType(String name) throws DatabaseOperationException {
+ return executeOperationSafely((session) -> {
+ var criteriaBuilder = session.getCriteriaBuilder();
+ var criteriaQuery = criteriaBuilder.createQuery(OffensiveSpeechType.class);
+ var root = criteriaQuery.from(OffensiveSpeechType.class);
+ criteriaQuery.select(root)
+ .where(criteriaBuilder.equal(root.get("name"), name));
+
+ OffensiveSpeechType offensiveSpeechType;
+ try {
+ offensiveSpeechType = session.createQuery(criteriaQuery).getSingleResult();
+ } catch (NoResultException e) {
+ // If not found, create a new one
+ offensiveSpeechType = new OffensiveSpeechType(name);
+ session.save(offensiveSpeechType);
+ }
+ return offensiveSpeechType;
+ });
+ }
+
public List getKeywordDistributionsByString(Class clazz, String topic, int limit) throws DatabaseOperationException {
return executeOperationSafely((session) -> {
var builder = session.getCriteriaBuilder();
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index 2ce83cc9..62529be4 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -45,6 +45,8 @@
import org.texttechnologylab.models.imp.LogStatus;
import org.texttechnologylab.models.negation.*;
import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeechType;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeechValue;
import org.texttechnologylab.models.rag.DocumentChunkEmbedding;
import org.texttechnologylab.models.rag.DocumentSentenceEmbedding;
import org.texttechnologylab.models.topic.TopicValueBase;
@@ -1430,27 +1432,32 @@ private void setOffensiveSpeech(Document document, JCas jCas) {
logger.warn("OffensiveSpeech annotation without offensives found. Skipping this annotation.");
return;
}
- if (offensives.size() != 2) {
- logger.warn("OffensiveSpeech annotation with " + offensives.size() + " offensives found. Expected 2. Skipping this annotation.");
- return;
- }
- final String offensiveKey = "Offensive";
- final String nonOffensiveKey = "Not Offensive";
- OptionalDouble offensiveScore = offensives.stream()
- .filter(oc -> oc.getKey().equals(offensiveKey))
- .map(AnnotationComment::getValue)
- .mapToDouble(Double::parseDouble)
- .findFirst();
- OptionalDouble nonOffensiveScore = offensives.stream()
- .filter(oc -> oc.getKey().equals(nonOffensiveKey))
- .map(AnnotationComment::getValue)
- .mapToDouble(Double::parseDouble).findFirst();
- if (offensiveScore.isEmpty() || nonOffensiveScore.isEmpty()) {
- logger.warn("OffensiveSpeech annotation without offensive or non-offensive score found. Skipping this annotation.");
- return;
+
+ List offensivesList = new ArrayList<>();
+ for (AnnotationComment offensive : offensives) {
+ String offensiveSpeechName = offensive.getKey();
+ OffensiveSpeechType offensiveSpeechType;
+ try {
+ offensiveSpeechType = db.getOrCreateOffensiveSpeechType(offensiveSpeechName);
+ } catch (DatabaseOperationException e) {
+ logger.warn("Unknown OffensiveSpeechType: " + offensiveSpeechName + ". Skipping this annotation.");
+ continue;
+ }
+ double value;
+ try {
+ value = Double.parseDouble(offensive.getValue());
+ }
+ catch (NumberFormatException e) {
+ logger.warn("Invalid value for OffensiveSpeechType: " + offensiveSpeechName + ". Skipping this annotation.");
+ continue;
+ }
+ OffensiveSpeechValue offensiveSpeechValue = new OffensiveSpeechValue();
+ offensiveSpeechValue.setOffensiveSpeechType(offensiveSpeechType);
+ offensiveSpeechValue.setValue(value);
+ offensiveSpeechValue.setOffensiveSpeech(offensiveSpeech);
+ offensivesList.add(offensiveSpeechValue);
}
- offensiveSpeech.setOffensive(offensiveScore.getAsDouble());
- offensiveSpeech.setNonOffensive(nonOffensiveScore.getAsDouble());
+ offensiveSpeech.setOffensiveSpeechValues(offensivesList);
offensiveSpeeches.add(offensiveSpeech);
});
From 32be6312c784aad6b9196e2965459394b663d21d Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Fri, 25 Jul 2025 17:37:12 +0200
Subject: [PATCH 086/114] Rename database table names for OffensiveSpeech,
OffensiveSpeechType, and OffensiveSpeechValue to remove underscores
---
.../models/offensiveSpeech/OffensiveSpeech.java | 2 +-
.../models/offensiveSpeech/OffensiveSpeechType.java | 2 +-
.../models/offensiveSpeech/OffensiveSpeechValue.java | 6 +++---
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
index 2a9023a0..29c45045 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
@@ -13,7 +13,7 @@
import java.util.List;
@Entity
-@Table(name = "offensive_speech")
+@Table(name = "offensivespeech")
@Typesystem(types = {org.texttechnologylab.annotation.OffensiveSpeech.class})
public class OffensiveSpeech extends UIMAAnnotation implements WikiModel {
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechType.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechType.java
index de77b8bd..d1bbad48 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechType.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechType.java
@@ -9,7 +9,7 @@
import javax.persistence.Table;
@Entity
-@Table(name = "offensive_speech_type")
+@Table(name = "offensivespeech_type")
public class OffensiveSpeechType extends ModelBase {
@Getter
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechValue.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechValue.java
index 64bd0bd4..15bc1f1a 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechValue.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeechValue.java
@@ -7,19 +7,19 @@
import javax.persistence.*;
@Entity
-@Table(name = "offensive_speech_value")
+@Table(name = "offensivespeech_value")
public class OffensiveSpeechValue extends ModelBase {
@Getter
@Setter
@ManyToOne
- @JoinColumn(name = "offensive_speech_type_id", nullable = false)
+ @JoinColumn(name = "offensivespeech_type_id", nullable = false)
private OffensiveSpeechType offensiveSpeechType;
@Getter
@Setter
@ManyToOne
- @JoinColumn(name = "offensive_speech_id", nullable = false)
+ @JoinColumn(name = "offensivespeech_id", nullable = false)
private OffensiveSpeech offensiveSpeech;
@Getter
From 7d52b6612aab2a0665f53fe98629771738752305 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Fri, 25 Jul 2025 17:21:40 +0200
Subject: [PATCH 087/114] Add Offensive Speech annotation in
corpusAnnotations.ftl
---
.../templates/corpus/components/corpusAnnotations.ftl | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl b/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl
index cfd27136..fdf69176 100644
--- a/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl
+++ b/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl
@@ -186,5 +186,16 @@
+
+
+ <#assign isChecked = "" />
+ <#if corpusConfig.getAnnotations().isOffensiveSpeech() == true>
+ <#assign isChecked = "checked"/>
+ #if>
+
+
+
+
+
\ No newline at end of file
From 240eccad7980b19892692cea6b63c6131580bb35 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Fri, 25 Jul 2025 17:39:34 +0200
Subject: [PATCH 088/114] Add OffensiveSpeech model to LexiconService
---
.../java/org/texttechnologylab/services/LexiconService.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/LexiconService.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/LexiconService.java
index b6cf739e..74e967e2 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/LexiconService.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/LexiconService.java
@@ -13,6 +13,7 @@
import org.texttechnologylab.models.biofid.GnFinderTaxon;
import org.texttechnologylab.models.corpus.*;
import org.texttechnologylab.models.negation.*;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
import org.texttechnologylab.models.topic.UnifiedTopic;
import org.texttechnologylab.models.viewModels.lexicon.LexiconOccurrenceViewModel;
import org.texttechnologylab.utils.SystemStatus;
@@ -47,7 +48,8 @@ public class LexiconService {
Cue.class,
Scope.class,
XScope.class,
- UnifiedTopic.class));
+ UnifiedTopic.class,
+ OffensiveSpeech.class));
public LexiconService(PostgresqlDataInterface_Impl db) {
this.db = db;
From 1fe22564e3f2b327c8f7c1da678591a25fef8c55 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Fri, 25 Jul 2025 17:42:31 +0200
Subject: [PATCH 089/114] Add styling for Offensive Speech entries in lexicon
view
---
uce.portal/resources/templates/css/lexicon.css | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/uce.portal/resources/templates/css/lexicon.css b/uce.portal/resources/templates/css/lexicon.css
index a025a286..b351aefe 100644
--- a/uce.portal/resources/templates/css/lexicon.css
+++ b/uce.portal/resources/templates/css/lexicon.css
@@ -108,6 +108,10 @@
border-left-color: darkred;
}
+.lexicon-view .lexicon-entry[data-type="offensivespeech"]{
+ border-left-color: silver;
+}
+
.lexicon-view .filter-container{
width: 100%;
position: sticky;
From dc02c79d179ad02c04214e0987a94c5e98800af1 Mon Sep 17 00:00:00 2001
From: mirelamuza
Date: Fri, 25 Jul 2025 18:17:54 +0200
Subject: [PATCH 090/114] Add Offensive Speech annotation page and view model
---
.../pages/offensiveSpeechAnnotationPage.ftl | 55 +++++++++++++++++++
.../offensiveSpeech/OffensiveSpeech.java | 7 +++
...siveSpeechAnnotationWikiPageViewModel.java | 7 +++
.../PostgresqlDataInterface_Impl.java | 13 +++++
.../services/WikiService.java | 15 +++++
.../org/texttechnologylab/routes/WikiApi.java | 3 +
6 files changed, 100 insertions(+)
create mode 100644 uce.portal/resources/templates/wiki/pages/offensiveSpeechAnnotationPage.ftl
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/viewModels/wiki/OffensiveSpeechAnnotationWikiPageViewModel.java
diff --git a/uce.portal/resources/templates/wiki/pages/offensiveSpeechAnnotationPage.ftl b/uce.portal/resources/templates/wiki/pages/offensiveSpeechAnnotationPage.ftl
new file mode 100644
index 00000000..75c21d59
--- /dev/null
+++ b/uce.portal/resources/templates/wiki/pages/offensiveSpeechAnnotationPage.ftl
@@ -0,0 +1,55 @@
+
+
+
+
+ <#include "*/wiki/components/breadcrumbs.ftl">
+
+
+
+
+ <#include "*/wiki/components/metadata.ftl">
+
+
+
+
+
+
+ <#assign offensiveSpeech = vm.getWikiModel()>
+
+ <#list offensiveSpeech.collectOffensiveSpeechValues() as propertyPair>
+
+
+
+
+
+
+ #list>
+
+
+
+
+
+
+ <#assign document = vm.getDocument()>
+ <#assign searchId = "">
+ <#assign reduced = true>
+ <#include '*/search/components/documentCardContent.ftl' >
+
+
+
+
+
+ <#assign unique = (vm.getWikiModel().getUnique())!"none">
+ <#assign height = 500>
+ <#if unique != "none">
+
+ <#include "*/wiki/components/linkableSpace.ftl">
+
+ #if>
+
+
+
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
index 29c45045..55637393 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
@@ -8,6 +8,7 @@
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
+import org.texttechnologylab.utils.Pair;
import javax.persistence.*;
import java.util.List;
@@ -53,4 +54,10 @@ public String getWikiId() {
return "OS" + "-" + this.getId();
}
+ public List> collectOffensiveSpeechValues() {
+ return this.offensiveSpeechValues.stream()
+ .map(value -> new Pair<>(value.getOffensiveSpeechType().getName(), value.getValue()))
+ .toList();
+ }
+
}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/viewModels/wiki/OffensiveSpeechAnnotationWikiPageViewModel.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/viewModels/wiki/OffensiveSpeechAnnotationWikiPageViewModel.java
new file mode 100644
index 00000000..ea5b8c01
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/viewModels/wiki/OffensiveSpeechAnnotationWikiPageViewModel.java
@@ -0,0 +1,7 @@
+package org.texttechnologylab.models.viewModels.wiki;
+
+public class OffensiveSpeechAnnotationWikiPageViewModel extends AnnotationWikiPageViewModel{
+
+
+
+}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index 03ef50ed..93d84b0c 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -30,7 +30,9 @@
import org.texttechnologylab.models.imp.ImportLog;
import org.texttechnologylab.models.imp.UCEImport;
import org.texttechnologylab.models.negation.CompleteNegation;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeechType;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeechValue;
import org.texttechnologylab.models.search.*;
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicWord;
@@ -1377,6 +1379,17 @@ public synchronized OffensiveSpeechType getOrCreateOffensiveSpeechType(String na
});
}
+ public OffensiveSpeech getInitializedOffensiveSpeechById(long id) throws DatabaseOperationException {
+ return executeOperationSafely((session) -> {
+ var offensiveSpeech = session.get(OffensiveSpeech.class, id);
+ Hibernate.initialize(offensiveSpeech.getOffensiveSpeechValues());
+ for (OffensiveSpeechValue value : offensiveSpeech.getOffensiveSpeechValues()) {
+ Hibernate.initialize(value.getOffensiveSpeechType());
+ }
+ return offensiveSpeech;
+ });
+ }
+
public List getKeywordDistributionsByString(Class clazz, String topic, int limit) throws DatabaseOperationException {
return executeOperationSafely((session) -> {
var builder = session.getCriteriaBuilder();
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/WikiService.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/WikiService.java
index 8fdb832c..6a002a3a 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/WikiService.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/WikiService.java
@@ -109,6 +109,21 @@ public UnifiedTopicWikiPageViewModel buildUnifiedTopicWikiPageViewModel(long id,
return viewModel;
}
+ /**
+ * Builds a view model to render a offensive speech annotation wiki page
+ */
+ public OffensiveSpeechAnnotationWikiPageViewModel buildOffensiveSpeechAnnotationWikiPageViewModel(long id, String coveredText) throws DatabaseOperationException {
+ var viewModel = new OffensiveSpeechAnnotationWikiPageViewModel();
+ var offensiveSpeech = db.getInitializedOffensiveSpeechById(id);
+ viewModel.setWikiModel(offensiveSpeech);
+ viewModel.setDocument(db.getDocumentById(offensiveSpeech.getDocumentId()));
+ viewModel.setCorpus(db.getCorpusById(viewModel.getDocument().getCorpusId()).getViewModel());
+ viewModel.setCoveredText(coveredText);
+ viewModel.setAnnotationType("OffensiveSpeech");
+
+ return viewModel;
+ }
+
/**
* Gets a DocumentTopicDistributionWikiPageViewModel to render a Wikipage for that topic distribution
*/
diff --git a/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/WikiApi.java b/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/WikiApi.java
index 38f1b077..b2557a31 100644
--- a/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/WikiApi.java
+++ b/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/WikiApi.java
@@ -165,6 +165,9 @@ public WikiApi(ApplicationContext serviceContext, Configuration freemarkerConfig
} else if (type.startsWith("DTR")) {
model.put("vm", wikiService.buildTopicWikiPageViewModel(id, coveredText));
renderView = "/wiki/pages/topicPage.ftl";
+ } else if (type.startsWith("OS")) {
+ model.put("vm", wikiService.buildOffensiveSpeechAnnotationWikiPageViewModel(id, coveredText));
+ renderView = "/wiki/pages/offensiveSpeechAnnotationPage.ftl";
} else {
// The type part of the wikiId was unknown. Throw an error.
logger.warn("Someone tried to query a wiki page of a type that does not exist in UCE. This shouldn't happen.");
From ea4e33f52671f719da425f7d3e9dfcc4c708e612 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Fri, 25 Jul 2025 18:56:37 +0200
Subject: [PATCH 091/114] Add ToxicValue and ToxicType classes with
relationships to Toxic
---
.../texttechnologylab/models/toxic/Toxic.java | 30 +++++++----------
.../models/toxic/ToxicType.java | 26 +++++++++++++++
.../models/toxic/ToxicValue.java | 33 +++++++++++++++++++
3 files changed, 70 insertions(+), 19 deletions(-)
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/ToxicType.java
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/ToxicValue.java
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
index 46aa6556..3cc355e7 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
@@ -1,20 +1,27 @@
package org.texttechnologylab.models.toxic;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.Fetch;
+import org.hibernate.annotations.FetchMode;
import org.texttechnologylab.annotations.Typesystem;
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
import javax.persistence.*;
+import java.util.List;
@Entity
@Table(name = "toxic")
@Typesystem(types = {org.texttechnologylab.annotation.Toxic.class})
public class Toxic extends UIMAAnnotation implements WikiModel {
- @Column(name = "toxic", nullable = false)
- private double toxic;
- @Column(name = "non_toxic", nullable = false)
- private double nonToxic;
+
+ @Getter
+ @Setter
+ @OneToMany(mappedBy = "toxic", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+ @Fetch(value = FetchMode.SUBSELECT)
+ private List toxicValues;
@ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "document_id", nullable = false)
@@ -33,21 +40,6 @@ public Toxic(int begin, int end, String coveredText) {
setCoveredText(coveredText);
}
- public double getToxic() {
- return toxic;
- }
- public void setToxic(double newToxic) {
- this.toxic = newToxic;
- }
-
- public double getNonToxic() {
- return nonToxic;
- }
-
- public void setNonToxic(double nonToxic) {
- this.nonToxic = nonToxic;
- }
-
public Document getDocument() {
return document;
}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/ToxicType.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/ToxicType.java
new file mode 100644
index 00000000..8db08c50
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/ToxicType.java
@@ -0,0 +1,26 @@
+package org.texttechnologylab.models.toxic;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.texttechnologylab.models.ModelBase;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "toxic_type")
+public class ToxicType extends ModelBase {
+
+ @Getter
+ @Setter
+ @Column(name = "name", nullable = false, unique = true)
+ private String name;
+
+ public ToxicType() {}
+
+ public ToxicType(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/ToxicValue.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/ToxicValue.java
new file mode 100644
index 00000000..756d2037
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/ToxicValue.java
@@ -0,0 +1,33 @@
+package org.texttechnologylab.models.toxic;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.texttechnologylab.models.ModelBase;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "toxic_value")
+public class ToxicValue extends ModelBase {
+
+ @Getter
+ @Setter
+ @ManyToOne
+ @JoinColumn(name = "toxic_id", nullable = false)
+ private Toxic toxic;
+
+ @Getter
+ @Setter
+ @ManyToOne
+ @JoinColumn(name = "toxic_type_id", nullable = false)
+ private ToxicType toxicType;
+
+ @Getter
+ @Setter
+ @Column(name = "value", nullable = false)
+ private double value;
+
+ public ToxicValue() {
+ }
+
+}
From 2ee136faefc6fdcb369c842fdda855d4d4839ba1 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Fri, 25 Jul 2025 18:58:58 +0200
Subject: [PATCH 092/114] Add ToxicValue and ToxicType classes to Hibernate
metadata sources
---
.../main/java/org/texttechnologylab/config/HibernateConf.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
index 905962ea..f9504a3f 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
@@ -22,6 +22,8 @@
import org.texttechnologylab.models.topic.TopicWord;
import org.texttechnologylab.models.topic.UnifiedTopic;
import org.texttechnologylab.models.toxic.Toxic;
+import org.texttechnologylab.models.toxic.ToxicType;
+import org.texttechnologylab.models.toxic.ToxicValue;
import java.util.HashMap;
@@ -83,6 +85,8 @@ public static SessionFactory buildSessionFactory() {
metadataSources.addAnnotatedClass(TopicValueBaseWithScore.class);
// toxic
metadataSources.addAnnotatedClass(Toxic.class);
+ metadataSources.addAnnotatedClass(ToxicValue.class);
+ metadataSources.addAnnotatedClass(ToxicType.class);
metadataSources.addAnnotatedClass(DocumentTopThreeTopics.class);
var metadata = metadataSources.buildMetadata();
From e47c9e70660c17a20ca9c3af47eac62466db8e0a Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Fri, 25 Jul 2025 19:40:01 +0200
Subject: [PATCH 093/114] Add ToxicValue and ToxicType handling in Importer and
Data Interface
---
.../PostgresqlDataInterface_Impl.java | 19 +++++++++++++
.../java/org/texttechnologylab/Importer.java | 27 +++++++++++++++++--
2 files changed, 44 insertions(+), 2 deletions(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index dc9b4541..6fb88345 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -34,6 +34,7 @@
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicWord;
import org.texttechnologylab.models.topic.UnifiedTopic;
+import org.texttechnologylab.models.toxic.ToxicType;
import org.texttechnologylab.models.util.HealthStatus;
import org.texttechnologylab.utils.ReflectionUtils;
import org.texttechnologylab.utils.StringUtils;
@@ -1356,6 +1357,24 @@ public UnifiedTopic getInitializedUnifiedTopicById(long id) throws DatabaseOpera
});
}
+ public synchronized ToxicType getOrCreateToxicType(String type) throws DatabaseOperationException {
+ return executeOperationSafely((session) -> {
+ var criteriaBuilder = session.getCriteriaBuilder();
+ var criteriaQuery = criteriaBuilder.createQuery(ToxicType.class);
+ var root = criteriaQuery.from(ToxicType.class);
+ criteriaQuery.select(root).where(criteriaBuilder.equal(root.get("name"), type));
+
+ ToxicType toxicType;
+ try {
+ toxicType = session.createQuery(criteriaQuery).getSingleResult();
+ } catch (NoResultException e) {
+ toxicType = new ToxicType(type);
+ session.save(toxicType);
+ }
+ return toxicType;
+ });
+ }
+
public List getKeywordDistributionsByString(Class clazz, String topic, int limit) throws DatabaseOperationException {
return executeOperationSafely((session) -> {
var builder = session.getCriteriaBuilder();
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index f6eb8ce5..b47aa863 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -50,6 +50,8 @@
import org.texttechnologylab.models.topic.TopicWord;
import org.texttechnologylab.models.topic.UnifiedTopic;
import org.texttechnologylab.models.toxic.Toxic;
+import org.texttechnologylab.models.toxic.ToxicType;
+import org.texttechnologylab.models.toxic.ToxicValue;
import org.texttechnologylab.services.*;
import org.texttechnologylab.utils.*;
import org.texttechnologylab.models.negation.CompleteNegation;
@@ -1426,8 +1428,29 @@ private void setToxic(Document document, JCas jCas) {
Toxic toxic = new Toxic(t.getBegin(), t.getEnd());
toxic.setDocument(document);
toxic.setCoveredText(t.getCoveredText());
- toxic.setToxic(t.getToxic());
- toxic.setNonToxic(t.getNonToxic());
+
+ List toxicValues = new ArrayList<>();
+ ToxicType toxicType, nonToxicType;
+ try {
+ toxicType = db.getOrCreateToxicType("toxic");
+ nonToxicType = db.getOrCreateToxicType("non-toxic");
+ } catch (DatabaseOperationException e) {
+ logger.error("Error while fetching ToxicType or NonToxicType from database.", e);
+ return;
+ }
+
+ ToxicValue toxicValue = new ToxicValue();
+ toxicValue.setToxicType(toxicType);
+ toxicValue.setValue(t.getToxic());
+ toxicValue.setToxic(toxic);
+ toxicValues.add(toxicValue);
+ ToxicValue nonToxicValue = new ToxicValue();
+ nonToxicValue.setToxicType(nonToxicType);
+ nonToxicValue.setValue(t.getNonToxic());
+ nonToxicValue.setToxic(toxic);
+ toxicValues.add(nonToxicValue);
+
+ toxic.setToxicValues(toxicValues);
toxics.add(toxic);
});
From e7ac6392929155a25594a893181071602dab36ab Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Fri, 25 Jul 2025 19:41:09 +0200
Subject: [PATCH 094/114] Handle toxic annotations in Importer to set page
context
---
.../src/main/java/org/texttechnologylab/Importer.java | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index b47aa863..1c6f576e 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -999,6 +999,12 @@ private void updateAnnotationsWithPageId(Document document, Page page, boolean i
anno.setPage(page);
}
}
+ if (document.getToxics() != null) {
+ for (var anno : document.getToxics().stream().filter(t ->
+ (t.getBegin() >= page.getBegin() && t.getEnd() <= page.getEnd()) || (t.getPage() == null && isLastPage)).toList()) {
+ anno.setPage(page);
+ }
+ }
}
/**
From ccdd87bee8c0453b50f6e205b787d1cd8f9b3721 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Fri, 25 Jul 2025 19:50:41 +0200
Subject: [PATCH 095/114] Update condition check in document reader to use
'rendered' class instead of 'dirty'
---
uce.portal/resources/templates/js/documentReader.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/uce.portal/resources/templates/js/documentReader.js b/uce.portal/resources/templates/js/documentReader.js
index 754acb88..379ade22 100644
--- a/uce.portal/resources/templates/js/documentReader.js
+++ b/uce.portal/resources/templates/js/documentReader.js
@@ -1485,7 +1485,7 @@ function renderEmotionDevelopment(containerId) {
return tooltipContent;
};
- if (container.classList.contains('dirty')) {
+ if (container.classList.contains('rendered')) {
const chartId = container.getAttribute('chart-id');
if (!chartId) {
console.error('No chart ID found for container:', containerId);
From 29a663a144e71ddd17905a06c4cd43bb7ea5c223 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Thu, 7 Aug 2025 12:18:54 +0200
Subject: [PATCH 096/114] Add toxic annotation checkbox to corpus annotations
---
.../templates/corpus/components/corpusAnnotations.ftl | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl b/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl
index cfd27136..ffaa726e 100644
--- a/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl
+++ b/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl
@@ -186,5 +186,16 @@
+
+
+ <#assign isChecked = "" />
+ <#if corpusConfig.getAnnotations().isToxic() == true>
+ <#assign isChecked = "checked"/>
+ #if>
+
+
+
+
+
\ No newline at end of file
From 887d1a3a54926624e0b3db498bbea9c759cbb30e Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Thu, 7 Aug 2025 12:21:17 +0200
Subject: [PATCH 097/114] Add styling for toxic entries in lexicon and include
Toxic model in LexiconService
---
uce.portal/resources/templates/css/lexicon.css | 4 ++++
.../java/org/texttechnologylab/services/LexiconService.java | 5 ++++-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/uce.portal/resources/templates/css/lexicon.css b/uce.portal/resources/templates/css/lexicon.css
index a025a286..2575e52c 100644
--- a/uce.portal/resources/templates/css/lexicon.css
+++ b/uce.portal/resources/templates/css/lexicon.css
@@ -108,6 +108,10 @@
border-left-color: darkred;
}
+.lexicon-view .lexicon-entry[data-type="toxic"]{
+ border-left-color: darkorange;
+}
+
.lexicon-view .filter-container{
width: 100%;
position: sticky;
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/LexiconService.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/LexiconService.java
index b6cf739e..c2e866ec 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/LexiconService.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/LexiconService.java
@@ -14,6 +14,7 @@
import org.texttechnologylab.models.corpus.*;
import org.texttechnologylab.models.negation.*;
import org.texttechnologylab.models.topic.UnifiedTopic;
+import org.texttechnologylab.models.toxic.Toxic;
import org.texttechnologylab.models.viewModels.lexicon.LexiconOccurrenceViewModel;
import org.texttechnologylab.utils.SystemStatus;
@@ -47,7 +48,9 @@ public class LexiconService {
Cue.class,
Scope.class,
XScope.class,
- UnifiedTopic.class));
+ UnifiedTopic.class,
+ Toxic.class
+ ));
public LexiconService(PostgresqlDataInterface_Impl db) {
this.db = db;
From 85df96df4f8c9714fcdc86ce7c94eaaec7ab7e27 Mon Sep 17 00:00:00 2001
From: Xinran Wang
Date: Thu, 7 Aug 2025 14:11:23 +0200
Subject: [PATCH 098/114] Add Toxic annotation wiki page view model and
rendering logic
---
.../wiki/pages/toxicAnnotationPage.ftl | 55 +++++++++++++++++++
.../ToxicAnnotationWikiPageViewModel.java | 7 +++
.../PostgresqlDataInterface_Impl.java | 12 ++++
.../services/WikiService.java | 15 +++++
.../org/texttechnologylab/routes/WikiApi.java | 3 +
5 files changed, 92 insertions(+)
create mode 100644 uce.portal/resources/templates/wiki/pages/toxicAnnotationPage.ftl
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/viewModels/wiki/ToxicAnnotationWikiPageViewModel.java
diff --git a/uce.portal/resources/templates/wiki/pages/toxicAnnotationPage.ftl b/uce.portal/resources/templates/wiki/pages/toxicAnnotationPage.ftl
new file mode 100644
index 00000000..84b4bfe7
--- /dev/null
+++ b/uce.portal/resources/templates/wiki/pages/toxicAnnotationPage.ftl
@@ -0,0 +1,55 @@
+
+
+
+
+ <#include "*/wiki/components/breadcrumbs.ftl">
+
+
+
+
+ <#include "*/wiki/components/metadata.ftl">
+
+
+
+
+
+
+ <#assign toxic = vm.getWikiModel()>
+
+ <#list toxic.getToxicValues() as tv>
+
+
+
+
+
+
+ #list>
+
+
+
+
+
+
+ <#assign document = vm.getDocument()>
+ <#assign searchId = "">
+ <#assign reduced = true>
+ <#include '*/search/components/documentCardContent.ftl' >
+
+
+
+
+
+ <#assign unique = (vm.getWikiModel().getUnique())!"none">
+ <#assign height = 500>
+ <#if unique != "none">
+
+ <#include "*/wiki/components/linkableSpace.ftl">
+
+ #if>
+
+
+
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/viewModels/wiki/ToxicAnnotationWikiPageViewModel.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/viewModels/wiki/ToxicAnnotationWikiPageViewModel.java
new file mode 100644
index 00000000..808bc432
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/viewModels/wiki/ToxicAnnotationWikiPageViewModel.java
@@ -0,0 +1,7 @@
+package org.texttechnologylab.models.viewModels.wiki;
+
+public class ToxicAnnotationWikiPageViewModel extends AnnotationWikiPageViewModel{
+
+
+
+}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index 6fb88345..f03d0355 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -34,6 +34,7 @@
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicWord;
import org.texttechnologylab.models.topic.UnifiedTopic;
+import org.texttechnologylab.models.toxic.Toxic;
import org.texttechnologylab.models.toxic.ToxicType;
import org.texttechnologylab.models.util.HealthStatus;
import org.texttechnologylab.utils.ReflectionUtils;
@@ -1357,6 +1358,17 @@ public UnifiedTopic getInitializedUnifiedTopicById(long id) throws DatabaseOpera
});
}
+ public Toxic getInitializedToxicById(long id) throws DatabaseOperationException {
+ return executeOperationSafely((session) -> {
+ var toxic = session.get(Toxic.class, id);
+ Hibernate.initialize(toxic.getToxicValues());
+ for(var t:toxic.getToxicValues()){
+ Hibernate.initialize(t.getToxicType());
+ }
+ return toxic;
+ });
+ }
+
public synchronized ToxicType getOrCreateToxicType(String type) throws DatabaseOperationException {
return executeOperationSafely((session) -> {
var criteriaBuilder = session.getCriteriaBuilder();
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/WikiService.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/WikiService.java
index 8fdb832c..faf45c78 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/WikiService.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/WikiService.java
@@ -109,6 +109,21 @@ public UnifiedTopicWikiPageViewModel buildUnifiedTopicWikiPageViewModel(long id,
return viewModel;
}
+ /**
+ * Builds a view model to render a toxic annotation wiki page
+ */
+ public ToxicAnnotationWikiPageViewModel buildToxicAnnotationWikiPageViewModel(long id, String coveredText) throws DatabaseOperationException {
+ var viewModel = new ToxicAnnotationWikiPageViewModel();
+ var toxic = db.getInitializedToxicById(id);
+ viewModel.setWikiModel(toxic);
+ viewModel.setDocument(db.getDocumentById(toxic.getDocumentId()));
+ viewModel.setCorpus(db.getCorpusById(viewModel.getDocument().getCorpusId()).getViewModel());
+ viewModel.setCoveredText(coveredText);
+ viewModel.setAnnotationType("Toxic");
+
+ return viewModel;
+ }
+
/**
* Gets a DocumentTopicDistributionWikiPageViewModel to render a Wikipage for that topic distribution
*/
diff --git a/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/WikiApi.java b/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/WikiApi.java
index 38f1b077..353c6604 100644
--- a/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/WikiApi.java
+++ b/uce.portal/uce.web/src/main/java/org/texttechnologylab/routes/WikiApi.java
@@ -162,6 +162,9 @@ public WikiApi(ApplicationContext serviceContext, Configuration freemarkerConfig
} else if (type.startsWith("UT")) {
model.put("vm", wikiService.buildUnifiedTopicWikiPageViewModel(id, coveredText));
renderView = "/wiki/pages/unifiedTopicAnnotationPage.ftl";
+ } else if (type.startsWith("T")) {
+ model.put("vm", wikiService.buildToxicAnnotationWikiPageViewModel(id, coveredText));
+ renderView = "/wiki/pages/toxicAnnotationPage.ftl";
} else if (type.startsWith("DTR")) {
model.put("vm", wikiService.buildTopicWikiPageViewModel(id, coveredText));
renderView = "/wiki/pages/topicPage.ftl";
From fd4ee9bd6aaf82f44b1b74374b48188da5bc6410 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Tue, 12 Aug 2025 21:54:56 +0200
Subject: [PATCH 099/114] Add NamedModel annotation to OffensiveSpeech and
Toxic classes
---
.../models/offensiveSpeech/OffensiveSpeech.java | 2 ++
.../src/main/java/org/texttechnologylab/models/toxic/Toxic.java | 2 ++
2 files changed, 4 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
index 55637393..550d0634 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
@@ -8,6 +8,7 @@
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
+import org.texttechnologylab.models.modelInfo.NamedModel;
import org.texttechnologylab.utils.Pair;
import javax.persistence.*;
@@ -16,6 +17,7 @@
@Entity
@Table(name = "offensivespeech")
@Typesystem(types = {org.texttechnologylab.annotation.OffensiveSpeech.class})
+@NamedModel(name = "offensivespeech")
public class OffensiveSpeech extends UIMAAnnotation implements WikiModel {
@Getter
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
index 3cc355e7..bdadaac7 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
@@ -8,6 +8,7 @@
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
+import org.texttechnologylab.models.modelInfo.NamedModel;
import javax.persistence.*;
import java.util.List;
@@ -15,6 +16,7 @@
@Entity
@Table(name = "toxic")
@Typesystem(types = {org.texttechnologylab.annotation.Toxic.class})
+@NamedModel(name = "toxic")
public class Toxic extends UIMAAnnotation implements WikiModel {
@Getter
From 3cbf0e472b964ce890b3ed7da77e1a2b1939af32 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Tue, 12 Aug 2025 21:58:25 +0200
Subject: [PATCH 100/114] Handle model category and model version creation for
OffensiveSpeech in Importer
---
.../java/org/texttechnologylab/Importer.java | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index 615b8fb3..a62714b3 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -1457,12 +1457,30 @@ private void setUnifiedTopic(Document document, JCas jCas) {
}
private void setOffensiveSpeech(Document document, JCas jCas) {
+ ModelCategory modelCategory;
+ try {
+ modelCategory = db.getOrCreateModelCategory(OffensiveSpeech.class);
+ } catch (DatabaseOperationException e) {
+ logger.error("Error while getting or creating model category for OffensiveSpeech", e);
+ return;
+ }
+
List offensiveSpeeches = new ArrayList<>();
JCasUtil.select(jCas, org.texttechnologylab.annotation.OffensiveSpeech.class).forEach(os -> {
OffensiveSpeech offensiveSpeech = new OffensiveSpeech(os.getBegin(), os.getEnd());
offensiveSpeech.setDocument(document);
offensiveSpeech.setCoveredText(os.getCoveredText());
+ try {
+ MetaData modelMetaData = os.getModel();
+ Model model = db.getOrCreateModel(modelMetaData.getModelName());
+ ModelVersion modelVersion = db.getOrCreateModelVersion(model, modelMetaData.getModelVersion());
+ offensiveSpeech.setModelVersion(modelVersion);
+ db.registerModelToCategoryAssociation(model, modelCategory);
+ } catch (DatabaseOperationException e) {
+ logger.error("Error while getting or creating model for OffensiveSpeech", e);
+ return;
+ }
FSArray offensives = os.getOffensives();
if (offensives == null) {
From ba50516651513ff83e698d220b2a5226e7e98c18 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Tue, 12 Aug 2025 21:59:04 +0200
Subject: [PATCH 101/114] Add model category and version handling for Toxic in
setToxic method
---
.../java/org/texttechnologylab/Importer.java | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index a62714b3..261048e4 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -1523,6 +1523,14 @@ private void setOffensiveSpeech(Document document, JCas jCas) {
* Selects and sets the toxicities to a document.
*/
private void setToxic(Document document, JCas jCas) {
+ ModelCategory modelCategory;
+ try {
+ modelCategory = db.getOrCreateModelCategory(Toxic.class);
+ } catch (DatabaseOperationException e) {
+ logger.error("Error while getting or creating model category for Toxic", e);
+ return;
+ }
+
List toxics = new ArrayList<>();
JCasUtil.select(jCas, org.texttechnologylab.annotation.Toxic.class).forEach(t -> {
@@ -1530,6 +1538,17 @@ private void setToxic(Document document, JCas jCas) {
toxic.setDocument(document);
toxic.setCoveredText(t.getCoveredText());
+ try {
+ MetaData modelMetaData = t.getModel();
+ Model model = db.getOrCreateModel(modelMetaData.getModelName());
+ ModelVersion modelVersion = db.getOrCreateModelVersion(model, modelMetaData.getModelVersion());
+ toxic.setModelVersion(modelVersion);
+ db.registerModelToCategoryAssociation(model, modelCategory);
+ } catch (DatabaseOperationException e) {
+ logger.error("Error while getting or creating model for Toxic", e);
+ return;
+ }
+
List toxicValues = new ArrayList<>();
ToxicType toxicType, nonToxicType;
try {
From 98348947b10efa012991469fa1aa4b587b112674 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Tue, 12 Aug 2025 22:26:51 +0200
Subject: [PATCH 102/114] Add offensive speech handling and UI markers in
document processing
---
.../templates/css/document-reader.css | 28 +++++++++++++++++++
.../models/UIMAAnnotation.java | 24 ++++++++++++++++
.../models/corpus/Document.java | 5 ++--
.../offensiveSpeech/OffensiveSpeech.java | 25 +++++++++++++++--
.../PostgresqlDataInterface_Impl.java | 12 ++++++++
5 files changed, 89 insertions(+), 5 deletions(-)
diff --git a/uce.portal/resources/templates/css/document-reader.css b/uce.portal/resources/templates/css/document-reader.css
index e62a6793..bbbbc17a 100644
--- a/uce.portal/resources/templates/css/document-reader.css
+++ b/uce.portal/resources/templates/css/document-reader.css
@@ -343,6 +343,11 @@ body {
position: relative;
}
+.document-content .colorable-offensive {
+ transition: background-color 0.3s ease;
+ position: relative;
+}
+
.document-content .animated-topic-scroll {
transition: box-shadow 0.3s ease-in-out;
box-shadow: 0 0 10px 5px rgba(255, 255, 0, 0.5);
@@ -388,6 +393,29 @@ body {
padding: 2px 4px;
}
+.document-content .offensive-marker {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 20px; /* minimum size */
+ height: 20px;
+ padding: 0 4px; /* horizontal padding */
+ border-radius: 20%;
+ border: 1px solid #333;
+ font-size: 14px;
+ font-weight: bold;
+ background-color: white;
+ color: #333;
+ cursor: pointer;
+ margin-left: 4px;
+}
+
+.document-content .offensive-covered.highlight {
+ background-color: rgba(255, 0, 0, 0.2);
+ border-radius: 3px;
+ padding: 2px 4px;
+}
+
.side-bar {
width: 500px;
transition: 0.5s;
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java
index 5e301c56..3246e49a 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java
@@ -9,6 +9,7 @@
import org.texttechnologylab.models.emotion.Emotion;
import org.texttechnologylab.models.modelInfo.ModelVersion;
import org.texttechnologylab.models.negation.*;
+import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
import org.texttechnologylab.models.topic.UnifiedTopic;
import org.texttechnologylab.utils.StringUtils;
@@ -135,6 +136,9 @@ public String buildHTMLString(List annotations, String coveredTe
Map emotionMarkers = new TreeMap<>();
Map emotionCoverWrappersStart = new TreeMap<>();
Map emotionCoverWrappersEnd = new TreeMap<>();
+ Map offensiveSpeechMarkers = new TreeMap<>();
+ Map offensiveSpeechCoverWrappersStart = new TreeMap<>();
+ Map offensiveSpeechCoverWrappersEnd = new TreeMap<>();
for (var annotation : annotations) {
@@ -186,6 +190,17 @@ public String buildHTMLString(List annotations, String coveredTe
continue;
}
+ if (annotation instanceof OffensiveSpeech offensiveSpeech) {
+ var start = offensiveSpeech.getBegin() - offset - errorOffset;
+ var end = offensiveSpeech.getEnd() - offset - errorOffset;
+
+ offensiveSpeechCoverWrappersStart.put(start, offensiveSpeech.generateOffensiveSpeechCoveredStartSpan());
+ offensiveSpeechCoverWrappersEnd.put(end, "");
+
+ offensiveSpeechMarkers.put(end, offensiveSpeech.generateOffensiveSpeechMarker());
+ continue;
+ }
+
var start = annotation.getBegin() - offset - errorOffset;
var end = annotation.getEnd() - offset - errorOffset;
@@ -208,6 +223,9 @@ public String buildHTMLString(List annotations, String coveredTe
if (emotionCoverWrappersEnd.containsKey(i)) {
finalText.append(emotionCoverWrappersEnd.get(i));
}
+ if (offensiveSpeechCoverWrappersEnd.containsKey(i)) {
+ finalText.append(offensiveSpeechCoverWrappersEnd.get(i));
+ }
if (endTags.containsKey(i)) {
//finalText.append(endTags.get(i).getFirst());
for (var tag : endTags.get(i)) {
@@ -216,6 +234,9 @@ public String buildHTMLString(List annotations, String coveredTe
}
// Insert start spans
+ if (offensiveSpeechCoverWrappersStart.containsKey(i)) {
+ finalText.append(offensiveSpeechCoverWrappersStart.get(i));
+ }
if (emotionCoverWrappersStart.containsKey(i)) {
finalText.append(emotionCoverWrappersStart.get(i));
}
@@ -230,6 +251,9 @@ public String buildHTMLString(List annotations, String coveredTe
if (emotionMarkers.containsKey(i)) {
finalText.append(emotionMarkers.get(i));
}
+ if (offensiveSpeechMarkers.containsKey(i)) {
+ finalText.append(offensiveSpeechMarkers.get(i));
+ }
if (startTags.containsKey(i)) {
finalText.append(generateMultiHTMLTag(startTags.get(i)));
}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
index d9a9553c..6ae37a25 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
@@ -23,8 +23,6 @@
import org.texttechnologylab.models.modelInfo.ModelNameHelper;
import org.texttechnologylab.models.negation.*;
import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
-import org.texttechnologylab.models.search.AnnotationSearchResult;
-import org.texttechnologylab.models.search.PageSnippet;
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicValueBaseWithScore;
import org.texttechnologylab.models.topic.UnifiedTopic;
@@ -388,6 +386,9 @@ public List getAllAnnotations(int pagesSkip, int pagesTake, Map<
// emotions
int emotionModelId = modelSelection.getOrDefault(ModelNameHelper.getModelName(Emotion.class), -1);
annotations.addAll(emotions.stream().filter(a -> a.getBegin() >= pagesBegin && a.getEnd() <= pagesEnd).filter(e -> emotionModelId == -1 || e.getModelVersion().getModel().getId() == emotionModelId).toList());
+ // offensiveSpeech
+ int offensiveSpeechModelId = modelSelection.getOrDefault(ModelNameHelper.getModelName(OffensiveSpeech.class), -1);
+ annotations.addAll(offensiveSpeeches.stream().filter(a -> a.getBegin() >= pagesBegin && a.getEnd() <= pagesEnd).filter(e -> offensiveSpeechModelId == -1 || e.getModelVersion().getModel().getId() == offensiveSpeechModelId).toList());
annotations.sort(Comparator.comparingInt(UIMAAnnotation::getBegin));
return annotations;
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
index 550d0634..7b9d2054 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/offensiveSpeech/OffensiveSpeech.java
@@ -12,7 +12,9 @@
import org.texttechnologylab.utils.Pair;
import javax.persistence.*;
+import java.util.Comparator;
import java.util.List;
+import java.util.UUID;
@Entity
@Table(name = "offensivespeech")
@@ -51,15 +53,32 @@ public void setDocument(Document document) {
this.document = document;
}
+ private OffensiveSpeechValue getRepresentativeOffensiveSpeechValue() {
+ if (this.offensiveSpeechValues != null && !this.offensiveSpeechValues.isEmpty()) {
+ return this.offensiveSpeechValues.stream().max(Comparator.comparingDouble(OffensiveSpeechValue::getValue)).orElse(null);
+ }
+ return null;
+ }
+
+ public String generateOffensiveSpeechMarker() {
+ OffensiveSpeechValue rep = getRepresentativeOffensiveSpeechValue();
+ String repValue = rep != null ? rep.getOffensiveSpeechType().getName() : "";
+ return String.format("", this.getWikiId(), this.getWikiId(), this.getCoveredText(), repValue);
+ }
+
+ public String generateOffensiveSpeechCoveredStartSpan() {
+ OffensiveSpeechValue rep = getRepresentativeOffensiveSpeechValue();
+ String repValue = rep != null ? rep.getOffensiveSpeechType().getName() : "";
+ return String.format("", UUID.randomUUID(), this.getWikiId(), this.getCoveredText(), repValue);
+ }
+
@Override
public String getWikiId() {
return "OS" + "-" + this.getId();
}
public List> collectOffensiveSpeechValues() {
- return this.offensiveSpeechValues.stream()
- .map(value -> new Pair<>(value.getOffensiveSpeechType().getName(), value.getValue()))
- .toList();
+ return this.offensiveSpeechValues.stream().map(value -> new Pair<>(value.getOffensiveSpeechType().getName(), value.getValue())).toList();
}
}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index 630af4f2..c769eae1 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -2432,6 +2432,18 @@ private Document initializeCompleteDocument(Document doc, int skipPages, int pag
}
}
+ // offensive speech
+ Hibernate.initialize(doc.getOffensiveSpeeches());
+
+ for (var offensiveSpeech : doc.getOffensiveSpeeches()) {
+ Hibernate.initialize(offensiveSpeech.getOffensiveSpeechValues());
+ Hibernate.initialize(offensiveSpeech.getModelVersion());
+ Hibernate.initialize(offensiveSpeech.getModelVersion().getModel());
+ for (var osv : offensiveSpeech.getOffensiveSpeechValues()) {
+ Hibernate.initialize(osv.getOffensiveSpeechType());
+ }
+ }
+
for (var link : doc.getWikipediaLinks()) {
Hibernate.initialize(link.getWikiDataHyponyms());
}
From e6bfd9d03b842c1d8337cc704085ccb0ae3c5bc2 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Tue, 12 Aug 2025 22:41:13 +0200
Subject: [PATCH 103/114] Add handling and UI elements for toxic annotations in
document processing
---
.../templates/css/document-reader.css | 28 +++++++++++++++++++
.../models/UIMAAnnotation.java | 24 ++++++++++++++++
.../models/corpus/Document.java | 3 ++
.../texttechnologylab/models/toxic/Toxic.java | 21 ++++++++++++++
.../PostgresqlDataInterface_Impl.java | 12 ++++++++
5 files changed, 88 insertions(+)
diff --git a/uce.portal/resources/templates/css/document-reader.css b/uce.portal/resources/templates/css/document-reader.css
index bbbbc17a..7b4b337a 100644
--- a/uce.portal/resources/templates/css/document-reader.css
+++ b/uce.portal/resources/templates/css/document-reader.css
@@ -348,6 +348,11 @@ body {
position: relative;
}
+.document-content .colorable-toxic:hover {
+ transition: background-color 0.3s ease;
+ position: relative;
+}
+
.document-content .animated-topic-scroll {
transition: box-shadow 0.3s ease-in-out;
box-shadow: 0 0 10px 5px rgba(255, 255, 0, 0.5);
@@ -416,6 +421,29 @@ body {
padding: 2px 4px;
}
+.document-content .toxic-marker {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 20px; /* minimum size */
+ height: 20px;
+ padding: 0 4px; /* horizontal padding */
+ border-radius: 20%;
+ border: 1px solid #333;
+ font-size: 14px;
+ font-weight: bold;
+ background-color: white;
+ color: #333;
+ cursor: pointer;
+ margin-left: 4px;
+}
+
+.document-content .toxic-covered.highlight {
+ background-color: rgba(255, 0, 0, 0.2);
+ border-radius: 3px;
+ padding: 2px 4px;
+}
+
.side-bar {
width: 500px;
transition: 0.5s;
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java
index 3246e49a..da970104 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java
@@ -11,6 +11,7 @@
import org.texttechnologylab.models.negation.*;
import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
import org.texttechnologylab.models.topic.UnifiedTopic;
+import org.texttechnologylab.models.toxic.Toxic;
import org.texttechnologylab.utils.StringUtils;
import javax.persistence.*;
@@ -139,6 +140,9 @@ public String buildHTMLString(List annotations, String coveredTe
Map offensiveSpeechMarkers = new TreeMap<>();
Map offensiveSpeechCoverWrappersStart = new TreeMap<>();
Map offensiveSpeechCoverWrappersEnd = new TreeMap<>();
+ Map toxicMarkers = new TreeMap<>();
+ Map toxicCoverWrappersStart = new TreeMap<>();
+ Map toxicCoverWrappersEnd = new TreeMap<>();
for (var annotation : annotations) {
@@ -201,6 +205,17 @@ public String buildHTMLString(List annotations, String coveredTe
continue;
}
+ if (annotation instanceof Toxic toxic) {
+ var start = toxic.getBegin() - offset - errorOffset;
+ var end = toxic.getEnd() - offset - errorOffset;
+
+ toxicCoverWrappersStart.put(start, toxic.generateToxicCoveredStartSpan());
+ toxicCoverWrappersEnd.put(end, "");
+
+ toxicMarkers.put(end, toxic.generateToxicMarker());
+ continue;
+ }
+
var start = annotation.getBegin() - offset - errorOffset;
var end = annotation.getEnd() - offset - errorOffset;
@@ -226,6 +241,9 @@ public String buildHTMLString(List annotations, String coveredTe
if (offensiveSpeechCoverWrappersEnd.containsKey(i)) {
finalText.append(offensiveSpeechCoverWrappersEnd.get(i));
}
+ if (toxicCoverWrappersEnd.containsKey(i)) {
+ finalText.append(toxicCoverWrappersEnd.get(i));
+ }
if (endTags.containsKey(i)) {
//finalText.append(endTags.get(i).getFirst());
for (var tag : endTags.get(i)) {
@@ -234,6 +252,9 @@ public String buildHTMLString(List annotations, String coveredTe
}
// Insert start spans
+ if (toxicCoverWrappersStart.containsKey(i)) {
+ finalText.append(toxicCoverWrappersStart.get(i));
+ }
if (offensiveSpeechCoverWrappersStart.containsKey(i)) {
finalText.append(offensiveSpeechCoverWrappersStart.get(i));
}
@@ -254,6 +275,9 @@ public String buildHTMLString(List annotations, String coveredTe
if (offensiveSpeechMarkers.containsKey(i)) {
finalText.append(offensiveSpeechMarkers.get(i));
}
+ if (toxicMarkers.containsKey(i)) {
+ finalText.append(toxicMarkers.get(i));
+ }
if (startTags.containsKey(i)) {
finalText.append(generateMultiHTMLTag(startTags.get(i)));
}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
index 6ae37a25..f7811560 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
@@ -389,6 +389,9 @@ public List getAllAnnotations(int pagesSkip, int pagesTake, Map<
// offensiveSpeech
int offensiveSpeechModelId = modelSelection.getOrDefault(ModelNameHelper.getModelName(OffensiveSpeech.class), -1);
annotations.addAll(offensiveSpeeches.stream().filter(a -> a.getBegin() >= pagesBegin && a.getEnd() <= pagesEnd).filter(e -> offensiveSpeechModelId == -1 || e.getModelVersion().getModel().getId() == offensiveSpeechModelId).toList());
+ // toxic
+ int toxicModelId = modelSelection.getOrDefault(ModelNameHelper.getModelName(Toxic.class), -1);
+ annotations.addAll(toxics.stream().filter(a -> a.getBegin() >= pagesBegin && a.getEnd() <= pagesEnd).filter(e -> toxicModelId == -1 || e.getModelVersion().getModel().getId() == toxicModelId).toList());
annotations.sort(Comparator.comparingInt(UIMAAnnotation::getBegin));
return annotations;
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
index bdadaac7..e9a406a0 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/toxic/Toxic.java
@@ -11,7 +11,9 @@
import org.texttechnologylab.models.modelInfo.NamedModel;
import javax.persistence.*;
+import java.util.Comparator;
import java.util.List;
+import java.util.UUID;
@Entity
@Table(name = "toxic")
@@ -50,6 +52,25 @@ public void setDocument(Document document) {
this.document = document;
}
+ private ToxicValue getRepresentativeToxicValue() {
+ if (this.toxicValues != null && !this.toxicValues.isEmpty()) {
+ return this.toxicValues.stream().max(Comparator.comparingDouble(ToxicValue::getValue)).orElse(null);
+ }
+ return null;
+ }
+
+ public String generateToxicMarker() {
+ ToxicValue rep = getRepresentativeToxicValue();
+ String repValue = rep != null ? rep.getToxicType().getName() : "";
+ return String.format("", this.getWikiId(), this.getWikiId(), this.getCoveredText(), repValue);
+ }
+
+ public String generateToxicCoveredStartSpan() {
+ ToxicValue rep = getRepresentativeToxicValue();
+ String repValue = rep != null ? rep.getToxicType().getName() : "";
+ return String.format("", UUID.randomUUID(), this.getWikiId(), this.getCoveredText(), repValue);
+ }
+
@Override
public String getWikiId() {
return "T" + "-" + this.getId();
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index c769eae1..fa54b996 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -2444,6 +2444,18 @@ private Document initializeCompleteDocument(Document doc, int skipPages, int pag
}
}
+ // toxic
+ Hibernate.initialize(doc.getToxics());
+
+ for (var toxic : doc.getToxics()) {
+ Hibernate.initialize(toxic.getToxicValues());
+ Hibernate.initialize(toxic.getModelVersion());
+ Hibernate.initialize(toxic.getModelVersion().getModel());
+ for (var tv : toxic.getToxicValues()) {
+ Hibernate.initialize(tv.getToxicType());
+ }
+ }
+
for (var link : doc.getWikipediaLinks()) {
Hibernate.initialize(link.getWikiDataHyponyms());
}
From 912e272f76e35b2612c686a1734f8aad813d8c3f Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Tue, 12 Aug 2025 22:44:29 +0200
Subject: [PATCH 104/114] Add category handling for OffensiveSpeech and Toxic
in document processing
---
.../java/org/texttechnologylab/models/corpus/Document.java | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
index f7811560..f7d3159e 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
@@ -461,6 +461,12 @@ public List getModelCategories() {
if (!emotions.isEmpty()) {
categories.add(ModelNameHelper.getModelName(Emotion.class));
}
+ if (!offensiveSpeeches.isEmpty()) {
+ categories.add(ModelNameHelper.getModelName(OffensiveSpeech.class));
+ }
+ if (!toxics.isEmpty()) {
+ categories.add(ModelNameHelper.getModelName(Toxic.class));
+ }
return categories;
}
From 184933797f4821ea0673ba8018b5bb62faef7b82 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Tue, 12 Aug 2025 22:49:53 +0200
Subject: [PATCH 105/114] Add flexed-column class for improved layout in
document reader view
---
uce.portal/resources/templates/css/site.css | 5 +++++
uce.portal/resources/templates/reader/documentReaderView.ftl | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/uce.portal/resources/templates/css/site.css b/uce.portal/resources/templates/css/site.css
index 09df94bd..259c1d68 100644
--- a/uce.portal/resources/templates/css/site.css
+++ b/uce.portal/resources/templates/css/site.css
@@ -232,6 +232,11 @@ nav .selected-nav-btn.text::before {
display: flex;
}
+.flexed-column {
+ display: flex;
+ flex-direction: column;
+}
+
.display-none {
display: none;
}
diff --git a/uce.portal/resources/templates/reader/documentReaderView.ftl b/uce.portal/resources/templates/reader/documentReaderView.ftl
index 77788436..6ca96e8e 100644
--- a/uce.portal/resources/templates/reader/documentReaderView.ftl
+++ b/uce.portal/resources/templates/reader/documentReaderView.ftl
@@ -264,7 +264,7 @@
<#if modelCategories?has_content && modelCategories?size gt 0>
${languageResource.get("modelSelection")}
-
+
<#list modelCategories as category>
From 8e4afbf13d62e05ec4376ea1ae2abdcf02210ff8 Mon Sep 17 00:00:00 2001
From: imer
Date: Wed, 13 Aug 2025 16:20:33 +0200
Subject: [PATCH 106/114] Dynamic Sentiment Types
---
.../models/sentiment/Sentiment.java | 19 ++++--------
.../models/sentiment/SentimentType.java | 25 ++++++++++++++++
.../models/sentiment/SentimentValue.java | 29 +++++++++++++++++++
3 files changed, 60 insertions(+), 13 deletions(-)
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/SentimentType.java
create mode 100644 uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/SentimentValue.java
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
index c2ae6ecd..b8e48df5 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
@@ -2,12 +2,15 @@
import lombok.Getter;
import lombok.Setter;
+import org.hibernate.annotations.Fetch;
+import org.hibernate.annotations.FetchMode;
import org.texttechnologylab.annotations.Typesystem;
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
import javax.persistence.*;
+import java.util.List;
@Entity
@Table(name = "sentiment")
@@ -21,19 +24,9 @@ public class Sentiment extends UIMAAnnotation implements WikiModel {
@Getter
@Setter
- @Column(name = "probability_positive", nullable = false)
- private double probabilityPositive;
-
- @Getter
- @Setter
- @Column(name = "probability_neutral", nullable = false)
- private double probabilityNeutral;
-
- @Getter
- @Setter
- @Column(name = "probability_negative", nullable = false)
- private double probabilityNegative;
-
+ @OneToMany(mappedBy = "sentiment", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+ @Fetch(value = FetchMode.SUBSELECT)
+ private List sentimentValues;
@ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "document_id", nullable = false)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/SentimentType.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/SentimentType.java
new file mode 100644
index 00000000..d1289b83
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/SentimentType.java
@@ -0,0 +1,25 @@
+package org.texttechnologylab.models.sentiment;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.texttechnologylab.models.ModelBase;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "sentiment_type")
+public class SentimentType extends ModelBase {
+ @Getter
+ @Setter
+ @Column(name = "name", nullable = false, unique = true)
+ private String name;
+
+ public SentimentType() {
+ }
+ public SentimentType(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/SentimentValue.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/SentimentValue.java
new file mode 100644
index 00000000..f782e1f4
--- /dev/null
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/SentimentValue.java
@@ -0,0 +1,29 @@
+package org.texttechnologylab.models.sentiment;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.texttechnologylab.models.ModelBase;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "sentiment_value")
+public class SentimentValue extends ModelBase {
+ @Getter
+ @Setter
+ @ManyToOne
+ @JoinColumn(name = "sentiment_id", nullable = false)
+ private Sentiment sentiment;
+ @Getter
+ @Setter
+ @ManyToOne
+ @JoinColumn(name = "sentiment_type_id", nullable = false)
+ private SentimentType sentimentType;
+ @Getter
+ @Setter
+ @Column(name = "value", nullable = false)
+ private double value;
+
+ public SentimentValue() {}
+
+}
From e8db73da1a4e99e0444f44668a9476806c17d5da Mon Sep 17 00:00:00 2001
From: imer
Date: Wed, 13 Aug 2025 16:32:20 +0200
Subject: [PATCH 107/114] add datainterface methods for sentiment
---
.../PostgresqlDataInterface_Impl.java | 31 +++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index dc9b4541..48d66658 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -31,6 +31,8 @@
import org.texttechnologylab.models.imp.UCEImport;
import org.texttechnologylab.models.negation.CompleteNegation;
import org.texttechnologylab.models.search.*;
+import org.texttechnologylab.models.sentiment.Sentiment;
+import org.texttechnologylab.models.sentiment.SentimentType;
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicWord;
import org.texttechnologylab.models.topic.UnifiedTopic;
@@ -1356,6 +1358,35 @@ public UnifiedTopic getInitializedUnifiedTopicById(long id) throws DatabaseOpera
});
}
+ public Sentiment getInitializedSentimentById(long id) throws DatabaseOperationException {
+ return executeOperationSafely((session) -> {
+ var sentiment = session.get(Sentiment.class, id);
+ Hibernate.initialize(sentiment.getSentimentValues());
+ for (var s : sentiment.getSentimentValues()) {
+ Hibernate.initialize(s.getSentimentType());
+ }
+ return sentiment;
+ });
+ }
+
+ public synchronized SentimentType getOrCreateSentimentType(String name) throws DatabaseOperationException {
+ return executeOperationSafely((session) -> {
+ var criteriaBuilder = session.getCriteriaBuilder();
+ var criteriaQuery = criteriaBuilder.createQuery(SentimentType.class);
+ var root = criteriaQuery.from(SentimentType.class);
+ criteriaQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name));
+
+ SentimentType sentimentType;
+ try {
+ sentimentType = session.createQuery(criteriaQuery).getSingleResult();
+ } catch (NoResultException e) {
+ sentimentType = new SentimentType(name);
+ session.save(sentimentType);
+ }
+ return sentimentType;
+ });
+ }
+
public List getKeywordDistributionsByString(Class clazz, String topic, int limit) throws DatabaseOperationException {
return executeOperationSafely((session) -> {
var builder = session.getCriteriaBuilder();
From 641e4bc71e8decf5224e548a95d53b7589b5a37e Mon Sep 17 00:00:00 2001
From: imer
Date: Wed, 13 Aug 2025 16:45:49 +0200
Subject: [PATCH 108/114] added sentiment values to Importer
---
.../java/org/texttechnologylab/Importer.java | 36 +++++++++++++++++--
1 file changed, 33 insertions(+), 3 deletions(-)
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index 66097393..11476809 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -46,6 +46,8 @@
import org.texttechnologylab.models.rag.DocumentChunkEmbedding;
import org.texttechnologylab.models.rag.DocumentSentenceEmbedding;
import org.texttechnologylab.models.sentiment.Sentiment;
+import org.texttechnologylab.models.sentiment.SentimentType;
+import org.texttechnologylab.models.sentiment.SentimentValue;
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicValueBaseWithScore;
import org.texttechnologylab.models.topic.TopicWord;
@@ -1431,9 +1433,37 @@ private void setSentiment(Document document, JCas jCas){
sentiment.setDocument(document);
sentiment.setSentiment(s.getSentiment());
- sentiment.setProbabilityPositive(s.getProbabilityPositive());
- sentiment.setProbabilityNegative(s.getProbabilityNegative());
- sentiment.setProbabilityNeutral(s.getProbabilityNeutral());
+
+ List sentimentValues = new ArrayList<>();
+ SentimentType positiveType, negativeType, neutralType;
+ try {
+ positiveType = db.getOrCreateSentimentType("positive");
+ negativeType = db.getOrCreateSentimentType("negative");
+ neutralType = db.getOrCreateSentimentType("neutral");
+ } catch (DatabaseOperationException e) {
+ logger.error("Error while fetching Sentiment Types", e);
+ return;
+ }
+
+ SentimentValue positiveValue = new SentimentValue();
+ positiveValue.setSentimentType(positiveType);
+ positiveValue.setValue(s.getProbabilityPositive());
+ positiveValue.setSentiment(sentiment);
+ sentimentValues.add(positiveValue);
+
+ SentimentValue negativeValue = new SentimentValue();
+ negativeValue.setSentimentType(negativeType);
+ negativeValue.setValue(s.getProbabilityNegative());
+ negativeValue.setSentiment(sentiment);
+ sentimentValues.add(negativeValue);
+
+ SentimentValue neutralValue = new SentimentValue();
+ neutralValue.setSentimentType(neutralType);
+ neutralValue.setValue(s.getProbabilityNeutral());
+ neutralValue.setSentiment(sentiment);
+ sentimentValues.add(neutralValue);
+
+ sentiment.setSentimentValues(sentimentValues);
sentiment.setCoveredText(s.getCoveredText());
sentiments.add(sentiment);
From 2c7b9f4bb2f97fdecd3b268f198c1a7bb26fdb6c Mon Sep 17 00:00:00 2001
From: imer
Date: Wed, 13 Aug 2025 16:52:46 +0200
Subject: [PATCH 109/114] add classes to HibernateConf
---
.../main/java/org/texttechnologylab/config/HibernateConf.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
index d7d31b82..e4c97a69 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/config/HibernateConf.java
@@ -18,6 +18,8 @@
import org.texttechnologylab.models.imp.UCEImport;
import org.texttechnologylab.models.negation.*;
import org.texttechnologylab.models.sentiment.Sentiment;
+import org.texttechnologylab.models.sentiment.SentimentType;
+import org.texttechnologylab.models.sentiment.SentimentValue;
import org.texttechnologylab.models.topic.TopicValueBase;
import org.texttechnologylab.models.topic.TopicValueBaseWithScore;
import org.texttechnologylab.models.topic.TopicWord;
@@ -83,6 +85,8 @@ public static SessionFactory buildSessionFactory() {
metadataSources.addAnnotatedClass(TopicValueBaseWithScore.class);
// sentiment
metadataSources.addAnnotatedClass(Sentiment.class);
+ metadataSources.addAnnotatedClass(SentimentType.class);
+ metadataSources.addAnnotatedClass(SentimentValue.class);
metadataSources.addAnnotatedClass(DocumentTopThreeTopics.class);
var metadata = metadataSources.buildMetadata();
From 1524475f2cd9a842434b0db7c5d0f1f992f7a6f8 Mon Sep 17 00:00:00 2001
From: imer
Date: Wed, 13 Aug 2025 16:58:34 +0200
Subject: [PATCH 110/114] fix merge related issues
---
.../org/texttechnologylab/models/sentiment/Sentiment.java | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
index d4679935..b45c0229 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
@@ -61,10 +61,8 @@ public String getWikiId() {
}
public List> loopThroughProperties() {
- return List.of(
- new Pair<>("probability positive", Double.toString(probabilityPositive)),
- new Pair<>("probability negative", Double.toString(probabilityNegative)),
- new Pair<>("probability neutral", Double.toString(probabilityNeutral))
- );
+ return sentimentValues.stream()
+ .map(v -> new Pair<>(v.getSentimentType().getName(), String.format("%.6f", v.getValue())))
+ .toList();
}
}
From edebe1cdf9012614b2e72a04185f8bbde619fe55 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Wed, 13 Aug 2025 17:24:06 +0200
Subject: [PATCH 111/114] Add sentiment checkbox to corpus annotations for
improved UI
---
.../templates/corpus/components/corpusAnnotations.ftl | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl b/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl
index 40fa849e..bbb0df4d 100644
--- a/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl
+++ b/uce.portal/resources/templates/corpus/components/corpusAnnotations.ftl
@@ -219,5 +219,16 @@
+
+
+ <#assign isChecked = "" />
+ <#if corpusConfig.getAnnotations().isSentiment() == true>
+ <#assign isChecked = "checked"/>
+ #if>
+
+
+
+
+
\ No newline at end of file
From 43f5a0e43fb6cf221276b446a10fcf592bece37c Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Wed, 13 Aug 2025 17:44:24 +0200
Subject: [PATCH 112/114] Add model information display to annotation pages and
update data interface
---
.../templates/wiki/components/modelInfo.ftl | 21 +++++++++++++++++++
.../wiki/pages/emotionAnnotationPage.ftl | 6 ++++++
.../pages/offensiveSpeechAnnotationPage.ftl | 6 ++++++
.../wiki/pages/sentimentAnnotationPage.ftl | 6 ++++++
.../wiki/pages/toxicAnnotationPage.ftl | 6 ++++++
.../PostgresqlDataInterface_Impl.java | 16 ++++++++++++++
6 files changed, 61 insertions(+)
create mode 100644 uce.portal/resources/templates/wiki/components/modelInfo.ftl
diff --git a/uce.portal/resources/templates/wiki/components/modelInfo.ftl b/uce.portal/resources/templates/wiki/components/modelInfo.ftl
new file mode 100644
index 00000000..3f71413e
--- /dev/null
+++ b/uce.portal/resources/templates/wiki/components/modelInfo.ftl
@@ -0,0 +1,21 @@
+<#if model.getModelVersion()??>
+ <#assign modelVersionObject = model.getModelVersion()>
+ <#assign modelObject = modelVersionObject.getModel()>
+ <#assign modelVersion = modelVersionObject.getVersion()!>
+ <#assign modelName = modelObject.getName()!>
+<#else>
+ <#assign modelVersion = "N/A">
+ <#assign modelName = "N/A">
+#if>
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uce.portal/resources/templates/wiki/pages/emotionAnnotationPage.ftl b/uce.portal/resources/templates/wiki/pages/emotionAnnotationPage.ftl
index 1b012c8c..574d15e2 100644
--- a/uce.portal/resources/templates/wiki/pages/emotionAnnotationPage.ftl
+++ b/uce.portal/resources/templates/wiki/pages/emotionAnnotationPage.ftl
@@ -10,6 +10,12 @@
<#include "*/wiki/components/metadata.ftl">
+
+
+ <#assign model = vm.getWikiModel()>
+ <#include "*/wiki/components/modelInfo.ftl">
+
+
diff --git a/uce.portal/resources/templates/wiki/pages/offensiveSpeechAnnotationPage.ftl b/uce.portal/resources/templates/wiki/pages/offensiveSpeechAnnotationPage.ftl
index 75c21d59..d960757e 100644
--- a/uce.portal/resources/templates/wiki/pages/offensiveSpeechAnnotationPage.ftl
+++ b/uce.portal/resources/templates/wiki/pages/offensiveSpeechAnnotationPage.ftl
@@ -10,6 +10,12 @@
<#include "*/wiki/components/metadata.ftl">
+
+
+ <#assign model = vm.getWikiModel()>
+ <#include "*/wiki/components/modelInfo.ftl">
+
+
diff --git a/uce.portal/resources/templates/wiki/pages/sentimentAnnotationPage.ftl b/uce.portal/resources/templates/wiki/pages/sentimentAnnotationPage.ftl
index 15baabcb..db0e6fa5 100644
--- a/uce.portal/resources/templates/wiki/pages/sentimentAnnotationPage.ftl
+++ b/uce.portal/resources/templates/wiki/pages/sentimentAnnotationPage.ftl
@@ -10,6 +10,12 @@
<#include "*/wiki/components/metadata.ftl">
+
+
+ <#assign model = vm.getWikiModel()>
+ <#include "*/wiki/components/modelInfo.ftl">
+
+
diff --git a/uce.portal/resources/templates/wiki/pages/toxicAnnotationPage.ftl b/uce.portal/resources/templates/wiki/pages/toxicAnnotationPage.ftl
index 84b4bfe7..63b8c638 100644
--- a/uce.portal/resources/templates/wiki/pages/toxicAnnotationPage.ftl
+++ b/uce.portal/resources/templates/wiki/pages/toxicAnnotationPage.ftl
@@ -10,6 +10,12 @@
<#include "*/wiki/components/metadata.ftl">
+
+
+ <#assign model = vm.getWikiModel()>
+ <#include "*/wiki/components/modelInfo.ftl">
+
+
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index 39f337e8..9a77cdc9 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -1424,6 +1424,10 @@ public Sentiment getInitializedSentimentById(long id) throws DatabaseOperationEx
for (var s : sentiment.getSentimentValues()) {
Hibernate.initialize(s.getSentimentType());
}
+ Hibernate.initialize(sentiment.getModelVersion());
+ if (sentiment.getModelVersion() != null) {
+ Hibernate.initialize(sentiment.getModelVersion().getModel());
+ }
return sentiment;
});
}
@@ -1473,6 +1477,10 @@ public OffensiveSpeech getInitializedOffensiveSpeechById(long id) throws Databas
for (OffensiveSpeechValue value : offensiveSpeech.getOffensiveSpeechValues()) {
Hibernate.initialize(value.getOffensiveSpeechType());
}
+ Hibernate.initialize(offensiveSpeech.getModelVersion());
+ if (offensiveSpeech.getModelVersion() != null) {
+ Hibernate.initialize(offensiveSpeech.getModelVersion().getModel());
+ }
return offensiveSpeech;
});
}
@@ -1484,6 +1492,10 @@ public Toxic getInitializedToxicById(long id) throws DatabaseOperationException
for(var t:toxic.getToxicValues()){
Hibernate.initialize(t.getToxicType());
}
+ Hibernate.initialize(toxic.getModelVersion());
+ if (toxic.getModelVersion() != null) {
+ Hibernate.initialize(toxic.getModelVersion().getModel());
+ }
return toxic;
});
}
@@ -1513,6 +1525,10 @@ public Emotion getInitializedEmotionById(long id) throws DatabaseOperationExcept
for (var ev : emotion.getEmotionValues()) {
Hibernate.initialize(ev.getEmotionType());
}
+ Hibernate.initialize(emotion.getModelVersion());
+ if (emotion.getModelVersion() != null) {
+ Hibernate.initialize(emotion.getModelVersion().getModel());
+ }
return emotion;
});
}
From 82b7393d39f32d05b7032b2c051ae89b86ebe223 Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Wed, 13 Aug 2025 17:50:26 +0200
Subject: [PATCH 113/114] Add model handling in sentiment processing
---
.../models/sentiment/Sentiment.java | 3 ++-
.../java/org/texttechnologylab/Importer.java | 18 ++++++++++++++++++
2 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
index b45c0229..1f1f2dab 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
@@ -8,15 +8,16 @@
import org.texttechnologylab.models.UIMAAnnotation;
import org.texttechnologylab.models.WikiModel;
import org.texttechnologylab.models.corpus.Document;
+import org.texttechnologylab.models.modelInfo.NamedModel;
import org.texttechnologylab.utils.Pair;
import javax.persistence.*;
import java.util.List;
-import java.util.ArrayList;
@Entity
@Table(name = "sentiment")
@Typesystem(types = {org.texttechnologylab.annotation.SentimentModel.class})
+@NamedModel(name = "sentiment")
public class Sentiment extends UIMAAnnotation implements WikiModel {
@Getter
diff --git a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
index cae89340..dd23bf8c 100644
--- a/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
+++ b/uce.portal/uce.corpus-importer/src/main/java/org/texttechnologylab/Importer.java
@@ -1472,12 +1472,30 @@ private void setUnifiedTopic(Document document, JCas jCas) {
}
private void setSentiment(Document document, JCas jCas){
+ ModelCategory modelCategory;
+ try {
+ modelCategory = db.getOrCreateModelCategory(Sentiment.class);
+ } catch (DatabaseOperationException e) {
+ logger.error("Error while getting or creating model category for Sentiment", e);
+ return;
+ }
List sentiments = new ArrayList<>();
JCasUtil.select(jCas, org.texttechnologylab.annotation.SentimentModel.class).forEach(s -> {
Sentiment sentiment = new Sentiment(s.getBegin(), s.getEnd());
sentiment.setDocument(document);
+ try {
+ MetaData modelMetaData = s.getModel();
+ Model model = db.getOrCreateModel(modelMetaData.getModelName());
+ ModelVersion modelVersion = db.getOrCreateModelVersion(model, modelMetaData.getModelVersion());
+ sentiment.setModelVersion(modelVersion);
+ db.registerModelToCategoryAssociation(model, modelCategory);
+ } catch (DatabaseOperationException e) {
+ logger.error("Error while getting or creating model for Toxic", e);
+ return;
+ }
+
sentiment.setSentiment(s.getSentiment());
List sentimentValues = new ArrayList<>();
From a2913732d8803a0778678f341f7f5642eb805a3c Mon Sep 17 00:00:00 2001
From: UnleqitQ
Date: Wed, 13 Aug 2025 18:03:11 +0200
Subject: [PATCH 114/114] Add sentiment handling and UI enhancements for
sentiment markers
---
.../templates/css/document-reader.css | 30 ++++++++++++++++++-
.../models/UIMAAnnotation.java | 24 +++++++++++++++
.../models/corpus/Document.java | 6 ++++
.../models/sentiment/Sentiment.java | 23 ++++++++++++++
.../PostgresqlDataInterface_Impl.java | 12 ++++++++
5 files changed, 94 insertions(+), 1 deletion(-)
diff --git a/uce.portal/resources/templates/css/document-reader.css b/uce.portal/resources/templates/css/document-reader.css
index 7b4b337a..5d4d9879 100644
--- a/uce.portal/resources/templates/css/document-reader.css
+++ b/uce.portal/resources/templates/css/document-reader.css
@@ -348,7 +348,12 @@ body {
position: relative;
}
-.document-content .colorable-toxic:hover {
+.document-content .colorable-toxic {
+ transition: background-color 0.3s ease;
+ position: relative;
+}
+
+.document-content .colorable-sentiment {
transition: background-color 0.3s ease;
position: relative;
}
@@ -444,6 +449,29 @@ body {
padding: 2px 4px;
}
+.document-content .sentiment-marker {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 20px; /* minimum size */
+ height: 20px;
+ padding: 0 4px; /* horizontal padding */
+ border-radius: 20%;
+ border: 1px solid #333;
+ font-size: 14px;
+ font-weight: bold;
+ background-color: white;
+ color: #333;
+ cursor: pointer;
+ margin-left: 4px;
+}
+
+.document-content .sentiment-covered.highlight {
+ background-color: rgba(255, 0, 0, 0.2);
+ border-radius: 3px;
+ padding: 2px 4px;
+}
+
.side-bar {
width: 500px;
transition: 0.5s;
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java
index da970104..467fddef 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/UIMAAnnotation.java
@@ -10,6 +10,7 @@
import org.texttechnologylab.models.modelInfo.ModelVersion;
import org.texttechnologylab.models.negation.*;
import org.texttechnologylab.models.offensiveSpeech.OffensiveSpeech;
+import org.texttechnologylab.models.sentiment.Sentiment;
import org.texttechnologylab.models.topic.UnifiedTopic;
import org.texttechnologylab.models.toxic.Toxic;
import org.texttechnologylab.utils.StringUtils;
@@ -143,6 +144,9 @@ public String buildHTMLString(List annotations, String coveredTe
Map toxicMarkers = new TreeMap<>();
Map toxicCoverWrappersStart = new TreeMap<>();
Map toxicCoverWrappersEnd = new TreeMap<>();
+ Map sentimentMarkers = new TreeMap<>();
+ Map sentimentCoverWrappersStart = new TreeMap<>();
+ Map sentimentCoverWrappersEnd = new TreeMap<>();
for (var annotation : annotations) {
@@ -216,6 +220,17 @@ public String buildHTMLString(List annotations, String coveredTe
continue;
}
+ if (annotation instanceof Sentiment sentiment) {
+ var start = sentiment.getBegin() - offset - errorOffset;
+ var end = sentiment.getEnd() - offset - errorOffset;
+
+ sentimentCoverWrappersStart.put(start, sentiment.generateSentimentCoveredStartSpan());
+ sentimentCoverWrappersEnd.put(end, "");
+
+ sentimentMarkers.put(end, sentiment.generateSentimentMarker());
+ continue;
+ }
+
var start = annotation.getBegin() - offset - errorOffset;
var end = annotation.getEnd() - offset - errorOffset;
@@ -244,6 +259,9 @@ public String buildHTMLString(List annotations, String coveredTe
if (toxicCoverWrappersEnd.containsKey(i)) {
finalText.append(toxicCoverWrappersEnd.get(i));
}
+ if (sentimentCoverWrappersEnd.containsKey(i)) {
+ finalText.append(sentimentCoverWrappersEnd.get(i));
+ }
if (endTags.containsKey(i)) {
//finalText.append(endTags.get(i).getFirst());
for (var tag : endTags.get(i)) {
@@ -252,6 +270,9 @@ public String buildHTMLString(List annotations, String coveredTe
}
// Insert start spans
+ if (sentimentCoverWrappersStart.containsKey(i)) {
+ finalText.append(sentimentCoverWrappersStart.get(i));
+ }
if (toxicCoverWrappersStart.containsKey(i)) {
finalText.append(toxicCoverWrappersStart.get(i));
}
@@ -278,6 +299,9 @@ public String buildHTMLString(List annotations, String coveredTe
if (toxicMarkers.containsKey(i)) {
finalText.append(toxicMarkers.get(i));
}
+ if (sentimentMarkers.containsKey(i)) {
+ finalText.append(sentimentMarkers.get(i));
+ }
if (startTags.containsKey(i)) {
finalText.append(generateMultiHTMLTag(startTags.get(i)));
}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
index 11ecc7ae..f6b69dfa 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/corpus/Document.java
@@ -399,6 +399,9 @@ public List getAllAnnotations(int pagesSkip, int pagesTake, Map<
// toxic
int toxicModelId = modelSelection.getOrDefault(ModelNameHelper.getModelName(Toxic.class), -1);
annotations.addAll(toxics.stream().filter(a -> a.getBegin() >= pagesBegin && a.getEnd() <= pagesEnd).filter(e -> toxicModelId == -1 || e.getModelVersion().getModel().getId() == toxicModelId).toList());
+ // sentiment
+ int sentimentModelId = modelSelection.getOrDefault(ModelNameHelper.getModelName(Sentiment.class), -1);
+ annotations.addAll(sentiments.stream().filter(a -> a.getBegin() >= pagesBegin && a.getEnd() <= pagesEnd).filter(e -> sentimentModelId == -1 || e.getModelVersion().getModel().getId() == sentimentModelId).toList());
annotations.sort(Comparator.comparingInt(UIMAAnnotation::getBegin));
return annotations;
@@ -474,6 +477,9 @@ public List getModelCategories() {
if (!toxics.isEmpty()) {
categories.add(ModelNameHelper.getModelName(Toxic.class));
}
+ if (!sentiments.isEmpty()) {
+ categories.add(ModelNameHelper.getModelName(Sentiment.class));
+ }
return categories;
}
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
index 1f1f2dab..acde4be4 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/models/sentiment/Sentiment.java
@@ -12,7 +12,9 @@
import org.texttechnologylab.utils.Pair;
import javax.persistence.*;
+import java.util.Comparator;
import java.util.List;
+import java.util.UUID;
@Entity
@Table(name = "sentiment")
@@ -56,6 +58,27 @@ public void setDocument(Document document) {
this.document = document;
}
+ private SentimentValue getRepresentativeSentimentValue() {
+ if (this.sentimentValues != null && !this.sentimentValues.isEmpty()) {
+ return this.sentimentValues.stream()
+ .max(Comparator.comparingDouble(SentimentValue::getValue))
+ .orElse(null);
+ }
+ return null;
+ }
+
+ public String generateSentimentMarker() {
+ SentimentValue rep = getRepresentativeSentimentValue();
+ String repValue = rep != null ? rep.getSentimentType().getName() : "";
+ return String.format("", this.getWikiId(), this.getWikiId(), this.getCoveredText(), repValue);
+ }
+
+ public String generateSentimentCoveredStartSpan() {
+ SentimentValue rep = getRepresentativeSentimentValue();
+ String repValue = rep != null ? rep.getSentimentType().getName() : "";
+ return String.format("", UUID.randomUUID(), this.getWikiId(), this.getCoveredText(), repValue);
+ }
+
@Override
public String getWikiId() {
return "S" + "-" + this.getId();
diff --git a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
index 9a77cdc9..a6511c73 100644
--- a/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
+++ b/uce.portal/uce.common/src/main/java/org/texttechnologylab/services/PostgresqlDataInterface_Impl.java
@@ -2503,6 +2503,18 @@ private Document initializeCompleteDocument(Document doc, int skipPages, int pag
}
}
+ // sentiment
+ Hibernate.initialize(doc.getSentiments());
+
+ for (var sentiment : doc.getSentiments()) {
+ Hibernate.initialize(sentiment.getSentimentValues());
+ Hibernate.initialize(sentiment.getModelVersion());
+ Hibernate.initialize(sentiment.getModelVersion().getModel());
+ for (var sv : sentiment.getSentimentValues()) {
+ Hibernate.initialize(sv.getSentimentType());
+ }
+ }
+
for (var link : doc.getWikipediaLinks()) {
Hibernate.initialize(link.getWikiDataHyponyms());
}