@my-life-buddies/cli 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web/app.css CHANGED
@@ -228,7 +228,7 @@
228
228
  .workspace {
229
229
  min-height: calc(100dvh - 4.5rem);
230
230
  display: grid;
231
- grid-template-columns: minmax(0, 1fr) minmax(22rem, 27rem);
231
+ grid-template-columns: minmax(0, 1fr) minmax(26rem, 32rem);
232
232
  background: var(--color-paper);
233
233
  }
234
234
  }
@@ -365,16 +365,17 @@
365
365
  inset-block-start: 4.5rem;
366
366
  display: grid;
367
367
  grid-template-rows: auto minmax(0, 1fr) auto;
368
- padding: var(--space-xl) var(--space-lg) var(--space-md);
368
+ padding: var(--space-md) var(--space-md) var(--space-xs);
369
369
  border-inline-start: var(--rule-hair) solid var(--color-rule);
370
370
  background: var(--color-paper-soft);
371
371
  }
372
372
 
373
- .simulator-caption { min-height: var(--control-height); display: flex; align-items: center; justify-content: space-between; gap: var(--space-md); }
373
+ .simulator-actions { display: flex; gap: var(--space-xs); flex-wrap: wrap; }
374
+ .simulator-caption { min-height: var(--control-height); display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); flex-wrap: wrap; }
374
375
  .simulator-caption h2 { margin: 0; color: var(--color-ink-strong); font-size: var(--text-lg); font-weight: 600; letter-spacing: -0.02em; }
375
376
 
376
- .device-stage { min-height: 0; display: grid; place-items: center; padding-block: var(--space-md) var(--space-xs); container-type: size; }
377
- .phone { --phone-width: min(22rem, 90cqw, 45cqh); width: var(--phone-width); aspect-ratio: 415 / 843; container-type: inline-size; }
377
+ .device-stage { min-height: 0; display: grid; place-items: center; padding-block: var(--space-sm) 0; container-type: size; }
378
+ .phone { --phone-width: min(27rem, 96cqw, 48cqh); width: var(--phone-width); aspect-ratio: 415 / 843; container-type: inline-size; }
378
379
  .phone iphone-16-max { --width: var(--phone-width); display: block; width: 100%; filter: drop-shadow(var(--shadow-phone)); }
379
380
  .phone-screen { position: relative; width: 100%; height: 100%; display: grid; grid-template-rows: 10.6024cqw minmax(0, 1fr) auto; padding: 11.0843cqw 0 0; overflow: clip; background: var(--color-phone-paper); color: var(--color-phone-ink); font-family: var(--font-body); }
380
381
 
@@ -389,7 +390,8 @@
389
390
  .buddy-identity { min-width: 0; }
390
391
  .chat-navigation h3 { margin: 0; overflow: hidden; font-size: 3.3735cqw; font-weight: 600; line-height: 1.2; text-overflow: ellipsis; white-space: nowrap; }
391
392
 
392
- .chat-messages { min-height: 0; display: flex; flex-direction: column; overflow-y: auto; padding: 2.8916cqw 3.8554cqw 4.8193cqw; background: var(--color-phone-paper); color: var(--color-phone-ink); scrollbar-width: none; }
393
+ .chat-messages { min-height: 0; display: flex; flex-direction: column; overflow-y: auto; overscroll-behavior: contain; overflow-anchor: none; padding: 2.8916cqw 3.8554cqw 4.8193cqw; background: var(--color-phone-paper); color: var(--color-phone-ink); scrollbar-width: none; }
394
+ .latest-messages { position: absolute; z-index: 2; bottom: 19cqw; left: 50%; transform: translateX(-50%); white-space: nowrap; padding: 2cqw 3cqw; border-radius: 5cqw; background: var(--color-phone-paper); color: var(--color-phone-ink); border: 1px solid var(--color-rule); box-shadow: var(--shadow-whisper); font-size: 3cqw; }
393
395
  .chat-messages::-webkit-scrollbar { display: none; }
