mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-05-01 17:45:26 +00:00
Partial fix to fix prompt loading issues. Messages + Phase summary display on load broken.
This commit is contained in:
parent
f9864f85d9
commit
16e3b45264
18 changed files with 377 additions and 94 deletions
49
diplomacy/web/src/gui/components/PhaseSummaryBottomSheet.jsx
Normal file
49
diplomacy/web/src/gui/components/PhaseSummaryBottomSheet.jsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* A simple bottom sheet that slides up from the bottom of the screen,
|
||||
* showing the current phase summary.
|
||||
*/
|
||||
export function PhaseSummaryBottomSheet({ phase, summaryText, onClose }) {
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
maxHeight: '40%',
|
||||
backgroundColor: '#fff',
|
||||
boxShadow: '0 -2px 8px rgba(0,0,0,0.25)',
|
||||
zIndex: 9999,
|
||||
overflowY: 'auto',
|
||||
transition: 'transform 0.3s ease-in-out',
|
||||
transform: 'translateY(0%)'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '0.5rem',
|
||||
background: '#f2f2f2',
|
||||
borderBottom: '1px solid #ccc'
|
||||
}}>
|
||||
<h5 className="mb-0">Summary for {phase}:</h5>
|
||||
<button className="btn btn-sm btn-danger" onClick={onClose}>Close</button>
|
||||
</div>
|
||||
<div style={{
|
||||
padding: '1rem',
|
||||
overflowY: 'auto'
|
||||
}}>
|
||||
{summaryText || "No phase summary available."}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PhaseSummaryBottomSheet.propTypes = {
|
||||
phase: PropTypes.string,
|
||||
summaryText: PropTypes.string,
|
||||
onClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
@ -21,6 +21,7 @@ export class MessageView extends React.Component {
|
|||
// message
|
||||
render() {
|
||||
const message = this.props.message;
|
||||
console.log("DEBUG: In <MessageView>, message prop =>", message);
|
||||
const owner = this.props.owner;
|
||||
const id = this.props.id ? {id: this.props.id} : {};
|
||||
const messagesLines = message.message.replace('\r\n', '\n')
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import {SvgModern} from "../maps/modern/SvgModern";
|
|||
import {SvgPure} from "../maps/pure/SvgPure";
|
||||
import {MapData} from "../utils/map_data";
|
||||
import {Queue} from "../../diplomacy/utils/queue";
|
||||
import {PhaseSummaryView} from "../components/phase_summary_view";
|
||||
import {PhaseSummaryBottomSheet} from "../components/PhaseSummaryBottomSheet";
|
||||
|
||||
const HotKey = require('react-shortcut');
|
||||
|
||||
|
|
@ -140,7 +140,10 @@ export class ContentGame extends React.Component {
|
|||
power: null,
|
||||
orderBuildingType: null,
|
||||
orderBuildingPath: [],
|
||||
showAbbreviations: true
|
||||
showAbbreviations: true,
|
||||
showPhaseSummarySheet: false,
|
||||
bottomSheetVisible: true,
|
||||
summaryVisible: true
|
||||
};
|
||||
|
||||
// Bind some class methods to this instance.
|
||||
|
|
@ -807,15 +810,15 @@ export class ContentGame extends React.Component {
|
|||
onChangePastPhaseIndex(increment) {
|
||||
const selectObject = document.getElementById('select-past-phase');
|
||||
if (selectObject) {
|
||||
// Let's simply increase or decrease index of showed past phase.
|
||||
const index = selectObject.selectedIndex;
|
||||
const newIndex = index + (increment ? 1 : -1);
|
||||
if (newIndex >= 0 && newIndex < selectObject.length) {
|
||||
selectObject.selectedIndex = newIndex;
|
||||
this.__change_past_phase(parseInt(selectObject.options[newIndex].value, 10), (increment ? 0 : 1));
|
||||
}
|
||||
const index = selectObject.selectedIndex;
|
||||
const newIndex = index + (increment ? 1 : -1);
|
||||
if (newIndex >= 0 && newIndex < selectObject.length) {
|
||||
selectObject.selectedIndex = newIndex;
|
||||
// Ensure we do parseInt with a base of 10
|
||||
this.__change_past_phase(parseInt(selectObject.options[newIndex].value, 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onIncrementPastPhase(event) {
|
||||
this.onChangePastPhaseIndex(true);
|
||||
|
|
@ -885,21 +888,18 @@ export class ContentGame extends React.Component {
|
|||
renderPastMessages(engine, role) {
|
||||
const messageChannels = engine.getMessageChannels(role, true);
|
||||
const tabNames = [];
|
||||
for (let powerName of Object.keys(engine.powers)) if (powerName !== role)
|
||||
tabNames.push(powerName);
|
||||
for (let powerName of Object.keys(engine.powers)) {
|
||||
if (powerName !== role) tabNames.push(powerName);
|
||||
}
|
||||
tabNames.sort();
|
||||
tabNames.push('GLOBAL');
|
||||
const titles = tabNames.map(tabName => (tabName === 'GLOBAL' ? tabName : tabName.substr(0, 3)));
|
||||
const currentTabId = this.state.tabPastMessages || tabNames[0];
|
||||
|
||||
let summaryText = "";
|
||||
if (engine.phase_summaries && engine.phase_summaries.hasOwnProperty(engine.phasePrior)) {
|
||||
summaryText = engine.phase_summaries[engine.phasePrior];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className={'panel-messages'} key={'panel-messages'}>
|
||||
<PhaseSummaryView phase={engine.phasePrior} summaryText={summaryText} />
|
||||
<div className={'panel-messages'} key={'panel-messages'}>
|
||||
{/* Messages. */}
|
||||
<Tabs menu={tabNames} titles={titles} onChange={this.onChangeTabPastMessages} active={currentTabId}>
|
||||
{tabNames.map(protagonist => (
|
||||
|
|
@ -1157,6 +1157,25 @@ export class ContentGame extends React.Component {
|
|||
renderTabMessages(toDisplay, initialEngine, currentPowerName) {
|
||||
const {engine, pastPhases, phaseIndex} = this.__get_engine_to_display(initialEngine);
|
||||
|
||||
let allMessages = [];
|
||||
const history = engine.message_history || {};
|
||||
|
||||
for (const [key, val] of Object.entries(history)) {
|
||||
if (val && typeof val === 'object') {
|
||||
if (typeof val.values === 'function') {
|
||||
const arr = Array.from(val.values());
|
||||
allMessages.push(...arr);
|
||||
} else {
|
||||
allMessages.push(...Object.values(val));
|
||||
}
|
||||
} else {
|
||||
console.log(`[renderTabMessages] Skipping key="${key}" =>`, val);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("DEBUG: combined allMessages =>", allMessages);
|
||||
|
||||
if (!toDisplay) return null;
|
||||
return (
|
||||
<Tab id={'tab-phase-history'} display={toDisplay}>
|
||||
<Row>
|
||||
|
|
@ -1168,11 +1187,11 @@ export class ContentGame extends React.Component {
|
|||
</div>
|
||||
<div className={'col-xl'}>
|
||||
{this.__form_phases(pastPhases, phaseIndex)}
|
||||
{pastPhases[phaseIndex] === initialEngine.phase ? (
|
||||
this.renderCurrentMessages(initialEngine, currentPowerName)
|
||||
) : (
|
||||
this.renderPastMessages(engine, currentPowerName)
|
||||
)}
|
||||
{pastPhases[phaseIndex] === initialEngine.phase ? (
|
||||
this.renderCurrentMessages(initialEngine, currentPowerName)
|
||||
) : (
|
||||
this.renderPastMessages(engine, currentPowerName)
|
||||
)}
|
||||
</div>
|
||||
</Row>
|
||||
{toDisplay && <HotKey keys={['arrowleft']} onKeysCoincide={this.onDecrementPastPhase}/>}
|
||||
|
|
@ -1224,9 +1243,18 @@ export class ContentGame extends React.Component {
|
|||
// [ React.Component overridden methods.
|
||||
|
||||
render() {
|
||||
this.props.data.displayed = true;
|
||||
const page = this.context;
|
||||
const engine = this.props.data;
|
||||
|
||||
// 1) Quick log to confirm engine is not null/undefined
|
||||
console.log("Render content_game with engine:", engine);
|
||||
|
||||
if (!engine) {
|
||||
console.warn("Engine is null or undefined. Rendering fallback or forcing a logout?");
|
||||
return <main><div>Error: No engine found.</div></main>;
|
||||
}
|
||||
|
||||
this.props.data.displayed = true;
|
||||
const title = ContentGame.gameTitle(engine);
|
||||
const navigation = [
|
||||
['Help', () => page.dialog(onClose => <Help onClose={onClose}/>)],
|
||||
|
|
@ -1343,6 +1371,39 @@ export class ContentGame extends React.Component {
|
|||
</div>
|
||||
);
|
||||
|
||||
// Guard referencing engine.phase_summaries
|
||||
const phaseSummaries = engine.phase_summaries || {};
|
||||
const currentPhase = engine.phase || "(unknown phase)";
|
||||
let summaryText = "";
|
||||
// Attempt to get a prior phase or fallback to current if prior is not available
|
||||
const priorPhase = engine.phasePrior;
|
||||
if (engine.phase_summaries) {
|
||||
if (priorPhase && engine.phase_summaries.hasOwnProperty(priorPhase)) {
|
||||
summaryText = engine.phase_summaries[priorPhase];
|
||||
} else if (engine.phase && engine.phase_summaries.hasOwnProperty(engine.phase)) {
|
||||
summaryText = engine.phase_summaries[engine.phase];
|
||||
}
|
||||
}
|
||||
const summaryForCurrentPhase =
|
||||
phaseSummaries.hasOwnProperty(currentPhase) ? phaseSummaries[currentPhase] : "";
|
||||
|
||||
// Example: Flatten messages from all known phases
|
||||
let allMessages = [];
|
||||
if (engine.message_history) {
|
||||
for (const phaseName of Object.keys(engine.message_history)) {
|
||||
const phaseMsgsDict = engine.message_history[phaseName];
|
||||
// Convert { time_sent: messageObj, ... } to an array
|
||||
const phaseMsgsArray = Object.values(phaseMsgsDict);
|
||||
allMessages = allMessages.concat(phaseMsgsArray);
|
||||
}
|
||||
console.log('engine.message_history =>', engine.message_history);
|
||||
for (const [key, value] of Object.entries(engine.message_history)) {
|
||||
console.log('key =>', key, 'value =>', value);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("DEBUG: content_game messages =>", allMessages, "length:", allMessages.length);
|
||||
|
||||
return (
|
||||
<main>
|
||||
<Helmet>
|
||||
|
|
@ -1367,14 +1428,29 @@ export class ContentGame extends React.Component {
|
|||
currentTabOrderCreation
|
||||
)) || ''}
|
||||
</Tabs>
|
||||
{/* Debug log before rendering the bottom sheet */}
|
||||
{console.log("Rendering PhaseSummaryBottomSheet with phase:", currentPhase, "summary length:", summaryForCurrentPhase.length)}
|
||||
|
||||
<PhaseSummaryBottomSheet
|
||||
visible={this.state.summaryVisible}
|
||||
phase={currentPhase}
|
||||
summaryText={summaryText}
|
||||
onClose={() => this.setState({ summaryVisible: false })}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
window.scrollTo(0, 0);
|
||||
if (this.props.data.client)
|
||||
this.reloadDeadlineTimer(this.props.data.client);
|
||||
if (this.props.data.client) {
|
||||
this.reloadDeadlineTimer(this.props.data.client).then(() => {
|
||||
if (this._isMounted) {
|
||||
this.setState({ doneLoading: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
this.props.data.displayed = true;
|
||||
// Try to prevent scrolling when pressing keys Home and End.
|
||||
document.onkeydown = (event) => {
|
||||
|
|
@ -1395,6 +1471,7 @@ export class ContentGame extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
this.clearScheduleTimeout();
|
||||
this.props.data.displayed = false;
|
||||
document.onkeydown = null;
|
||||
|
|
|
|||
|
|
@ -31,52 +31,107 @@ export function loadGameFromDisk() {
|
|||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const savedData = JSON.parse(reader.result);
|
||||
const gameObject = {};
|
||||
gameObject.game_id = `(local) ${savedData.id}`;
|
||||
gameObject.map_name = savedData.map;
|
||||
gameObject.rules = savedData.rules;
|
||||
gameObject.state_history = {};
|
||||
gameObject.message_history = {};
|
||||
gameObject.order_history = {};
|
||||
gameObject.result_history = {};
|
||||
console.log('[loadGameFromDisk] Reading JSON file...');
|
||||
let savedData;
|
||||
try {
|
||||
savedData = JSON.parse(reader.result);
|
||||
} catch (e) {
|
||||
onError('Could not parse JSON file: ' + e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Load all saved phases (expect the latest one) to history fields.
|
||||
const gameObject = {
|
||||
game_id: `(local) ${savedData.id}`,
|
||||
map_name: savedData.map,
|
||||
rules: savedData.rules,
|
||||
state_history: {},
|
||||
message_history: {},
|
||||
order_history: {},
|
||||
result_history: {},
|
||||
phase_summaries: {}
|
||||
};
|
||||
|
||||
// Load older phases into history
|
||||
for (let i = 0; i < savedData.phases.length - 1; ++i) {
|
||||
const savedPhase = savedData.phases[i];
|
||||
const gameState = savedPhase.state;
|
||||
const phaseOrders = savedPhase.orders || {};
|
||||
const phaseResults = savedPhase.results || {};
|
||||
const phaseMessages = {};
|
||||
if (savedPhase.messages) {
|
||||
for (let message of savedPhase.messages) {
|
||||
phaseMessages[message.time_sent] = message;
|
||||
|
||||
// 1) Fix or parse messages if they're a string
|
||||
let phaseMessages = savedPhase.messages;
|
||||
if (typeof phaseMessages === 'string') {
|
||||
console.warn('[loadGameFromDisk] Phase', savedPhase.name,
|
||||
'has messages as string. Attempting fallback parse...');
|
||||
// If it starts with "SortedDict", we can't trivially parse.
|
||||
// Minimal fallback: set to empty object or parse if you have a custom parser.
|
||||
if (phaseMessages.startsWith('SortedDict{')) {
|
||||
phaseMessages = {};
|
||||
}
|
||||
} else if (!phaseMessages) {
|
||||
phaseMessages = {};
|
||||
} else {
|
||||
// Convert array -> object keyed by time_sent
|
||||
const obj = {};
|
||||
for (const msg of phaseMessages) {
|
||||
if (msg && msg.time_sent !== undefined) {
|
||||
obj[msg.time_sent] = msg;
|
||||
}
|
||||
}
|
||||
phaseMessages = obj;
|
||||
}
|
||||
if (!gameState.name)
|
||||
gameState.name = savedPhase.name;
|
||||
|
||||
if (!gameState.name) gameState.name = savedPhase.name;
|
||||
|
||||
gameObject.state_history[gameState.name] = gameState;
|
||||
gameObject.message_history[gameState.name] = phaseMessages;
|
||||
gameObject.order_history[gameState.name] = phaseOrders;
|
||||
gameObject.result_history[gameState.name] = phaseResults;
|
||||
|
||||
// Summaries
|
||||
if (savedPhase.summary) {
|
||||
console.log(`[loadGameFromDisk] Loading summaries for phase`, gameState.name);
|
||||
gameObject.phase_summaries[gameState.name] = savedPhase.summary;
|
||||
} else {
|
||||
console.log(`[loadGameFromDisk] No summary for phase ${savedPhase.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Load latest phase separately and use it later to define the current game phase.
|
||||
// Load latest phase
|
||||
const latestPhase = savedData.phases[savedData.phases.length - 1];
|
||||
const latestGameState = latestPhase.state;
|
||||
const latestPhaseOrders = latestPhase.orders || {};
|
||||
const latestPhaseResults = latestPhase.results || {};
|
||||
const latestPhaseMessages = {};
|
||||
if (latestPhase.messages) {
|
||||
for (let message of latestPhase.messages) {
|
||||
latestPhaseMessages[message.time_sent] = message;
|
||||
let latestPhaseMessages = latestPhase.messages;
|
||||
if (typeof latestPhaseMessages === 'string') {
|
||||
console.warn('[loadGameFromDisk] Latest phase has messages as string. Fallback parse...');
|
||||
if (latestPhaseMessages.startsWith('SortedDict{')) {
|
||||
latestPhaseMessages = {};
|
||||
}
|
||||
} else if (!latestPhaseMessages) {
|
||||
latestPhaseMessages = {};
|
||||
} else {
|
||||
const obj = {};
|
||||
for (const msg of latestPhaseMessages) {
|
||||
if (msg && msg.time_sent !== undefined) {
|
||||
obj[msg.time_sent] = msg;
|
||||
}
|
||||
}
|
||||
latestPhaseMessages = obj;
|
||||
}
|
||||
|
||||
if (!latestGameState.name)
|
||||
latestGameState.name = latestPhase.name;
|
||||
// TODO: NB: What if latest phase in loaded JSON contains order results? Not sure if it is well handled.
|
||||
gameObject.result_history[latestGameState.name] = latestPhaseResults;
|
||||
|
||||
if (latestPhase.summary) {
|
||||
console.log(`[loadGameFromDisk] Loading summary for latest phase ${latestGameState.name}:`, latestPhase.summary);
|
||||
gameObject.phase_summaries[latestGameState.name] = latestPhase.summary;
|
||||
} else {
|
||||
console.log(`[loadGameFromDisk] No summary for latest phase ${latestGameState.name}`);
|
||||
}
|
||||
|
||||
// Final game metadata
|
||||
gameObject.messages = [];
|
||||
gameObject.role = STRINGS.OBSERVER_TYPE;
|
||||
gameObject.status = STRINGS.COMPLETED;
|
||||
|
|
@ -84,15 +139,17 @@ export function loadGameFromDisk() {
|
|||
gameObject.deadline = 0;
|
||||
gameObject.n_controls = 0;
|
||||
gameObject.registration_password = '';
|
||||
|
||||
const game = new Game(gameObject);
|
||||
|
||||
// Set game current phase and state using latest phase found in JSON file.
|
||||
// Set the current phase to the latest
|
||||
game.setPhaseData({
|
||||
name: latestGameState.name,
|
||||
state: latestGameState,
|
||||
orders: latestPhaseOrders,
|
||||
messages: latestPhaseMessages
|
||||
});
|
||||
|
||||
onLoad(game);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue