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

[8372] comments_async: redesign ai report #1676

Merged
merged 1 commit into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,45 @@ import { render, fireEvent, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import AiReport from '../ai_report'

test('Test render <AiReport> with Read More button', () => {
render(
<AiReport
Report={{ explanation: 'This is the ai report', show_in_discussion: true }}
/>
)
const comment = screen.getByText('Read more')
expect(comment).toBeTruthy()
})
describe('Test AiReport', () => {
test('renders with Read More button', () => {
render(
<AiReport
report={{ label: [['cattest', 'test label']], confidence: [['cattest', 0.65]], explanation: { cattest: [['word', 0.61]] }, show_in_discussion: true }}
/>
)
const comment = screen.getByText('Read more')
expect(comment).toBeInTheDocument()
})

test('functionality of Read More button', () => {
render(
<AiReport
report={{ label: [['cattest', 'test label']], confidence: [['cattest', 0.65]], explanation: { cattest: [['word', 0.61]] }, show_in_discussion: true }}
/>
)
const readMore = screen.getByText('Read more')
expect(readMore).toBeInTheDocument()
const button = screen.getByRole('button')
expect(button).toBeInTheDocument()
fireEvent.click(button)
const readLess = screen.getByText('Show less')
expect(readLess).toBeInTheDocument()
expect(readLess).toBeInTheDocument()
})

test('Test functionality of Read More <AiReport>', () => {
render(
<AiReport
Report={{ explanation: 'This is the ai report', show_in_discussion: true }}
/>
)
const readMore = screen.getByText('Read more')
expect(readMore).toBeTruthy()
const button = screen.getByRole('button')
expect(button).toBeTruthy()
fireEvent.click(button)
const readLess = screen.getByText('Show less')
expect(readLess).toBeTruthy()
test('shows percentage for each label', async () => {
render(
<AiReport
report={{ label: [['cattest', 'test label']], confidence: [0.65], explanation: { cattest: [['word', 0.61]] }, show_in_discussion: true }}
/>
)
const readMore = screen.getByText('Read more')
expect(readMore).toBeInTheDocument()
const button = screen.getByRole('button')
expect(button).toBeInTheDocument()
fireEvent.click(button)
const percent = screen.getByText(/65%/)
expect(percent).toBeInTheDocument()
})
})
68 changes: 57 additions & 11 deletions adhocracy4/comments_async/static/comments_async/ai_report.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import django from 'django'
import { SwitchButton } from '../../../static/SwitchButton'

const translated = {
intro: django.pgettext('defakts', 'This comment contains disinformation. Defakts uses an Artificial ' +
'Intelligence to scan content for disinformation. Disinformation often shows ' +
'certain characteristics that allow for a reliable identification.'),
expandableBar: django.pgettext('defakts', 'The defakt AI has found evidence of disinformation.'),
intro: django.pgettext('defakts', 'Defakts uses artificial intelligence to check content for disinformation based on certain linguistic characteristics.'),
confidenceScore: django.pgettext('defakts', 'The AI considers some of the characteristics of disinformation to be fulfilled based on the following words in the comment. The probability is given in % for each characteristic.'),
cta: django.pgettext('defakts', 'If you want to know more about what the characteristics mean, please visit our [FAQs]. Here you will also find advice on how to respond constructively to disinformation.'),
ariaReadMore: django.pgettext('defakts', 'Click to view the AI explanation for reporting this comment.'),
ariaReadLess: django.pgettext('defakts', 'Click to hide the AI explanation for reporting this comment.'),
readMore: django.pgettext('defakts', 'Read more'),
Expand All @@ -14,7 +15,7 @@ const translated = {
hideInfoSwitch: django.pgettext('defakts', 'Hide AI info from users')
}

export const AiReport = ({ Report, notificationPk, toggleShowAiReport }) => {
export const AiReport = ({ report, notificationPk, toggleShowAiReport }) => {
const [isExpanded, setIsExpanded] = useState()

const toggleExpand = () => {
Expand All @@ -32,31 +33,76 @@ export const AiReport = ({ Report, notificationPk, toggleShowAiReport }) => {
</button>
)

const confidenceToPercent = (confidence) => {
const percentFormat = new Intl.NumberFormat('default', {
style: 'percent',
minimumFractionDigits: 0,
maximumFractionDigits: 0
})
return percentFormat.format(confidence)
}

const extractLabelWords = (label) => {
goapunk marked this conversation as resolved.
Show resolved Hide resolved
const words = report.explanation[label]
return words.slice(0, 3).map(word => word[0]).join(', ')
}

const renderExplanation = (
<ul>
{report.label.map(([key, description], index) => (
<li key={key}>
<span>{description.charAt(0).toUpperCase() + description.slice(1)} </span>
<span>({confidenceToPercent(report.confidence[index])}): </span>
<span>{extractLabelWords(key)}</span>
</li>
))}
</ul>
)

const renderCTA = () => {
// replace "[FAQs]" with anchor
const [preText, placeholderText, postText] = translated.cta.split(/(\[.*?\])/)
// remove square brackets
const anchorText = placeholderText.replace(/\[|\]/g, '')
return (
<>
{preText} <a href={report.faq_url} target="_blank" rel="noreferrer">{anchorText}</a> {postText}
</>
)
}

return (
<div className="alert alert--danger mb-4">
<div className="d-flex text-start mb-4">
<div className="alert alert--danger mb-2 pb-0">
<div className="d-flex text-start">
<i
className="fas fa-exclamation-circle text-danger pt-1 pe-2"
aria-hidden="True"
/>

{!isExpanded
? (
<p className="pe-4">{translated.intro} {toggleReadMore}</p>
<p className="pe-4">{translated.expandableBar} {toggleReadMore}</p>
)
: (
<div className="pe-4">
<p>{translated.intro}</p>
<p>{Report.explanation} {toggleReadMore}</p>
<p>{translated.expandableBar}</p>
<p>
<span>{translated.intro} </span>
<span>{translated.confidenceScore}</span>
</p>
{renderExplanation}
<p>
{renderCTA()} {toggleReadMore}
</p>
</div>
)}
</div>
{toggleShowAiReport &&
<div className="d-flex text-start">
<div className="d-flex text-start mt-3 mb-3">
<SwitchButton
id={notificationPk}
onClickCallback={toggleShowAiReport}
isChecked={Report.show_in_discussion}
isChecked={report.show_in_discussion}
switchLabelOn={translated.hideInfoSwitch}
switchLabelOff={translated.showInfoSwitch}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ export default class Comment extends React.Component {

{this.props.aiReport && this.props.aiReport.show_in_discussion &&
<AiReport
Report={this.props.aiReport}
report={this.props.aiReport}
/>}

{this.state.showModStatement && this.state.moderatorFeedback &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ const CommentList = (props) => {
<ul className="u-list-reset a4-comments">
{
props.comments.map((comment, index) => {
// don't show ai report if there are no labels
let aiReport = null
if (comment.ai_report && comment.ai_report.label.length > 0) {
aiReport = comment.ai_report
}
return (
<Comment
comment_categories={comment.comment_categories}
Expand Down Expand Up @@ -60,7 +65,7 @@ const CommentList = (props) => {
wouldHaveCommentingPermission={props.wouldHaveCommentingPermission}
projectIsPublic={props.projectIsPublic}
moderatorFeedback={comment.moderator_feedback ? comment.moderator_feedback : null}
aiReport={comment.ai_report ? comment.ai_report : null}
aiReport={aiReport}
useTermsOfUse={props.useTermsOfUse}
agreedTermsOfUse={props.agreedTermsOfUse}
orgTermsUrl={props.orgTermsUrl}
Expand Down
5 changes: 5 additions & 0 deletions changelog/8372.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### Changed

- redesign AI report to be readable
- only show AI report when there's a useful label (catnodecis and catneutral don't
count as labels)