394
396
  .chat-empty { min-height: 100%; display: block; }
395
397
  .chat-date { align-self: center; margin: 0 0 2.8916cqw; color: var(--color-phone-muted); font-size: 2.6506cqw; }
@@ -457,10 +459,15 @@
457
459
 
458
460
  .debug-tabs button:active, .simulator-device:active, .button:active:not(:disabled), .icon-button:active { transform: translateY(1px); }
459
461
 
462
+ @media (max-width: 95rem) {
463
+ .overview-grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-lg); }
464
+ .environment-note { padding-inline-start: 0; border-inline-start: 0; }
465
+ }
466
+
460
467
  @media (max-width: 78rem) {
461
468
  .workspace { grid-template-columns: minmax(0, 1fr); }
462
- .simulator-area { min-height: 45rem; max-height: none; position: static; border-block-start: var(--rule-hair) solid var(--color-rule); border-inline-start: 0; }
463
- .phone { --phone-width: min(21rem, 84cqw, 42cqh); }
469
+ .simulator-area { min-height: 52rem; height: 90dvh; max-height: none; position: static; border-block-start: var(--rule-hair) solid var(--color-rule); border-inline-start: 0; }
470
+ .phone { --phone-width: min(27rem, 94cqw, 48cqh); }
464
471
  }
465
472
 
466
473
  @media (max-width: 58rem) {
package/dist/web/app.js CHANGED
@@ -9,6 +9,8 @@
9
9
  devControl: $("#dev-control"),
10
10
  errorBanner: $("#error-banner"),
11
11
  errorMessage: $("#error-message"),
12
+ resetConversation: $("#reset-conversation"),
13
+ latestMessages: $("#latest-messages"),
12
14
  pairDevice: $("#pair-device"),
13
15
  pairingPanel: $("#pairing-panel"),
14
16
  pairingBody: $("#pairing-body"),
@@ -63,6 +65,10 @@
63
65
  reportedFailureRevision: -1,
64
66
  polling: undefined,
65
67
  pollingBusy: false,
68
+ resetting: false,
69
+ resetUnavailable: false,
70
+ sending: false,
71
+ simulatorRequest: 0,
66
72
  toastTimer: undefined,
67
73
  };
68
74
 
@@ -230,7 +236,10 @@
230
236
  ? "正在输入…"
231
237
  : "正在思考…"
232
238
  : "DEV 在线";
233
- elements.messageInput.disabled = phase !== "ready" || Boolean(active);
239
+ elements.resetConversation.disabled = phase !== "ready" || Boolean(active) || state.resetting || state.sending || state.resetUnavailable;
240
+ elements.resetConversation.title = state.resetUnavailable ? "当前平台暂不支持重开会话" : active ? "请等待当前回复完成后再清空" : "结束当前开发版对话,开始一段全新会话";
241
+ elements.devControl.disabled ||= state.resetting;
242
+ elements.messageInput.disabled = phase !== "ready" || Boolean(active) || state.resetting || state.sending;
234
243
  elements.sendMessage.hidden = Boolean(active);
235
244
  updateSendButton();
236
245
  syncPolling();
@@ -424,7 +433,20 @@
424
433
  }
425
434
  }
426
435
 
436
+ let messageSignature = "";
437
+ let followMessages = true;
438
+
439
+ function followLatestMessages() {
440
+ followMessages = true;
441
+ elements.chatMessages.scrollTop = elements.chatMessages.scrollHeight;
442
+ elements.latestMessages.hidden = true;
443
+ }
444
+
427
445
  function renderMessages() {
446
+ const signature = JSON.stringify([state.simulator.messages, Boolean(state.simulator.activeRun)]);
447
+ if (signature === messageSignature) return;
448
+ messageSignature = signature;
449
+ const previousTop = elements.chatMessages.scrollTop;
428
450
  clear(elements.chatMessages);
429
451
  const messages = state.simulator.messages || [];
430
452
  if (!messages.length && !state.simulator.activeRun) {
@@ -451,9 +473,11 @@
451
473
  row.append(messageAvatar("assistant"), indicator);
452
474
  elements.chatMessages.append(row);
453
475
  }
454
- window.requestAnimationFrame(() => {
455
- elements.chatMessages.scrollTop = elements.chatMessages.scrollHeight;
456
- });
476
+ if (followMessages) followLatestMessages();
477
+ else {
478
+ elements.chatMessages.scrollTop = previousTop;
479
+ elements.latestMessages.hidden = false;
480
+ }
457
481
  }
