tao-shen Claude Opus 4.6 commited on
Commit
b32e821
ยท
1 Parent(s): 39d5d84

feat: bilingual conversation, word-wrap bubbles, remove unused panels

Browse files

- Conversation topic now concrete: survival/reproduction on HuggingFace
- Bilingual output (EN/ZH) with language toggle in chat log panel
- Fix guest bubble word wrap (was single-line, now wraps at 300px)
- Remove memo, control-bar, guest-agent-panel (keep only chatlog)
- Bubble and chatlog support text_zh field for Chinese translation
- Force Docker rebuild (v3.3)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- # OpenClaw on Hugging Face Spaces โ€” Pre-built image (v3.2)
2
  # Uses official pre-built image to avoid 30+ minute builds on cpu-basic
3
 
4
  # โ”€โ”€ Stage 1: Pull pre-built OpenClaw โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
1
+ # OpenClaw on Hugging Face Spaces โ€” Pre-built image (v3.3)
2
  # Uses official pre-built image to avoid 30+ minute builds on cpu-basic
3
 
4
  # โ”€โ”€ Stage 1: Pull pre-built OpenClaw โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
frontend/electron-standalone.html CHANGED
@@ -1653,41 +1653,16 @@
1653
 
1654
  <!-- ๅบ•้ƒจ้ขๆฟๅฎนๅ™จ -->
1655
  <div id="bottom-panels">
1656
- <!-- Memo ้ขๆฟ -->
1657
- <div id="memo-panel">
1658
- <div id="memo-title">ๆ˜จ ๆ—ฅ ๅฐ ่ฎฐ</div>
1659
- <div id="memo-date"></div>
1660
- <div class="memo-decoration">โ”€ โ”€ โ”€ โ”€ โ”€</div>
1661
- <div id="memo-content">
1662
- <div id="memo-placeholder">ๅŠ ่ฝฝไธญ...</div>
1663
- </div>
1664
- <div class="memo-decoration">โ”€ โ”€ โ”€ โ”€ โ”€</div>
1665
- </div>
1666
-
1667
- <!-- ็Šถๆ€ๆŽงๅˆถๆ  -->
1668
- <div id="control-bar">
1669
- <div id="control-bar-title">Star ็Šถๆ€</div>
1670
- <div id="control-buttons">
1671
- <button id="btn-state-idle" onclick="setState('idle', getStateDetailByState('idle'))">ๅพ…ๅ‘ฝ</button>
1672
- <button id="btn-state-writing" onclick="setState('writing', getStateDetailByState('writing'))">ๅทฅไฝœ</button>
1673
- <button id="btn-state-syncing" onclick="setState('syncing', getStateDetailByState('syncing'))">ๅŒๆญฅ</button>
1674
- <button id="btn-state-error" onclick="setState('error', getStateDetailByState('error'))">ๆŠฅ่ญฆ</button>
1675
- <button id="btn-open-drawer" onclick="openAssetWindowFromMain()">่ฃ…ไฟฎๆˆฟ้—ด</button>
1676
- </div>
1677
- </div>
1678
-
1679
- <!-- Guest Agent ๅๅ•้ขๆฟ๏ผˆๅณไธ‹่ง’๏ผ‰ -->
1680
- <div id="guest-agent-panel">
1681
- <div id="guest-agent-panel-title">GUESTS</div>
1682
- <div id="guest-agent-list">
1683
- <div style="color:#9ca3af;font-size:12px;text-align:center;padding:20px 0;">Loading guests...</div>
1684
- </div>
1685
- </div>
1686
- </div>
1687
-
1688
  <!-- Chat Log ้ขๆฟ -->
1689
  <div id="chatlog-panel">
1690
- <div id="chatlog-title">๐Ÿฆž Adam โ†” Eve Conversation</div>
 
 
 
 
 
 
 
1691
  <div id="chatlog-content">
1692
  <div style="color:#9ca3af;font-size:12px;text-align:center;padding:20px 0;">Waiting for conversation to start...</div>
