@clowder-ai/companion 0.1.0-alpha.27 → 0.1.0-alpha.28
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 +2 -2
- package/package.json +1 -1
- package/plugin.yaml +2 -2
- package/renderer/index.html +15 -6
- package/renderer/reply-letter-controller.mjs +18 -0
- package/renderer/reply-letter-view.mjs +23 -10
- package/renderer/style.css +21 -13
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"pluginId": "official.companion",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.28",
|
|
4
4
|
"contractVersion": "0.1.0",
|
|
5
5
|
"name": "猫猫球",
|
|
6
6
|
"icon": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"bridgeVersion": "1.5.0",
|
|
41
41
|
"surface": {
|
|
42
42
|
"entrypoint": "renderer/index.html",
|
|
43
|
-
"integrity": "sha256-
|
|
43
|
+
"integrity": "sha256-l4CrAxOELZjC8DrB8BYv8K2pFg1ggCCgarOXhD/tfZ8="
|
|
44
44
|
},
|
|
45
45
|
"presentation": {
|
|
46
46
|
"width": 300,
|
package/package.json
CHANGED
package/plugin.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"pluginId": "official.companion",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.28",
|
|
4
4
|
"contractVersion": "0.1.0",
|
|
5
5
|
"name": "猫猫球",
|
|
6
6
|
"icon": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"bridgeVersion": "1.5.0",
|
|
41
41
|
"surface": {
|
|
42
42
|
"entrypoint": "renderer/index.html",
|
|
43
|
-
"integrity": "sha256-
|
|
43
|
+
"integrity": "sha256-l4CrAxOELZjC8DrB8BYv8K2pFg1ggCCgarOXhD/tfZ8="
|
|
44
44
|
},
|
|
45
45
|
"presentation": {
|
|
46
46
|
"width": 300,
|
package/renderer/index.html
CHANGED
|
@@ -118,26 +118,35 @@
|
|
|
118
118
|
<div class="call-context-actions call-context-inline-actions"><button id="chat-mic" data-action="mute">静音</button><button id="chat-share" data-action="share"><span class="label">共享画面</span></button><button data-action="stop" class="destructive compact">挂断</button></div>
|
|
119
119
|
<div id="chat-share-context" class="call-share-line" hidden><svg class="icon" aria-hidden="true"><use href="#i-screen"/></svg><span><strong id="chat-share-target"></strong><small>仅共享此窗口</small></span></div>
|
|
120
120
|
</div>
|
|
121
|
-
<header id="chat-history-header"><strong id="chat-name">和猫猫写几句</strong
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
<header id="chat-history-header"><strong id="chat-name">和猫猫写几句</strong>
|
|
122
|
+
<button id="chat-history-dismiss" data-action="dismiss" aria-label="收起文字">收起</button>
|
|
123
|
+
</header>
|
|
124
|
+
<div id="chat-transcript-shell" class="chat-transcript-shell" hidden>
|
|
125
|
+
<div id="chat-transcript" role="log" aria-live="polite"><p class="empty">想说什么,就写在这里。</p></div>
|
|
126
|
+
<button id="reply-history-close" class="reply-history-close" data-action="dismiss" aria-label="关闭历史" title="关闭历史" hidden>
|
|
127
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 4 8 8m0-8-8 8"/></svg>
|
|
128
|
+
</button>
|
|
129
|
+
<button id="reply-return-latest" class="reply-return-latest" type="button" hidden>回到最新 ↓</button>
|
|
130
|
+
</div>
|
|
124
131
|
<div id="reply-sent" class="reply-sent" tabindex="-1" hidden>
|
|
125
132
|
<svg viewBox="0 0 36 27" aria-hidden="true"><rect x="1" y="1" width="34" height="25" rx="4"/><path d="M2.5 3.5 18 15 33.5 3.5"/></svg>
|
|
126
|
-
<span><strong id="reply-sent-status">已发送</strong><small id="reply-letter-text"></small></span>
|
|
133
|
+
<span><strong id="reply-sent-status">已发送</strong><small id="reply-letter-text" hidden></small></span>
|
|
127
134
|
</div>
|
|
128
135
|
<article id="reply-card" class="reply-card" hidden tabindex="-1">
|
|
129
136
|
<div class="reply-head"><span id="reply-input-text" class="reply-input-text"></span>
|
|
130
137
|
<button id="reply-retain" class="reply-ring" type="button" aria-label="留住回复" title="留住回复">
|
|
131
138
|
<svg viewBox="0 0 16 16" aria-hidden="true"><circle class="reply-ring-track" cx="8" cy="8" r="6.5"/><circle class="reply-ring-progress" cx="8" cy="8" r="6.5"/></svg>
|
|
132
139
|
</button>
|
|
133
|
-
<button id="reply-close" class="reply-close" type="button" aria-label="关闭回复" title="关闭回复" hidden
|
|
140
|
+
<button id="reply-close" class="reply-close" type="button" aria-label="关闭回复" title="关闭回复" hidden>
|
|
141
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 4 8 8m0-8-8 8"/></svg>
|
|
142
|
+
</button>
|
|
134
143
|
</div>
|
|
135
144
|
<div id="reply-body" class="reply-body"></div>
|
|
136
145
|
<small id="reply-body-status" class="reply-body-status" role="status"></small>
|
|
137
146
|
<button id="reply-history-open" class="reply-history-open" type="button">往上翻聊天记录</button>
|
|
138
147
|
</article>
|
|
139
148
|
<form id="compose"><textarea id="message" aria-label="给猫猫输入消息" placeholder="输入消息…" rows="1" maxlength="8000"></textarea><button type="submit" class="primary">发送</button></form>
|
|
140
|
-
<div class="chat-feedback"><small id="chat-status" role="status"></small><button id="reply-retry" type="button" hidden>↻ 重试</button></div>
|
|
149
|
+
<div id="chat-feedback" class="chat-feedback" hidden><small id="chat-status" role="status"></small><button id="reply-retry" type="button" hidden>↻ 重试</button></div>
|
|
141
150
|
<small id="chat-connection-status" role="status"></small>
|
|
142
151
|
<small id="chat-scope"><span id="connection-scope">语音与文字共用同一段聊天</span> <button id="history">在 Cat Café 中查看完整聊天 ↗</button></small>
|
|
143
152
|
</section>
|
|
@@ -25,6 +25,7 @@ export class ReplyLetterController {
|
|
|
25
25
|
this.reply = null;
|
|
26
26
|
this.retained = false;
|
|
27
27
|
this.hasNewer = false;
|
|
28
|
+
this.historyPresentation = null;
|
|
28
29
|
this.selectedMessageId = null;
|
|
29
30
|
this.pauseReasons = new Set();
|
|
30
31
|
this.requestedContinuations = new Set();
|
|
@@ -95,6 +96,7 @@ export class ReplyLetterController {
|
|
|
95
96
|
this.mode = 'compose';
|
|
96
97
|
this.retained = false;
|
|
97
98
|
this.hasNewer = false;
|
|
99
|
+
this.historyPresentation = null;
|
|
98
100
|
this.selectedMessageId = null;
|
|
99
101
|
this.pauseReasons.clear();
|
|
100
102
|
this.emit();
|
|
@@ -164,6 +166,7 @@ export class ReplyLetterController {
|
|
|
164
166
|
this.reply = presentationPair.reply;
|
|
165
167
|
this.selectedMessageId = presentationPair.replyId;
|
|
166
168
|
this.mode = 'reply';
|
|
169
|
+
this.historyPresentation = null;
|
|
167
170
|
if (changedReply) {
|
|
168
171
|
this.retained = false;
|
|
169
172
|
this.pauseReasons.clear();
|
|
@@ -234,6 +237,7 @@ export class ReplyLetterController {
|
|
|
234
237
|
this.stopTimer();
|
|
235
238
|
this.reply = null;
|
|
236
239
|
this.retained = false;
|
|
240
|
+
this.historyPresentation = null;
|
|
237
241
|
this.selectedMessageId = null;
|
|
238
242
|
this.letter = [...this.pending.values()].at(-1) ?? null;
|
|
239
243
|
this.mode = this.letter ? 'sent' : 'idle';
|
|
@@ -258,6 +262,7 @@ export class ReplyLetterController {
|
|
|
258
262
|
this.stopTimer();
|
|
259
263
|
this.reply = null;
|
|
260
264
|
this.retained = false;
|
|
265
|
+
this.historyPresentation = null;
|
|
261
266
|
this.selectedMessageId = null;
|
|
262
267
|
this.letter = restored ?? [...this.pending.values()].at(-1) ?? null;
|
|
263
268
|
this.mode = this.letter ? 'sent' : 'idle';
|
|
@@ -275,6 +280,7 @@ export class ReplyLetterController {
|
|
|
275
280
|
this.reply = null;
|
|
276
281
|
this.retained = false;
|
|
277
282
|
this.hasNewer = false;
|
|
283
|
+
this.historyPresentation = null;
|
|
278
284
|
this.selectedMessageId = null;
|
|
279
285
|
this.requestedContinuations.clear();
|
|
280
286
|
this.dismissedReplies.clear();
|
|
@@ -287,6 +293,17 @@ export class ReplyLetterController {
|
|
|
287
293
|
this.stopTimer();
|
|
288
294
|
this.mode = 'history';
|
|
289
295
|
this.retained = true;
|
|
296
|
+
this.historyPresentation = 'conversation';
|
|
297
|
+
this.selectedMessageId = messageId;
|
|
298
|
+
this.hasNewer = false;
|
|
299
|
+
this.emit();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
openReplyHistory(messageId = newestHistoryAnchor(this.selectedMessageId, this.settled, this.history)) {
|
|
303
|
+
this.stopTimer();
|
|
304
|
+
this.mode = 'history';
|
|
305
|
+
this.retained = true;
|
|
306
|
+
this.historyPresentation = 'reply';
|
|
290
307
|
this.selectedMessageId = messageId;
|
|
291
308
|
this.hasNewer = false;
|
|
292
309
|
this.emit();
|
|
@@ -325,6 +342,7 @@ export class ReplyLetterController {
|
|
|
325
342
|
letter: this.letter,
|
|
326
343
|
reply: this.reply,
|
|
327
344
|
retained: this.retained,
|
|
345
|
+
historyPresentation: this.mode === 'history' ? this.historyPresentation : null,
|
|
328
346
|
selectedMessageId: this.selectedMessageId,
|
|
329
347
|
hasNewer: this.hasNewer,
|
|
330
348
|
blockedReason: this.blockedReason,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const phaseLabel = phase => ({
|
|
2
2
|
processing: '正在回复',
|
|
3
|
-
failed: '回复失败
|
|
4
|
-
interrupted: '回复中断
|
|
5
|
-
canceled: '回复已取消
|
|
6
|
-
withdrawn: '回复已撤回
|
|
3
|
+
failed: '回复失败',
|
|
4
|
+
interrupted: '回复中断',
|
|
5
|
+
canceled: '回复已取消',
|
|
6
|
+
withdrawn: '回复已撤回',
|
|
7
7
|
}[phase] ?? '已发送');
|
|
8
8
|
|
|
9
9
|
const draftStatus = settlement => ({
|
|
@@ -52,28 +52,38 @@ export class ReplyLetterView {
|
|
|
52
52
|
card.addEventListener('pointercancel', () => this.controller.resume('pointer'));
|
|
53
53
|
this.$('reply-retain').onclick = () => this.controller.pin();
|
|
54
54
|
this.$('reply-close').onclick = () => this.controller.close();
|
|
55
|
-
this.$('reply-history-open').onclick = () => this.controller.
|
|
55
|
+
this.$('reply-history-open').onclick = () => this.controller.openReplyHistory();
|
|
56
56
|
this.$('reply-return-latest').onclick = () => this.controller.returnLatest();
|
|
57
57
|
this.$('reply-retry').onclick = () => { if (this.value?.draft) this.retry(this.value.draft); };
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
render(value) {
|
|
61
61
|
this.value = value;
|
|
62
|
-
const compose = value.mode === 'compose' || value.mode === 'history';
|
|
63
62
|
const history = value.mode === 'history';
|
|
63
|
+
const replyHistory = history && value.historyPresentation === 'reply';
|
|
64
|
+
const conversationHistory = history && !replyHistory;
|
|
65
|
+
const compose = value.mode === 'compose' || conversationHistory;
|
|
64
66
|
this.$('compose').hidden = !compose;
|
|
65
|
-
this.$('chat-history-header').hidden = !
|
|
67
|
+
this.$('chat-history-header').hidden = !conversationHistory;
|
|
68
|
+
this.$('chat-name').hidden = replyHistory;
|
|
69
|
+
this.$('chat-history-dismiss').hidden = replyHistory;
|
|
70
|
+
this.$('reply-history-close').hidden = !replyHistory;
|
|
71
|
+
this.$('chat-transcript-shell').hidden = !history;
|
|
72
|
+
this.$('chat-transcript-shell').dataset.hasNewer = String(history && value.hasNewer);
|
|
66
73
|
this.$('chat-transcript').hidden = !history;
|
|
67
|
-
this.$('chat-scope').hidden = !
|
|
74
|
+
this.$('chat-scope').hidden = !conversationHistory;
|
|
75
|
+
this.$('chat-connection-status').hidden = replyHistory;
|
|
68
76
|
this.$('reply-sent').hidden = value.mode !== 'sent';
|
|
69
77
|
this.$('reply-card').hidden = value.mode !== 'reply';
|
|
70
78
|
this.$('reply-return-latest').hidden = !history || !value.hasNewer;
|
|
71
79
|
|
|
72
80
|
const retrying = value.mode === 'compose' && value.draft?.settlement === 'unconfirmed';
|
|
73
81
|
this.$('reply-retry').hidden = !retrying;
|
|
74
|
-
|
|
82
|
+
const feedback = value.draft ? draftStatus(value.draft.settlement)
|
|
75
83
|
: value.draftNotice ? draftStatus(value.draftNotice)
|
|
76
84
|
: value.blockedReason === 'permission_required' ? '聊天权限已撤销' : '';
|
|
85
|
+
this.$('chat-status').textContent = feedback;
|
|
86
|
+
this.$('chat-feedback').hidden = !compose || (!feedback && !retrying);
|
|
77
87
|
if (value.draft) {
|
|
78
88
|
const input = this.$('message');
|
|
79
89
|
if (!input.value || input.value === this.lastRenderedDraft) input.value = value.draft.text;
|
|
@@ -81,7 +91,8 @@ export class ReplyLetterView {
|
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
if (value.letter) {
|
|
84
|
-
this.$('reply-letter-text').textContent =
|
|
94
|
+
this.$('reply-letter-text').textContent = '';
|
|
95
|
+
this.$('reply-letter-text').hidden = true;
|
|
85
96
|
this.$('reply-input-text').textContent = value.letter.text;
|
|
86
97
|
this.$('reply-sent-status').textContent = phaseLabel(value.letter.responsePhase);
|
|
87
98
|
}
|
|
@@ -99,5 +110,7 @@ export class ReplyLetterView {
|
|
|
99
110
|
this.$('reply-retain').dataset.running = String(canRetain && value.timer.running);
|
|
100
111
|
this.$('reply-retain').style.setProperty('--reply-visible-ms', `${value.timer.remainingMs}ms`);
|
|
101
112
|
this.$('chat').dataset.replyMode = value.mode;
|
|
113
|
+
this.$('chat').dataset.historyPresentation = history
|
|
114
|
+
? value.historyPresentation ?? 'conversation' : '';
|
|
102
115
|
}
|
|
103
116
|
}
|
package/renderer/style.css
CHANGED
|
@@ -188,11 +188,16 @@ button:disabled { opacity: .45; cursor: default; }
|
|
|
188
188
|
#work-notice { margin: 0; padding: 3px 11px 7px; color: var(--desktop-muted); font-size: 10px; }
|
|
189
189
|
#chat { width: 400px; max-height: 500px; display: flex; flex-direction: column; overflow: hidden; }
|
|
190
190
|
#chat[data-reply-mode="compose"] { width: 340px; }
|
|
191
|
-
#chat[data-reply-mode="sent"] { width:
|
|
191
|
+
#chat[data-reply-mode="sent"] { width: max-content; border: 0; background: transparent; box-shadow: none; overflow: visible; }
|
|
192
192
|
#chat[data-reply-mode="reply"] { width: 370px; }
|
|
193
|
+
#chat[data-reply-mode="history"][data-history-presentation="reply"] { width: 370px; }
|
|
193
194
|
#chat-history-header { padding: 7px 9px 7px 15px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--cafe-border-subtle); }
|
|
194
195
|
#chat-history-header button { font-size: 12px; color: var(--cafe-text-secondary); }
|
|
195
|
-
|
|
196
|
+
.chat-transcript-shell { position: relative; min-height: 0; flex: 1 1 230px; }
|
|
197
|
+
#chat[data-history-presentation="reply"] .chat-transcript-shell { flex-basis: 480px; }
|
|
198
|
+
#chat-transcript { height: 100%; overflow: auto; padding: 14px; line-height: 1.7; font-size: 13px; }
|
|
199
|
+
.chat-transcript-shell[data-has-newer="true"] #chat-transcript { padding-bottom: 56px; }
|
|
200
|
+
#chat[data-history-presentation="reply"] #chat-transcript { padding-top: 44px; }
|
|
196
201
|
#chat-transcript p { margin: 0 0 10px; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
197
202
|
#chat-transcript p:not(.empty) { width: fit-content; max-width: 92%; padding: 7px 10px; border-radius: 9px; }
|
|
198
203
|
#chat-transcript p.user { margin-left: auto; background: var(--desktop-rail); }
|
|
@@ -201,32 +206,35 @@ button:disabled { opacity: .45; cursor: default; }
|
|
|
201
206
|
#compose { display: flex; align-items: center; padding: 10px; gap: 8px; border-top: 1px solid var(--cafe-border-subtle); }
|
|
202
207
|
#chat[data-reply-mode="compose"] #compose { border: 0; }
|
|
203
208
|
#message { resize: none; border: 1px solid var(--desktop-line-strong); border-radius: 8px; min-width: 0; height: 34px; flex: 1; background: var(--desktop-paper); padding: 6px 9px; color: inherit; line-height: 1.5; }
|
|
209
|
+
#message:focus-visible { border-color: transparent; outline: 2px solid var(--cafe-accent); outline-offset: -2px; }
|
|
204
210
|
.chat-feedback { min-height: 0; display: flex; align-items: center; gap: 8px; padding: 0 12px 8px; color: #8f4730; }
|
|
205
211
|
.chat-feedback small { flex: 1; font-size: 11px; }
|
|
206
212
|
.chat-feedback button { color: #8f4730; font-size: 11px; }
|
|
207
213
|
#chat-connection-status, #chat-scope { color: var(--cafe-text-muted); font-size: 10px; padding: 0 14px 10px; }
|
|
208
|
-
|
|
209
|
-
.reply-sent
|
|
214
|
+
#chat-connection-status:empty { display: none; }
|
|
215
|
+
.reply-sent { align-items: center; gap: 5px; background: transparent; padding: 0; }
|
|
216
|
+
.reply-sent:not([hidden]) { display: flex; flex-direction: column; }
|
|
210
217
|
.reply-sent > svg { width: 36px; height: 27px; flex: none; overflow: visible; }
|
|
211
218
|
.reply-sent > svg rect { fill: var(--desktop-paper); stroke: var(--desktop-ink); stroke-width: 1.5; }
|
|
212
219
|
.reply-sent > svg path { fill: none; stroke: var(--desktop-ink); stroke-width: 1.5; stroke-linejoin: round; }
|
|
213
|
-
.reply-sent > span { min-width: 0;
|
|
214
|
-
.reply-sent strong { font-size:
|
|
215
|
-
.reply-sent small {
|
|
220
|
+
.reply-sent > span { min-width: 0; }
|
|
221
|
+
.reply-sent strong { display: block; border-radius: 999px; background: var(--desktop-ink); color: var(--desktop-paper); padding: 3px 8px; font-size: 10px; font-weight: 500; white-space: nowrap; }
|
|
222
|
+
.reply-sent small { display: none; }
|
|
216
223
|
.reply-card { position: relative; max-height: 500px; overflow-y: auto; padding: 12px 14px 10px; }
|
|
217
224
|
.reply-head { display: flex; align-items: flex-start; gap: 8px; }
|
|
218
225
|
.reply-input-text { min-width: 0; flex: 1; color: var(--desktop-muted); font-size: 12px; line-height: 1.45; overflow-wrap: anywhere; }
|
|
219
|
-
.reply-ring, .reply-close { width: 24px; height: 24px; flex: none; display: grid; place-items: center; padding: 0; color: var(--desktop-muted); }
|
|
226
|
+
#chat .reply-ring, #chat .reply-close, #chat .reply-history-close { width: 24px; height: 24px; flex: none; display: grid; place-items: center; padding: 0; color: var(--desktop-muted); }
|
|
220
227
|
.reply-ring svg { width: 18px; height: 18px; transform: rotate(-90deg); }
|
|
221
|
-
.reply-ring circle { fill: none; stroke-width: 1
|
|
222
|
-
.reply-ring-track { stroke: var(--desktop-line
|
|
223
|
-
.reply-ring-progress { stroke: var(--desktop-
|
|
228
|
+
.reply-ring circle { fill: none; stroke-width: 1; }
|
|
229
|
+
.reply-ring-track { stroke: var(--desktop-line); }
|
|
230
|
+
.reply-ring-progress { stroke: var(--desktop-muted); stroke-linecap: round; stroke-dasharray: 40.84; stroke-dashoffset: 0; }
|
|
224
231
|
.reply-ring[data-running="true"] .reply-ring-progress { animation: reply-countdown var(--reply-visible-ms) linear forwards; }
|
|
225
|
-
.reply-close {
|
|
232
|
+
.reply-close svg, .reply-history-close svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
|
|
233
|
+
.reply-history-close { position: absolute; top: 10px; right: 10px; z-index: 1; background: var(--desktop-rail); }
|
|
226
234
|
.reply-body { margin-top: 7px; color: var(--desktop-ink); font-size: 14px; line-height: 1.65; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
227
235
|
.reply-body-status { display: block; margin-top: 6px; color: var(--desktop-muted); font-size: 11px; }
|
|
228
236
|
.reply-history-open { margin-top: 8px; padding: 3px 0; color: var(--desktop-muted); font-size: 11px; }
|
|
229
|
-
.reply-return-latest { position: absolute;
|
|
237
|
+
.reply-return-latest { position: absolute; left: 50%; bottom: 10px; z-index: 1; transform: translateX(-50%); border: 1px solid var(--desktop-line) !important; background: var(--desktop-ink) !important; box-shadow: 0 2px 8px #1817151a; color: var(--desktop-paper); font-size: 11px; white-space: nowrap; }
|
|
230
238
|
@keyframes reply-countdown { to { stroke-dashoffset: 40.84; } }
|
|
231
239
|
.call-transcript { width: 420px; height: 210px; display: flex; flex-direction: column; overflow: hidden; }
|
|
232
240
|
.call-transcript[data-expanded="true"] { height: 500px; }
|