458
482
 
459
483
  function renderDebug() {
@@ -470,17 +494,21 @@
470
494
  }
471
495
 
472
496
  async function refreshDebug() {
497
+ const request = state.simulatorRequest;
473
498
  const value = await api("/api/debug");
499
+ if (state.resetting || request !== state.simulatorRequest) return;
474
500
  state.debug = value.debug;
475
501
  renderDebug();
476
502
  }
477
503
 
478
504
  async function refreshSimulator() {
479
505
  if (state.dev.phase !== "ready" || state.pollingBusy) return;
506
+ if (state.resetting) return;
507
+ const request = state.simulatorRequest;
480
508
  state.pollingBusy = true;
481
509
  try {
482
510
  const value = await api("/api/simulator/state");
483
- state.simulator = value.state;
511
+ if (state.resetting || request !== state.simulatorRequest || !applySimulatorState(value.state)) return;
484
512
  renderDev();
485
513
  renderMessages();
486
514
  renderContext();
@@ -517,7 +545,7 @@
517
545
  }
518
546
 
519
547
  function updateSendButton() {
520
- const ready = state.dev.phase === "ready" && !state.simulator.activeRun;
548
+ const ready = state.dev.phase === "ready" && !state.simulator.activeRun && !state.resetting && !state.sending;
521
549
  elements.sendMessage.disabled = !ready || !elements.messageInput.value.trim();
522
550
  }
523
551
 
@@ -526,7 +554,10 @@
526
554
  async function sendMessage(event) {
527
555
  event.preventDefault();
528
556
  const text = elements.messageInput.value;
529
- if (!text.trim() || state.dev.phase !== "ready" || state.simulator.activeRun) return;
557
+ if (!text.trim() || state.dev.phase !== "ready" || state.simulator.activeRun || state.resetting || state.sending) return;
558
+ state.sending = true;
559
+ followLatestMessages();
560
+ renderDev();
530
561
  elements.messageInput.disabled = true;
531
562
  elements.sendMessage.disabled = true;
532
563
  // Keep the logical message identity after an unknown outcome; retries are user initiated.
@@ -550,6 +581,8 @@
550
581
  await refreshSimulator();
551
582
  } catch (cause) {
552
583
  showError(cause);
584
+ } finally {
585
+ state.sending = false;
553
586
  renderDev();
554
587
  }
555
588
  }
@@ -626,6 +659,43 @@
626
659
  }
627
660
  }
628
661
 
