Skip to content

Commit

Permalink
Make the AI stats table show up in Reports view for CMs doing AI asse…
Browse files Browse the repository at this point in the history
…ssment.
  • Loading branch information
GreenAsJade committed Feb 18, 2025
1 parent d14184b commit a18fd92
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 71 deletions.
11 changes: 2 additions & 9 deletions src/components/Player/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { PlayerIcon } from "@/components/PlayerIcon";
import * as player_cache from "@/lib/player_cache";
import * as preferences from "@/lib/preferences";
import online_status from "@/lib/online_status";
import { ReportContext } from "@/contexts/ReportContext";

/* There are cases where what we are handed is some odd looking dirty data. We
* should probably start warning about remaining uses of these fields and then
Expand Down Expand Up @@ -73,14 +74,6 @@ export interface PlayerProperties {
forceShowRank?: boolean;
}

type ShowPlayersInReportContextType = {
reporter: player_cache.PlayerCacheEntry;
reported: player_cache.PlayerCacheEntry;
};

export const ShowPlayersInReportContext =
React.createContext<ShowPlayersInReportContextType | null>(null);

export function Player(props: PlayerProperties): React.ReactElement {
const user = data.get("user");
const player_id: number =
Expand All @@ -107,7 +100,7 @@ export function Player(props: PlayerProperties): React.ReactElement {
const base = player || historical;
const combined = base ? Object.assign({}, base, historical ? historical : {}) : null;

const viewReportContext = React.useContext(ShowPlayersInReportContext);
const viewReportContext = React.useContext(ReportContext);

React.useEffect(() => {
if (!props.disableCacheUpdate) {
Expand Down
18 changes: 18 additions & 0 deletions src/contexts/ReportContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (C) Online-Go.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*/
import * as React from "react";
import * as player_cache from "@/lib/player_cache";

type ReportContextType = {
reporter: player_cache.PlayerCacheEntry;
reported: player_cache.PlayerCacheEntry;
moderator_powers: number;
};

export const ReportContext = React.createContext<ReportContextType | null>(null);
156 changes: 98 additions & 58 deletions src/views/Game/AIReview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ import {
Goban,
encodeMoves,
encodeMove,
GobanRenderer,
} from "goban";
import { game_control } from "./game_control";
import { alert } from "@/lib/swal_config";
import { GobanContext } from "./goban_context";
import { ReportContext } from "@/contexts/ReportContext";
import { MODERATOR_POWERS } from "@/lib/moderation";

