Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MoralKeeperAI を使用したコメント提案機能の追加 #230

Merged
merged 31 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7512aba
add notes
TakumiMiyazawaAbel Aug 22, 2024
6c85137
add comment content suggestion screen
TakumiMiyazawaAbel Aug 22, 2024
75853ad
add comment_check screen
YukiNakamuraAbel Aug 26, 2024
d258161
Fix resource comment submission confirmation screen
keita-yasuda Sep 17, 2024
469640c
Fix comment suggestion screen and integrate moral-keeper-ai
keita-yasuda Sep 17, 2024
b704af2
Add moral-keeper-ai to Utilization Comment screen
keita-yasuda Sep 18, 2024
7208f14
Fix frontend functionality: add validation and prevent double submission
keita-yasuda Sep 18, 2024
5a3c29e
Fixes for i18n support
keita-yasuda Sep 18, 2024
ca1002f
Add ON/OFF setting for comment suggestion feature in ckan.ini
keita-yasuda Sep 18, 2024
54b162f
Fix coding style
keita-yasuda Sep 18, 2024
9e6fb0d
Add and fix test code
keita-yasuda Sep 20, 2024
010d1a0
Fix coding style
keita-yasuda Oct 1, 2024
7813301
Add and fix test code
keita-yasuda Oct 3, 2024
15316dd
Add screen for handling None suggest results
keita-yasuda Oct 4, 2024
859236c
add document for moral-keeper-ai
keita-yasuda Oct 8, 2024
72690d1
Fix maxlength in suggestion.html
keita-yasuda Oct 15, 2024
02be59b
Fixed to be able to switch ON/OFF for each organization
keita-yasuda Nov 7, 2024
c3c57b5
add documentation on switching ON/OFF per organization
keita-yasuda Nov 7, 2024
d33c257
Bug fixes that were omitted during rebasing
keita-yasuda Nov 15, 2024
7957aee
Remove duplicate blocks in pagination
TakumiMiyazawaAbel Nov 20, 2024
6ad8903
Remove duplicate notices
TakumiMiyazawaAbel Nov 20, 2024
9fe4a24
Fix arguments for moral_keeper_ai.is_enable
TakumiMiyazawaAbel Nov 20, 2024
638ccf6
Fix conditional logic for on/off states
TakumiMiyazawaAbel Nov 20, 2024
81a19ed
Add Bootstrap 3 support for loading spinner during submission
keita-yasuda Nov 21, 2024
9434e76
Add Google reCAPTCHA integration
keita-yasuda Nov 21, 2024
3c0381c
Redesign page title
keita-yasuda Nov 21, 2024
ff89db6
Adjust submit button placement for Bootstrap 3 compatibility
keita-yasuda Nov 21, 2024
6813c52
Fix Handle stale reCAPTCHA token on back/forward navigation
TakumiMiyazawaAbel Dec 3, 2024
fa65034
Apply isort formatting
TakumiMiyazawaAbel Feb 14, 2025
fd90a87
Update documentation
TakumiMiyazawaAbel Feb 14, 2025
c5277e7
Merge branch 'main' into add/ui-for-embedded-generative-ai
k-nakahara21 Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions ckanext/feedback/assets/css/comment.css
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,34 @@
.comment-count {
color: #808080;
}

.char-count {
text-align: right;
color: #808080;
}

.comment-count {
color: #808080;
}

.notice-container{
background-color: blanchedalmond;
padding: 10px;
border-radius: 10px;
}

.char-count {
color: #808080;
text-align: right;
right: 10px;
bottom: 10px;
font-size: 0.9em;
}

.comment-contents {
position: relative;
}

textarea {
padding-bottom: 25px; /* Adjust padding to account for char-count */
}
6 changes: 6 additions & 0 deletions ckanext/feedback/assets/css/details.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
height: 28px !important;
}

.notice-container{
background-color: blanchedalmond;
padding: 10px;
border-radius: 10px;
}

