viveworker 0.5.2 → 0.5.5
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/README.md +2 -2
- package/package.json +1 -1
- package/scripts/a2a-cli.mjs +1 -1
- package/scripts/moltbook-api.mjs +56 -16
- package/scripts/moltbook-watcher.mjs +30 -11
- package/scripts/viveworker-bridge.mjs +585 -72
- package/web/app.css +175 -1
- package/web/app.js +408 -17
- package/web/i18n.js +88 -2
package/web/app.js
CHANGED
|
@@ -50,9 +50,12 @@ const state = {
|
|
|
50
50
|
moltbookScoutStatus: null,
|
|
51
51
|
moltbookRecentTitlesExpanded: 0,
|
|
52
52
|
a2aRelayStatus: null,
|
|
53
|
+
a2aShareStatus: null,
|
|
54
|
+
a2aShareRecentExpanded: 0,
|
|
53
55
|
a2aTaskExecutorPick: "codex",
|
|
54
56
|
pushNotice: "",
|
|
55
57
|
pushError: "",
|
|
58
|
+
ambientSuggestionCopyState: null,
|
|
56
59
|
deviceNotice: "",
|
|
57
60
|
deviceError: "",
|
|
58
61
|
imageViewer: null,
|
|
@@ -218,6 +221,7 @@ async function refreshAuthenticatedState() {
|
|
|
218
221
|
await refreshPushStatus();
|
|
219
222
|
await fetchMoltbookScoutStatus();
|
|
220
223
|
await fetchA2aRelayStatus();
|
|
224
|
+
await fetchA2aShareStatus();
|
|
221
225
|
ensureCurrentSelection();
|
|
222
226
|
}
|
|
223
227
|
|
|
@@ -347,6 +351,18 @@ async function fetchA2aRelayStatus() {
|
|
|
347
351
|
}
|
|
348
352
|
}
|
|
349
353
|
|
|
354
|
+
async function fetchA2aShareStatus() {
|
|
355
|
+
if (!state.session?.a2aShareEnabled) {
|
|
356
|
+
state.a2aShareStatus = null;
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
state.a2aShareStatus = await apiGet("/api/share/status");
|
|
361
|
+
} catch {
|
|
362
|
+
state.a2aShareStatus = null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
350
366
|
async function getClientPushState() {
|
|
351
367
|
const registration = state.serviceWorkerRegistration || (await navigator.serviceWorker?.ready.catch(() => null));
|
|
352
368
|
if (registration) {
|
|
@@ -661,6 +677,7 @@ function timelineKindFilterOptions() {
|
|
|
661
677
|
const codexClaudeOptions = [
|
|
662
678
|
allOption,
|
|
663
679
|
{ id: "messages", label: L("timeline.kindFilter.messages"), icon: "timeline" },
|
|
680
|
+
{ id: "suggestions", label: L("timeline.kindFilter.suggestions"), icon: "suggestions" },
|
|
664
681
|
{ id: "files", label: L("timeline.kindFilter.files"), icon: "file-event" },
|
|
665
682
|
{ id: "approvals", label: L("timeline.kindFilter.approvals"), icon: "approval" },
|
|
666
683
|
{ id: "plans", label: L("timeline.kindFilter.plans"), icon: "plan" },
|
|
@@ -695,6 +712,8 @@ function timelineEntryMatchesKindFilter(entry, filterId) {
|
|
|
695
712
|
switch (filterId) {
|
|
696
713
|
case "messages":
|
|
697
714
|
return TIMELINE_MESSAGE_KINDS.has(kind);
|
|
715
|
+
case "suggestions":
|
|
716
|
+
return kind === "ambient_suggestions";
|
|
698
717
|
case "files":
|
|
699
718
|
return kind === "file_event";
|
|
700
719
|
case "approvals":
|
|
@@ -2332,6 +2351,10 @@ function timelineEntryThreadLabel(item, isMessage) {
|
|
|
2332
2351
|
}
|
|
2333
2352
|
|
|
2334
2353
|
function timelineEntryPrimaryText(item, status, { isMessageLike = false, isFileEvent = false } = {}) {
|
|
2354
|
+
if (item?.kind === "ambient_suggestions") {
|
|
2355
|
+
return item.summary || fallbackSummaryForKind(item.kind, status, item.provider);
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2335
2358
|
if (isMessageLike) {
|
|
2336
2359
|
return item.summary || fallbackSummaryForKind(item.kind, status, item.provider);
|
|
2337
2360
|
}
|
|
@@ -2344,6 +2367,10 @@ function timelineEntryPrimaryText(item, status, { isMessageLike = false, isFileE
|
|
|
2344
2367
|
}
|
|
2345
2368
|
|
|
2346
2369
|
function timelineEntrySecondaryText(item, status, primaryText, { isMessageLike = false, isFileEvent = false } = {}) {
|
|
2370
|
+
if (item?.kind === "ambient_suggestions") {
|
|
2371
|
+
return "";
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2347
2374
|
if (isMessageLike) {
|
|
2348
2375
|
return "";
|
|
2349
2376
|
}
|
|
@@ -2865,6 +2892,7 @@ function buildSettingsContext() {
|
|
|
2865
2892
|
}),
|
|
2866
2893
|
moltbookScout: state.moltbookScoutStatus,
|
|
2867
2894
|
a2aRelay: state.a2aRelayStatus,
|
|
2895
|
+
a2aShare: state.a2aShareStatus,
|
|
2868
2896
|
};
|
|
2869
2897
|
}
|
|
2870
2898
|
|
|
@@ -3025,6 +3053,13 @@ function settingsPageMeta(page) {
|
|
|
3025
3053
|
description: L("settings.a2aRelay.copy"),
|
|
3026
3054
|
icon: "link",
|
|
3027
3055
|
};
|
|
3056
|
+
case "a2aShare":
|
|
3057
|
+
return {
|
|
3058
|
+
id: "a2aShare",
|
|
3059
|
+
title: L("settings.a2aShare.title"),
|
|
3060
|
+
description: L("settings.a2aShare.copy"),
|
|
3061
|
+
icon: "link",
|
|
3062
|
+
};
|
|
3028
3063
|
case "a2aExecutor":
|
|
3029
3064
|
// Executor settings integrated into a2aRelay page — redirect.
|
|
3030
3065
|
return settingsPageMeta("a2aRelay");
|
|
@@ -3097,13 +3132,13 @@ function renderSettingsRoot(context, { mobile }) {
|
|
|
3097
3132
|
`
|
|
3098
3133
|
}
|
|
3099
3134
|
${renderSettingsGroup(L("settings.group.general"), generalRows)}
|
|
3100
|
-
${(state.session?.moltbookEnabled || state.session?.a2aRelayEnabled) ? renderSettingsGroup(L("settings.group.integrations"), [
|
|
3135
|
+
${(state.session?.moltbookEnabled || state.session?.a2aRelayEnabled || state.session?.a2aShareEnabled) ? renderSettingsGroup(L("settings.group.integrations"), [
|
|
3101
3136
|
state.session?.moltbookEnabled ? renderSettingsNavRow({
|
|
3102
3137
|
page: "moltbook",
|
|
3103
3138
|
icon: "item",
|
|
3104
3139
|
title: L("settings.moltbook.title"),
|
|
3105
3140
|
subtitle: L("settings.moltbook.subtitle"),
|
|
3106
|
-
value: context.moltbookScout?.enabled ?
|
|
3141
|
+
value: context.moltbookScout?.enabled ? L("settings.status.enabled") : L("settings.status.disabled"),
|
|
3107
3142
|
}) : "",
|
|
3108
3143
|
state.session?.a2aRelayEnabled ? renderSettingsNavRow({
|
|
3109
3144
|
page: "a2aRelay",
|
|
@@ -3112,6 +3147,13 @@ function renderSettingsRoot(context, { mobile }) {
|
|
|
3112
3147
|
subtitle: L("settings.a2aRelay.subtitle"),
|
|
3113
3148
|
value: context.a2aRelay?.connected ? L("settings.status.connected") : L("settings.a2aRelay.status.disconnected"),
|
|
3114
3149
|
}) : "",
|
|
3150
|
+
state.session?.a2aShareEnabled ? renderSettingsNavRow({
|
|
3151
|
+
page: "a2aShare",
|
|
3152
|
+
icon: "link",
|
|
3153
|
+
title: L("settings.a2aShare.title"),
|
|
3154
|
+
subtitle: L("settings.a2aShare.subtitle"),
|
|
3155
|
+
value: context.a2aShare?.enabled ? L("settings.status.enabled") : L("settings.status.disabled"),
|
|
3156
|
+
}) : "",
|
|
3115
3157
|
].filter(Boolean)) : ""}
|
|
3116
3158
|
${renderSettingsGroup(L("settings.pairing.title"), deviceRows)}
|
|
3117
3159
|
${renderSettingsGroup(L("settings.group.advanced"), advancedRows)}
|
|
@@ -3163,6 +3205,9 @@ function renderSettingsSubpage(context, { mobile }) {
|
|
|
3163
3205
|
case "a2aExecutor":
|
|
3164
3206
|
content = renderSettingsA2aRelayPage(context);
|
|
3165
3207
|
break;
|
|
3208
|
+
case "a2aShare":
|
|
3209
|
+
content = renderSettingsA2aSharePage(context);
|
|
3210
|
+
break;
|
|
3166
3211
|
default:
|
|
3167
3212
|
content = "";
|
|
3168
3213
|
}
|
|
@@ -3392,8 +3437,16 @@ function renderSettingsMoltbookPage(context) {
|
|
|
3392
3437
|
renderSettingsInfoRow(L("settings.row.moltbookBatchTopScore"), String(scout.batch.topScore)),
|
|
3393
3438
|
renderSettingsInfoRow(L("settings.row.moltbookBatchRemaining"), `${Math.floor(scout.batch.remainingSeconds / 60)}:${String(scout.batch.remainingSeconds % 60).padStart(2, "0")}`),
|
|
3394
3439
|
] : [];
|
|
3440
|
+
const accountRow = scout.account?.name && scout.account?.profileUrl
|
|
3441
|
+
? renderSettingsInfoRow(
|
|
3442
|
+
L("settings.row.moltbookAccount"),
|
|
3443
|
+
`<a href="${escapeHtml(scout.account.profileUrl)}" target="_blank" rel="noopener">${escapeHtml(scout.account.name)}</a>`,
|
|
3444
|
+
{ rawValue: true }
|
|
3445
|
+
)
|
|
3446
|
+
: null;
|
|
3395
3447
|
return `
|
|
3396
3448
|
<div class="settings-page">
|
|
3449
|
+
${accountRow ? renderSettingsGroup("", [accountRow]) : ""}
|
|
3397
3450
|
${renderSettingsGroup("", [
|
|
3398
3451
|
renderSettingsInfoRow(L("settings.row.moltbookQuota"), `${scout.sentToday} / ${scout.maxDaily}`),
|
|
3399
3452
|
renderSettingsInfoRow(L("settings.row.moltbookComposed"), `${scout.composedToday || 0} / 3`),
|
|
@@ -3417,7 +3470,19 @@ function renderSettingsMoltbookPage(context) {
|
|
|
3417
3470
|
const link = postId
|
|
3418
3471
|
? `<a href="https://www.moltbook.com/post/${escapeHtml(postId)}" target="_blank" rel="noopener">${escapeHtml(title)}</a>`
|
|
3419
3472
|
: escapeHtml(title);
|
|
3420
|
-
|
|
3473
|
+
const iconName = type === "reply" ? "moltbook-reply" : "moltbook-draft";
|
|
3474
|
+
const iconTone = type === "reply" ? "settings-icon-entry__icon--reply" : "settings-icon-entry__icon--post";
|
|
3475
|
+
return `
|
|
3476
|
+
<div class="settings-compose-entry settings-icon-entry">
|
|
3477
|
+
<span class="settings-icon-entry__icon ${iconTone}" aria-hidden="true">${renderIcon(iconName)}</span>
|
|
3478
|
+
<span class="settings-icon-entry__body">
|
|
3479
|
+
<span class="settings-icon-entry__title-row">
|
|
3480
|
+
<span class="settings-compose-entry__title">${link}</span>
|
|
3481
|
+
${badge}
|
|
3482
|
+
</span>
|
|
3483
|
+
</span>
|
|
3484
|
+
</div>
|
|
3485
|
+
`;
|
|
3421
3486
|
});
|
|
3422
3487
|
if (hasMore) {
|
|
3423
3488
|
const remaining = titles.length - visibleCount;
|
|
@@ -3445,7 +3510,7 @@ function renderSettingsA2aRelayPage(context) {
|
|
|
3445
3510
|
: relay.polling
|
|
3446
3511
|
? L("settings.a2aRelay.status.polling")
|
|
3447
3512
|
: L("settings.a2aRelay.status.disconnected");
|
|
3448
|
-
const profileUrl = `${relay.relayUrl}/${relay.userId}`;
|
|
3513
|
+
const profileUrl = `${relay.relayUrl}/u/${relay.userId}`;
|
|
3449
3514
|
const userIdLink = `<a href="${escapeHtml(profileUrl)}" target="_blank" rel="noopener">${escapeHtml(relay.userId)}</a>`;
|
|
3450
3515
|
const relayHost = (() => { try { return new URL(relay.relayUrl).host; } catch { return relay.relayUrl; } })();
|
|
3451
3516
|
const publicChecked = relay.acceptPublicTasks === true;
|
|
@@ -3465,20 +3530,22 @@ function renderSettingsA2aRelayPage(context) {
|
|
|
3465
3530
|
|
|
3466
3531
|
return `
|
|
3467
3532
|
<div class="settings-page">
|
|
3533
|
+
${renderSettingsGroup("", [
|
|
3534
|
+
renderSettingsInfoRow(L("settings.row.a2aStatus"), statusLabel),
|
|
3535
|
+
renderSettingsInfoRow(L("settings.row.a2aUserId"), userIdLink, { rawValue: true }),
|
|
3536
|
+
renderSettingsInfoRow(L("settings.row.a2aRelay"), relayHost),
|
|
3537
|
+
renderSettingsInfoRow(L("settings.row.a2aApiKey"), relay.apiKeyConfigured ? L("settings.a2aRelay.apiKey.configured") : L("settings.a2aRelay.apiKey.notConfigured")),
|
|
3538
|
+
relay.lastPollAtMs
|
|
3539
|
+
? renderSettingsInfoRow(L("settings.row.a2aLastPoll"), new Date(relay.lastPollAtMs).toLocaleString(state.locale))
|
|
3540
|
+
: "",
|
|
3541
|
+
].filter(Boolean))}
|
|
3468
3542
|
${(() => {
|
|
3469
3543
|
const stats = relay.taskStats || { received: 0, completed: 0, denied: 0 };
|
|
3470
|
-
return renderSettingsGroup("", [
|
|
3471
|
-
renderSettingsInfoRow(L("settings.row.a2aStatus"), statusLabel),
|
|
3472
|
-
renderSettingsInfoRow(L("settings.row.a2aUserId"), userIdLink, { rawValue: true }),
|
|
3473
|
-
renderSettingsInfoRow(L("settings.row.a2aRelay"), relayHost),
|
|
3474
|
-
renderSettingsInfoRow(L("settings.row.a2aApiKey"), relay.apiKeyConfigured ? L("settings.a2aRelay.apiKey.configured") : L("settings.a2aRelay.apiKey.notConfigured")),
|
|
3475
|
-
relay.lastPollAtMs
|
|
3476
|
-
? renderSettingsInfoRow(L("settings.row.a2aLastPoll"), new Date(relay.lastPollAtMs).toLocaleString(state.locale))
|
|
3477
|
-
: "",
|
|
3544
|
+
return renderSettingsGroup(L("settings.a2aRelay.taskStats.title"), [
|
|
3478
3545
|
renderSettingsInfoRow(L("settings.row.a2aTaskReceived"), String(stats.received)),
|
|
3479
3546
|
renderSettingsInfoRow(L("settings.row.a2aTaskCompleted"), String(stats.completed)),
|
|
3480
3547
|
renderSettingsInfoRow(L("settings.row.a2aTaskDenied"), String(stats.denied)),
|
|
3481
|
-
]
|
|
3548
|
+
]);
|
|
3482
3549
|
})()}
|
|
3483
3550
|
<section class="settings-group">
|
|
3484
3551
|
<p class="settings-group__title">${escapeHtml(L("settings.a2aRelay.publicTasks.title"))}</p>
|
|
@@ -3506,6 +3573,151 @@ function renderSettingsA2aRelayPage(context) {
|
|
|
3506
3573
|
`;
|
|
3507
3574
|
}
|
|
3508
3575
|
|
|
3576
|
+
function renderSettingsA2aSharePage(context) {
|
|
3577
|
+
const share = context.a2aShare;
|
|
3578
|
+
if (!share?.enabled) {
|
|
3579
|
+
return `
|
|
3580
|
+
<div class="settings-page">
|
|
3581
|
+
<p class="settings-page-copy muted">${escapeHtml(L("settings.a2aShare.unavailable"))}</p>
|
|
3582
|
+
</div>
|
|
3583
|
+
`;
|
|
3584
|
+
}
|
|
3585
|
+
const quota = share.quota || { bytes: 0, maxBytes: 0, count: 0, maxCount: 0 };
|
|
3586
|
+
const limits = share.limits || {};
|
|
3587
|
+
const items = Array.isArray(share.items) ? share.items : [];
|
|
3588
|
+
const statusLabel = share.error
|
|
3589
|
+
? L("settings.a2aShare.status.unreachable")
|
|
3590
|
+
: L("settings.status.enabled");
|
|
3591
|
+
const formatBytes = (bytes) => {
|
|
3592
|
+
if (!Number.isFinite(bytes)) return "—";
|
|
3593
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
3594
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
3595
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
3596
|
+
};
|
|
3597
|
+
const storageValue = share.quota
|
|
3598
|
+
? `${formatBytes(quota.bytes)} / ${formatBytes(quota.maxBytes || limits.maxTotalBytes || 0)}`
|
|
3599
|
+
: "—";
|
|
3600
|
+
const filesValue = share.quota
|
|
3601
|
+
? `${quota.count} / ${quota.maxCount || limits.maxFiles || 0}`
|
|
3602
|
+
: "—";
|
|
3603
|
+
const storageRows = [
|
|
3604
|
+
renderSettingsInfoRow(L("settings.row.a2aShareStorage"), storageValue),
|
|
3605
|
+
renderSettingsInfoRow(L("settings.row.a2aShareFiles"), filesValue),
|
|
3606
|
+
renderSettingsInfoRow(L("settings.row.a2aShareMaxFileSize"), formatBytes(limits.maxFileBytes || 0)),
|
|
3607
|
+
renderSettingsInfoRow(
|
|
3608
|
+
L("settings.row.a2aShareDefaultExpiry"),
|
|
3609
|
+
L("settings.a2aShare.days", { count: limits.defaultExpiresDays || 30 })
|
|
3610
|
+
),
|
|
3611
|
+
renderSettingsInfoRow(
|
|
3612
|
+
L("settings.row.a2aShareUploadRate"),
|
|
3613
|
+
L("settings.a2aShare.ratePerHour", { count: limits.uploadRatePerHour || 10 })
|
|
3614
|
+
),
|
|
3615
|
+
];
|
|
3616
|
+
|
|
3617
|
+
const PAGE_SIZE = 5;
|
|
3618
|
+
const visibleCount = state.a2aShareRecentExpanded || PAGE_SIZE;
|
|
3619
|
+
const visible = items.slice(0, visibleCount);
|
|
3620
|
+
const hasMore = items.length > visibleCount;
|
|
3621
|
+
const filesList = visible.map((item) => {
|
|
3622
|
+
const passwordLabel = L("settings.a2aShare.passwordProtected");
|
|
3623
|
+
const lock = item.hasPassword
|
|
3624
|
+
? `<span class="settings-compose-badge settings-compose-badge--reply" role="img" title="${escapeHtml(passwordLabel)}" aria-label="${escapeHtml(passwordLabel)}">${renderIcon("lock")}</span>`
|
|
3625
|
+
: "";
|
|
3626
|
+
const label = escapeHtml(item.originalName || item.slug);
|
|
3627
|
+
const link = item.url
|
|
3628
|
+
? `<a href="${escapeHtml(item.url)}" target="_blank" rel="noopener">${label}</a>`
|
|
3629
|
+
: label;
|
|
3630
|
+
const sizeText = escapeHtml(formatBytes(item.size || 0));
|
|
3631
|
+
const createdText = item.createdAtMs
|
|
3632
|
+
? escapeHtml(formatRelativeAge(Date.now() - item.createdAtMs))
|
|
3633
|
+
: "";
|
|
3634
|
+
const expiresText = item.expiresAtMs
|
|
3635
|
+
? escapeHtml(formatExpiresIn(item.expiresAtMs - Date.now()))
|
|
3636
|
+
: "";
|
|
3637
|
+
const meta = [sizeText, createdText, expiresText].filter(Boolean).join(" · ");
|
|
3638
|
+
return `
|
|
3639
|
+
<div class="settings-compose-entry settings-icon-entry">
|
|
3640
|
+
<span class="settings-icon-entry__icon settings-icon-entry__icon--file" aria-hidden="true">${renderIcon("file-event")}</span>
|
|
3641
|
+
<span class="settings-icon-entry__body">
|
|
3642
|
+
<span class="settings-icon-entry__title-row">
|
|
3643
|
+
<span class="settings-compose-entry__title">${link}</span>
|
|
3644
|
+
${lock}
|
|
3645
|
+
</span>
|
|
3646
|
+
${meta ? `<span class="settings-compose-entry__meta muted">${meta}</span>` : ""}
|
|
3647
|
+
</span>
|
|
3648
|
+
</div>
|
|
3649
|
+
`;
|
|
3650
|
+
});
|
|
3651
|
+
if (hasMore) {
|
|
3652
|
+
const remaining = items.length - visibleCount;
|
|
3653
|
+
filesList.push(
|
|
3654
|
+
`<button type="button" class="settings-compose-more" data-a2a-share-files-more>${escapeHtml(L("settings.a2aShare.showMore", { count: remaining }))}</button>`
|
|
3655
|
+
);
|
|
3656
|
+
} else if (items.length > PAGE_SIZE) {
|
|
3657
|
+
filesList.push(
|
|
3658
|
+
`<button type="button" class="settings-compose-more" data-a2a-share-files-collapse>${escapeHtml(L("settings.a2aShare.showLess"))}</button>`
|
|
3659
|
+
);
|
|
3660
|
+
}
|
|
3661
|
+
|
|
3662
|
+
return `
|
|
3663
|
+
<div class="settings-page">
|
|
3664
|
+
${renderSettingsGroup("", [
|
|
3665
|
+
renderSettingsInfoRow(L("settings.row.a2aShareStatus"), statusLabel),
|
|
3666
|
+
renderSettingsInfoRow(L("settings.row.a2aShareEndpoint"), share.shareHost || share.shareUrl || ""),
|
|
3667
|
+
renderSettingsInfoRow(L("settings.row.a2aShareUserId"), share.userId || ""),
|
|
3668
|
+
])}
|
|
3669
|
+
${renderSettingsGroup(L("settings.a2aShare.storage.title"), storageRows)}
|
|
3670
|
+
${items.length
|
|
3671
|
+
? renderSettingsGroup(L("settings.a2aShare.files.title"), filesList)
|
|
3672
|
+
: renderSettingsGroup(L("settings.a2aShare.files.title"), [
|
|
3673
|
+
`<p class="settings-group__description muted">${escapeHtml(L("settings.a2aShare.files.empty"))}</p>`,
|
|
3674
|
+
])}
|
|
3675
|
+
${share.error ? `<p class="settings-page-copy muted">${escapeHtml(L("settings.a2aShare.error", { reason: share.error }))}</p>` : ""}
|
|
3676
|
+
</div>
|
|
3677
|
+
`;
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
function formatRelativeAge(ms) {
|
|
3681
|
+
if (!Number.isFinite(ms) || ms < 0) return "";
|
|
3682
|
+
// Intl.RelativeTimeFormat with numeric:"auto" gives us locale-aware phrasing
|
|
3683
|
+
// ("5 minutes ago" / "5分前") without needing dedicated translation keys.
|
|
3684
|
+
const locale = state.locale || DEFAULT_LOCALE;
|
|
3685
|
+
let rtf;
|
|
3686
|
+
try {
|
|
3687
|
+
rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
|
3688
|
+
} catch {
|
|
3689
|
+
rtf = null;
|
|
3690
|
+
}
|
|
3691
|
+
const sec = Math.floor(ms / 1000);
|
|
3692
|
+
const pick = (value, unit, fallback) => (rtf ? rtf.format(-value, unit) : fallback);
|
|
3693
|
+
if (sec < 60) return pick(sec, "second", `${sec}s ago`);
|
|
3694
|
+
const min = Math.floor(sec / 60);
|
|
3695
|
+
if (min < 60) return pick(min, "minute", `${min}m ago`);
|
|
3696
|
+
const hr = Math.floor(min / 60);
|
|
3697
|
+
if (hr < 24) return pick(hr, "hour", `${hr}h ago`);
|
|
3698
|
+
const day = Math.floor(hr / 24);
|
|
3699
|
+
if (day < 30) return pick(day, "day", `${day}d ago`);
|
|
3700
|
+
const mo = Math.floor(day / 30);
|
|
3701
|
+
if (mo < 12) return pick(mo, "month", `${mo}mo ago`);
|
|
3702
|
+
return pick(Math.floor(mo / 12), "year", `${Math.floor(mo / 12)}y ago`);
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
function formatExpiresIn(ms) {
|
|
3706
|
+
if (!Number.isFinite(ms)) return "";
|
|
3707
|
+
if (ms <= 0) return L("settings.a2aShare.expired");
|
|
3708
|
+
const locale = state.locale || DEFAULT_LOCALE;
|
|
3709
|
+
let rtf;
|
|
3710
|
+
try {
|
|
3711
|
+
rtf = new Intl.RelativeTimeFormat(locale, { numeric: "always" });
|
|
3712
|
+
} catch {
|
|
3713
|
+
rtf = null;
|
|
3714
|
+
}
|
|
3715
|
+
const day = Math.floor(ms / 86400000);
|
|
3716
|
+
if (day >= 1) return rtf ? rtf.format(day, "day") : `in ${day}d`;
|
|
3717
|
+
const hr = Math.max(1, Math.floor(ms / 3600000));
|
|
3718
|
+
return rtf ? rtf.format(hr, "hour") : `in ${hr}h`;
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3509
3721
|
function renderSettingsInfoRow(label, value, options = {}) {
|
|
3510
3722
|
const rowClassName = ["settings-info-row", options.rowClassName || ""].filter(Boolean).join(" ");
|
|
3511
3723
|
const valueClassName = ["settings-info-row__value", options.valueClassName || ""].filter(Boolean).join(" ");
|
|
@@ -3626,8 +3838,9 @@ function renderStandardDetailDesktop(detail) {
|
|
|
3626
3838
|
${renderMoltbookDraftComposer(detail)}
|
|
3627
3839
|
${renderA2ATaskDetail(detail)}
|
|
3628
3840
|
${renderThreadShareDetail(detail)}
|
|
3841
|
+
${renderAmbientSuggestionsSection(detail)}
|
|
3629
3842
|
${
|
|
3630
|
-
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task" || detail.kind === "a2a_task_result" || detail.kind === "thread_share"
|
|
3843
|
+
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task" || detail.kind === "a2a_task_result" || detail.kind === "thread_share" || detail.kind === "ambient_suggestions"
|
|
3631
3844
|
? ""
|
|
3632
3845
|
: plainIntro
|
|
3633
3846
|
? plainIntro
|
|
@@ -3664,8 +3877,9 @@ function renderStandardDetailMobile(detail) {
|
|
|
3664
3877
|
${renderMoltbookDraftComposer(detail, { mobile: true })}
|
|
3665
3878
|
${renderA2ATaskDetail(detail, { mobile: true })}
|
|
3666
3879
|
${renderThreadShareDetail(detail, { mobile: true })}
|
|
3880
|
+
${renderAmbientSuggestionsSection(detail, { mobile: true })}
|
|
3667
3881
|
${
|
|
3668
|
-
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task" || detail.kind === "a2a_task_result" || detail.kind === "thread_share"
|
|
3882
|
+
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task" || detail.kind === "a2a_task_result" || detail.kind === "thread_share" || detail.kind === "ambient_suggestions"
|
|
3669
3883
|
? ""
|
|
3670
3884
|
: plainIntro
|
|
3671
3885
|
? plainIntro
|
|
@@ -3690,6 +3904,65 @@ function renderStandardDetailMobile(detail) {
|
|
|
3690
3904
|
`;
|
|
3691
3905
|
}
|
|
3692
3906
|
|
|
3907
|
+
function renderAmbientSuggestionsSection(detail, options = {}) {
|
|
3908
|
+
if (detail?.kind !== "ambient_suggestions") {
|
|
3909
|
+
return "";
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
const suggestions = normalizeClientAmbientSuggestions(detail?.suggestions);
|
|
3913
|
+
if (suggestions.length === 0) {
|
|
3914
|
+
return `
|
|
3915
|
+
<section class="detail-card detail-card--body ${options.mobile ? "detail-card--mobile" : ""}">
|
|
3916
|
+
<div class="detail-body markdown">${detail.messageHtml || ""}</div>
|
|
3917
|
+
</section>
|
|
3918
|
+
`;
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
return `
|
|
3922
|
+
<section class="detail-card detail-card--body ${options.mobile ? "detail-card--mobile" : ""}">
|
|
3923
|
+
<div class="ambient-suggestions">
|
|
3924
|
+
${detail.messageHtml ? `<div class="ambient-suggestions__intro markdown">${detail.messageHtml}</div>` : ""}
|
|
3925
|
+
<div class="ambient-suggestions__list">
|
|
3926
|
+
${suggestions
|
|
3927
|
+
.map((suggestion, index) => renderAmbientSuggestionCard(detail, suggestion, index))
|
|
3928
|
+
.join("")}
|
|
3929
|
+
</div>
|
|
3930
|
+
</div>
|
|
3931
|
+
</section>
|
|
3932
|
+
`;
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
function renderAmbientSuggestionCard(detail, suggestion, index) {
|
|
3936
|
+
const copyKey = ambientSuggestionCopyKey(detail?.token || "", suggestion?.id || "", index);
|
|
3937
|
+
const copyStatus = state.ambientSuggestionCopyState?.key === copyKey
|
|
3938
|
+
? state.ambientSuggestionCopyState?.status || ""
|
|
3939
|
+
: "";
|
|
3940
|
+
const copyLabel = copyStatus === "success"
|
|
3941
|
+
? L("detail.ambientSuggestions.copyPromptDone")
|
|
3942
|
+
: copyStatus === "error"
|
|
3943
|
+
? L("detail.ambientSuggestions.copyPromptFailed")
|
|
3944
|
+
: L("detail.ambientSuggestions.copyPrompt");
|
|
3945
|
+
return `
|
|
3946
|
+
<article class="ambient-suggestion-card">
|
|
3947
|
+
<div class="ambient-suggestion-card__header">
|
|
3948
|
+
<h3 class="ambient-suggestion-card__title">${escapeHtml(suggestion.title)}</h3>
|
|
3949
|
+
<button
|
|
3950
|
+
type="button"
|
|
3951
|
+
class="secondary ambient-suggestion-card__copy-button"
|
|
3952
|
+
data-copy-ambient-suggestion
|
|
3953
|
+
data-copy-ambient-suggestion-token="${escapeHtml(detail?.token || "")}"
|
|
3954
|
+
data-copy-ambient-suggestion-index="${escapeHtml(String(index))}"
|
|
3955
|
+
>${escapeHtml(copyLabel)}</button>
|
|
3956
|
+
</div>
|
|
3957
|
+
${suggestion.description ? `<p class="ambient-suggestion-card__description">${escapeHtml(suggestion.description)}</p>` : ""}
|
|
3958
|
+
<div class="ambient-suggestion-card__prompt-wrap">
|
|
3959
|
+
<p class="ambient-suggestion-card__prompt-label">${escapeHtml(L("detail.ambientSuggestions.prompt"))}</p>
|
|
3960
|
+
<pre class="ambient-suggestion-card__prompt">${escapeHtml(suggestion.prompt)}</pre>
|
|
3961
|
+
</div>
|
|
3962
|
+
</article>
|
|
3963
|
+
`;
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3693
3966
|
function renderDetailPlainIntro(detail, options = {}) {
|
|
3694
3967
|
if (!["approval", "diff_thread", "file_event"].includes(detail?.kind || "")) {
|
|
3695
3968
|
return "";
|
|
@@ -5091,6 +5364,41 @@ function bindShellInteractions() {
|
|
|
5091
5364
|
});
|
|
5092
5365
|
}
|
|
5093
5366
|
|
|
5367
|
+
for (const button of document.querySelectorAll("[data-copy-ambient-suggestion]")) {
|
|
5368
|
+
button.addEventListener("click", async (event) => {
|
|
5369
|
+
event.preventDefault();
|
|
5370
|
+
event.stopPropagation();
|
|
5371
|
+
const detail = state.currentDetail;
|
|
5372
|
+
if (!detail || detail.kind !== "ambient_suggestions") {
|
|
5373
|
+
return;
|
|
5374
|
+
}
|
|
5375
|
+
if ((button.dataset.copyAmbientSuggestionToken || "") !== (detail.token || "")) {
|
|
5376
|
+
return;
|
|
5377
|
+
}
|
|
5378
|
+
const index = Math.max(0, Number(button.dataset.copyAmbientSuggestionIndex) || 0);
|
|
5379
|
+
const suggestions = normalizeClientAmbientSuggestions(detail.suggestions);
|
|
5380
|
+
const suggestion = suggestions[index];
|
|
5381
|
+
if (!suggestion?.prompt) {
|
|
5382
|
+
return;
|
|
5383
|
+
}
|
|
5384
|
+
const copyKey = ambientSuggestionCopyKey(detail.token || "", suggestion.id || "", index);
|
|
5385
|
+
try {
|
|
5386
|
+
await copyTextToClipboard(suggestion.prompt);
|
|
5387
|
+
state.ambientSuggestionCopyState = { key: copyKey, status: "success" };
|
|
5388
|
+
} catch {
|
|
5389
|
+
state.ambientSuggestionCopyState = { key: copyKey, status: "error" };
|
|
5390
|
+
}
|
|
5391
|
+
await renderShell();
|
|
5392
|
+
window.setTimeout(async () => {
|
|
5393
|
+
if (state.ambientSuggestionCopyState?.key !== copyKey) {
|
|
5394
|
+
return;
|
|
5395
|
+
}
|
|
5396
|
+
state.ambientSuggestionCopyState = null;
|
|
5397
|
+
await renderShell();
|
|
5398
|
+
}, 1600);
|
|
5399
|
+
});
|
|
5400
|
+
}
|
|
5401
|
+
|
|
5094
5402
|
for (const button of document.querySelectorAll("[data-diff-thread-file-toggle]")) {
|
|
5095
5403
|
button.addEventListener("click", async (event) => {
|
|
5096
5404
|
event.preventDefault();
|
|
@@ -5393,6 +5701,19 @@ function bindShellInteractions() {
|
|
|
5393
5701
|
});
|
|
5394
5702
|
}
|
|
5395
5703
|
|
|
5704
|
+
for (const btn of document.querySelectorAll("[data-a2a-share-files-more]")) {
|
|
5705
|
+
btn.addEventListener("click", async () => {
|
|
5706
|
+
state.a2aShareRecentExpanded = (state.a2aShareRecentExpanded || 5) + 5;
|
|
5707
|
+
await renderShell();
|
|
5708
|
+
});
|
|
5709
|
+
}
|
|
5710
|
+
for (const btn of document.querySelectorAll("[data-a2a-share-files-collapse]")) {
|
|
5711
|
+
btn.addEventListener("click", async () => {
|
|
5712
|
+
state.a2aShareRecentExpanded = 0;
|
|
5713
|
+
await renderShell();
|
|
5714
|
+
});
|
|
5715
|
+
}
|
|
5716
|
+
|
|
5396
5717
|
for (const button of document.querySelectorAll("[data-locale-option]")) {
|
|
5397
5718
|
button.addEventListener("click", async () => {
|
|
5398
5719
|
state.pushError = "";
|
|
@@ -6102,7 +6423,7 @@ function tabForItemKind(kind, fallback) {
|
|
|
6102
6423
|
if (kind === "diff_thread") {
|
|
6103
6424
|
return "diff";
|
|
6104
6425
|
}
|
|
6105
|
-
if (kind === "file_event") {
|
|
6426
|
+
if (kind === "file_event" || kind === "ambient_suggestions") {
|
|
6106
6427
|
return "timeline";
|
|
6107
6428
|
}
|
|
6108
6429
|
if (TIMELINE_MESSAGE_KINDS.has(kind)) {
|
|
@@ -6134,6 +6455,8 @@ function kindMeta(kind, item) {
|
|
|
6134
6455
|
return { label: L("common.assistantCommentary"), tone: "plan", icon: "assistant-commentary" };
|
|
6135
6456
|
case "assistant_final":
|
|
6136
6457
|
return { label: L("common.assistantFinal"), tone: "completion", icon: "assistant-final" };
|
|
6458
|
+
case "ambient_suggestions":
|
|
6459
|
+
return { label: L("common.ambientSuggestions"), tone: "neutral", icon: "suggestions" };
|
|
6137
6460
|
case "approval":
|
|
6138
6461
|
return { label: L("common.approval"), tone: "approval", icon: "approval" };
|
|
6139
6462
|
case "plan":
|
|
@@ -6179,6 +6502,9 @@ function itemIntentText(kind, status = "pending", provider) {
|
|
|
6179
6502
|
if (kind === "file_event") {
|
|
6180
6503
|
return L("intent.fileEvent");
|
|
6181
6504
|
}
|
|
6505
|
+
if (kind === "ambient_suggestions") {
|
|
6506
|
+
return L("intent.ambientSuggestions");
|
|
6507
|
+
}
|
|
6182
6508
|
if (kind === "user_message") {
|
|
6183
6509
|
return L("intent.userMessage");
|
|
6184
6510
|
}
|
|
@@ -6213,6 +6539,9 @@ function detailIntentText(detail) {
|
|
|
6213
6539
|
if (detail.kind === "file_event") {
|
|
6214
6540
|
return itemIntentText(detail.kind, "timeline", provider);
|
|
6215
6541
|
}
|
|
6542
|
+
if (detail.kind === "ambient_suggestions") {
|
|
6543
|
+
return itemIntentText(detail.kind, "timeline", provider);
|
|
6544
|
+
}
|
|
6216
6545
|
if (TIMELINE_MESSAGE_KINDS.has(detail.kind)) {
|
|
6217
6546
|
return itemIntentText(detail.kind, "timeline", provider);
|
|
6218
6547
|
}
|
|
@@ -6231,7 +6560,11 @@ function renderDetailTitle(detail) {
|
|
|
6231
6560
|
}
|
|
6232
6561
|
|
|
6233
6562
|
function detailDisplayTitle(detail) {
|
|
6234
|
-
const threadLabel =
|
|
6563
|
+
const threadLabel = sanitizeThreadLabelForDisplay(detail?.threadLabel || "", detail?.threadId || "");
|
|
6564
|
+
if (detail?.kind === "ambient_suggestions") {
|
|
6565
|
+
const ambientTitle = sanitizeThreadLabelForDisplay(detail?.title || "", detail?.threadId || "");
|
|
6566
|
+
return ambientTitle || L("common.ambientSuggestions");
|
|
6567
|
+
}
|
|
6235
6568
|
if (threadLabel) {
|
|
6236
6569
|
return threadLabel;
|
|
6237
6570
|
}
|
|
@@ -6279,6 +6612,8 @@ function fallbackSummaryForKind(kind, status, provider) {
|
|
|
6279
6612
|
return L("summary.diffThread");
|
|
6280
6613
|
case "file_event":
|
|
6281
6614
|
return L("summary.fileEvent", vars);
|
|
6615
|
+
case "ambient_suggestions":
|
|
6616
|
+
return L("summary.ambientSuggestions", { count: 0, firstTitle: "", more: 0 });
|
|
6282
6617
|
case "user_message":
|
|
6283
6618
|
return L("summary.userMessage");
|
|
6284
6619
|
case "assistant_commentary":
|
|
@@ -6495,6 +6830,8 @@ function renderIcon(name) {
|
|
|
6495
6830
|
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 6.2v5.6"/><path d="M9.2 9h5.6"/><path d="M6 14.8a6.7 6.7 0 0 0 12 0"/><path d="M8 4.8a7.6 7.6 0 0 1 8 0"/></svg>`;
|
|
6496
6831
|
case "assistant-final":
|
|
6497
6832
|
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.85" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6.5h12a1.8 1.8 0 0 1 1.8 1.8v6.1A1.8 1.8 0 0 1 18 16.2h-5.3L9 19.4v-3.2H6a1.8 1.8 0 0 1-1.8-1.8V8.3A1.8 1.8 0 0 1 6 6.5Z"/><path d="m9.2 11.3 1.7 1.7 3.6-3.8"/></svg>`;
|
|
6833
|
+
case "suggestions":
|
|
6834
|
+
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.8 13 6.7l3 .5-2.2 2.2.5 3-2.3-1.2-2.3 1.2.5-3L8 7.2l3-.5Z"/><path d="M6.2 14.6h11.6"/><path d="M8.4 18.2h7.2"/></svg>`;
|
|
6498
6835
|
case "completed":
|
|
6499
6836
|
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="m8.7 12.2 2.1 2.1 4.6-4.8"/></svg>`;
|
|
6500
6837
|
case "settings":
|
|
@@ -6521,6 +6858,8 @@ function renderIcon(name) {
|
|
|
6521
6858
|
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 7h14"/><path d="M8 12h8"/><path d="M10.5 17h3"/></svg>`;
|
|
6522
6859
|
case "check":
|
|
6523
6860
|
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6.8 12.5 3.2 3.2 7.2-7.4"/></svg>`;
|
|
6861
|
+
case "lock":
|
|
6862
|
+
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="5.5" y="10.5" width="13" height="9" rx="2"/><path d="M8 10.5V7.5a4 4 0 0 1 8 0v3"/></svg>`;
|
|
6524
6863
|
case "back":
|
|
6525
6864
|
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>`;
|
|
6526
6865
|
case "chevron-down":
|
|
@@ -6767,6 +7106,58 @@ function normalizeClientFileRefs(fileRefs) {
|
|
|
6767
7106
|
return deduped;
|
|
6768
7107
|
}
|
|
6769
7108
|
|
|
7109
|
+
function normalizeClientAmbientSuggestions(value) {
|
|
7110
|
+
if (!Array.isArray(value)) {
|
|
7111
|
+
return [];
|
|
7112
|
+
}
|
|
7113
|
+
return value
|
|
7114
|
+
.map((entry) => {
|
|
7115
|
+
const title = normalizeClientText(entry?.title);
|
|
7116
|
+
const prompt = normalizeClientText(entry?.prompt);
|
|
7117
|
+
if (!title || !prompt) {
|
|
7118
|
+
return null;
|
|
7119
|
+
}
|
|
7120
|
+
return {
|
|
7121
|
+
id: normalizeClientText(entry?.id),
|
|
7122
|
+
title,
|
|
7123
|
+
prompt,
|
|
7124
|
+
description: normalizeClientText(entry?.description),
|
|
7125
|
+
};
|
|
7126
|
+
})
|
|
7127
|
+
.filter(Boolean);
|
|
7128
|
+
}
|
|
7129
|
+
|
|
7130
|
+
function ambientSuggestionCopyKey(token, suggestionId, index) {
|
|
7131
|
+
return [normalizeClientText(token), normalizeClientText(suggestionId), String(Math.max(0, Number(index) || 0))].join(":");
|
|
7132
|
+
}
|
|
7133
|
+
|
|
7134
|
+
async function copyTextToClipboard(text) {
|
|
7135
|
+
const normalized = String(text ?? "");
|
|
7136
|
+
if (!normalized) {
|
|
7137
|
+
throw new Error("empty");
|
|
7138
|
+
}
|
|
7139
|
+
|
|
7140
|
+
if (navigator.clipboard?.writeText) {
|
|
7141
|
+
await navigator.clipboard.writeText(normalized);
|
|
7142
|
+
return;
|
|
7143
|
+
}
|
|
7144
|
+
|
|
7145
|
+
const textarea = document.createElement("textarea");
|
|
7146
|
+
textarea.value = normalized;
|
|
7147
|
+
textarea.setAttribute("readonly", "true");
|
|
7148
|
+
textarea.style.position = "fixed";
|
|
7149
|
+
textarea.style.opacity = "0";
|
|
7150
|
+
textarea.style.pointerEvents = "none";
|
|
7151
|
+
document.body.appendChild(textarea);
|
|
7152
|
+
textarea.select();
|
|
7153
|
+
textarea.setSelectionRange(0, textarea.value.length);
|
|
7154
|
+
const copied = document.execCommand("copy");
|
|
7155
|
+
document.body.removeChild(textarea);
|
|
7156
|
+
if (!copied) {
|
|
7157
|
+
throw new Error("copy-failed");
|
|
7158
|
+
}
|
|
7159
|
+
}
|
|
7160
|
+
|
|
6770
7161
|
function fileRefLabel(fileRef) {
|
|
6771
7162
|
const normalized = normalizeClientText(fileRef);
|
|
6772
7163
|
if (!normalized) {
|