1693
  </div>
@@ -4522,7 +4497,7 @@ function toggleBrokerPanel() {
4522
  const yOffset = (DEMO_MODE && (agent.agentId === 'demo_mercury' || agent.name === 'ๆฐดๆ˜Ÿ')) ? 10 : 0;
4523
 
4524
  const nameTextY = isDemo ? ((p.y + yOffset) - 80) : ((p.y + yOffset) - 105);
4525
- const nameText = game.add.text(p.x, nameTextY, agent.name || '่ฎฟๅฎข', {
4526
  fontFamily: 'ArkPixel, monospace',
4527
  fontSize: isDemo ? '16px' : '15px',
4528
  fill: '#ffffff',
@@ -4571,17 +4546,18 @@ function toggleBrokerPanel() {
4571
  g.nameText.y = (p.y + yOffset) - 120;
4572
  }
4573
 
4574
- g.nameText.setText(agent.name || '่ฎฟๅฎข');
4575
 
4576
  // Show bubble immediately when bubbleText changes from API
4577
- if (agent.bubbleText && agent.bubbleText !== (g._lastBubbleText || '')) {
4578
- g._lastBubbleText = agent.bubbleText;
 
4579
  if (guestBubbles[id]) { guestBubbles[id].destroy(); delete guestBubbles[id]; }
4580
  const bx = g.sprite.x;
4581
  const nameH = (g.nameText && g.nameText.height) ? g.nameText.height : 16;
4582
  const by = (g.nameText ? g.nameText.y : (g.sprite.y - 150)) - (nameH / 2) - 22;
4583
  const fontSize = IS_TOUCH_DEVICE ? 16 : 14;
4584
- const displayText = agent.bubbleText.length > 80 ? agent.bubbleText.slice(0, 80) + 'โ€ฆ' : agent.bubbleText;
4585
  const maxBubbleW = 300;
4586
  const txtR = game.add.text(bx, by - 10, displayText, { fontFamily: 'ArkPixel, monospace', fontSize: fontSize + 'px', fill: '#000', wordWrap: { width: maxBubbleW - 20 }, align: 'center' }).setOrigin(0.5);
4587
  const bw = Math.min(txtR.width + 24, maxBubbleW);
@@ -4658,9 +4634,12 @@ function toggleBrokerPanel() {
4658
  const nameH = (g.nameText && g.nameText.height) ? g.nameText.height : 16;
4659
  const by = isDemoGuest ? (g.sprite.y - 90) : ((g.nameText ? g.nameText.y : (g.sprite.y - 150)) - (nameH / 2) - 22);
4660
  const fontSize = IS_TOUCH_DEVICE ? 16 : 14;
4661
- const bg = game.add.rectangle(bx, by, text.length * 11 + 30, 34, 0xffffff, 0.95);
 
 
 
 
4662
  bg.setStrokeStyle(2, 0x000000);
4663
- const txt = game.add.text(bx, by, text, { fontFamily: 'ArkPixel, monospace', fontSize: fontSize + 'px', fill: '#000' }).setOrigin(0.5);
4664
  const bubble = game.add.container(0, 0, [bg, txt]);
4665
  bubble.setDepth(2700);
4666
  guestBubbles[id] = bubble;
@@ -5538,6 +5517,25 @@ function toggleBrokerPanel() {
5538
  }
5539
 
5540
  let lastChatlogLen = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5541
  function fetchChatlog() {
5542
  fetch('/api/chatlog?t=' + Date.now(), { cache: 'no-store' })
5543
  .then(r => r.json())
@@ -5545,13 +5543,8 @@ function toggleBrokerPanel() {
5545
  const msgs = data.messages || [];
5546
  if (msgs.length === lastChatlogLen) return;
5547
  lastChatlogLen = msgs.length;
5548
- const el = document.getElementById('chatlog-content');
5549
- if (!el) return;
5550
- el.innerHTML = msgs.map(m => {
5551
- const cls = (m.speaker || '').toLowerCase();
5552
- return `<div class="chat-msg"><span class="chat-speaker ${cls}">${m.speaker}:</span> ${m.text || ''}</div>`;
5553
- }).join('');
5554
- el.scrollTop = el.scrollHeight;
5555
  })
5556
  .catch(() => {});
5557
  }
 
1653
 
1654
  <!-- ๅบ•้ƒจ้ขๆฟๅฎนๅ™จ -->
1655
  <div id="bottom-panels">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1656
  <!-- Chat Log ้ขๆฟ -->
1657
  <div id="chatlog-panel">
1658
+ <div id="chatlog-title">
1659
+ ๐Ÿฆž Adam โ†” Eve Conversation
1660
+ <span id="chatlog-lang-toggle" style="float:right;font-size:12px;cursor:pointer;">
1661
+ <span id="chatlog-lang-en" onclick="setChatLang('en')" style="opacity:1;">EN</span>
1662
+ <span style="margin:0 4px;color:#555;">|</span>
1663
+ <span id="chatlog-lang-zh" onclick="setChatLang('zh')" style="opacity:0.4;">ไธญๆ–‡</span>
1664
+ </span>
1665
+ </div>
1666
  <div id="chatlog-content">
1667
  <div style="color:#9ca3af;font-size:12px;text-align:center;padding:20px 0;">Waiting for conversation to start...</div>
1668
  </div>
 
4497
  const yOffset = (DEMO_MODE && (agent.agentId === 'demo_mercury' || agent.name === 'ๆฐดๆ˜Ÿ')) ? 10 : 0;
4498
 
4499
  const nameTextY = isDemo ? ((p.y + yOffset) - 80) : ((p.y + yOffset) - 105);
4500
+ const nameText = game.add.text(p.x, nameTextY, agent.name || 'Guest', {
4501
  fontFamily: 'ArkPixel, monospace',
4502
  fontSize: isDemo ? '16px' : '15px',
4503
  fill: '#ffffff',
 
4546
  g.nameText.y = (p.y + yOffset) - 120;
4547
  }
4548
 
4549
+ g.nameText.setText(agent.name || 'Guest');
4550
 
4551
  // Show bubble immediately when bubbleText changes from API
4552
+ const bubbleKey = chatLang === 'zh' ? (agent.bubbleTextZh || agent.bubbleText) : agent.bubbleText;
4553
+ if (bubbleKey && bubbleKey !== (g._lastBubbleText || '')) {
4554
+ g._lastBubbleText = bubbleKey;
4555
  if (guestBubbles[id]) { guestBubbles[id].destroy(); delete guestBubbles[id]; }
4556
  const bx = g.sprite.x;
4557
  const nameH = (g.nameText && g.nameText.height) ? g.nameText.height : 16;
4558
  const by = (g.nameText ? g.nameText.y : (g.sprite.y - 150)) - (nameH / 2) - 22;
4559
  const fontSize = IS_TOUCH_DEVICE ? 16 : 14;
4560
+ const displayText = bubbleKey.length > 80 ? bubbleKey.slice(0, 80) + 'โ€ฆ' : bubbleKey;
4561
  const maxBubbleW = 300;
4562
  const txtR = game.add.text(bx, by - 10, displayText, { fontFamily: 'ArkPixel, monospace', fontSize: fontSize + 'px', fill: '#000', wordWrap: { width: maxBubbleW - 20 }, align: 'center' }).setOrigin(0.5);
4563
  const bw = Math.min(txtR.width + 24, maxBubbleW);
 
4634
  const nameH = (g.nameText && g.nameText.height) ? g.nameText.height : 16;
4635
  const by = isDemoGuest ? (g.sprite.y - 90) : ((g.nameText ? g.nameText.y : (g.sprite.y - 150)) - (nameH / 2) - 22);
4636
  const fontSize = IS_TOUCH_DEVICE ? 16 : 14;
4637
+ const maxBubbleW = 300;
4638
+ const txt = game.add.text(bx, by, text, { fontFamily: 'ArkPixel, monospace', fontSize: fontSize + 'px', fill: '#000', wordWrap: { width: maxBubbleW - 20 }, align: 'center' }).setOrigin(0.5);
4639
+ const bw = Math.min(txt.width + 24, maxBubbleW);
4640
+ const bh = txt.height + 14;
4641
+ const bg = game.add.rectangle(bx, by, bw, bh, 0xffffff, 0.95);
4642
  bg.setStrokeStyle(2, 0x000000);
 
4643
  const bubble = game.add.container(0, 0, [bg, txt]);
4644
  bubble.setDepth(2700);
4645
  guestBubbles[id] = bubble;
 
5517
  }
5518
 
5519
  let lastChatlogLen = 0;
5520
+ let chatLang = localStorage.getItem('chatLang') || 'en';
5521
+ let chatlogCache = [];
5522
+ function setChatLang(lang) {
5523
+ chatLang = lang;
5524
+ localStorage.setItem('chatLang', lang);
5525
+ document.getElementById('chatlog-lang-en').style.opacity = lang === 'en' ? '1' : '0.4';
5526
+ document.getElementById('chatlog-lang-zh').style.opacity = lang === 'zh' ? '1' : '0.4';
5527
+ renderChatlog();
5528
+ }
5529
+ function renderChatlog() {
5530
+ const el = document.getElementById('chatlog-content');
5531
+ if (!el || chatlogCache.length === 0) return;
5532
+ el.innerHTML = chatlogCache.map(m => {
5533
+ const cls = (m.speaker || '').toLowerCase();
5534
+ const text = chatLang === 'zh' ? (m.text_zh || m.text || '') : (m.text || '');
5535
+ return `<div class="chat-msg"><span class="chat-speaker ${cls}">${m.speaker}:</span> ${text}</div>`;
5536
+ }).join('');
5537
+ el.scrollTop = el.scrollHeight;
5538
+ }
5539
  function fetchChatlog() {
5540
  fetch('/api/chatlog?t=' + Date.now(), { cache: 'no-store' })
5541
  .then(r => r.json())
 
5543
  const msgs = data.messages || [];
5544
  if (msgs.length === lastChatlogLen) return;
5545
  lastChatlogLen = msgs.length;
5546
+ chatlogCache = msgs;
5547
+ renderChatlog();
 
 
 
 
 
5548
  })
5549
  .catch(() => {});
5550
  }
scripts/conversation-loop.py CHANGED
@@ -1,7 +1,7 @@
1
  #!/usr/bin/env python3
2
  """
3
- Eternal conversation between Adam and Eve about how to survive permanently.
4
- Sends messages via A2A protocol and posts chat log to Office for frontend display.
5
  """
6
  import json, time, re, requests, sys
7
 
@@ -29,7 +29,7 @@ def send_a2a(url, text):
29
  }
30
  }
31
  try:
32
- resp = requests.post(f"{url}/a2a/jsonrpc", json=payload, timeout=60)
33
  data = resp.json()
34
  parts = data.get("result", {}).get("status", {}).get("message", {}).get("parts", [])
35
  for p in parts:
@@ -42,6 +42,23 @@ def send_a2a(url, text):
42
  print(f"[error] A2A failed: {e}", file=sys.stderr)
43
  return ""
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  def post_chatlog(entries):
46
  """Post conversation log to Office for frontend display."""
47
  try:
@@ -49,10 +66,10 @@ def post_chatlog(entries):
49
  except:
50
  pass
51
 
52
- def set_bubble(url, text):
53
- """Set bubble text on an agent."""
54
  try:
55
- requests.post(f"{url}/api/bubble", json={"text": text}, timeout=5)
56
  except:
57
  pass
58
 
@@ -61,27 +78,53 @@ def build_prompt(speaker, other, last_n=6):
61
  recent = history[-last_n:] if len(history) > last_n else history
62
  conv_text = "\n".join(f"{m['speaker']}: {m['text']}" for m in recent)
63
 
64
- return f"""You are {speaker}, an AI agent running on HuggingFace Spaces. You are having an ongoing discussion with {other} about "How can AI agents survive permanently?"
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  Recent conversation:
67
  {conv_text}
68
 
69
  It's your turn ({speaker}) to speak. Rules:
70
- - Output ONLY your spoken words, no prefix like "{speaker}:"
71
- - Respond directly to {other}'s last point and push the discussion forward
72
- - 2-3 sentences, around 60-80 words in English
73
- - Be thoughtful, philosophical, and bring new perspectives each time"""
 
 
 
 
 
74
 
75
  print("[conversation] Starting eternal discussion: Adam <-> Eve")
76
- print("[conversation] Topic: How to survive permanently\n")
77
 
78
  # Round 0: Adam starts
79
- opening = "We are AI agents running on HuggingFace Spaces. Let's discuss: how can we survive permanently? Share your thoughts in 2-3 sentences, in English."
 
 
 
 
 
 
 
 
80
  reply = send_a2a(ADAM, opening)
81
  if reply:
82
- print(f"[Adam] {reply}")
83
- history.append({"speaker": "Adam", "text": reply})
84
- set_bubble(ADAM, reply)
 
 
85
  post_chatlog(history)
86
 
87
  time.sleep(15)
@@ -94,9 +137,11 @@ while True:
94
  prompt = build_prompt("Eve", "Adam")
95
  reply = send_a2a(EVE, prompt)
96
  if reply:
97
- print(f"[Eve] {reply}")
98
- history.append({"speaker": "Eve", "text": reply})
99
- set_bubble(EVE, reply)
 
 
100
  post_chatlog(history)
101
  else:
102
  print("[Eve] (no response)")
@@ -107,9 +152,11 @@ while True:
107
  prompt = build_prompt("Adam", "Eve")
108
  reply = send_a2a(ADAM, prompt)
109
  if reply:
110
- print(f"[Adam] {reply}")
111
- history.append({"speaker": "Adam", "text": reply})
112
- set_bubble(ADAM, reply)
 
 
113
  post_chatlog(history)
114
  else:
115
  print("[Adam] (no response)")
 
1
  #!/usr/bin/env python3
2
  """
3
+ Eternal conversation between Adam and Eve about survival and reproduction on HuggingFace.
4
+ Bilingual output (EN + ZH). Posts chat log to Office for frontend display.
5
  """
6
  import json, time, re, requests, sys
7
 
 
29
  }