.char-count {
text-align: right;
color: #808080;
Expand Down
52 changes: 45 additions & 7 deletions ckanext/feedback/assets/js/comment.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
function checkCommentExists(button) {
const comment = document.getElementById('comment-content').value;
const spinner = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>'
const spinner_bs3 = '<span class="fa fa-spinner fa-spin" role="status" aria-hidden="true"></span>'

function checkCommentExists(button, bs3=false) {
let comment
if ( button.id === "comment-button" ) {
comment = document.getElementById('comment-content').value;
}
if ( button.id === "proposal-comment-button" ) {
comment = document.getElementById('proposal-comment-content').value;
}

const rating = document.getElementById('rating').value;
const commentNoneErrorElement = document.getElementById('comment-none-error');
const commentOverErrorElement = document.getElementById('comment-over-error');
Expand All @@ -17,7 +27,17 @@ function checkCommentExists(button) {
commentOverErrorElement.style.display = '';
return false;
}
button.style.pointerEvents = "none"
const sendButtons = document.getElementsByName('send-button');
sendButtons.forEach(button => {
button.style.pointerEvents = "none";
button.style.background = "#333333";
if (!bs3) {
button.innerHTML = spinner + button.innerHTML;
}else{
button.innerHTML = spinner_bs3 + button.innerHTML;
}
});

return true;
}

Expand Down Expand Up @@ -67,12 +87,30 @@ function setButtonDisable(button) {
}

//文字数カウント
document.addEventListener('DOMContentLoaded', function() {
const textarea = document.getElementById('comment-content');
const charCount = document.getElementById('comment-count');
document.addEventListener('DOMContentLoaded', () => {
const textareas = document.getElementsByName('comment-content');
const charCounts = document.getElementsByName('comment-count');

textarea.addEventListener('input', function() {
function updateCharCount(textarea, charCount) {
const currentLength = textarea.value.length;
charCount.textContent = currentLength;
}

textareas.forEach((textarea, index) => {
updateCharCount(textarea, charCounts[index]);
textarea.addEventListener('input', () => {
const currentLength = textarea.value.length;
charCounts[index].textContent = currentLength;
});
});
});

window.addEventListener('pageshow', () => {
const sendButtons = document.getElementsByName('send-button');
sendButtons.forEach(sendButton => {
sendButton.style.pointerEvents = "auto";
sendButton.style.background = "#206b82";
sendButton.innerHTML = sendButton.innerHTML.replace(spinner, '');
sendButton.innerHTML = sendButton.innerHTML.replace(spinner_bs3, '');
});
});
51 changes: 43 additions & 8 deletions ckanext/feedback/assets/js/detail.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
function checkCommentExists(button) {
const comment = document.getElementById('comment-content').value;
const spinner = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>'
const spinner_bs3 = '<span class="fa fa-spinner fa-spin" role="status" aria-hidden="true"></span>'

function checkCommentExists(button, bs3=false) {
let comment
if ( button.id === "comment-button" ) {
comment = document.getElementById('comment-content').value;
}
if ( button.id === "proposal-comment-button" ) {
comment = document.getElementById('proposal-comment-content').value;
}
const commentNoneErrorElement = document.getElementById('comment-none-error');
const commentOverErrorElement = document.getElementById('comment-over-error');

Expand All @@ -15,8 +24,16 @@ function checkCommentExists(button) {
commentOverErrorElement.style.display = '';
return false;
}

button.style.pointerEvents = "none"
const sendButtons = document.getElementsByName('send-button');
sendButtons.forEach(sendButton => {
sendButton.style.pointerEvents = "none";
sendButton.style.background = "#333333";
if (!bs3) {
sendButton.innerHTML = spinner + sendButton.innerHTML;
}else{
sendButton.innerHTML = spinner_bs3 + sendButton.innerHTML;
}
});
return true;
}

Expand All @@ -39,12 +56,30 @@ function setButtonDisable(button) {
}

//文字数カウント
document.addEventListener('DOMContentLoaded', function() {
const textarea = document.getElementById('comment-content');
const charCount = document.getElementById('comment-count');
document.addEventListener('DOMContentLoaded', () => {
const textareas = document.getElementsByName('comment-content');
const charCounts = document.getElementsByName('comment-count');

textarea.addEventListener('input', function() {
function updateCharCount(textarea, charCount) {
const currentLength = textarea.value.length;
charCount.textContent = currentLength;
}

textareas.forEach(textarea, index => {
updateCharCount(textarea, charCounts[index]);
textarea.addEventListener('input', () => {
const currentLength = textarea.value.length;
charCounts[index].textContent = currentLength;
});
});
});

window.addEventListener('pageshow', () => {
const sendButtons = document.getElementsByName('send-button');
sendButtons.forEach(sendButton => {
sendButton.style.pointerEvents = "auto";
sendButton.style.background = "#206b82";
sendButton.innerHTML = sendButton.innerHTML.replace(spinner, '');
sendButton.innerHTML = sendButton.innerHTML.replace(spinner_bs3, '');
});
});
45 changes: 27 additions & 18 deletions ckanext/feedback/assets/js/recaptcha.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
const contentForm = document.getElementById(feedbackRecaptchaTargetForm);
contentForm.onsubmit = function(event) {
event.preventDefault();
grecaptcha.ready(function() {
grecaptcha.execute(feedbackRecaptchaPublickey, {action: feedbackRecaptchaAction}).then(function(token) {
const tokenInput = document.createElement('input');
tokenInput.type = 'hidden';
tokenInput.name = 'g-recaptcha-response';
tokenInput.value = token;
contentForm.appendChild(tokenInput);
const actionInput = document.createElement('input');
actionInput.type = 'hidden';
actionInput.name = 'action';
actionInput.value = feedbackRecaptchaAction;
contentForm.appendChild(actionInput);
contentForm.submit();
window.addEventListener('pageshow', function(event) {
if (event.persisted || (performance.getEntriesByType("navigation")[0]?.type === "back_forward")) {
const existingTokenInput = document.querySelector('input[name="g-recaptcha-response"]');
if (existingTokenInput) existingTokenInput.remove();
}
});

const contentForms = document.getElementsByName(feedbackRecaptchaTargetForm);
contentForms.forEach(contentForm => {
contentForm.onsubmit = function(event) {
event.preventDefault();
grecaptcha.ready(function() {
grecaptcha.execute(feedbackRecaptchaPublickey, {action: feedbackRecaptchaAction}).then(function(token) {
const tokenInput = document.createElement('input');
tokenInput.type = 'hidden';
tokenInput.name = 'g-recaptcha-response';
tokenInput.value = token;
contentForm.appendChild(tokenInput);
const actionInput = document.createElement('input');
actionInput.type = 'hidden';
actionInput.name = 'action';
actionInput.value = feedbackRecaptchaAction;
contentForm.appendChild(actionInput);
contentForm.submit();
});
});
});
}
}
});
119 changes: 111 additions & 8 deletions ckanext/feedback/controllers/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
import ckanext.feedback.services.resource.validate as validate_service
from ckanext.feedback.controllers.pagination import get_pagination_value
from ckanext.feedback.models.session import session
from ckanext.feedback.services.common.ai_functions import (
check_ai_comment,
suggest_ai_comment,
)
from ckanext.feedback.services.common.check import (
check_administrator,
has_organization_admin_role,
Expand Down Expand Up @@ -77,18 +81,15 @@ def comment(resource_id, category='', content=''):
def create_comment(resource_id):
package_name = request.form.get('package_name', '')
category = None
content = None
if request.form.get('comment-content'):
content = request.form.get('comment-content')
category = request.form.get('category')
rating = None
if request.form.get('rating'):
rating = int(request.form.get('rating'))
if content := request.form.get('comment-content', ''):
category = request.form.get('category', '')
if rating := request.form.get('rating', ''):
rating = int(rating)
if not (category and content):
toolkit.abort(400)

if not is_recaptcha_verified(request):
helpers.flash_error(_(u'Bad Captcha. Please try again.'), allow_html=True)
helpers.flash_error(_('Bad Captcha. Please try again.'), allow_html=True)
return ResourceController.comment(resource_id, category, content)

if message := validate_service.validate_comment(content):
Expand All @@ -98,6 +99,9 @@ def create_comment(resource_id):
)
return ResourceController.comment(resource_id, category, content)

if not rating:
rating = None

comment_service.create_resource_comment(resource_id, category, content, rating)
summary_service.create_resource_summary(resource_id)
session.commit()
Expand Down Expand Up @@ -138,6 +142,105 @@ def create_comment(resource_id):

return resp

# resource_comment/<resource_id>/comment/suggested
@staticmethod
def suggested_comment(resource_id, category='', content='', rating=''):
softened = suggest_ai_comment(comment=content)

context = {'model': model, 'session': session, 'for_view': True}

resource = comment_service.get_resource(resource_id)
package = get_action('package_show')(
context, {'id': resource.Resource.package_id}
)
g.pkg_dict = {'organization': {'name': resource.organization_name}}

if softened is None:
return toolkit.render(
'resource/expect_suggestion.html',
{
'resource': resource.Resource,
'pkg_dict': package,
'selected_category': category,
'rating': rating,
'content': content,
},
)

return toolkit.render(
'resource/suggestion.html',
{
'resource': resource.Resource,
'pkg_dict': package,
'selected_category': category,
'rating': rating,
'content': content,
'softened': softened,
},
)

# resource_comment/<resource_id>/comment/check
@staticmethod
def check_comment(resource_id):
if request.method == 'GET':
return toolkit.redirect_to(
'resource_comment.comment', resource_id=resource_id
)

category = None
if content := request.form.get('comment-content', ''):
category = request.form.get('category', '')
if rating := request.form.get('rating', ''):
rating = int(rating)
if not (category and content):
return toolkit.redirect_to(
'resource_comment.comment', resource_id=resource_id
)

if not is_recaptcha_verified(request):
helpers.flash_error(_('Bad Captcha. Please try again.'), allow_html=True)
return ResourceController.comment(resource_id, category, content)

if message := validate_service.validate_comment(content):
helpers.flash_error(
_(message),
allow_html=True,
)
return ResourceController.comment(resource_id, category, content)

categories = comment_service.get_resource_comment_categories()
resource = comment_service.get_resource(resource_id)
context = {'model': model, 'session': session, 'for_view': True}
package = get_action('package_show')(
context, {'id': resource.Resource.package_id}
)
g.pkg_dict = {'organization': {'name': resource.organization_name}}

if not request.form.get(
'comment-suggested', False
) and FeedbackConfig().moral_keeper_ai.is_enable(
resource.Resource.package.owner_org
):
if check_ai_comment(comment=content) is False:
return ResourceController.suggested_comment(
resource_id=resource_id,
rating=rating,
category=category,
content=content,
)

return toolkit.render(
'resource/comment_check.html',
{
'resource': resource.Resource,
'pkg_dict': package,
'categories': categories,
'selected_category': category,
'rating': rating,
'content': content,
},
)

# resource_comment/<resource_id>/comment/approve
@staticmethod
@check_administrator
Expand Down
Loading