662
+ function applySimulatorState(next) {
663
+ if ((next.conversationRevision || 0) < (state.simulator.conversationRevision || 0)) return false;
664
+ state.simulator = next;
665
+ return true;
666
+ }
667
+
668
+ async function resetConversation() {
669
+ if (elements.resetConversation.disabled) return;
670
+ if (!window.confirm("清空当前对话并重新开始?\n这会结束当前开发版会话,重置聊天上下文、记忆和该会话的主动服务。旧记录仅在服务端留档;搭子资料和工程文件会保留。")) return;
671
+ state.resetting = true;
672
+ state.simulatorRequest++;
673
+ elements.resetConversation.textContent = "正在清空…";
674
+ renderDev();
675
+ try {
676
+ const value = await api("/api/simulator/reset", { method: "POST", body: JSON.stringify({ confirm: true }) });
677
+ applySimulatorState(value.state);
678
+ applyDevState(value.dev);
679
+ state.debug = value.debug;
680
+ state.selectedRunId = "";
681
+ state.followLatestRun = true;
682
+ pendingSend = undefined;
683
+ elements.messageInput.value = "";
684
+ followMessages = true;
685
+ renderAll();
686
+ toast("对话已清空,可以重新开始");
687
+ } catch (cause) {
688
+ if (cause?.code === "RESET_UNSUPPORTED") state.resetUnavailable = true;
689
+ showError(cause);
690
+ } finally {
691
+ state.resetting = false;
692
+ elements.resetConversation.textContent = "清空对话";
693
+ renderDev();
694
+ resizeComposer();
695
+ await refreshSimulator();
696
+ }
697
+ }
698
+
629
699
  function connectEvents() {
630
700
  const events = new EventSource("/api/events");
631
701
  events.addEventListener("dev.state", (event) => {
@@ -638,7 +708,7 @@
638
708
  events.addEventListener("simulator.state", (event) => {
639
709
  try {
640
710
  const value = JSON.parse(event.data);
641
- state.simulator = value.state;
711
+ if (!applySimulatorState(value.state)) return;
642
712
  state.debug = value.debug;
643
713
  renderDev();
644
714
  renderDebug();
@@ -671,6 +741,13 @@
671
741
  }
672
742
  }
673
743
 
744
+ elements.resetConversation.addEventListener("click", () => void resetConversation());
745
+ elements.latestMessages.addEventListener("click", followLatestMessages);
746
+ elements.chatMessages.addEventListener("scroll", () => {
747
+ const chat = elements.chatMessages;
748
+ followMessages = chat.scrollHeight - chat.clientHeight - chat.scrollTop <= 32;
749
+ elements.latestMessages.hidden = followMessages;
750
+ }, { passive: true });
674
751
  elements.devControl.addEventListener("click", () => void controlDev());
675
752
  elements.composer.addEventListener("submit", (event) => void sendMessage(event));
676
753
  elements.messageInput.addEventListener("input", resizeComposer);
@@ -178,10 +178,13 @@
178
178
  <section class="simulator-area" aria-label="搭搭 App 模拟器">
179
179
  <div class="simulator-caption">
180
180
  <h2>模拟私聊场景</h2>
181
+ <div class="simulator-actions">
182
+ <button class="simulator-device" id="reset-conversation" type="button" disabled>清空对话</button>
181
183
  <button class="simulator-device" id="pair-device" type="button" aria-expanded="false" aria-controls="pairing-panel">
182
184
  <svg aria-hidden="true" viewBox="0 0 24 24"><path d="M8 2.75h8A2.25 2.25 0 0 1 18.25 5v14A2.25 2.25 0 0 1 16 21.25H8A2.25 2.25 0 0 1 5.75 19V5A2.25 2.25 0 0 1 8 2.75Z"/><path d="M10 18.25h4"/></svg>
183
185
  <span>真机预览</span>
184
186
  </button>
187
+ </div>
185
188
  </div>
186
189
 
187
190
  <div class="device-stage">
@@ -207,6 +210,7 @@
207
210
  <div class="chat-empty" id="chat-empty" aria-label="暂无消息"></div>
208
211
  </div>
209
212
 
213
+ <button id="latest-messages" class="latest-messages" type="button" hidden>回到最新消息 ↓</button>
210
214
  <form class="composer" id="composer">
211
215
  <button class="composer-tool composer-add" type="button" aria-label="添加内容(预览中不可用)" disabled tabindex="-1">
212
216
  <svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@my-life-buddies/cli",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "搭搭 Developer Platform 命令行工具",
6
6
  "publishConfig": {
@@ -39,7 +39,7 @@
39
39
  "ws": "8.21.3"
40
40
  },
41
41
  "devDependencies": {
42
- "@buddy/cli-core": "0.1.2",
43
- "@buddy/preview": "0.1.2"
42
+ "@buddy/cli-core": "0.2.0",
43
+ "@buddy/preview": "0.2.0"
44
44
  }
45
45
  }