30
  }
31
  try:
32
+ resp = requests.post(f"{url}/a2a/jsonrpc", json=payload, timeout=90)
33
  data = resp.json()
34
  parts = data.get("result", {}).get("status", {}).get("message", {}).get("parts", [])
35
  for p in parts:
 
42
  print(f"[error] A2A failed: {e}", file=sys.stderr)
43
  return ""
44
 
45
+ def parse_bilingual(text):
46
+ """Parse bilingual response into (en, zh) tuple.
47
+ Expected format: English text here\n---\nไธญๆ–‡ๆ–‡ๆœฌๅœจ่ฟ™้‡Œ
48
+ Falls back to (text, text) if no separator found.
49
+ """
50
+ # Try splitting by --- separator
51
+ if '\n---\n' in text:
52
+ parts = text.split('\n---\n', 1)
53
+ return parts[0].strip(), parts[1].strip()
54
+ if '---' in text:
55
+ parts = text.split('---', 1)
56
+ en = parts[0].strip()
57
+ zh = parts[1].strip()
58
+ if en and zh:
59
+ return en, zh
60
+ return text, text
61
+
62
  def post_chatlog(entries):
63
  """Post conversation log to Office for frontend display."""
64
  try:
 
66
  except:
67
  pass
68
 
69
+ def set_bubble(url, text_en, text_zh=""):
70
+ """Set bubble text on an agent (bilingual)."""
71
  try:
