hiwork-core 0.0.22 → 0.0.29
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/cordis.patch.yml +20 -4
- package/lib/ai-router-client.js +142 -1
- package/lib/client.js +961 -33
- package/lib/index.js +2185 -353
- package/lib/knowledge-config.js +273 -0
- package/lib/pms-client.js +216 -61
- package/lib/pms-todo-bridge.js +332 -313
- package/lib/types/ai-router-client.d.ts +61 -0
- package/lib/types/client/contracts.d.ts +10 -8
- package/lib/types/client/hero-greeting.d.ts +0 -11
- package/lib/types/client/pms-todo.d.ts +67 -49
- package/lib/types/client/styles.d.ts +28 -0
- package/lib/types/client/types.d.ts +13 -1
- package/lib/types/client/voice/MicButton.d.ts +24 -0
- package/lib/types/client/voice/VoiceOverlay.d.ts +37 -0
- package/lib/types/client/voice/composer.d.ts +98 -0
- package/lib/types/client/voice/entry.d.ts +46 -0
- package/lib/types/client/voice/recording.d.ts +57 -0
- package/lib/types/client/voice/voice-data.d.ts +101 -0
- package/lib/types/client/voice/voice-styles.d.ts +9 -0
- package/lib/types/index.d.ts +3 -1
- package/lib/types/knowledge-config.d.ts +115 -0
- package/lib/types/pms-client.d.ts +99 -34
- package/lib/types/pms-todo-bridge.d.ts +210 -184
- package/lib/types/voice-rpc.d.ts +133 -0
- package/lib/types/web-search.d.ts +123 -0
- package/lib/voice-rpc.js +211 -0
- package/lib/web-search.js +259 -0
- package/package.json +5 -3
package/lib/client.js
CHANGED
|
@@ -112,6 +112,235 @@ var interactionCss = [
|
|
|
112
112
|
// 品牌蓝按钮(登录门禁)hover:提亮而非换底色,保留渐变。
|
|
113
113
|
`[data-hiwork-core-brand]:hover:not(:disabled) { filter: brightness(1.08) !important; }`
|
|
114
114
|
].join("\n");
|
|
115
|
+
var CORE_STYLE_OWNER = "hiwork-core";
|
|
116
|
+
function ownStyleTag(style, cssTagId) {
|
|
117
|
+
style.setAttribute("data-plugin", CORE_STYLE_OWNER);
|
|
118
|
+
style.setAttribute("data-plugin-css", cssTagId);
|
|
119
|
+
}
|
|
120
|
+
var voiceCss = [
|
|
121
|
+
/* ---- 工具栏话筒按钮 ---- */
|
|
122
|
+
`.hiwv-micBtn {`,
|
|
123
|
+
` display: inline-flex;`,
|
|
124
|
+
` align-items: center;`,
|
|
125
|
+
` justify-content: center;`,
|
|
126
|
+
` width: 28px;`,
|
|
127
|
+
` height: 28px;`,
|
|
128
|
+
` padding: 0;`,
|
|
129
|
+
` border: none;`,
|
|
130
|
+
` border-radius: 8px;`,
|
|
131
|
+
` background: transparent;`,
|
|
132
|
+
` color: inherit;`,
|
|
133
|
+
` cursor: pointer;`,
|
|
134
|
+
` opacity: 0.85;`,
|
|
135
|
+
` flex: none;`,
|
|
136
|
+
`}`,
|
|
137
|
+
`.hiwv-micBtn:hover {`,
|
|
138
|
+
` opacity: 1;`,
|
|
139
|
+
` background: rgba(127, 127, 127, 0.14);`,
|
|
140
|
+
`}`,
|
|
141
|
+
`.hiwv-micBtn:active {`,
|
|
142
|
+
` background: rgba(127, 127, 127, 0.22);`,
|
|
143
|
+
`}`,
|
|
144
|
+
/* ---- 录音浮层 ---- */
|
|
145
|
+
`.hiwv-backdrop {`,
|
|
146
|
+
` position: fixed;`,
|
|
147
|
+
` inset: 0;`,
|
|
148
|
+
` z-index: 2147483000;`,
|
|
149
|
+
` display: flex;`,
|
|
150
|
+
` align-items: center;`,
|
|
151
|
+
` justify-content: center;`,
|
|
152
|
+
` background: rgba(0, 0, 0, 0.45);`,
|
|
153
|
+
` animation: hiwv-fade-in 140ms ease-out;`,
|
|
154
|
+
`}`,
|
|
155
|
+
`@keyframes hiwv-fade-in {`,
|
|
156
|
+
` from { opacity: 0; }`,
|
|
157
|
+
` to { opacity: 1; }`,
|
|
158
|
+
`}`,
|
|
159
|
+
`.hiwv-panel {`,
|
|
160
|
+
` display: flex;`,
|
|
161
|
+
` flex-direction: column;`,
|
|
162
|
+
` width: min(560px, calc(100vw - 64px));`,
|
|
163
|
+
` min-width: 420px;`,
|
|
164
|
+
` border-radius: 16px;`,
|
|
165
|
+
` padding: 24px 28px 22px;`,
|
|
166
|
+
` background: #ffffff;`,
|
|
167
|
+
` color: #1f2329;`,
|
|
168
|
+
` box-shadow: 0 18px 60px rgba(0, 0, 0, 0.28);`,
|
|
169
|
+
` animation: hiwv-pop-in 160ms cubic-bezier(0.2, 0.9, 0.3, 1.2);`,
|
|
170
|
+
`}`,
|
|
171
|
+
`@keyframes hiwv-pop-in {`,
|
|
172
|
+
` from { transform: scale(0.96); opacity: 0; }`,
|
|
173
|
+
` to { transform: scale(1); opacity: 1; }`,
|
|
174
|
+
`}`,
|
|
175
|
+
`.hiwv-head {`,
|
|
176
|
+
` display: flex;`,
|
|
177
|
+
` align-items: baseline;`,
|
|
178
|
+
` justify-content: space-between;`,
|
|
179
|
+
` gap: 12px;`,
|
|
180
|
+
`}`,
|
|
181
|
+
`.hiwv-title {`,
|
|
182
|
+
` font-size: 17px;`,
|
|
183
|
+
` font-weight: 600;`,
|
|
184
|
+
`}`,
|
|
185
|
+
`.hiwv-hint {`,
|
|
186
|
+
` font-size: 12px;`,
|
|
187
|
+
` color: #8a8f99;`,
|
|
188
|
+
` font-variant-numeric: tabular-nums;`,
|
|
189
|
+
`}`,
|
|
190
|
+
`.hiwv-stage {`,
|
|
191
|
+
` display: flex;`,
|
|
192
|
+
` flex-direction: column;`,
|
|
193
|
+
` align-items: center;`,
|
|
194
|
+
` justify-content: center;`,
|
|
195
|
+
` gap: 20px;`,
|
|
196
|
+
` min-height: 216px;`,
|
|
197
|
+
` padding: 30px 8px 22px;`,
|
|
198
|
+
`}`,
|
|
199
|
+
`.hiwv-stage.hiwv-stageIdle {`,
|
|
200
|
+
` gap: 14px;`,
|
|
201
|
+
`}`,
|
|
202
|
+
`.hiwv-orb {`,
|
|
203
|
+
` width: 76px;`,
|
|
204
|
+
` height: 76px;`,
|
|
205
|
+
` border-radius: 50%;`,
|
|
206
|
+
` display: flex;`,
|
|
207
|
+
` align-items: center;`,
|
|
208
|
+
` justify-content: center;`,
|
|
209
|
+
` color: #ffffff;`,
|
|
210
|
+
` background: #3370ff;`,
|
|
211
|
+
` animation: hiwv-orb-pulse 1.6s ease-out infinite;`,
|
|
212
|
+
` flex: none;`,
|
|
213
|
+
`}`,
|
|
214
|
+
`.hiwv-orb.hiwv-orbBusy {`,
|
|
215
|
+
` animation: hiwv-orb-breathe 1.2s ease-in-out infinite;`,
|
|
216
|
+
`}`,
|
|
217
|
+
`@keyframes hiwv-orb-pulse {`,
|
|
218
|
+
` 0% { box-shadow: 0 0 0 0 rgba(51, 112, 255, 0.35); }`,
|
|
219
|
+
` 70% { box-shadow: 0 0 0 22px rgba(51, 112, 255, 0); }`,
|
|
220
|
+
` 100% { box-shadow: 0 0 0 0 rgba(51, 112, 255, 0); }`,
|
|
221
|
+
`}`,
|
|
222
|
+
`@keyframes hiwv-orb-breathe {`,
|
|
223
|
+
` 0%, 100% { opacity: 1; }`,
|
|
224
|
+
` 50% { opacity: 0.65; }`,
|
|
225
|
+
`}`,
|
|
226
|
+
`.hiwv-bars {`,
|
|
227
|
+
` display: flex;`,
|
|
228
|
+
` align-items: center;`,
|
|
229
|
+
` gap: 3px;`,
|
|
230
|
+
` height: 56px;`,
|
|
231
|
+
`}`,
|
|
232
|
+
`.hiwv-bars i {`,
|
|
233
|
+
` display: block;`,
|
|
234
|
+
` width: 4px;`,
|
|
235
|
+
` min-height: 8px;`,
|
|
236
|
+
` border-radius: 2px;`,
|
|
237
|
+
` background: #3370ff;`,
|
|
238
|
+
` opacity: 0.85;`,
|
|
239
|
+
` transition: height 80ms linear;`,
|
|
240
|
+
`}`,
|
|
241
|
+
`.hiwv-bars i.hiwv-barIdle {`,
|
|
242
|
+
` background: #b9c0cc;`,
|
|
243
|
+
` opacity: 0.5;`,
|
|
244
|
+
`}`,
|
|
245
|
+
`.hiwv-status {`,
|
|
246
|
+
` font-size: 13px;`,
|
|
247
|
+
` color: #5f6672;`,
|
|
248
|
+
`}`,
|
|
249
|
+
/* ---- 错误 / 结果态 ---- */
|
|
250
|
+
`.hiwv-errorBox {`,
|
|
251
|
+
` display: flex;`,
|
|
252
|
+
` flex-direction: column;`,
|
|
253
|
+
` align-items: center;`,
|
|
254
|
+
` gap: 14px;`,
|
|
255
|
+
` width: 100%;`,
|
|
256
|
+
`}`,
|
|
257
|
+
`.hiwv-errorMsg {`,
|
|
258
|
+
` font-size: 14px;`,
|
|
259
|
+
` line-height: 1.6;`,
|
|
260
|
+
` color: #b3261e;`,
|
|
261
|
+
` text-align: center;`,
|
|
262
|
+
` max-width: 420px;`,
|
|
263
|
+
`}`,
|
|
264
|
+
`.hiwv-recognized {`,
|
|
265
|
+
` display: flex;`,
|
|
266
|
+
` flex-direction: column;`,
|
|
267
|
+
` gap: 10px;`,
|
|
268
|
+
` width: 100%;`,
|
|
269
|
+
` max-width: 460px;`,
|
|
270
|
+
` padding: 14px;`,
|
|
271
|
+
` border-radius: 10px;`,
|
|
272
|
+
` background: rgba(127, 127, 127, 0.08);`,
|
|
273
|
+
`}`,
|
|
274
|
+
`.hiwv-recognizedText {`,
|
|
275
|
+
` font-size: 14px;`,
|
|
276
|
+
` line-height: 1.6;`,
|
|
277
|
+
` max-height: 96px;`,
|
|
278
|
+
` overflow: auto;`,
|
|
279
|
+
` white-space: pre-wrap;`,
|
|
280
|
+
` word-break: break-word;`,
|
|
281
|
+
`}`,
|
|
282
|
+
/* ---- 操作区 ---- */
|
|
283
|
+
`.hiwv-actions {`,
|
|
284
|
+
` display: flex;`,
|
|
285
|
+
` justify-content: flex-end;`,
|
|
286
|
+
` gap: 10px;`,
|
|
287
|
+
` padding-top: 4px;`,
|
|
288
|
+
`}`,
|
|
289
|
+
`.hiwv-btn {`,
|
|
290
|
+
` appearance: none;`,
|
|
291
|
+
` border-radius: 8px;`,
|
|
292
|
+
` padding: 7px 20px;`,
|
|
293
|
+
` font-size: 13px;`,
|
|
294
|
+
` line-height: 20px;`,
|
|
295
|
+
` cursor: pointer;`,
|
|
296
|
+
` border: 1px solid transparent;`,
|
|
297
|
+
`}`,
|
|
298
|
+
`.hiwv-btn:disabled {`,
|
|
299
|
+
` cursor: default;`,
|
|
300
|
+
` opacity: 0.55;`,
|
|
301
|
+
`}`,
|
|
302
|
+
`.hiwv-btnPrimary {`,
|
|
303
|
+
` background: #3370ff;`,
|
|
304
|
+
` color: #ffffff;`,
|
|
305
|
+
`}`,
|
|
306
|
+
`.hiwv-btnPrimary:hover:not(:disabled) {`,
|
|
307
|
+
` background: #2b5fd9;`,
|
|
308
|
+
`}`,
|
|
309
|
+
`.hiwv-btnSecondary {`,
|
|
310
|
+
` background: transparent;`,
|
|
311
|
+
` color: inherit;`,
|
|
312
|
+
` border-color: rgba(127, 127, 127, 0.35);`,
|
|
313
|
+
`}`,
|
|
314
|
+
`.hiwv-btnSecondary:hover:not(:disabled) {`,
|
|
315
|
+
` background: rgba(127, 127, 127, 0.1);`,
|
|
316
|
+
`}`,
|
|
317
|
+
/* ---- 深色主题 ---- */
|
|
318
|
+
`@media (prefers-color-scheme: dark) {`,
|
|
319
|
+
` .hiwv-panel {`,
|
|
320
|
+
` background: #24262b;`,
|
|
321
|
+
` color: #e8eaed;`,
|
|
322
|
+
` }`,
|
|
323
|
+
` .hiwv-hint,`,
|
|
324
|
+
` .hiwv-status {`,
|
|
325
|
+
` color: #9aa0ab;`,
|
|
326
|
+
` }`,
|
|
327
|
+
` .hiwv-bars i.hiwv-barIdle {`,
|
|
328
|
+
` background: #565b66;`,
|
|
329
|
+
` }`,
|
|
330
|
+
` .hiwv-btnSecondary {`,
|
|
331
|
+
` border-color: rgba(255, 255, 255, 0.22);`,
|
|
332
|
+
` }`,
|
|
333
|
+
` .hiwv-btnSecondary:hover:not(:disabled) {`,
|
|
334
|
+
` background: rgba(255, 255, 255, 0.08);`,
|
|
335
|
+
` }`,
|
|
336
|
+
` .hiwv-recognized {`,
|
|
337
|
+
` background: rgba(255, 255, 255, 0.07);`,
|
|
338
|
+
` }`,
|
|
339
|
+
` .hiwv-errorMsg {`,
|
|
340
|
+
` color: #ff8a80;`,
|
|
341
|
+
` }`,
|
|
342
|
+
`}`
|
|
343
|
+
].join("\n");
|
|
115
344
|
|
|
116
345
|
// src/client/controller.ts
|
|
117
346
|
var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
@@ -812,7 +1041,7 @@ function LogoutIcon() {
|
|
|
812
1041
|
viewBox: "0 0 16 16",
|
|
813
1042
|
fill: "none",
|
|
814
1043
|
stroke: "currentColor",
|
|
815
|
-
strokeWidth: 1.
|
|
1044
|
+
strokeWidth: 1.25,
|
|
816
1045
|
strokeLinecap: "round",
|
|
817
1046
|
strokeLinejoin: "round",
|
|
818
1047
|
"aria-hidden": "true",
|
|
@@ -1285,6 +1514,7 @@ function ensureHeroStyle(doc = document) {
|
|
|
1285
1514
|
if (doc.head === null) return;
|
|
1286
1515
|
const style = doc.createElement("style");
|
|
1287
1516
|
style.id = HERO_STYLE_ID;
|
|
1517
|
+
ownStyleTag(style, "hiwork-core/hero-greeting.css");
|
|
1288
1518
|
style.textContent = HERO_STYLE;
|
|
1289
1519
|
doc.head.append(style);
|
|
1290
1520
|
}
|
|
@@ -1337,6 +1567,7 @@ function ensureManualStyle(doc) {
|
|
|
1337
1567
|
if (doc.head === null) return;
|
|
1338
1568
|
const style = doc.createElement("style");
|
|
1339
1569
|
style.id = MANUAL_STYLE_ID;
|
|
1570
|
+
ownStyleTag(style, "hiwork-core/manual-link.css");
|
|
1340
1571
|
style.textContent = MANUAL_STYLE;
|
|
1341
1572
|
doc.head.append(style);
|
|
1342
1573
|
}
|
|
@@ -1425,6 +1656,7 @@ function ensureDailyCardStyle(doc) {
|
|
|
1425
1656
|
if (doc.head === null) return;
|
|
1426
1657
|
const style = doc.createElement("style");
|
|
1427
1658
|
style.id = DAILY_CARD_STYLE_ID;
|
|
1659
|
+
ownStyleTag(style, "hiwork-core/daily-card.css");
|
|
1428
1660
|
style.textContent = DAILY_CARD_STYLE;
|
|
1429
1661
|
doc.head.append(style);
|
|
1430
1662
|
}
|
|
@@ -2764,8 +2996,8 @@ var DEFAULT_NEW_SESSION_LABEL = "\u65B0\u4F1A\u8BDD";
|
|
|
2764
2996
|
var STYLE_ELEMENT_ID = "hiwork-feature-center-styles";
|
|
2765
2997
|
var TOOLTIP_DELAY_MS = 500;
|
|
2766
2998
|
var TOOLTIP_GAP_PX = 8;
|
|
2767
|
-
var DEFAULT_NEW_SESSION_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"
|
|
2768
|
-
var DEFAULT_FEATURE_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"
|
|
2999
|
+
var DEFAULT_NEW_SESSION_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.00003 0.3237C3.76075 0.3237 0.32373 3.76072 0.32373 8C0.32373 9.17603 0.589121 10.2922 1.0632 11.2901L1.35291 11.8989L2.5705 11.3205L2.28079 10.7117C1.89079 9.89074 1.67301 8.97167 1.67301 8C1.67301 4.50546 4.50549 1.67298 8.00003 1.67298C11.4946 1.67298 14.3271 4.50546 14.3271 8C14.3271 11.4945 11.4946 14.327 8.00003 14.327C7.28473 14.327 6.76077 14.277 6.29621 14.1487C5.83857 14.0224 5.40441 13.8109 4.88514 13.4488C4.12569 12.919 3.03778 12.7316 2.141 13.2978L2.12682 13.307L2.11264 13.3171L1.34886 13.854L1.79659 15.188L2.86122 14.4384C3.19068 14.2305 3.68325 14.2542 4.11326 14.5539C4.72789 14.9826 5.30042 15.2724 5.93762 15.4484C6.56803 15.6224 7.22776 15.6763 8.00003 15.6763C12.2393 15.6763 15.6763 12.2393 15.6763 8C15.6763 3.76072 12.2393 0.3237 8.00003 0.3237ZM7.32033 4.82535V7.32536H4.82538V8.67464H7.32033V11.1747H8.6696V8.67464H11.1747V7.32536H8.6696V4.82535H7.32033Z" fill="currentColor"></path></svg>';
|
|
3000
|
+
var DEFAULT_FEATURE_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.84457 5.06199C11.6605 4.93876 14.7962 6.14848 14.8484 7.76397C14.8875 8.97461 13.1838 10.0696 10.7215 10.5942" stroke="currentColor" stroke-width="1.2"></path><path d="M5.12742 8.07731C5.00419 4.26138 6.21391 1.12568 7.8294 1.07351C9.04004 1.03441 10.135 2.73808 10.6596 5.20037" stroke="currentColor" stroke-width="1.2"></path><path d="M8.02457 10.6802C4.20865 10.8034 1.07294 9.5937 1.02077 7.97821C0.981678 6.76758 2.68535 5.67262 5.14763 5.14798" stroke="currentColor" stroke-width="1.2"></path><path d="M10.7476 7.89535C10.8708 11.7113 9.66109 14.847 8.0456 14.8991C6.83496 14.9382 5.74 13.2346 5.21536 10.7723" stroke="currentColor" stroke-width="1.2"></path></svg>';
|
|
2769
3001
|
var SESSION_ROW_SELECTOR = [
|
|
2770
3002
|
'[class*="sessionRow"]',
|
|
2771
3003
|
'[class*="projectRow"]',
|
|
@@ -2856,6 +3088,7 @@ function mountFeatureCenter(options) {
|
|
|
2856
3088
|
const style = document.createElement("style");
|
|
2857
3089
|
style.id = STYLE_ELEMENT_ID;
|
|
2858
3090
|
style.setAttribute(OWNER_ATTRIBUTE, owner);
|
|
3091
|
+
ownStyleTag(style, "hiwork-core/feature-center.css");
|
|
2859
3092
|
style.textContent = featureCenterCss;
|
|
2860
3093
|
document.head.appendChild(style);
|
|
2861
3094
|
let menuElement;
|
|
@@ -3827,6 +4060,7 @@ var ManagedWorkbenchStore = class {
|
|
|
3827
4060
|
|
|
3828
4061
|
// src/client/pms-todo.ts
|
|
3829
4062
|
var PMS_TODO_CHANNEL = "/hiwork-pms-todo";
|
|
4063
|
+
var PMS_TODO_STATUS_REASONS = ["no-assignment", "gateway-signed-out", "unavailable"];
|
|
3830
4064
|
var PmsTodoRpcError = class extends Error {
|
|
3831
4065
|
constructor(code, message) {
|
|
3832
4066
|
super(message);
|
|
@@ -3894,11 +4128,15 @@ function toStatus(value) {
|
|
|
3894
4128
|
if (!isRecord3(value)) throw new PmsTodoRpcError(INTERNAL_CODE, INVALID_RESPONSE_MESSAGE4);
|
|
3895
4129
|
const username = text5(value["username"]);
|
|
3896
4130
|
const expiresAt = text5(value["expiresAt"]);
|
|
4131
|
+
const userId = value["userId"];
|
|
4132
|
+
const reason = value["reason"];
|
|
3897
4133
|
return {
|
|
3898
4134
|
configured: value["configured"] === true,
|
|
3899
4135
|
loggedIn: value["loggedIn"] === true,
|
|
3900
4136
|
...username === void 0 ? {} : { username },
|
|
3901
|
-
...expiresAt === void 0 ? {} : { expiresAt }
|
|
4137
|
+
...expiresAt === void 0 ? {} : { expiresAt },
|
|
4138
|
+
...isInteger(userId) ? { userId } : {},
|
|
4139
|
+
...typeof reason === "string" && PMS_TODO_STATUS_REASONS.includes(reason) ? { reason } : {}
|
|
3902
4140
|
};
|
|
3903
4141
|
}
|
|
3904
4142
|
function toToday(value) {
|
|
@@ -4005,7 +4243,7 @@ var PmsTodoStore = class {
|
|
|
4005
4243
|
/**
|
|
4006
4244
|
* 在飞请求**连同它所属的世代与端点**一起记账。
|
|
4007
4245
|
*
|
|
4008
|
-
* 世代:`clear()
|
|
4246
|
+
* 世代:`clear()`(换账号)之后,账上那条是**上一个人**发起的,它的待办属于上一个人
|
|
4009
4247
|
* ——复用它等于让新账号看到上一个人的待办。端点:`today` 与 `statistics` 的返回值形状不同,
|
|
4010
4248
|
* 只有同一个端点的并发调用才允许合并。
|
|
4011
4249
|
*/
|
|
@@ -4018,8 +4256,9 @@ var PmsTodoStore = class {
|
|
|
4018
4256
|
* 本地镜像(同步)。从没同步过返回 `undefined`。
|
|
4019
4257
|
*
|
|
4020
4258
|
* ⚠️ 本方法有一次**惰性探测**:镜像还空着且从没探过时,它顺手发起一次 `status` 端点调用
|
|
4021
|
-
* (结果到达后写镜像 +
|
|
4022
|
-
*
|
|
4259
|
+
* (结果到达后写镜像 + 通知订阅者)。理由——渲染是同步的,而「网关里有没有 pms 分配、手上
|
|
4260
|
+
* 有没有令牌」只有主机知道;没有这一步,`status` 端点就永远没有调用方,界面也只能靠一次
|
|
4261
|
+
* 失败的 `today()` 去反推(而且那反推出来的原因往往正好是错的)。
|
|
4023
4262
|
* 因此调用方看到 `undefined` **不是**「未配置」,而是「还不知道」:必须订阅。
|
|
4024
4263
|
*/
|
|
4025
4264
|
status() {
|
|
@@ -4040,22 +4279,6 @@ var PmsTodoStore = class {
|
|
|
4040
4279
|
const { value, generation } = await this.call("status", "status", {}, toStatus, signal);
|
|
4041
4280
|
return this.adopt(value, generation);
|
|
4042
4281
|
}
|
|
4043
|
-
/**
|
|
4044
|
-
* 登录(凭据交给 Host;**本层不留存密码、不写任何缓存**)。
|
|
4045
|
-
*
|
|
4046
|
-
* 成功时把返回的状态写进镜像——登录是「身份刚变过」,镜像必须跟着变。
|
|
4047
|
-
*/
|
|
4048
|
-
async login(input) {
|
|
4049
|
-
const payload = { username: input.username, password: input.password };
|
|
4050
|
-
if (input.remember !== void 0) payload["remember"] = input.remember;
|
|
4051
|
-
const { value, generation } = await this.call("login", "login", payload, toStatus);
|
|
4052
|
-
return this.adopt(value, generation);
|
|
4053
|
-
}
|
|
4054
|
-
/** 登出(Host 侧清凭据);返回登出后的状态并写镜像。 */
|
|
4055
|
-
async logout() {
|
|
4056
|
-
const { value, generation } = await this.call("logout", "logout", {}, toStatus);
|
|
4057
|
-
return this.adopt(value, generation);
|
|
4058
|
-
}
|
|
4059
4282
|
/**
|
|
4060
4283
|
* 今日待办。
|
|
4061
4284
|
*
|
|
@@ -4084,7 +4307,7 @@ var PmsTodoStore = class {
|
|
|
4084
4307
|
/**
|
|
4085
4308
|
* **我的未完成项目任务**(spec §3 D7):`myTasks` 端点的取数。
|
|
4086
4309
|
*
|
|
4087
|
-
* 入参是**空对象**,没有 `userId`——「谁的任务」由 Host
|
|
4310
|
+
* 入参是**空对象**,没有 `userId`——「谁的任务」由 Host 从 mint 响应里取(客户端连自己的
|
|
4088
4311
|
* PMS userId 都拿不到,这是 §3 D6 形状的一部分)。
|
|
4089
4312
|
*
|
|
4090
4313
|
* 与 `today` 同款:**不做本地缓存**(返回刚拉到的那一份),但**并发调用合并**(同一时刻问两遍
|
|
@@ -4117,7 +4340,8 @@ var PmsTodoStore = class {
|
|
|
4117
4340
|
};
|
|
4118
4341
|
}
|
|
4119
4342
|
/**
|
|
4120
|
-
*
|
|
4343
|
+
* 清空镜像(换账号:状态是**按人**的——分配是按人下发的、令牌是按人换的,留着会串号),
|
|
4344
|
+
* 并**作废在飞的请求**。
|
|
4121
4345
|
*
|
|
4122
4346
|
* 只清镜像是不够的:清空那一刻正有一次拉取在飞时,那次拉取是**上一个人**发起的。所以世代
|
|
4123
4347
|
* +1:那之后的调用者一律不复用旧世代的那条,自己重新发一次 RPC。用世代号而**不是**把
|
|
@@ -4180,6 +4404,676 @@ var PmsTodoStore = class {
|
|
|
4180
4404
|
}
|
|
4181
4405
|
};
|
|
4182
4406
|
|
|
4407
|
+
// src/client/voice/MicButton.tsx
|
|
4408
|
+
var import_react8 = require("react");
|
|
4409
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
4410
|
+
function MicIcon({ size }) {
|
|
4411
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
|
|
4412
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "9", y: "2.75", width: "6", height: "11.5", rx: "3", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
4413
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M5.5 11.5a6.5 6.5 0 0 0 13 0", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
|
|
4414
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 18v3.25", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
|
|
4415
|
+
] });
|
|
4416
|
+
}
|
|
4417
|
+
function MicButton({ open }) {
|
|
4418
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
4419
|
+
"button",
|
|
4420
|
+
{
|
|
4421
|
+
type: "button",
|
|
4422
|
+
className: "hiwv-micBtn",
|
|
4423
|
+
title: "\u8BED\u97F3\u8F93\u5165",
|
|
4424
|
+
"aria-label": "\u8BED\u97F3\u8F93\u5165",
|
|
4425
|
+
"aria-haspopup": "dialog",
|
|
4426
|
+
onClick: () => open(),
|
|
4427
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MicIcon, { size: 18 })
|
|
4428
|
+
}
|
|
4429
|
+
);
|
|
4430
|
+
}
|
|
4431
|
+
|
|
4432
|
+
// src/client/voice/entry.ts
|
|
4433
|
+
var import_react10 = require("react");
|
|
4434
|
+
var import_client3 = require("react-dom/client");
|
|
4435
|
+
|
|
4436
|
+
// src/client/voice/composer.ts
|
|
4437
|
+
var WRITABLE_PHASES = /* @__PURE__ */ new Set(["plain", "claimed"]);
|
|
4438
|
+
function detectTextLength(state) {
|
|
4439
|
+
const occurrences = state.occurrences;
|
|
4440
|
+
if (occurrences === void 0) return state.draft.length;
|
|
4441
|
+
let folded = 0;
|
|
4442
|
+
for (const occurrence of occurrences) {
|
|
4443
|
+
if (occurrence.length > 1) folded += occurrence.length - 1;
|
|
4444
|
+
}
|
|
4445
|
+
return Math.max(0, state.draft.length - folded);
|
|
4446
|
+
}
|
|
4447
|
+
function insertDraftText(sessions, text6, sessionId) {
|
|
4448
|
+
if (sessions === void 0) return { ok: false, reason: "no-target" };
|
|
4449
|
+
let target;
|
|
4450
|
+
try {
|
|
4451
|
+
target = sessionId ?? sessions.list?.getSnapshot().current;
|
|
4452
|
+
} catch {
|
|
4453
|
+
return { ok: false, reason: "no-target" };
|
|
4454
|
+
}
|
|
4455
|
+
if (target === void 0) return { ok: false, reason: "no-target" };
|
|
4456
|
+
let actx;
|
|
4457
|
+
try {
|
|
4458
|
+
actx = sessions.scope?.(target) ?? sessions.binding?.(target)?.ctx;
|
|
4459
|
+
} catch {
|
|
4460
|
+
return { ok: false, reason: "no-target" };
|
|
4461
|
+
}
|
|
4462
|
+
if (actx === void 0 || actx === null) return { ok: false, reason: "no-target" };
|
|
4463
|
+
let surface;
|
|
4464
|
+
try {
|
|
4465
|
+
const conversation = actx.get?.("conversation");
|
|
4466
|
+
surface = conversation?.input.for(actx);
|
|
4467
|
+
} catch {
|
|
4468
|
+
return { ok: false, reason: "no-target" };
|
|
4469
|
+
}
|
|
4470
|
+
if (surface === void 0) return { ok: false, reason: "no-target" };
|
|
4471
|
+
if (typeof surface.insertText !== "function") return { ok: false, reason: "no-target" };
|
|
4472
|
+
let snapshot;
|
|
4473
|
+
try {
|
|
4474
|
+
snapshot = surface.state.getSnapshot();
|
|
4475
|
+
} catch {
|
|
4476
|
+
return { ok: false, reason: "no-target" };
|
|
4477
|
+
}
|
|
4478
|
+
if (snapshot.phase !== void 0 && !WRITABLE_PHASES.has(snapshot.phase)) return { ok: false, reason: "busy" };
|
|
4479
|
+
const end = detectTextLength(snapshot);
|
|
4480
|
+
try {
|
|
4481
|
+
const inserted = surface.insertText(text6, { start: end, end, draftRev: snapshot.draftRev });
|
|
4482
|
+
return inserted ? { ok: true } : { ok: false, reason: "rejected" };
|
|
4483
|
+
} catch {
|
|
4484
|
+
return { ok: false, reason: "rejected" };
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
|
|
4488
|
+
// src/client/voice/VoiceOverlay.tsx
|
|
4489
|
+
var import_react9 = require("react");
|
|
4490
|
+
|
|
4491
|
+
// src/client/voice/recording.ts
|
|
4492
|
+
var RecordingError = class extends Error {
|
|
4493
|
+
constructor(kind) {
|
|
4494
|
+
super(kind);
|
|
4495
|
+
this.kind = kind;
|
|
4496
|
+
this.name = "RecordingError";
|
|
4497
|
+
}
|
|
4498
|
+
};
|
|
4499
|
+
function encodeWave(samples) {
|
|
4500
|
+
const bytes = new Uint8Array(44 + samples.length * 2);
|
|
4501
|
+
const view = new DataView(bytes.buffer);
|
|
4502
|
+
const text6 = (at, value) => {
|
|
4503
|
+
for (let i = 0; i < value.length; i++) bytes[at + i] = value.charCodeAt(i);
|
|
4504
|
+
};
|
|
4505
|
+
text6(0, "RIFF");
|
|
4506
|
+
view.setUint32(4, bytes.length - 8, true);
|
|
4507
|
+
text6(8, "WAVE");
|
|
4508
|
+
text6(12, "fmt ");
|
|
4509
|
+
view.setUint32(16, 16, true);
|
|
4510
|
+
view.setUint16(20, 1, true);
|
|
4511
|
+
view.setUint16(22, 1, true);
|
|
4512
|
+
view.setUint32(24, 16e3, true);
|
|
4513
|
+
view.setUint32(28, 32e3, true);
|
|
4514
|
+
view.setUint16(32, 2, true);
|
|
4515
|
+
view.setUint16(34, 16, true);
|
|
4516
|
+
text6(36, "data");
|
|
4517
|
+
view.setUint32(40, samples.length * 2, true);
|
|
4518
|
+
for (const [i, sample] of samples.entries()) {
|
|
4519
|
+
const value = Math.max(-1, Math.min(1, sample));
|
|
4520
|
+
view.setInt16(44 + i * 2, Math.round(value * (value < 0 ? 32768 : 32767)), true);
|
|
4521
|
+
}
|
|
4522
|
+
return bytes;
|
|
4523
|
+
}
|
|
4524
|
+
function audioBase64(bytes) {
|
|
4525
|
+
let text6 = "";
|
|
4526
|
+
for (let i = 0; i < bytes.length; i += 8192) text6 += String.fromCharCode(...bytes.subarray(i, i + 8192));
|
|
4527
|
+
return btoa(text6);
|
|
4528
|
+
}
|
|
4529
|
+
function isSilentWav(bytes, threshold = 100) {
|
|
4530
|
+
for (let i = 44; i + 1 < bytes.length; i += 2) {
|
|
4531
|
+
const sample = (bytes[i] ?? 0) | (bytes[i + 1] ?? 0) << 8;
|
|
4532
|
+
if (Math.abs(sample) > threshold) return false;
|
|
4533
|
+
}
|
|
4534
|
+
return true;
|
|
4535
|
+
}
|
|
4536
|
+
var Recording = class {
|
|
4537
|
+
constructor(onDispose) {
|
|
4538
|
+
this.onDispose = onDispose;
|
|
4539
|
+
}
|
|
4540
|
+
stream;
|
|
4541
|
+
recorder;
|
|
4542
|
+
context;
|
|
4543
|
+
analyser;
|
|
4544
|
+
samples = new Float32Array(256);
|
|
4545
|
+
chunks = [];
|
|
4546
|
+
lifetime = new AbortController();
|
|
4547
|
+
disposal;
|
|
4548
|
+
/**
|
|
4549
|
+
* 申请麦克风并开始录音。
|
|
4550
|
+
* @param onError - 采集途中的失败回调(在异步资源释放完成前触发)。
|
|
4551
|
+
*/
|
|
4552
|
+
async start(onError) {
|
|
4553
|
+
const devices = navigator.mediaDevices;
|
|
4554
|
+
if (!devices || typeof MediaRecorder === "undefined") throw new RecordingError("unavailable");
|
|
4555
|
+
let stream;
|
|
4556
|
+
try {
|
|
4557
|
+
stream = await devices.getUserMedia({
|
|
4558
|
+
audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },
|
|
4559
|
+
video: false
|
|
4560
|
+
});
|
|
4561
|
+
} catch (error) {
|
|
4562
|
+
if (error instanceof DOMException && error.name === "NotAllowedError") throw new RecordingError("permission");
|
|
4563
|
+
throw error;
|
|
4564
|
+
}
|
|
4565
|
+
if (this.lifetime.signal.aborted) {
|
|
4566
|
+
stream.getTracks().forEach((track) => {
|
|
4567
|
+
track.stop();
|
|
4568
|
+
});
|
|
4569
|
+
throw new RecordingError("cancelled");
|
|
4570
|
+
}
|
|
4571
|
+
this.stream = stream;
|
|
4572
|
+
try {
|
|
4573
|
+
this.context = new AudioContext();
|
|
4574
|
+
if (this.context.state === "suspended") {
|
|
4575
|
+
await this.context.resume().catch(() => void 0);
|
|
4576
|
+
}
|
|
4577
|
+
this.analyser = this.context.createAnalyser();
|
|
4578
|
+
this.analyser.fftSize = this.samples.length;
|
|
4579
|
+
this.context.createMediaStreamSource(stream).connect(this.analyser);
|
|
4580
|
+
this.recorder = new MediaRecorder(stream);
|
|
4581
|
+
this.recorder.ondataavailable = (event) => {
|
|
4582
|
+
if (!this.lifetime.signal.aborted && event.data.size > 0) this.chunks.push(event.data);
|
|
4583
|
+
};
|
|
4584
|
+
this.recorder.onerror = () => {
|
|
4585
|
+
if (this.lifetime.signal.aborted) return;
|
|
4586
|
+
void this.dispose().catch(() => void 0);
|
|
4587
|
+
try {
|
|
4588
|
+
onError?.(new RecordingError("interrupted"));
|
|
4589
|
+
} catch (error) {
|
|
4590
|
+
console.error("Voice recording error handler failed", error);
|
|
4591
|
+
}
|
|
4592
|
+
};
|
|
4593
|
+
this.recorder.start();
|
|
4594
|
+
} catch (error) {
|
|
4595
|
+
await this.dispose();
|
|
4596
|
+
throw error;
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
/** 读实时麦克风电平(RMS,0~1 粗略值);不在采集态时恒为 0。 */
|
|
4600
|
+
amplitude() {
|
|
4601
|
+
if (!this.analyser) return 0;
|
|
4602
|
+
this.analyser.getFloatTimeDomainData(this.samples);
|
|
4603
|
+
let sum2 = 0;
|
|
4604
|
+
for (const sample of this.samples) sum2 += sample * sample;
|
|
4605
|
+
return Math.sqrt(sum2 / this.samples.length);
|
|
4606
|
+
}
|
|
4607
|
+
/**
|
|
4608
|
+
* 结束录音并重采样成 WAV。
|
|
4609
|
+
* @param maxDurationSeconds - 截掉超出上限的部分(定时器漂移保护)。
|
|
4610
|
+
*/
|
|
4611
|
+
async stop(maxDurationSeconds) {
|
|
4612
|
+
const recorder = this.recorder;
|
|
4613
|
+
const context = this.context;
|
|
4614
|
+
if (!recorder || !context || recorder.state !== "recording") {
|
|
4615
|
+
await this.dispose();
|
|
4616
|
+
throw new RecordingError("empty");
|
|
4617
|
+
}
|
|
4618
|
+
try {
|
|
4619
|
+
await new Promise((resolve, reject) => {
|
|
4620
|
+
recorder.onstop = () => {
|
|
4621
|
+
resolve();
|
|
4622
|
+
};
|
|
4623
|
+
recorder.onerror = () => {
|
|
4624
|
+
reject(new RecordingError("empty"));
|
|
4625
|
+
};
|
|
4626
|
+
recorder.stop();
|
|
4627
|
+
});
|
|
4628
|
+
this.stream?.getTracks().forEach((track) => {
|
|
4629
|
+
track.stop();
|
|
4630
|
+
});
|
|
4631
|
+
this.lifetime.signal.throwIfAborted();
|
|
4632
|
+
const blob = new Blob(this.chunks, { type: recorder.mimeType });
|
|
4633
|
+
if (blob.size === 0) throw new RecordingError("empty");
|
|
4634
|
+
const decoded = await context.decodeAudioData(await blob.arrayBuffer());
|
|
4635
|
+
this.lifetime.signal.throwIfAborted();
|
|
4636
|
+
const offline = new OfflineAudioContext(
|
|
4637
|
+
1,
|
|
4638
|
+
Math.max(1, Math.floor(Math.min(decoded.duration, maxDurationSeconds) * 16e3)),
|
|
4639
|
+
16e3
|
|
4640
|
+
);
|
|
4641
|
+
const source = offline.createBufferSource();
|
|
4642
|
+
source.buffer = decoded;
|
|
4643
|
+
source.connect(offline.destination);
|
|
4644
|
+
source.start();
|
|
4645
|
+
const resampled = await offline.startRendering();
|
|
4646
|
+
this.lifetime.signal.throwIfAborted();
|
|
4647
|
+
return encodeWave(resampled.getChannelData(0));
|
|
4648
|
+
} finally {
|
|
4649
|
+
await this.dispose();
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
/** 释放采集资源并作废未完成的授权竞态;重复调用安全。 */
|
|
4653
|
+
dispose() {
|
|
4654
|
+
if (!this.disposal) {
|
|
4655
|
+
let resolve;
|
|
4656
|
+
let reject;
|
|
4657
|
+
const closing = new Promise((res, rej) => {
|
|
4658
|
+
resolve = res;
|
|
4659
|
+
reject = rej;
|
|
4660
|
+
});
|
|
4661
|
+
this.disposal = closing;
|
|
4662
|
+
void this.release().then(resolve, reject);
|
|
4663
|
+
}
|
|
4664
|
+
return this.disposal;
|
|
4665
|
+
}
|
|
4666
|
+
async release() {
|
|
4667
|
+
this.lifetime.abort(new RecordingError("cancelled"));
|
|
4668
|
+
if (this.recorder?.state === "recording") this.recorder.stop();
|
|
4669
|
+
this.stream?.getTracks().forEach((track) => {
|
|
4670
|
+
track.stop();
|
|
4671
|
+
});
|
|
4672
|
+
this.stream = void 0;
|
|
4673
|
+
const context = this.context;
|
|
4674
|
+
this.context = void 0;
|
|
4675
|
+
this.analyser = void 0;
|
|
4676
|
+
this.chunks = [];
|
|
4677
|
+
try {
|
|
4678
|
+
if (context && context.state !== "closed") await context.close();
|
|
4679
|
+
} finally {
|
|
4680
|
+
this.onDispose();
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
};
|
|
4684
|
+
|
|
4685
|
+
// src/client/voice/voice-data.ts
|
|
4686
|
+
var VOICE_CHANNEL = "/hiwork-voice";
|
|
4687
|
+
var VOICE_DEFAULT_MAX_DURATION_SECONDS = 60;
|
|
4688
|
+
var VOICE_ERROR_TEXT_SESSION_EXPIRED = "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u518D\u8BD5";
|
|
4689
|
+
var VOICE_ERROR_TEXT_LIMIT_REACHED = "\u4ECA\u5929\u7684\u8BED\u97F3\u8BC6\u522B\u6B21\u6570\u5DF2\u7528\u5B8C\uFF0C\u660E\u5929\u518D\u6765";
|
|
4690
|
+
var VOICE_ERROR_TEXT_AUDIO_TOO_LARGE = "\u8FD9\u6BB5\u5F55\u97F3\u592A\u957F\u4E86\uFF0C\u8BF7\u5206\u6BB5\u8BF4\u5B8C";
|
|
4691
|
+
var VOICE_ERROR_TEXT_UPSTREAM = "\u8BED\u97F3\u8BC6\u522B\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
4692
|
+
var VOICE_ERROR_TEXT_UNREACHABLE = "\u8FDE\u63A5\u8BED\u97F3\u670D\u52A1\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
4693
|
+
var VOICE_INVALID_RESPONSE_TEXT = "\u8BED\u97F3\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
4694
|
+
var VOICE_UNRETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
4695
|
+
"SESSION_EXPIRED",
|
|
4696
|
+
"VOICE_DISABLED",
|
|
4697
|
+
"VOICE_NOT_CONFIGURED",
|
|
4698
|
+
"VOICE_LIMIT_REACHED",
|
|
4699
|
+
"VOICE_AUDIO_TOO_LARGE",
|
|
4700
|
+
"VOICE_BAD_REQUEST",
|
|
4701
|
+
"bad-request",
|
|
4702
|
+
"cancelled"
|
|
4703
|
+
]);
|
|
4704
|
+
var VoiceRpcError = class extends Error {
|
|
4705
|
+
constructor(code, message) {
|
|
4706
|
+
super(message);
|
|
4707
|
+
this.code = code;
|
|
4708
|
+
this.name = "VoiceRpcError";
|
|
4709
|
+
}
|
|
4710
|
+
};
|
|
4711
|
+
function unwrapVoiceResult(value) {
|
|
4712
|
+
if (typeof value !== "object" || value === null || !("ok" in value)) {
|
|
4713
|
+
throw new VoiceRpcError("internal", VOICE_INVALID_RESPONSE_TEXT);
|
|
4714
|
+
}
|
|
4715
|
+
const result = value;
|
|
4716
|
+
if (result.ok === true) return result.value;
|
|
4717
|
+
if (result.ok === false) {
|
|
4718
|
+
const code = typeof result.error?.code === "string" && result.error.code !== "" ? result.error.code : "internal";
|
|
4719
|
+
const message = typeof result.error?.message === "string" && result.error.message.trim() !== "" ? result.error.message : VOICE_ERROR_TEXT_UNREACHABLE;
|
|
4720
|
+
throw new VoiceRpcError(code, message);
|
|
4721
|
+
}
|
|
4722
|
+
throw new VoiceRpcError("internal", VOICE_INVALID_RESPONSE_TEXT);
|
|
4723
|
+
}
|
|
4724
|
+
function parseVoiceStatus(value) {
|
|
4725
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4726
|
+
throw new VoiceRpcError("internal", VOICE_INVALID_RESPONSE_TEXT);
|
|
4727
|
+
}
|
|
4728
|
+
const record = value;
|
|
4729
|
+
const seconds = record["maxDurationSeconds"];
|
|
4730
|
+
return {
|
|
4731
|
+
enabled: record["enabled"] === true,
|
|
4732
|
+
maxDurationSeconds: typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0 ? seconds : VOICE_DEFAULT_MAX_DURATION_SECONDS
|
|
4733
|
+
};
|
|
4734
|
+
}
|
|
4735
|
+
function parseVoiceTranscript(value) {
|
|
4736
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4737
|
+
throw new VoiceRpcError("internal", VOICE_INVALID_RESPONSE_TEXT);
|
|
4738
|
+
}
|
|
4739
|
+
const text6 = value["text"];
|
|
4740
|
+
if (typeof text6 !== "string") throw new VoiceRpcError("internal", VOICE_INVALID_RESPONSE_TEXT);
|
|
4741
|
+
return { text: text6 };
|
|
4742
|
+
}
|
|
4743
|
+
function voiceErrorMessageOf(error) {
|
|
4744
|
+
const code = error instanceof VoiceRpcError ? error.code : "";
|
|
4745
|
+
switch (code) {
|
|
4746
|
+
case "SESSION_EXPIRED":
|
|
4747
|
+
return VOICE_ERROR_TEXT_SESSION_EXPIRED;
|
|
4748
|
+
case "VOICE_LIMIT_REACHED":
|
|
4749
|
+
return VOICE_ERROR_TEXT_LIMIT_REACHED;
|
|
4750
|
+
case "VOICE_AUDIO_TOO_LARGE":
|
|
4751
|
+
return VOICE_ERROR_TEXT_AUDIO_TOO_LARGE;
|
|
4752
|
+
case "VOICE_UPSTREAM":
|
|
4753
|
+
return VOICE_ERROR_TEXT_UPSTREAM;
|
|
4754
|
+
default:
|
|
4755
|
+
return VOICE_ERROR_TEXT_UNREACHABLE;
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4758
|
+
function voiceErrorRetryable(error) {
|
|
4759
|
+
const code = error instanceof VoiceRpcError ? error.code : "";
|
|
4760
|
+
return !VOICE_UNRETRYABLE_CODES.has(code);
|
|
4761
|
+
}
|
|
4762
|
+
function createVoiceRuntime(rpc) {
|
|
4763
|
+
return {
|
|
4764
|
+
status: async () => parseVoiceStatus(unwrapVoiceResult(await rpc.call(VOICE_CHANNEL, "status", {}))),
|
|
4765
|
+
transcribe: async (audioBase642, signal) => parseVoiceTranscript(
|
|
4766
|
+
unwrapVoiceResult(await rpc.call(VOICE_CHANNEL, "transcribe", { audioBase64: audioBase642 }, signal))
|
|
4767
|
+
)
|
|
4768
|
+
};
|
|
4769
|
+
}
|
|
4770
|
+
|
|
4771
|
+
// src/client/voice/VoiceOverlay.tsx
|
|
4772
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
4773
|
+
var BAR_COUNT = 28;
|
|
4774
|
+
var LEVEL_INTERVAL_MS = 80;
|
|
4775
|
+
var TICK_INTERVAL_MS = 200;
|
|
4776
|
+
function formatClock(seconds) {
|
|
4777
|
+
const whole = Math.max(0, Math.floor(seconds));
|
|
4778
|
+
const m = Math.floor(whole / 60);
|
|
4779
|
+
const s = whole % 60;
|
|
4780
|
+
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
4781
|
+
}
|
|
4782
|
+
function rmsToLevel(rms) {
|
|
4783
|
+
return Math.min(1, Math.max(0, rms) * 6);
|
|
4784
|
+
}
|
|
4785
|
+
function MicIcon2({ size }) {
|
|
4786
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
|
|
4787
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("rect", { x: "9", y: "2.75", width: "6", height: "11.5", rx: "3", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
4788
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M5.5 11.5a6.5 6.5 0 0 0 13 0", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
|
|
4789
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 18v3.25", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
|
|
4790
|
+
] });
|
|
4791
|
+
}
|
|
4792
|
+
function VoiceOverlay(props) {
|
|
4793
|
+
const { runtime, onClose, insertDraft } = props;
|
|
4794
|
+
const [phase, setPhase] = (0, import_react9.useState)("checking");
|
|
4795
|
+
const [error, setError] = (0, import_react9.useState)(void 0);
|
|
4796
|
+
const [levels, setLevels] = (0, import_react9.useState)(() => Array.from({ length: BAR_COUNT }, () => 0.06));
|
|
4797
|
+
const [elapsed, setElapsed] = (0, import_react9.useState)(0);
|
|
4798
|
+
const [maxDuration, setMaxDuration] = (0, import_react9.useState)(60);
|
|
4799
|
+
const [copied, setCopied] = (0, import_react9.useState)(false);
|
|
4800
|
+
const recordingRef = (0, import_react9.useRef)(void 0);
|
|
4801
|
+
const wavRef = (0, import_react9.useRef)(void 0);
|
|
4802
|
+
const startedAtRef = (0, import_react9.useRef)(0);
|
|
4803
|
+
const abortRef = (0, import_react9.useRef)(void 0);
|
|
4804
|
+
const disposedRef = (0, import_react9.useRef)(false);
|
|
4805
|
+
const failWith = (message, opts = {}) => {
|
|
4806
|
+
if (disposedRef.current) return;
|
|
4807
|
+
setPhase("error");
|
|
4808
|
+
setError({
|
|
4809
|
+
message,
|
|
4810
|
+
holdWav: opts.holdWav ?? false,
|
|
4811
|
+
canRestart: opts.canRestart ?? true,
|
|
4812
|
+
...opts.recognizedText === void 0 ? {} : { recognizedText: opts.recognizedText }
|
|
4813
|
+
});
|
|
4814
|
+
};
|
|
4815
|
+
const beginRecording = () => {
|
|
4816
|
+
setError(void 0);
|
|
4817
|
+
setPhase("requesting");
|
|
4818
|
+
setElapsed(0);
|
|
4819
|
+
setLevels(Array.from({ length: BAR_COUNT }, () => 0.06));
|
|
4820
|
+
const recording2 = new Recording(() => {
|
|
4821
|
+
if (recordingRef.current === recording2) recordingRef.current = void 0;
|
|
4822
|
+
});
|
|
4823
|
+
recordingRef.current = recording2;
|
|
4824
|
+
recording2.start().then(() => {
|
|
4825
|
+
if (disposedRef.current) return;
|
|
4826
|
+
startedAtRef.current = Date.now();
|
|
4827
|
+
setPhase("recording");
|
|
4828
|
+
}).catch((err) => {
|
|
4829
|
+
if (disposedRef.current) return;
|
|
4830
|
+
if (err instanceof RecordingError) {
|
|
4831
|
+
if (err.kind === "permission") {
|
|
4832
|
+
failWith("\u6CA1\u6709\u9EA6\u514B\u98CE\u6743\u9650\uFF1A\u8BF7\u5728\u7CFB\u7EDF\u8BBE\u7F6E\u4E2D\u5141\u8BB8 HiWork \u4F7F\u7528\u9EA6\u514B\u98CE\uFF0C\u7136\u540E\u91CD\u8BD5\u3002", { canRestart: true });
|
|
4833
|
+
} else if (err.kind === "unavailable") {
|
|
4834
|
+
failWith("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u5F55\u97F3\uFF08\u627E\u4E0D\u5230\u53EF\u7528\u7684\u9EA6\u514B\u98CE\uFF09\u3002", { canRestart: false });
|
|
4835
|
+
} else if (err.kind === "cancelled") {
|
|
4836
|
+
onClose();
|
|
4837
|
+
} else {
|
|
4838
|
+
failWith("\u9EA6\u514B\u98CE\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002", { canRestart: true });
|
|
4839
|
+
}
|
|
4840
|
+
return;
|
|
4841
|
+
}
|
|
4842
|
+
failWith(err instanceof Error ? err.message : "\u9EA6\u514B\u98CE\u542F\u52A8\u5931\u8D25\u3002", { canRestart: true });
|
|
4843
|
+
});
|
|
4844
|
+
};
|
|
4845
|
+
const transcribeWav = () => {
|
|
4846
|
+
if (phase === "transcribing") return;
|
|
4847
|
+
const wav = wavRef.current;
|
|
4848
|
+
if (wav === void 0) return;
|
|
4849
|
+
setPhase("transcribing");
|
|
4850
|
+
setError(void 0);
|
|
4851
|
+
const abort = new AbortController();
|
|
4852
|
+
abortRef.current = abort;
|
|
4853
|
+
runtime.transcribe(audioBase64(wav), abort.signal).then(({ text: text6 }) => {
|
|
4854
|
+
if (disposedRef.current) return;
|
|
4855
|
+
if (text6.trim() === "") {
|
|
4856
|
+
failWith("\u6CA1\u6709\u8BC6\u522B\u5230\u8BF4\u8BDD\u5185\u5BB9\uFF0C\u8BF7\u9760\u8FD1\u9EA6\u514B\u98CE\u518D\u8BD5\u4E00\u6B21\u3002", { holdWav: true, canRestart: true });
|
|
4857
|
+
return;
|
|
4858
|
+
}
|
|
4859
|
+
const result = insertDraft(text6);
|
|
4860
|
+
if (result.ok) {
|
|
4861
|
+
onClose();
|
|
4862
|
+
return;
|
|
4863
|
+
}
|
|
4864
|
+
const reason = result.reason === "no-target" ? "\u6CA1\u6709\u627E\u5230\u6253\u5F00\u7684\u4F1A\u8BDD\u8F93\u5165\u6846\u3002" : result.reason === "busy" ? "\u8F93\u5165\u6846\u6B63\u5728\u63D0\u4EA4\u4E2D\uFF0C\u6682\u65F6\u4E0D\u80FD\u5199\u5165\u3002" : "\u5199\u5165\u8F93\u5165\u6846\u88AB\u62D2\u7EDD\u3002";
|
|
4865
|
+
failWith(`${reason}\u8BC6\u522B\u6587\u672C\u672A\u4E22\u5931\uFF0C\u53EF\u590D\u5236\u540E\u624B\u52A8\u7C98\u8D34\u3002`, {
|
|
4866
|
+
holdWav: false,
|
|
4867
|
+
canRestart: true,
|
|
4868
|
+
recognizedText: text6
|
|
4869
|
+
});
|
|
4870
|
+
}).catch((err) => {
|
|
4871
|
+
if (disposedRef.current || abort.signal.aborted) return;
|
|
4872
|
+
const retryable = voiceErrorRetryable(err);
|
|
4873
|
+
failWith(voiceErrorMessageOf(err), { holdWav: retryable, canRestart: true });
|
|
4874
|
+
});
|
|
4875
|
+
};
|
|
4876
|
+
const finish = () => {
|
|
4877
|
+
if (phase !== "recording") return;
|
|
4878
|
+
const recording2 = recordingRef.current;
|
|
4879
|
+
if (recording2 === void 0) return;
|
|
4880
|
+
setPhase("transcribing");
|
|
4881
|
+
recording2.stop(maxDuration).then((wav) => {
|
|
4882
|
+
if (disposedRef.current) return;
|
|
4883
|
+
if (isSilentWav(wav)) {
|
|
4884
|
+
failWith(
|
|
4885
|
+
"\u5F55\u97F3\u6CA1\u6709\u91C7\u5230\u58F0\u97F3\uFF1A\u591A\u534A\u662F\u7CFB\u7EDF\u62D2\u7EDD\u4E86\u9EA6\u514B\u98CE\u6743\u9650\uFF08macOS\uFF1A\u7CFB\u7EDF\u8BBE\u7F6E \u2192 \u9690\u79C1\u4E0E\u5B89\u5168\u6027 \u2192 \u9EA6\u514B\u98CE \u2192 \u52FE\u9009 HiWork\uFF09\uFF0C\u6253\u5F00\u540E\u518D\u5F55\u4E00\u6B21\u3002",
|
|
4886
|
+
{ holdWav: false }
|
|
4887
|
+
);
|
|
4888
|
+
return;
|
|
4889
|
+
}
|
|
4890
|
+
wavRef.current = wav;
|
|
4891
|
+
transcribeWav();
|
|
4892
|
+
}).catch((err) => {
|
|
4893
|
+
if (disposedRef.current) return;
|
|
4894
|
+
if (err instanceof RecordingError) {
|
|
4895
|
+
failWith(err.kind === "cancelled" ? "\u5F55\u97F3\u5DF2\u53D6\u6D88\u3002" : "\u8FD9\u6BB5\u5F55\u97F3\u662F\u7A7A\u7684\uFF0C\u8BF7\u518D\u8BD5\u4E00\u6B21\u3002", { holdWav: false });
|
|
4896
|
+
return;
|
|
4897
|
+
}
|
|
4898
|
+
failWith(err instanceof Error ? err.message : "\u5F55\u97F3\u5904\u7406\u5931\u8D25\u3002", { holdWav: false });
|
|
4899
|
+
});
|
|
4900
|
+
};
|
|
4901
|
+
(0, import_react9.useEffect)(() => {
|
|
4902
|
+
disposedRef.current = false;
|
|
4903
|
+
let cancelled = false;
|
|
4904
|
+
runtime.status().then((status) => {
|
|
4905
|
+
if (cancelled) return;
|
|
4906
|
+
setMaxDuration(status.maxDurationSeconds);
|
|
4907
|
+
if (status.enabled !== true) {
|
|
4908
|
+
failWith(VOICE_ERROR_TEXT_UNREACHABLE, { canRestart: false });
|
|
4909
|
+
return;
|
|
4910
|
+
}
|
|
4911
|
+
beginRecording();
|
|
4912
|
+
}).catch(() => {
|
|
4913
|
+
if (cancelled) return;
|
|
4914
|
+
failWith(VOICE_ERROR_TEXT_UNREACHABLE, { canRestart: false });
|
|
4915
|
+
});
|
|
4916
|
+
return () => {
|
|
4917
|
+
cancelled = true;
|
|
4918
|
+
disposedRef.current = true;
|
|
4919
|
+
abortRef.current?.abort();
|
|
4920
|
+
void recordingRef.current?.dispose().catch(() => void 0);
|
|
4921
|
+
recordingRef.current = void 0;
|
|
4922
|
+
};
|
|
4923
|
+
}, []);
|
|
4924
|
+
(0, import_react9.useEffect)(() => {
|
|
4925
|
+
if (phase !== "recording") return;
|
|
4926
|
+
const levelTimer = window.setInterval(() => {
|
|
4927
|
+
const rms = recordingRef.current?.amplitude() ?? 0;
|
|
4928
|
+
setLevels((prev) => [...prev.slice(1), Math.max(0.06, rmsToLevel(rms))]);
|
|
4929
|
+
}, LEVEL_INTERVAL_MS);
|
|
4930
|
+
const clockTimer = window.setInterval(() => {
|
|
4931
|
+
setElapsed((Date.now() - startedAtRef.current) / 1e3);
|
|
4932
|
+
}, TICK_INTERVAL_MS);
|
|
4933
|
+
return () => {
|
|
4934
|
+
window.clearInterval(levelTimer);
|
|
4935
|
+
window.clearInterval(clockTimer);
|
|
4936
|
+
};
|
|
4937
|
+
}, [phase]);
|
|
4938
|
+
(0, import_react9.useEffect)(() => {
|
|
4939
|
+
if (phase !== "recording") return;
|
|
4940
|
+
if (elapsed < maxDuration) return;
|
|
4941
|
+
finish();
|
|
4942
|
+
}, [phase, elapsed, maxDuration]);
|
|
4943
|
+
const handleClose = () => {
|
|
4944
|
+
abortRef.current?.abort();
|
|
4945
|
+
onClose();
|
|
4946
|
+
};
|
|
4947
|
+
(0, import_react9.useEffect)(() => {
|
|
4948
|
+
const onKey = (event) => {
|
|
4949
|
+
if (event.key !== "Escape") return;
|
|
4950
|
+
event.preventDefault();
|
|
4951
|
+
handleClose();
|
|
4952
|
+
};
|
|
4953
|
+
window.addEventListener("keydown", onKey);
|
|
4954
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
4955
|
+
}, []);
|
|
4956
|
+
const copyRecognized = () => {
|
|
4957
|
+
const text6 = error?.recognizedText;
|
|
4958
|
+
if (text6 === void 0) return;
|
|
4959
|
+
void navigator.clipboard.writeText(text6).then(() => setCopied(true)).catch(() => setCopied(false));
|
|
4960
|
+
};
|
|
4961
|
+
const recording = phase === "recording";
|
|
4962
|
+
const idle = phase === "checking" || phase === "requesting" || phase === "transcribing";
|
|
4963
|
+
const canRetryTranscribe = error?.holdWav === true && error.recognizedText === void 0;
|
|
4964
|
+
const canRestart = error?.canRestart === true;
|
|
4965
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
4966
|
+
"div",
|
|
4967
|
+
{
|
|
4968
|
+
className: "hiwv-backdrop",
|
|
4969
|
+
onMouseDown: (event) => {
|
|
4970
|
+
if (event.target === event.currentTarget && !idle) handleClose();
|
|
4971
|
+
},
|
|
4972
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hiwv-panel", role: "dialog", "aria-modal": "true", "aria-label": "\u8BED\u97F3\u8F93\u5165", children: [
|
|
4973
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hiwv-head", children: [
|
|
4974
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "hiwv-title", children: "\u8BED\u97F3\u8F93\u5165" }),
|
|
4975
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "hiwv-hint", children: recording ? `\u5DF2\u5F55 ${formatClock(elapsed)} / \u4E0A\u9650 ${formatClock(maxDuration)}` : `\u5355\u6B21\u6700\u957F ${formatClock(maxDuration)}` })
|
|
4976
|
+
] }),
|
|
4977
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `hiwv-stage${recording ? "" : " hiwv-stageIdle"}`, children: recording || phase === "transcribing" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
4978
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `hiwv-orb${phase === "transcribing" ? " hiwv-orbBusy" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MicIcon2, { size: 30 }) }),
|
|
4979
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hiwv-bars", "aria-hidden": "true", children: levels.map((level, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
4980
|
+
"i",
|
|
4981
|
+
{
|
|
4982
|
+
style: { height: `${Math.round(8 + level * 46)}px` },
|
|
4983
|
+
className: phase === "transcribing" ? "hiwv-barIdle" : void 0
|
|
4984
|
+
},
|
|
4985
|
+
index
|
|
4986
|
+
)) }),
|
|
4987
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hiwv-status", children: phase === "transcribing" ? "\u6B63\u5728\u8BC6\u522B\u2026" : "\u6B63\u5728\u8046\u542C\uFF0C\u8BB2\u5B8C\u540E\u70B9\u300C\u5B8C\u6210\u300D" })
|
|
4988
|
+
] }) : phase === "checking" || phase === "requesting" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hiwv-status", children: "\u6B63\u5728\u51C6\u5907\u9EA6\u514B\u98CE\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hiwv-errorBox", children: [
|
|
4989
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hiwv-errorMsg", children: error?.message ?? "\u51FA\u4E86\u70B9\u95EE\u9898\u3002" }),
|
|
4990
|
+
error?.recognizedText !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hiwv-recognized", children: [
|
|
4991
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hiwv-recognizedText", children: error.recognizedText }),
|
|
4992
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnSecondary", onClick: copyRecognized, children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6587\u672C" })
|
|
4993
|
+
] })
|
|
4994
|
+
] }) }),
|
|
4995
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hiwv-actions", children: [
|
|
4996
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnSecondary", onClick: handleClose, children: "\u53D6\u6D88" }),
|
|
4997
|
+
recording && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnPrimary", onClick: finish, children: "\u5B8C\u6210" }),
|
|
4998
|
+
phase === "error" && canRetryTranscribe && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnPrimary", onClick: transcribeWav, children: "\u91CD\u8BD5\u8BC6\u522B" }),
|
|
4999
|
+
phase === "error" && canRestart && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
5000
|
+
"button",
|
|
5001
|
+
{
|
|
5002
|
+
type: "button",
|
|
5003
|
+
className: `hiwv-btn ${canRetryTranscribe ? "hiwv-btnSecondary" : "hiwv-btnPrimary"}`,
|
|
5004
|
+
onClick: beginRecording,
|
|
5005
|
+
children: "\u518D\u5F55\u4E00\u6B21"
|
|
5006
|
+
}
|
|
5007
|
+
)
|
|
5008
|
+
] })
|
|
5009
|
+
] })
|
|
5010
|
+
}
|
|
5011
|
+
);
|
|
5012
|
+
}
|
|
5013
|
+
|
|
5014
|
+
// src/client/voice/voice-styles.ts
|
|
5015
|
+
var VOICE_STYLE_ID = "hiwork-core-voice-styles";
|
|
5016
|
+
var VOICE_STYLE_TAG_ID = "hiwork-core/voice.css";
|
|
5017
|
+
function ensureVoiceStyle(doc) {
|
|
5018
|
+
const existing = doc.getElementById(VOICE_STYLE_ID);
|
|
5019
|
+
if (existing !== null) {
|
|
5020
|
+
if (!existing.hasAttribute("data-plugin")) ownStyleTag(existing, VOICE_STYLE_TAG_ID);
|
|
5021
|
+
return;
|
|
5022
|
+
}
|
|
5023
|
+
const style = doc.createElement("style");
|
|
5024
|
+
style.id = VOICE_STYLE_ID;
|
|
5025
|
+
ownStyleTag(style, VOICE_STYLE_TAG_ID);
|
|
5026
|
+
style.textContent = voiceCss;
|
|
5027
|
+
doc.head.appendChild(style);
|
|
5028
|
+
}
|
|
5029
|
+
|
|
5030
|
+
// src/client/voice/entry.ts
|
|
5031
|
+
var VOICE_INPUT_SLOT = "conversation.input.right";
|
|
5032
|
+
var VOICE_INPUT_SLOT_ID = "hiwork-core-voice";
|
|
5033
|
+
var VOICE_INPUT_SLOT_ORDER = 10;
|
|
5034
|
+
var VOICE_ROOT_CLASS = "hiwv-root";
|
|
5035
|
+
function createOverlayController(deps) {
|
|
5036
|
+
let container;
|
|
5037
|
+
let root;
|
|
5038
|
+
const close = () => {
|
|
5039
|
+
const mounted = root;
|
|
5040
|
+
root = void 0;
|
|
5041
|
+
mounted?.unmount();
|
|
5042
|
+
container?.remove();
|
|
5043
|
+
container = void 0;
|
|
5044
|
+
};
|
|
5045
|
+
const open = (sessionId) => {
|
|
5046
|
+
if (root !== void 0) return;
|
|
5047
|
+
const doc = deps.doc;
|
|
5048
|
+
if (doc === void 0) return;
|
|
5049
|
+
container = doc.createElement("div");
|
|
5050
|
+
container.className = VOICE_ROOT_CLASS;
|
|
5051
|
+
doc.body.appendChild(container);
|
|
5052
|
+
root = (0, import_client3.createRoot)(container);
|
|
5053
|
+
root.render(
|
|
5054
|
+
(0, import_react10.createElement)(VoiceOverlay, {
|
|
5055
|
+
runtime: deps.runtime,
|
|
5056
|
+
onClose: close,
|
|
5057
|
+
// 会话 id 缺省(拿不到槽位注入)时由 insertDraftText 退到当前打开会话;
|
|
5058
|
+
// 服务本身**在插入那一刻现取**(装配期它可能还没上线,见 VoiceEntryDeps.resolveSessions)。
|
|
5059
|
+
insertDraft: (text6) => insertDraftText(deps.resolveSessions(), text6, sessionId)
|
|
5060
|
+
})
|
|
5061
|
+
);
|
|
5062
|
+
};
|
|
5063
|
+
return { open, close };
|
|
5064
|
+
}
|
|
5065
|
+
async function installVoiceInput(deps) {
|
|
5066
|
+
const doc = deps.document ?? (typeof document === "undefined" ? void 0 : document);
|
|
5067
|
+
const runtime = createVoiceRuntime(deps.rpc);
|
|
5068
|
+
const overlay = createOverlayController({ runtime, resolveSessions: deps.resolveSessions, doc });
|
|
5069
|
+
if (doc !== void 0) ensureVoiceStyle(doc);
|
|
5070
|
+
const status = await runtime.status().catch(() => void 0);
|
|
5071
|
+
const dispose = () => overlay.close();
|
|
5072
|
+
if (deps.signal?.aborted === true || status?.enabled !== true) return { enabled: false, dispose };
|
|
5073
|
+
deps.registerMic({ open: (sessionId) => overlay.open(sessionId) });
|
|
5074
|
+
return { enabled: true, dispose };
|
|
5075
|
+
}
|
|
5076
|
+
|
|
4183
5077
|
// src/client/index.ts
|
|
4184
5078
|
var inject = ["slots", "locale", "remote", "remote.credentials", "settingsScope", "connection"];
|
|
4185
5079
|
var INTERACTION_STYLE_ID = "hiwork-core-interaction-styles";
|
|
@@ -4188,6 +5082,7 @@ function injectInteractionStyles() {
|
|
|
4188
5082
|
if (document.getElementById(INTERACTION_STYLE_ID) !== null) return;
|
|
4189
5083
|
const style = document.createElement("style");
|
|
4190
5084
|
style.id = INTERACTION_STYLE_ID;
|
|
5085
|
+
ownStyleTag(style, "hiwork-core/interaction-styles.css");
|
|
4191
5086
|
style.textContent = [interactionCss, brandCss, frameCss, hostHideCss, composerCss].join("\n");
|
|
4192
5087
|
document.head.appendChild(style);
|
|
4193
5088
|
}
|
|
@@ -4236,6 +5131,39 @@ function apply(ctx) {
|
|
|
4236
5131
|
order: 20,
|
|
4237
5132
|
inject: () => ({ store: sessionCost })
|
|
4238
5133
|
}, SessionCost));
|
|
5134
|
+
ctx.effect(() => {
|
|
5135
|
+
const controller2 = new AbortController();
|
|
5136
|
+
let handle;
|
|
5137
|
+
void installVoiceInput({
|
|
5138
|
+
rpc: connection.rpc,
|
|
5139
|
+
// sessions 不在 inject 列表里(那是可选能力),只能按结构读取;**必须现取**:
|
|
5140
|
+
// 它比本插件晚上线,装配期 `ctx.get('sessions')` 是 undefined(真机实测)。装配期
|
|
5141
|
+
// 快照会把 undefined 冻进浮层控制器,每次录音都报「没有找到打开的会话输入框」。
|
|
5142
|
+
// 拿不到时仍按原样降级:草稿插入判定为「没有打开的会话」,浮层照常给出复制出口。
|
|
5143
|
+
resolveSessions: () => ctx.get("sessions"),
|
|
5144
|
+
registerMic: (inject2) => {
|
|
5145
|
+
ctx.slots.inject(VOICE_INPUT_SLOT, () => ctx.slots.register({
|
|
5146
|
+
name: VOICE_INPUT_SLOT,
|
|
5147
|
+
id: VOICE_INPUT_SLOT_ID,
|
|
5148
|
+
order: VOICE_INPUT_SLOT_ORDER,
|
|
5149
|
+
// session 作用域 list 槽:inject 工厂的会话 id 由框架下发,绑进 open 里。
|
|
5150
|
+
inject: (sessionId) => ({ open: () => inject2.open(sessionId) })
|
|
5151
|
+
}, MicButton));
|
|
5152
|
+
},
|
|
5153
|
+
signal: controller2.signal
|
|
5154
|
+
}).then((installed) => {
|
|
5155
|
+
if (controller2.signal.aborted) {
|
|
5156
|
+
installed.dispose();
|
|
5157
|
+
return;
|
|
5158
|
+
}
|
|
5159
|
+
handle = installed;
|
|
5160
|
+
}).catch(() => void 0);
|
|
5161
|
+
return () => {
|
|
5162
|
+
controller2.abort();
|
|
5163
|
+
handle?.dispose();
|
|
5164
|
+
handle = void 0;
|
|
5165
|
+
};
|
|
5166
|
+
}, "hiwork-core: voice input");
|
|
4239
5167
|
ctx.effect(() => observeAccountChange(controller.inject().hooks.aiRouterCard, () => {
|
|
4240
5168
|
usage.clear();
|
|
4241
5169
|
sessionCost.clear();
|
|
@@ -4266,7 +5194,7 @@ function apply(ctx) {
|
|
|
4266
5194
|
function installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench, pmsTodo) {
|
|
4267
5195
|
const featureRegistry = createFeatureRegistry();
|
|
4268
5196
|
let center;
|
|
4269
|
-
const
|
|
5197
|
+
const resolveSessions = () => ctx.get("sessions");
|
|
4270
5198
|
const locale = ctx.get("locale");
|
|
4271
5199
|
const face = {
|
|
4272
5200
|
featureRegistry,
|
|
@@ -4274,7 +5202,7 @@ function installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench,
|
|
|
4274
5202
|
// 顺序固定:先退出 takeover 恢复原生会话区,再交给宿主导航。
|
|
4275
5203
|
openNativeSession: (sessionId) => {
|
|
4276
5204
|
featureRegistry.openNative();
|
|
4277
|
-
|
|
5205
|
+
resolveSessions()?.open(sessionId);
|
|
4278
5206
|
},
|
|
4279
5207
|
openNativeHome: () => {
|
|
4280
5208
|
if (center !== void 0) {
|
|
@@ -4316,14 +5244,14 @@ function installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench,
|
|
|
4316
5244
|
},
|
|
4317
5245
|
// PMS 个人待办面:工作台域(hiwork-workbench)按这个成员判断「有没有这个能力」,老 core 上
|
|
4318
5246
|
// 它是 undefined,右栏 tab 降级成「请升级 HiWork 客户端」(spec §6 的降级矩阵第一行)。
|
|
4319
|
-
//
|
|
4320
|
-
//
|
|
4321
|
-
// `
|
|
5247
|
+
// 六项与 spec §4.4 逐字对应;核心**只发端点调用**——零输入登录(网关断言 → PMS 换令牌)
|
|
5248
|
+
// 整段在 Host 半边,客户端连断言都看不到(§3 D1 / D6)。
|
|
5249
|
+
// `setStatus` 在面里折成 void(§4.4 冻结的形状),仓库返回值留给诊断与测试。
|
|
5250
|
+
// `myTasks` / `completeTask` 是 §3 D7 追加的两项,**在面里也是可选成员**(旧 core 只有前六项,
|
|
4322
5251
|
// 消费方按 `face.pmsTodo.myTasks !== undefined` 判断,见 src/client/pms-todo.ts 的版本闸口)。
|
|
5252
|
+
// 没有 `login` / `logout`(spec §3 D3 随账号密码通道一起删除):本面不许再长出任何凭据入口。
|
|
4323
5253
|
pmsTodo: {
|
|
4324
5254
|
status: () => pmsTodo.status(),
|
|
4325
|
-
login: async (input) => await pmsTodo.login(input),
|
|
4326
|
-
logout: async () => await pmsTodo.logout(),
|
|
4327
5255
|
today: async (signal) => await pmsTodo.today(signal),
|
|
4328
5256
|
statistics: async (signal) => await pmsTodo.statistics(signal),
|
|
4329
5257
|
setStatus: async (input) => {
|
|
@@ -4341,7 +5269,7 @@ function installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench,
|
|
|
4341
5269
|
center = mountFeatureCenter({
|
|
4342
5270
|
registry: featureRegistry,
|
|
4343
5271
|
openNativeSession: (sessionId) => {
|
|
4344
|
-
|
|
5272
|
+
resolveSessions()?.open(sessionId);
|
|
4345
5273
|
},
|
|
4346
5274
|
...locale === void 0 ? {} : { subscribeLocale: (listener) => locale.subscribe(listener) }
|
|
4347
5275
|
});
|