export interface AIReviewEntry {
move_number: number;
win_rate: number;
Expand All @@ -59,6 +63,8 @@ interface AIReviewProperties {
game_id: number;
hidden: boolean;
onAIReviewSelected: (ai_review: JGOFAIReview) => void;
reportContext?: React.ContextType<typeof ReportContext>;
gobanContext?: React.ContextType<typeof GobanContext>;
}

interface AIReviewState {
Expand All @@ -69,24 +75,39 @@ interface AIReviewState {
selected_ai_review?: JGOFAIReview;
update_count: number;
worst_moves_shown: number;
table_set: boolean;
hide_table: boolean;
table_hidden: boolean;
}

export class AIReview extends React.Component<AIReviewProperties, AIReviewState> {
// this will be the full ai review we are working with, as opposed to
// selected_ai_review which will just contain some metadata from the
// postgres database
// We need this wrapped because we want to access two contexts,
// and we can't do that in a function component.
// This wil be a lot cleaner when we convert it to a function component.
export function AIReview(props: AIReviewProperties) {
return (
<ReportContext.Consumer>
{(reportContext) => (
<GobanContext.Consumer>
{(gobanContext) => (
<AIReviewClass
{...props}
reportContext={reportContext}
gobanContext={gobanContext}
/>
)}
</GobanContext.Consumer>
)}
</ReportContext.Consumer>
);
}

class AIReviewClass extends React.Component<AIReviewProperties, AIReviewState> {
ai_review?: JGOFAIReview;
table_rows!: string[][];
avg_score_loss!: number[];
median_score_loss!: number[];
moves_pending!: number;
max_entries!: number;

static contextType = GobanContext;
declare context: React.ContextType<typeof GobanContext>;

constructor(props: AIReviewProperties) {
super(props);
const state: AIReviewState = {
Expand All @@ -98,7 +119,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
// TODO: allow users to view more than 3 of these key moves
// See https://forums.online-go.com/t/top-3-moves-score-a-better-metric/32702/15
worst_moves_shown: 6,
table_set: false,
hide_table: false,
table_hidden: preferences.get("ai-summary-table-show"),
};
this.state = state;
Expand All @@ -113,17 +134,24 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
this.median_score_loss = ai_table_out.median_score_loss;
this.moves_pending = ai_table_out.moves_pending;
this.max_entries = ai_table_out.max_entries;
if (!data.get("user").is_moderator) {
this.setState({
table_set: true,
});
}

const user = data.get("user");
const canViewTable =
user.is_moderator ||
((this.props.reportContext?.moderator_powers ?? 0) &
MODERATOR_POWERS.ASSESS_AI_REPORTS) !==
0;

this.setState({
hide_table: !canViewTable,
});
}

componentDidUpdate(prevProps: AIReviewProperties) {
if (this.getGameId() !== this.getGameId(prevProps)) {
this.getAIReviewList();
}
if (!this.state.table_set) {
if (!this.state.hide_table) {
const ai_table_out = this.AiSummaryTableRowList();
this.table_rows = ai_table_out.ai_table_rows;
this.avg_score_loss = ai_table_out.avg_score_loss;
Expand Down Expand Up @@ -209,7 +237,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
.catch(errorLogger);
}

private static handicapOffset(goban: Goban): number {
private static handicapOffset(goban: GobanRenderer): number {
if (
goban &&
goban.engine &&
Expand Down Expand Up @@ -269,7 +297,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
this.updateAIReviewMetadata(ai_review);
this.setState({
selected_ai_review: ai_review,
table_set: false,
hide_table: false,
});
this.props.onAIReviewSelected(ai_review);
this.syncAIReview();
Expand Down Expand Up @@ -433,7 +461,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
throw new Error("ai_review not set");
}

const goban = this.context;
const goban = this.props.gobanContext;

if (!goban) {
throw new Error("goban not set");
Expand Down Expand Up @@ -747,7 +775,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
}

private requestAnalysisOfVariation(cur_move: MoveTree, trunk_move: MoveTree): boolean {
const goban = this.context;
const goban = this.props.gobanContext;
if (!goban) {
return false;
}
Expand Down Expand Up @@ -810,7 +838,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
throw new Error("ai_review not set");
}

const goban = this.context;
const goban = this.props.gobanContext;

if (!goban) {
throw new Error("goban not set");
Expand Down Expand Up @@ -890,7 +918,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
}

private AiSummaryTableRowList() {
const goban = this.context;
const goban = this.props.gobanContext;
if (!goban) {
throw new Error("goban not set");
}
Expand Down Expand Up @@ -929,7 +957,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>

if (!this.ai_review?.engine.includes("katago")) {
this.setState({
table_set: true,
hide_table: true,
});
return {
ai_table_rows: default_table_rows,
Expand All @@ -942,10 +970,10 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>

const handicap = goban.engine.handicap;
//only useful when there's free placement, handicap = 1 no offset needed.
let h_offset = AIReview.handicapOffset(goban);
let h_offset = AIReviewClass.handicapOffset(goban);
h_offset = h_offset === 1 ? 0 : h_offset;
const b_player = h_offset > 0 || handicap > 1 ? 1 : 0;
const move_player_list = AIReview.getPlayerColorsMoveList(goban);
const move_player_list = AIReviewClass.getPlayerColorsMoveList(goban);

if (this.ai_review?.type === "fast") {
const scores = this.ai_review?.scores;
Expand Down Expand Up @@ -1065,7 +1093,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
}

this.setState({
table_set: true,
hide_table: true,
});

return {
Expand Down Expand Up @@ -1212,7 +1240,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>

if (!check1 && !check2) {
this.setState({
table_set: true,
hide_table: true,
});
}

Expand All @@ -1239,7 +1267,7 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
return null;
}

const goban = this.context;
const goban = this.props.gobanContext;

if (!goban || !goban.engine) {
return null;
Expand Down Expand Up @@ -1527,23 +1555,31 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
/>
<div className="worst-moves-summary-toggle-container">
{this.renderWorstMoveList(worst_move_list)}
{(user.is_moderator || null) && (
<div className="ai-summary-toggler">
<span>
<i className="fa fa-table"></i>
</span>
<span>
<Toggle
checked={this.state.table_hidden}
onChange={(b) => {
preferences.set("ai-summary-table-show", b);
this.setState({ table_hidden: b });
//console.log(this.state.table_hidden);
}}
/>
</span>
</div>
)}
{(user.is_moderator ||
(this.props.reportContext &&
(data.get("user").moderator_powers &
MODERATOR_POWERS.ASSESS_AI_REPORTS) !==
0)) &&
this.ai_review?.engine.includes("katago") && (
<div className="ai-summary-toggler">
<span>
<i className="fa fa-table"></i>
</span>
<span>
<Toggle
checked={this.state.table_hidden}
onChange={(b) => {
preferences.set(
"ai-summary-table-show",
b,
);
this.setState({ table_hidden: b });
//console.log(this.state.table_hidden);
}}
/>
</span>
</div>
)}
</div>
{this.ai_review.scores && (
<div className="win-score-toggler">
Expand Down Expand Up @@ -1657,25 +1693,29 @@ export class AIReview extends React.Component<AIReviewProperties, AIReviewState>
</span>
</div>
)*/}
{data.get("user").is_moderator && this.ai_review?.engine.includes("katago") && (
<div>
<AiSummaryTable
heading_list={[_("Type"), _("Black"), "%", _("White"), "%"]}
body_list={this.table_rows}
avg_loss={this.avg_score_loss}
median_score_loss={this.median_score_loss}
table_hidden={this.state.table_hidden}
pending_entries={this.moves_pending}
max_entries={this.max_entries}
/>
</div>
)}
{(data.get("user").is_moderator ||
(this.props.reportContext &&
(data.get("user").moderator_powers & MODERATOR_POWERS.ASSESS_AI_REPORTS) !==
0)) &&
this.ai_review?.engine.includes("katago") && (
<div>
<AiSummaryTable
heading_list={[_("Type"), _("Black"), "%", _("White"), "%"]}
body_list={this.table_rows}
avg_loss={this.avg_score_loss}
median_score_loss={this.median_score_loss}
table_hidden={this.state.table_hidden}
pending_entries={this.moves_pending}
max_entries={this.max_entries}
/>
</div>
)}
</div>
);
}

public renderWorstMoveList(lst: AIReviewWorstMoveEntry[]): React.ReactElement | null {
const goban = this.context;
const goban = this.props.gobanContext;
if (!goban?.engine.move_tree || !this.ai_review) {
return null;
}
Expand Down
13 changes: 9 additions & 4 deletions src/views/ReportsCenter/ViewReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { report_manager } from "@/lib/report_manager";
import { Report } from "@/lib/report_util";
import { AutoTranslate } from "@/components/AutoTranslate";
import { interpolate, _, pgettext, llm_pgettext } from "@/lib/translate";
import { Player, ShowPlayersInReportContext } from "@/components/Player";
import { Player } from "@/components/Player";
import { Link } from "react-router-dom";
import { post } from "@/lib/requests";
import { PlayerCacheEntry } from "@/lib/player_cache";
Expand All @@ -44,6 +44,7 @@ import * as DynamicHelp from "react-dynamic-help";
import { MODERATOR_POWERS } from "@/lib/moderation";
import { KBShortcut } from "@/components/KBShortcut";
import { GobanRenderer } from "goban";
import { ReportContext } from "@/contexts/ReportContext";

interface ViewReportProps {
reports: Report[];
Expand Down Expand Up @@ -331,8 +332,12 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): R
<KBShortcut shortcut="left" action={nav_prev} />
<KBShortcut shortcut="right" action={nav_next} />

<ShowPlayersInReportContext.Provider
value={{ reporter: report.reporting_user, reported: report.reported_user }}
<ReportContext.Provider
value={{
reporter: report.reporting_user,
reported: report.reported_user,
moderator_powers: user.moderator_powers,
}}
>
<div id="ViewReport" className="show-players-in-report">
{isAnnulQueueModalOpen && (
Expand Down Expand Up @@ -795,7 +800,7 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): R
)}
</ErrorBoundary>
</div>
</ShowPlayersInReportContext.Provider>
</ReportContext.Provider>
</div>
);
}
Expand Down

0 comments on commit a18fd92

Please sign in to comment.