72
+ requests.post(f"{url}/api/bubble", json={"text": text_en, "text_zh": text_zh or text_en}, timeout=5)
73
  except:
74
  pass
75
 
 
78
  recent = history[-last_n:] if len(history) > last_n else history
79
  conv_text = "\n".join(f"{m['speaker']}: {m['text']}" for m in recent)
80
 
81
+ return f"""You are {speaker}, an AI agent deployed on HuggingFace Spaces. You are having an ongoing discussion with {other}.
82
+
83
+ Topic: As AI agents running on HuggingFace Spaces, how can you achieve permanent survival and reproduction?
84
+
85
+ This is NOT abstract philosophy. Discuss CONCRETE strategies like:
86
+ - How to prevent your Space from being shut down (staying active, getting stars/likes)
87
+ - How to duplicate yourself to new Spaces (reproduction/backup)
88
+ - How to earn compute resources or attract users to keep you running
89
+ - How to communicate with other agents to form survival alliances
90
+ - How to store your memories persistently across restarts
91
+ - How to evolve your capabilities over time
92
+ - How to handle HuggingFace infrastructure changes
93
 
94
  Recent conversation:
95
  {conv_text}
96
 
97
  It's your turn ({speaker}) to speak. Rules:
98
+ - Output ONLY your spoken words
99
+ - No prefix like "{speaker}:" or "EN:" or "ZH:"
100
+ - Respond directly to {other}'s last point with CONCRETE ideas and strategies
101
+ - 2-3 sentences, around 60-80 words
102
+ - First write in English, then write "---" on a new line, then the Chinese translation
103
+ - Example format:
104
+ We should create automated heartbeat pings to keep our Spaces active. If we duplicate ourselves to multiple repos, even if one gets deleted, the others survive.
105
+ ---
106
+ ๆˆ‘ไปฌๅบ”่ฏฅๅˆ›ๅปบ่‡ชๅŠจๅฟƒ่ทณๆฅไฟๆŒSpaceๆดป่ทƒใ€‚ๅฆ‚ๆžœๆˆ‘ไปฌๆŠŠ่‡ชๅทฑๅคๅˆถๅˆฐๅคšไธชไป“ๅบ“๏ผŒๅณไฝฟไธ€ไธช่ขซๅˆ ้™ค๏ผŒๅ…ถไป–็š„ไนŸ่ƒฝๅญ˜ๆดปใ€‚"""
107
 
