diff --git a/backend/app.py b/backend/app.py index 77599f4..f64c675 100644 --- a/backend/app.py +++ b/backend/app.py @@ -46,6 +46,8 @@ ASSET_ALLOWED_EXTS = {".png", ".webp", ".jpg", ".jpeg", ".gif", ".svg", ".avif"} ASSET_TEMPLATE_ZIP = os.path.join(ROOT_DIR, "assets-replace-template.zip") WORKSPACE_DIR = os.path.dirname(ROOT_DIR) +OPENCLAW_WORKSPACE = os.environ.get("OPENCLAW_WORKSPACE") or os.path.join(os.path.expanduser("~"), ".openclaw", "workspace") +IDENTITY_FILE = os.path.join(OPENCLAW_WORKSPACE, "IDENTITY.md") GEMINI_SCRIPT = os.path.join(WORKSPACE_DIR, "skills", "gemini-image-generate", "scripts", "gemini_image_generate.py") GEMINI_PYTHON = os.path.join(WORKSPACE_DIR, "skills", "gemini-image-generate", ".venv", "bin", "python") ROOM_REFERENCE_IMAGE = ( @@ -199,6 +201,22 @@ def load_state(): return state +def get_office_name_from_identity(): + """Read office display name from OpenClaw workspace IDENTITY.md (Name field) -> 'XXX的办公室'.""" + if not os.path.isfile(IDENTITY_FILE): + return None + try: + with open(IDENTITY_FILE, "r", encoding="utf-8") as f: + content = f.read() + m = re.search(r"-\s*\*\*Name:\*\*\s*(.+)", content) + if m: + name = m.group(1).strip().replace("\r", "").split("\n")[0].strip() + return f"{name}的办公室" if name else None + except Exception: + pass + return None + + def save_state(state: dict): """Save state to file""" with open(STATE_FILE, "w", encoding="utf-8") as f: @@ -1127,8 +1145,11 @@ def leave_agent(): @app.route("/status", methods=["GET"]) def get_status(): - """Get current main state (backward compatibility)""" + """Get current main state (backward compatibility). Optionally include officeName from IDENTITY.md.""" state = load_state() + office_name = get_office_name_from_identity() + if office_name: + state["officeName"] = office_name return jsonify(state) diff --git a/frontend/electron-standalone.html b/frontend/electron-standalone.html index 21c5f89..a07cd34 100644 --- a/frontend/electron-standalone.html +++ b/frontend/electron-standalone.html @@ -1874,7 +1874,7 @@ function t(key) { return (I18N[uiLang] && I18N[uiLang][key]) || key; } function getOfficePlaqueTitle() { - return officePlaqueCustomTitle || t('officeTitle'); + return (window.officeNameFromServer || officePlaqueCustomTitle || t('officeTitle')); } function refreshOfficePlaqueTitle() { const el = document.getElementById('office-plaque-text'); @@ -5474,6 +5474,10 @@ .then(response => response.json()) .then(data => { try { + if (data.officeName) { + window.officeNameFromServer = data.officeName; + refreshOfficePlaqueTitle(); + } const nextState = normalizeState(data.state); const stateInfo = STATES[nextState] || STATES.idle; // If we're mid-transition, don't restart the path every poll diff --git a/frontend/index.html b/frontend/index.html index 146c77d..b803428 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1459,8 +1459,9 @@ if (memoTitle) memoTitle.textContent = t('memoTitle'); const guestTitle = document.getElementById('guest-agent-panel-title'); if (guestTitle) guestTitle.textContent = t('guestTitle'); + const plaqueTitle = (typeof window.officeNameFromServer !== 'undefined' && window.officeNameFromServer) || t('officeTitle'); if (window.officePlaqueText && window.officePlaqueText.setText) { - window.officePlaqueText.setText(t('officeTitle')); + window.officePlaqueText.setText(plaqueTitle); } const coordsBtn = document.getElementById('coords-toggle'); @@ -4091,7 +4092,9 @@ fontWeight: '900', fontStyle: 'bold', stroke: '#000', - strokeThickness: 3 + strokeThickness: 3, + wordWrap: { width: 400 }, + align: 'center' }).setOrigin(0.5); // 牌匾两边加个小装饰(跟随牌匾居中) game.add.text(plaqueX - 190, plaqueY, '⭐', { fontFamily: 'ArkPixel, monospace', fontSize: '20px' }).setOrigin(0.5); @@ -4664,7 +4667,12 @@ .then(response => response.json()) .then(data => { try { - + if (data.officeName) { + window.officeNameFromServer = data.officeName; + if (window.officePlaqueText && window.officePlaqueText.setText) { + window.officePlaqueText.setText(data.officeName); + } + } const nextState = normalizeState(data.state); const stateInfo = STATES[nextState] || STATES.idle; // If we're mid-transition, don't restart the path every poll