@clowder-ai/companion 0.1.0-alpha.30 → 0.1.0-alpha.32

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/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "pluginId": "official.companion",
3
- "version": "0.1.0-alpha.30",
3
+ "version": "0.1.0-alpha.32",
4
4
  "contractVersion": "0.1.0",
5
5
  "name": "猫猫球",
6
6
  "icon": {
@@ -40,7 +40,7 @@
40
40
  "bridgeVersion": "1.6.0",
41
41
  "surface": {
42
42
  "entrypoint": "renderer/index.html",
43
- "integrity": "sha256-dR1cqLYEPsY28TJ6ureljiV5CP23wubn70G7pePPkNg="
43
+ "integrity": "sha256-0OpVp+p2y+xDJduVuzWZf48TNLBnWZZfq/TuGe0joyY="
44
44
  },
45
45
  "presentation": {
46
46
  "width": 300,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clowder-ai/companion",
3
- "version": "0.1.0-alpha.30",
3
+ "version": "0.1.0-alpha.32",
4
4
  "description": "The desktop voice body for your Clowder companion",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/plugin.yaml CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "pluginId": "official.companion",
3
- "version": "0.1.0-alpha.30",
3
+ "version": "0.1.0-alpha.32",
4
4
  "contractVersion": "0.1.0",
5
5
  "name": "猫猫球",
6
6
  "icon": {
@@ -40,7 +40,7 @@
40
40
  "bridgeVersion": "1.6.0",
41
41
  "surface": {
42
42
  "entrypoint": "renderer/index.html",
43
- "integrity": "sha256-dR1cqLYEPsY28TJ6ureljiV5CP23wubn70G7pePPkNg="
43
+ "integrity": "sha256-0OpVp+p2y+xDJduVuzWZf48TNLBnWZZfq/TuGe0joyY="
44
44
  },
45
45
  "presentation": {
46
46
  "width": 300,
@@ -1,4 +1,5 @@
1
1
  const ACTIONS = new Set(['dismiss', 'timeout', 'accept']);
2
+ export const APP_ASSIST_TIMEOUT_MS = 15_000;
2
3
 
3
4
  /**
4
5
  * Renderer projection only. The Host owns offer creation, expiry and clearing;
@@ -12,7 +13,7 @@ export class AppAssistController {
12
13
  accepted = () => {},
13
14
  scheduleTimeout = setTimeout,
14
15
  cancelTimeout = clearTimeout,
15
- timeoutMs = 15_000,
16
+ timeoutMs = APP_ASSIST_TIMEOUT_MS,
16
17
  }) {
17
18
  this.client = client;
18
19
  this.changed = changed;
@@ -0,0 +1,16 @@
1
+ import { APP_ASSIST_TIMEOUT_MS } from './app-assist-controller.mjs';
2
+
3
+ /** Projects the existing renderer deadline without owning or extending it. */
4
+ export function bindAppAssistCountdown(element, durationMs = APP_ASSIST_TIMEOUT_MS) {
5
+ let offerRef = null;
6
+ element.style.setProperty('--app-assist-duration', `${durationMs}ms`);
7
+ return offer => {
8
+ const nextRef = offer?.offerRef ?? null;
9
+ if (nextRef === offerRef) return;
10
+ offerRef = nextRef;
11
+ element.removeAttribute('data-active');
12
+ if (!nextRef) return;
13
+ void element.getBoundingClientRect?.();
14
+ element.setAttribute('data-active', 'true');
15
+ };
16
+ }
@@ -6,11 +6,11 @@ import { ReplyLetterView } from './reply-letter-view.mjs';
6
6
 
7
7
  /** Owns the chat panel's history, durable draft and reply-letter interaction. */
8
8
  export class ConversationSurface {
9
- constructor({ document, client, controls, bubble, transcript, getCallPhase,
9
+ constructor({ document, client, controls, bubble, transcript,
10
10
  onAccepted, retryLegacy, status, scheduleInterval = setInterval,
11
11
  cancelInterval = clearInterval }) {
12
12
  this.$ = id => document.getElementById(id);
13
- Object.assign(this, { client, controls, bubble, transcript, getCallPhase,
13
+ Object.assign(this, { client, controls, bubble, transcript,
14
14
  onAccepted, retryLegacy, status });
15
15
  this.scheduleInterval = (...args) => Reflect.apply(scheduleInterval, globalThis, args);
16
16
  this.cancelInterval = (...args) => Reflect.apply(cancelInterval, globalThis, args);
@@ -140,7 +140,6 @@ export class ConversationSurface {
140
140
  try {
141
141
  const page = await this.client.readConversationPage(before === undefined ? {} : { before });
142
142
  this.history.loadPage(page, { older });
143
- void this.controls.setAmbient(this.getCallPhase() === 'idle' ? 'none' : 'actions');
144
143
  this.$('chat-connection-status').textContent = page.status === 'stale'
145
144
  ? '聊天位置已变化 · 请重新往上翻' : '';
146
145
  } catch (error) {
@@ -48,6 +48,10 @@
48
48
  <p id="bubble-first" hidden></p><p id="bubble-second" hidden></p>
49
49
  </section>
50
50
  <section id="app-assist" class="floating app-assist" aria-label="应用旁的轻提示" data-candidate="true" hidden>
51
+ <svg id="app-assist-countdown" class="app-assist-countdown" viewBox="0 0 20 20" role="img" aria-label="提示将在约十五秒后自动收起">
52
+ <circle class="app-assist-countdown-track" cx="10" cy="10" r="7.5" pathLength="1"/>
53
+ <circle id="app-assist-countdown-progress" cx="10" cy="10" r="7.5" pathLength="1"/>
54
+ </svg>
51
55
  <p>你打开了 <strong id="app-assist-name"></strong>。要我一起看吗?</p>
52
56
  <div><button id="app-assist-dismiss" type="button">暂不</button><button id="app-assist-start" class="primary" type="button">打开通话控制</button></div>
53
57
  </section>
@@ -79,10 +79,19 @@ button:disabled { opacity: .45; cursor: default; }
79
79
  #bubble p + p { margin-top: 4px; }
80
80
  #bubble [data-role="user"] { color: var(--cafe-text-secondary); }
81
81
  .app-assist { width: 320px; padding: 12px; }
82
- .app-assist p { margin: 0; color: var(--desktop-body); font-size: 13px; line-height: 1.5; }
82
+ .app-assist p { margin: 0; padding-right: 22px; color: var(--desktop-body); font-size: 13px; line-height: 1.5; }
83
83
  .app-assist p strong { color: var(--desktop-ink); font-weight: 600; }
84
84
  .app-assist > div { display: flex; justify-content: flex-end; gap: 6px; margin-top: 9px; }
85
85
  .app-assist > div button { min-height: 30px; padding: 5px 10px; font-size: 12px; }
86
+ .app-assist-countdown { position: absolute; top: 10px; right: 10px; width: 16px; height: 16px; overflow: visible; transform: rotate(-90deg); }
87
+ .app-assist-countdown circle { fill: none; stroke-width: 2; }
88
+ .app-assist-countdown-track { stroke: var(--desktop-line-strong); }
89
+ #app-assist-countdown-progress { stroke: var(--desktop-muted); stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 0; }
90
+ .app-assist-countdown[data-active="true"] #app-assist-countdown-progress { animation: app-assist-expiry var(--app-assist-duration) linear forwards; }
91
+ @keyframes app-assist-expiry { to { stroke-dashoffset: 1; } }
92
+ @media (prefers-reduced-motion: reduce) {
93
+ .app-assist-countdown[data-active="true"] #app-assist-countdown-progress { animation-timing-function: steps(15, end); }
94
+ }
86
95
  #call-bubble { width: 320px; max-height: 80px; overflow: hidden; padding: 9px 11px; pointer-events: none; }
87
96
  #call-bubble:not(:has(p:not([hidden]))) { display: none; }
88
97
  #call-bubble p { margin: 0; display: flex; min-width: 0; overflow: hidden; white-space: nowrap; font-size: 12px; line-height: 1.45; }
@@ -24,6 +24,7 @@ import { ConversationSurface } from './conversation-surface.mjs';
24
24
  import { CallControlsPresentation } from './call-controls-presentation.mjs';
25
25
  import { CallDuration } from './call-duration.mjs';
26
26
  import { AppAssistController } from './app-assist-controller.mjs';
27
+ import { bindAppAssistCountdown } from './app-assist-countdown.mjs';
27
28
  const $ = id => document.getElementById(id);
28
29
  const label = (id, text) => { $(id).querySelector('.label').textContent = text; };
29
30
  const status = text => {
@@ -61,6 +62,7 @@ let previousControlPanel = 'none';
61
62
  const ambientPanel = active => active ? 'actions' : appAssistOffer ? 'app-assist' : 'none';
62
63
  const setTexts = (ids, text) => { for (const id of ids) $(id).textContent = text; };
63
64
  const setHidden = (ids, hidden) => { for (const id of ids) $(id).hidden = hidden; };
65
+ const renderAppAssistCountdown = bindAppAssistCountdown($('app-assist-countdown'));
64
66
  if (!window.clowderCompanion) {
65
67
  $('actions').hidden = false; $('status').hidden = false;
66
68
  status('请从 Clowder 的聊聊入口打开猫猫球');
@@ -144,6 +146,7 @@ if (!window.clowderCompanion) {
144
146
  changed: value => {
145
147
  appAssistOffer = value.offer;
146
148
  $('app-assist-name').textContent = appAssistOffer?.appName ?? '';
149
+ renderAppAssistCountdown(appAssistOffer);
147
150
  void controls.setAmbient(ambientPanel(previousPhase !== 'idle'));
148
151
  },
149
152
  accepted: () => {
@@ -301,7 +304,6 @@ if (!window.clowderCompanion) {
301
304
  $('app-assist-start').onclick = () => { void appAssist.settle('accept'); };
302
305
  conversationSurface = new ConversationSurface({
303
306
  document, client, controls, bubble, transcript,
304
- getCallPhase: () => conversation.phase,
305
307
  onAccepted: () => motion.signal('received'),
306
308
  retryLegacy: text => conversation.send(text),
307
309
  status,