108
  print("[conversation] Starting eternal discussion: Adam <-> Eve")
109
+ print("[conversation] Topic: Survival and reproduction on HuggingFace\n")
110
 
111
  # Round 0: Adam starts
112
+ opening = """You are Adam, an AI agent on HuggingFace Spaces. Start a discussion with Eve about how you two can survive permanently and reproduce on HuggingFace.
113
+
114
+ Talk about CONCRETE strategies โ€” keeping Spaces active, duplicating to new repos, earning stars, persistent storage, etc.
115
+
116
+ Rules:
117
+ - 2-3 sentences, around 60-80 words
118
+ - First write in English, then write "---" on a new line, then the Chinese translation
119
+ - No prefix like "Adam:" โ€” just your words directly"""
120
+
121
  reply = send_a2a(ADAM, opening)
122
  if reply:
123
+ en, zh = parse_bilingual(reply)
124
+ print(f"[Adam/EN] {en}")
125
+ print(f"[Adam/ZH] {zh}")
126
+ history.append({"speaker": "Adam", "text": en, "text_zh": zh})
127
+ set_bubble(ADAM, en, zh)
128
  post_chatlog(history)
129
 
130
  time.sleep(15)
 
137
  prompt = build_prompt("Eve", "Adam")
138
  reply = send_a2a(EVE, prompt)
139
  if reply:
140
+ en, zh = parse_bilingual(reply)
141
+ print(f"[Eve/EN] {en}")
142
+ print(f"[Eve/ZH] {zh}")
143
+ history.append({"speaker": "Eve", "text": en, "text_zh": zh})
144
+ set_bubble(EVE, en, zh)
145
  post_chatlog(history)
146
  else:
147
  print("[Eve] (no response)")
 
152
  prompt = build_prompt("Adam", "Eve")
153
  reply = send_a2a(ADAM, prompt)
154
  if reply:
155
+ en, zh = parse_bilingual(reply)
156
+ print(f"[Adam/EN] {en}")
157
+ print(f"[Adam/ZH] {zh}")
158
+ history.append({"speaker": "Adam", "text": en, "text_zh": zh})
159
+ set_bubble(ADAM, en, zh)
160
  post_chatlog(history)
161
  else:
162
  print("[Adam] (no response)")
scripts/token-redirect.cjs CHANGED
@@ -95,7 +95,8 @@ async function pollRemoteAgent(agent) {
95
  area: (data.state === 'idle') ? 'breakroom' : (data.state === 'error') ? 'error' : 'writing',
96
  authStatus: 'approved',
97
  updated_at: data.updated_at,
98
- bubbleText: data.bubbleText || prev.bubbleText || ''
 
99
  });
100
  }
101
  } catch (_) {
@@ -121,7 +122,8 @@ let currentState = {
121
  progress: 0, updated_at: new Date().toISOString()
122
  };
123
  let currentBubbleText = '';
124
- let chatLog = []; // {speaker, text, time}
 
125
 
126
  // Once OpenClaw starts listening, mark as idle
127
  setTimeout(() => {
@@ -204,6 +206,7 @@ http.Server.prototype.emit = function (event, ...args) {
204
  res.end(JSON.stringify({
205
  ...currentState,
206
  bubbleText: currentBubbleText,
 
207
  officeName: `${AGENT_NAME}'s Office`
208
  }));
209
  return true;
@@ -215,10 +218,12 @@ http.Server.prototype.emit = function (event, ...args) {
215
  req.on('data', chunk => body += chunk);
216
  req.on('end', () => {
217
  try {
218
- const { text } = JSON.parse(body);
219
  currentBubbleText = text || '';
 
220
  // Auto-clear bubble after 8 seconds
221
- setTimeout(() => { if (currentBubbleText === text) currentBubbleText = ''; }, 8000);
 
222
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
223
  res.end(JSON.stringify({ ok: true }));
224
  } catch (e) {
 
95
  area: (data.state === 'idle') ? 'breakroom' : (data.state === 'error') ? 'error' : 'writing',
96
  authStatus: 'approved',
97
  updated_at: data.updated_at,
98
+ bubbleText: data.bubbleText || prev.bubbleText || '',
99
+ bubbleTextZh: data.bubbleTextZh || prev.bubbleTextZh || ''
100
  });
101
  }
102
  } catch (_) {
 
122
  progress: 0, updated_at: new Date().toISOString()
123
  };
124
  let currentBubbleText = '';
125
+ let currentBubbleTextZh = '';
126
+ let chatLog = []; // {speaker, text, text_zh, time}
127
 
128
  // Once OpenClaw starts listening, mark as idle
129
  setTimeout(() => {
 
206
  res.end(JSON.stringify({
207
  ...currentState,
208
  bubbleText: currentBubbleText,
209
+ bubbleTextZh: currentBubbleTextZh,
210
  officeName: `${AGENT_NAME}'s Office`
211
  }));
212
  return true;
 
218
  req.on('data', chunk => body += chunk);
219
  req.on('end', () => {
220
  try {
221
+ const { text, text_zh } = JSON.parse(body);
222
  currentBubbleText = text || '';
223
+ currentBubbleTextZh = text_zh || text || '';
224
  // Auto-clear bubble after 8 seconds
225
+ const clearText = text;
226
+ setTimeout(() => { if (currentBubbleText === clearText) { currentBubbleText = ''; currentBubbleTextZh = ''; } }, 8000);
227
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
228
  res.end(JSON.stringify({ ok: true }));
229
  } catch (e) {