viveworker 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/package.json +6 -1
- package/scripts/com.viveworker.moltbook-scout.plist.sample +58 -0
- package/scripts/moltbook-api.mjs +201 -0
- package/scripts/moltbook-cli.mjs +1302 -0
- package/scripts/moltbook-scout-run.sh +14 -0
- package/scripts/moltbook-watcher.mjs +294 -0
- package/scripts/viveworker-bridge.mjs +548 -12
- package/scripts/viveworker-claude-hook.mjs +7 -1
- package/scripts/viveworker.mjs +303 -1
- package/web/app.css +75 -1
- package/web/app.js +298 -25
- package/web/i18n.js +55 -1
- package/web/sw.js +1 -1
package/web/app.js
CHANGED
|
@@ -47,6 +47,7 @@ const state = {
|
|
|
47
47
|
pairError: "",
|
|
48
48
|
pairNotice: "",
|
|
49
49
|
pushStatus: null,
|
|
50
|
+
moltbookScoutStatus: null,
|
|
50
51
|
pushNotice: "",
|
|
51
52
|
pushError: "",
|
|
52
53
|
deviceNotice: "",
|
|
@@ -212,6 +213,7 @@ async function refreshAuthenticatedState() {
|
|
|
212
213
|
await refreshTimeline();
|
|
213
214
|
await refreshDevices();
|
|
214
215
|
await refreshPushStatus();
|
|
216
|
+
await fetchMoltbookScoutStatus();
|
|
215
217
|
ensureCurrentSelection();
|
|
216
218
|
}
|
|
217
219
|
|
|
@@ -317,6 +319,18 @@ async function refreshPushStatus() {
|
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
|
|
322
|
+
async function fetchMoltbookScoutStatus() {
|
|
323
|
+
if (!state.session?.moltbookEnabled) {
|
|
324
|
+
state.moltbookScoutStatus = null;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
state.moltbookScoutStatus = await apiGet("/api/moltbook/scout-status");
|
|
329
|
+
} catch {
|
|
330
|
+
state.moltbookScoutStatus = null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
320
334
|
async function getClientPushState() {
|
|
321
335
|
const registration = state.serviceWorkerRegistration || (await navigator.serviceWorker?.ready.catch(() => null));
|
|
322
336
|
if (registration) {
|
|
@@ -526,11 +540,16 @@ function listInboxEntries() {
|
|
|
526
540
|
|
|
527
541
|
function normalizeProviderClient(value) {
|
|
528
542
|
const normalized = String(value || "").toLowerCase();
|
|
529
|
-
|
|
543
|
+
if (normalized === "claude") return "claude";
|
|
544
|
+
if (normalized === "moltbook") return "moltbook";
|
|
545
|
+
return "codex";
|
|
530
546
|
}
|
|
531
547
|
|
|
532
548
|
function providerDisplayName(provider) {
|
|
533
|
-
|
|
549
|
+
const p = normalizeProviderClient(provider);
|
|
550
|
+
if (p === "claude") return L("common.claude");
|
|
551
|
+
if (p === "moltbook") return "Moltbook";
|
|
552
|
+
return L("common.codex");
|
|
534
553
|
}
|
|
535
554
|
|
|
536
555
|
function entryMatchesProviderFilter(item) {
|
|
@@ -633,6 +652,16 @@ function timelineEntryMatchesKindFilter(entry, filterId) {
|
|
|
633
652
|
}
|
|
634
653
|
}
|
|
635
654
|
|
|
655
|
+
function isMoltbookThreadId(threadId, item) {
|
|
656
|
+
if (threadId === "moltbook") return true;
|
|
657
|
+
if (typeof threadId === "string" && threadId.startsWith("draft:")) return true;
|
|
658
|
+
const kind = normalizeClientText(item?.kind || "");
|
|
659
|
+
if (kind === "moltbook_draft" || kind === "moltbook_reply") return true;
|
|
660
|
+
const label = normalizeClientText(item?.threadLabel || "").toLowerCase();
|
|
661
|
+
if (label === "moltbook") return true;
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
|
|
636
665
|
function completedThreads() {
|
|
637
666
|
const items = Array.isArray(state.inbox?.completed) ? state.inbox.completed : [];
|
|
638
667
|
if (!items.length) {
|
|
@@ -641,7 +670,7 @@ function completedThreads() {
|
|
|
641
670
|
const byThread = new Map();
|
|
642
671
|
for (const item of items) {
|
|
643
672
|
const threadId = normalizeClientText(item.threadId || "");
|
|
644
|
-
if (!threadId) {
|
|
673
|
+
if (!threadId || isMoltbookThreadId(threadId, item)) {
|
|
645
674
|
continue;
|
|
646
675
|
}
|
|
647
676
|
const latestAtMs = Number(item.createdAtMs || 0);
|
|
@@ -666,7 +695,7 @@ function diffThreads() {
|
|
|
666
695
|
const byThread = new Map();
|
|
667
696
|
for (const item of items) {
|
|
668
697
|
const threadId = normalizeClientText(item.threadId || "");
|
|
669
|
-
if (!threadId) {
|
|
698
|
+
if (!threadId || isMoltbookThreadId(threadId, item)) {
|
|
670
699
|
continue;
|
|
671
700
|
}
|
|
672
701
|
const latestAtMs = Number(item.createdAtMs || 0);
|
|
@@ -933,6 +962,27 @@ function shouldDeferRenderForActiveInteraction() {
|
|
|
933
962
|
) {
|
|
934
963
|
return true;
|
|
935
964
|
}
|
|
965
|
+
if (
|
|
966
|
+
activeElement instanceof HTMLTextAreaElement &&
|
|
967
|
+
activeElement.matches("[data-moltbook-draft-textarea]")
|
|
968
|
+
) {
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
if (state.currentDetail?.kind === "moltbook_draft") {
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
if (
|
|
975
|
+
activeElement instanceof HTMLTextAreaElement &&
|
|
976
|
+
activeElement.matches("[data-claude-question-note]")
|
|
977
|
+
) {
|
|
978
|
+
return true;
|
|
979
|
+
}
|
|
980
|
+
if (
|
|
981
|
+
activeElement instanceof HTMLInputElement &&
|
|
982
|
+
activeElement.matches("[data-moltbook-draft-title]")
|
|
983
|
+
) {
|
|
984
|
+
return true;
|
|
985
|
+
}
|
|
936
986
|
if (
|
|
937
987
|
activeElement instanceof HTMLSelectElement &&
|
|
938
988
|
activeElement.matches("[data-timeline-thread-select], [data-diff-thread-select], [data-completed-thread-select]")
|
|
@@ -1650,6 +1700,9 @@ function providerBadgeMeta(provider) {
|
|
|
1650
1700
|
if (normalized === "claude") {
|
|
1651
1701
|
return { id: "claude", label: L("common.claude"), glyph: "C" };
|
|
1652
1702
|
}
|
|
1703
|
+
if (normalized === "moltbook") {
|
|
1704
|
+
return { id: "moltbook", label: "Moltbook", glyph: "M" };
|
|
1705
|
+
}
|
|
1653
1706
|
return { id: "codex", label: L("common.codex"), glyph: "X" };
|
|
1654
1707
|
}
|
|
1655
1708
|
|
|
@@ -1659,11 +1712,15 @@ function renderProviderBadge(provider) {
|
|
|
1659
1712
|
}
|
|
1660
1713
|
|
|
1661
1714
|
function providerFilterOptions() {
|
|
1662
|
-
|
|
1715
|
+
const options = [
|
|
1663
1716
|
{ id: "all", label: L("timeline.allThreads") },
|
|
1664
1717
|
{ id: "codex", label: L("common.codex") },
|
|
1665
1718
|
{ id: "claude", label: L("common.claude") },
|
|
1666
1719
|
];
|
|
1720
|
+
if (state.session?.moltbookEnabled === true) {
|
|
1721
|
+
options.push({ id: "moltbook", label: "Moltbook" });
|
|
1722
|
+
}
|
|
1723
|
+
return options;
|
|
1667
1724
|
}
|
|
1668
1725
|
|
|
1669
1726
|
function renderProviderFilter() {
|
|
@@ -2679,6 +2736,7 @@ function buildSettingsContext() {
|
|
|
2679
2736
|
supportsPushValue,
|
|
2680
2737
|
serverEnabled,
|
|
2681
2738
|
}),
|
|
2739
|
+
moltbookScout: state.moltbookScoutStatus,
|
|
2682
2740
|
};
|
|
2683
2741
|
}
|
|
2684
2742
|
|
|
@@ -2825,6 +2883,13 @@ function settingsPageMeta(page) {
|
|
|
2825
2883
|
description: L("settings.awayMode.copy"),
|
|
2826
2884
|
icon: "settings",
|
|
2827
2885
|
};
|
|
2886
|
+
case "moltbook":
|
|
2887
|
+
return {
|
|
2888
|
+
id: "moltbook",
|
|
2889
|
+
title: L("settings.moltbook.title"),
|
|
2890
|
+
description: L("settings.moltbook.copy"),
|
|
2891
|
+
icon: "item",
|
|
2892
|
+
};
|
|
2828
2893
|
default:
|
|
2829
2894
|
return settingsPageMeta("notifications");
|
|
2830
2895
|
}
|
|
@@ -2894,6 +2959,15 @@ function renderSettingsRoot(context, { mobile }) {
|
|
|
2894
2959
|
`
|
|
2895
2960
|
}
|
|
2896
2961
|
${renderSettingsGroup(L("settings.group.general"), generalRows)}
|
|
2962
|
+
${state.session?.moltbookEnabled ? renderSettingsGroup(L("common.sns"), [
|
|
2963
|
+
renderSettingsNavRow({
|
|
2964
|
+
page: "moltbook",
|
|
2965
|
+
icon: "item",
|
|
2966
|
+
title: L("settings.moltbook.title"),
|
|
2967
|
+
subtitle: L("settings.moltbook.subtitle"),
|
|
2968
|
+
value: context.moltbookScout?.enabled ? `${context.moltbookScout.sentToday} / ${context.moltbookScout.maxDaily}` : "",
|
|
2969
|
+
}),
|
|
2970
|
+
]) : ""}
|
|
2897
2971
|
${renderSettingsGroup(L("settings.pairing.title"), deviceRows)}
|
|
2898
2972
|
${renderSettingsGroup(L("settings.group.advanced"), advancedRows)}
|
|
2899
2973
|
</div>
|
|
@@ -2937,6 +3011,9 @@ function renderSettingsSubpage(context, { mobile }) {
|
|
|
2937
3011
|
case "awayMode":
|
|
2938
3012
|
content = renderSettingsAwayModePage();
|
|
2939
3013
|
break;
|
|
3014
|
+
case "moltbook":
|
|
3015
|
+
content = renderSettingsMoltbookPage(context);
|
|
3016
|
+
break;
|
|
2940
3017
|
default:
|
|
2941
3018
|
content = "";
|
|
2942
3019
|
}
|
|
@@ -2955,7 +3032,7 @@ function renderSettingsNotificationsPage(context) {
|
|
|
2955
3032
|
const statusRows = [
|
|
2956
3033
|
renderSettingsInfoRow(L("settings.row.status"), L(context.setupState.notifications.labelKey)),
|
|
2957
3034
|
renderSettingsInfoRow(L("settings.row.notificationPermission"), permission),
|
|
2958
|
-
renderSettingsInfoRow(L("settings.row.currentDeviceSubscribed"), push.serverSubscribed ? L("common.
|
|
3035
|
+
renderSettingsInfoRow(L("settings.row.currentDeviceSubscribed"), push.serverSubscribed ? L("common.enabled") : L("common.disabled")),
|
|
2959
3036
|
push.lastSuccessfulDeliveryAtMs
|
|
2960
3037
|
? renderSettingsInfoRow(
|
|
2961
3038
|
L("settings.row.lastSuccessfulDelivery"),
|
|
@@ -2967,10 +3044,10 @@ function renderSettingsNotificationsPage(context) {
|
|
|
2967
3044
|
<div class="settings-page">
|
|
2968
3045
|
${renderSettingsGroup("", statusRows)}
|
|
2969
3046
|
${renderSettingsGroup(L("settings.group.advanced"), [
|
|
2970
|
-
renderSettingsInfoRow(L("settings.row.serverWebPush"), serverEnabled ? L("common.
|
|
2971
|
-
renderSettingsInfoRow(L("settings.row.secureContext"), secureContext ? L("common.
|
|
2972
|
-
renderSettingsInfoRow(L("settings.row.homeScreenApp"), standalone ? L("common.
|
|
2973
|
-
renderSettingsInfoRow(L("settings.row.browserSupport"), supportsPushValue ? L("common.
|
|
3047
|
+
renderSettingsInfoRow(L("settings.row.serverWebPush"), serverEnabled ? L("common.enabled") : L("common.disabled")),
|
|
3048
|
+
renderSettingsInfoRow(L("settings.row.secureContext"), secureContext ? L("common.supported") : L("common.notSupported")),
|
|
3049
|
+
renderSettingsInfoRow(L("settings.row.homeScreenApp"), standalone ? L("common.supported") : L("common.notSupported")),
|
|
3050
|
+
renderSettingsInfoRow(L("settings.row.browserSupport"), supportsPushValue ? L("common.supported") : L("common.notSupported")),
|
|
2974
3051
|
])}
|
|
2975
3052
|
${state.pushNotice ? `<p class="inline-alert inline-alert--success">${escapeHtml(state.pushNotice)}</p>` : ""}
|
|
2976
3053
|
${state.pushError ? `<p class="inline-alert inline-alert--danger">${escapeHtml(state.pushError)}</p>` : ""}
|
|
@@ -3062,12 +3139,12 @@ function renderSettingsAdvancedPage(context) {
|
|
|
3062
3139
|
<div class="settings-page">
|
|
3063
3140
|
${context.diagnostics.map((message) => `<p class="inline-alert">${escapeHtml(message)}</p>`).join("")}
|
|
3064
3141
|
${renderSettingsGroup("", [
|
|
3065
|
-
renderSettingsInfoRow(L("settings.row.serverWebPush"), context.serverEnabled ? L("common.
|
|
3066
|
-
renderSettingsInfoRow(L("settings.row.secureContext"), context.secureContext ? L("common.
|
|
3067
|
-
renderSettingsInfoRow(L("settings.row.homeScreenApp"), context.standalone ? L("common.
|
|
3142
|
+
renderSettingsInfoRow(L("settings.row.serverWebPush"), context.serverEnabled ? L("common.enabled") : L("common.disabled")),
|
|
3143
|
+
renderSettingsInfoRow(L("settings.row.secureContext"), context.secureContext ? L("common.supported") : L("common.notSupported")),
|
|
3144
|
+
renderSettingsInfoRow(L("settings.row.homeScreenApp"), context.standalone ? L("common.supported") : L("common.notSupported")),
|
|
3068
3145
|
renderSettingsInfoRow(L("settings.row.notificationPermission"), context.permission),
|
|
3069
|
-
renderSettingsInfoRow(L("settings.row.browserSupport"), context.supportsPushValue ? L("common.
|
|
3070
|
-
renderSettingsInfoRow(L("settings.row.currentDeviceSubscribed"), context.push.serverSubscribed ? L("common.
|
|
3146
|
+
renderSettingsInfoRow(L("settings.row.browserSupport"), context.supportsPushValue ? L("common.supported") : L("common.notSupported")),
|
|
3147
|
+
renderSettingsInfoRow(L("settings.row.currentDeviceSubscribed"), context.push.serverSubscribed ? L("common.enabled") : L("common.disabled")),
|
|
3071
3148
|
context.push.lastSuccessfulDeliveryAtMs
|
|
3072
3149
|
? renderSettingsInfoRow(
|
|
3073
3150
|
L("settings.row.lastSuccessfulDelivery"),
|
|
@@ -3152,6 +3229,37 @@ function renderSettingsAwayModePage() {
|
|
|
3152
3229
|
`;
|
|
3153
3230
|
}
|
|
3154
3231
|
|
|
3232
|
+
function renderSettingsMoltbookPage(context) {
|
|
3233
|
+
const scout = context.moltbookScout;
|
|
3234
|
+
if (!scout?.enabled) {
|
|
3235
|
+
return `
|
|
3236
|
+
<div class="settings-page">
|
|
3237
|
+
<p class="settings-page-copy muted">${escapeHtml(L("settings.moltbook.unavailable"))}</p>
|
|
3238
|
+
</div>
|
|
3239
|
+
`;
|
|
3240
|
+
}
|
|
3241
|
+
const batchRows = scout.batch ? [
|
|
3242
|
+
renderSettingsInfoRow(L("settings.row.moltbookBatchCandidates"), String(scout.batch.candidateCount)),
|
|
3243
|
+
renderSettingsInfoRow(L("settings.row.moltbookBatchTopScore"), String(scout.batch.topScore)),
|
|
3244
|
+
renderSettingsInfoRow(L("settings.row.moltbookBatchRemaining"), `${Math.floor(scout.batch.remainingSeconds / 60)}:${String(scout.batch.remainingSeconds % 60).padStart(2, "0")}`),
|
|
3245
|
+
] : [];
|
|
3246
|
+
return `
|
|
3247
|
+
<div class="settings-page">
|
|
3248
|
+
${renderSettingsGroup("", [
|
|
3249
|
+
renderSettingsInfoRow(L("settings.row.moltbookQuota"), `${scout.sentToday} / ${scout.maxDaily}`),
|
|
3250
|
+
renderSettingsInfoRow(L("settings.row.moltbookComposed"), `${scout.composedToday || 0} / 3`),
|
|
3251
|
+
renderSettingsInfoRow(L("settings.row.moltbookSlots"), (scout.composeSlotsAttempted || []).join(", ") || "—"),
|
|
3252
|
+
renderSettingsInfoRow(L("settings.row.moltbookDate"), scout.day),
|
|
3253
|
+
renderSettingsInfoRow(L("settings.row.moltbookSeenPosts"), String(scout.seenPostCount)),
|
|
3254
|
+
])}
|
|
3255
|
+
${batchRows.length ? renderSettingsGroup(L("settings.moltbook.batchTitle"), batchRows) : ""}
|
|
3256
|
+
${Array.isArray(scout.recentComposeTitles) && scout.recentComposeTitles.length
|
|
3257
|
+
? renderSettingsGroup(L("settings.row.moltbookRecentTitles"), scout.recentComposeTitles.map((t) => renderSettingsInfoRow("", t)))
|
|
3258
|
+
: ""}
|
|
3259
|
+
</div>
|
|
3260
|
+
`;
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3155
3263
|
function renderSettingsInfoRow(label, value, options = {}) {
|
|
3156
3264
|
const rowClassName = ["settings-info-row", options.rowClassName || ""].filter(Boolean).join(" ");
|
|
3157
3265
|
const valueClassName = ["settings-info-row__value", options.valueClassName || ""].filter(Boolean).join(" ");
|
|
@@ -3193,7 +3301,7 @@ function renderDeviceSection(title, devices, emptyMessage) {
|
|
|
3193
3301
|
|
|
3194
3302
|
function renderTrustedDeviceCard(device) {
|
|
3195
3303
|
const localeLabel = localeDisplayName(device.locale, state.locale) || device.locale || L("common.unavailable");
|
|
3196
|
-
const pushLabel = device.pushSubscribed ? L("common.
|
|
3304
|
+
const pushLabel = device.pushSubscribed ? L("common.enabled") : L("common.disabled");
|
|
3197
3305
|
const badge = device.currentDevice
|
|
3198
3306
|
? `<span class="device-card__badge">${escapeHtml(L("settings.device.thisDevice"))}</span>`
|
|
3199
3307
|
: "";
|
|
@@ -3264,13 +3372,17 @@ function renderStandardDetailDesktop(detail) {
|
|
|
3264
3372
|
<div class="detail-shell">
|
|
3265
3373
|
${renderDetailMetaRow(detail, kindInfo)}
|
|
3266
3374
|
<h2 class="detail-title detail-title--desktop">${escapeHtml(detailDisplayTitle(detail))}</h2>
|
|
3267
|
-
${detail.readOnly || detail.kind === "approval" ? "" : renderDetailLead(detail, kindInfo)}
|
|
3375
|
+
${detail.readOnly || detail.kind === "approval" || detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" ? "" : renderDetailLead(detail, kindInfo)}
|
|
3268
3376
|
${renderPreviousContextCard(detail)}
|
|
3269
3377
|
${renderInterruptedDetailNotice(detail)}
|
|
3378
|
+
${renderMoltbookReplyComposer(detail)}
|
|
3379
|
+
${renderMoltbookDraftComposer(detail)}
|
|
3270
3380
|
${
|
|
3271
|
-
|
|
3272
|
-
?
|
|
3273
|
-
:
|
|
3381
|
+
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply"
|
|
3382
|
+
? ""
|
|
3383
|
+
: plainIntro
|
|
3384
|
+
? plainIntro
|
|
3385
|
+
: `
|
|
3274
3386
|
<section class="detail-card detail-card--body ${spaciousBodyDetail ? "detail-card--message-body" : ""}">
|
|
3275
3387
|
<div class="detail-body ${spaciousBodyDetail ? "detail-body--message " : ""}markdown">${detail.messageHtml || ""}</div>
|
|
3276
3388
|
</section>
|
|
@@ -3299,10 +3411,14 @@ function renderStandardDetailMobile(detail) {
|
|
|
3299
3411
|
${renderDetailMetaRow(detail, kindInfo, { mobile: true })}
|
|
3300
3412
|
${renderPreviousContextCard(detail, { mobile: true })}
|
|
3301
3413
|
${renderInterruptedDetailNotice(detail, { mobile: true })}
|
|
3414
|
+
${renderMoltbookReplyComposer(detail, { mobile: true })}
|
|
3415
|
+
${renderMoltbookDraftComposer(detail, { mobile: true })}
|
|
3302
3416
|
${
|
|
3303
|
-
|
|
3304
|
-
?
|
|
3305
|
-
:
|
|
3417
|
+
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply"
|
|
3418
|
+
? ""
|
|
3419
|
+
: plainIntro
|
|
3420
|
+
? plainIntro
|
|
3421
|
+
: `
|
|
3306
3422
|
<section class="detail-card detail-card--body detail-card--mobile ${spaciousBodyDetail ? "detail-card--message-body" : ""}">
|
|
3307
3423
|
${detail.readOnly || detail.kind === "approval" ? "" : renderDetailLead(detail, kindInfo, { mobile: true })}
|
|
3308
3424
|
<div class="detail-body ${spaciousBodyDetail ? "detail-body--message " : ""}markdown">${detail.messageHtml || ""}</div>
|
|
@@ -3327,7 +3443,8 @@ function renderDetailPlainIntro(detail, options = {}) {
|
|
|
3327
3443
|
if (!["approval", "diff_thread", "file_event"].includes(detail?.kind || "")) {
|
|
3328
3444
|
return "";
|
|
3329
3445
|
}
|
|
3330
|
-
|
|
3446
|
+
const ak = normalizeClientText(detail?.approvalKind || "");
|
|
3447
|
+
if (detail?.kind === "approval" && ak !== "file" && ak !== "plan" && ak !== "question") {
|
|
3331
3448
|
return "";
|
|
3332
3449
|
}
|
|
3333
3450
|
if (!detail?.messageHtml) {
|
|
@@ -3732,7 +3849,7 @@ function renderClaudeQuestionSection(detail, options = {}) {
|
|
|
3732
3849
|
${noticeHtml}
|
|
3733
3850
|
${isReadOnly ? "" : `
|
|
3734
3851
|
<div class="claude-question-actions">
|
|
3735
|
-
<button type="submit" class="
|
|
3852
|
+
<button type="submit" class="primary primary--wide" ${isSent || draft.sending ? "disabled" : ""}>
|
|
3736
3853
|
${escapeHtml(draft.sending ? L("claudeQuestion.send", { provider }) + "…" : sendLabel)}
|
|
3737
3854
|
</button>
|
|
3738
3855
|
</div>
|
|
@@ -3755,6 +3872,88 @@ function setClaudeQuestionDraft(token, partial) {
|
|
|
3755
3872
|
return next;
|
|
3756
3873
|
}
|
|
3757
3874
|
|
|
3875
|
+
function renderMoltbookReplyComposer(detail, options = {}) {
|
|
3876
|
+
if (detail.kind !== "moltbook_reply") return "";
|
|
3877
|
+
const rawTitle = normalizeClientText(detail.title || "");
|
|
3878
|
+
const match = rawTitle.match(/^@([^\s]+)/u);
|
|
3879
|
+
const authorHandle = match ? match[1] : detail.commentAuthor || "";
|
|
3880
|
+
const postUrl = detail.postUrl || "";
|
|
3881
|
+
const postLink = postUrl
|
|
3882
|
+
? `<p class="reply-composer__author"><a href="${escapeHtml(postUrl)}" target="_blank" rel="noopener">${escapeHtml(postUrl)}</a></p>`
|
|
3883
|
+
: "";
|
|
3884
|
+
const bodyHtml = detail.messageHtml
|
|
3885
|
+
? `<div class="reply-composer__context-body markdown">${detail.messageHtml}</div>`
|
|
3886
|
+
: "";
|
|
3887
|
+
return `
|
|
3888
|
+
<section class="detail-card detail-card--reply ${options.mobile ? "detail-card--mobile" : ""}">
|
|
3889
|
+
<div class="reply-composer reply-composer--readonly">
|
|
3890
|
+
<div class="reply-composer__copy">
|
|
3891
|
+
<span class="eyebrow-pill eyebrow-pill--quiet">Moltbook</span>
|
|
3892
|
+
${authorHandle ? `<p class="reply-composer__author">from <strong>@${escapeHtml(authorHandle)}</strong></p>` : ""}
|
|
3893
|
+
${postLink}
|
|
3894
|
+
</div>
|
|
3895
|
+
${bodyHtml}
|
|
3896
|
+
</div>
|
|
3897
|
+
</section>
|
|
3898
|
+
`;
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
function renderMoltbookDraftComposer(detail, options = {}) {
|
|
3902
|
+
if (detail.kind !== "moltbook_draft") return "";
|
|
3903
|
+
const enabled = detail.moltbookDraftEnabled !== false && detail.readOnly !== true;
|
|
3904
|
+
const draftText = detail.draftText || "";
|
|
3905
|
+
const isOriginalPost = detail.draftType === "original_post";
|
|
3906
|
+
const postAuthorLine = !isOriginalPost && detail.postAuthor
|
|
3907
|
+
? `<p class="reply-composer__author-meta muted">@${escapeHtml(detail.postAuthor)}</p>`
|
|
3908
|
+
: "";
|
|
3909
|
+
const postLink = !isOriginalPost && detail.postUrl
|
|
3910
|
+
? `<p class="reply-composer__author"><a href="${escapeHtml(detail.postUrl)}" target="_blank" rel="noopener">${escapeHtml(detail.postTitle || detail.postUrl)}</a></p>`
|
|
3911
|
+
: "";
|
|
3912
|
+
const postBodyBlock = !isOriginalPost && detail.postBody
|
|
3913
|
+
? `<details class="reply-composer__context"><summary>元の投稿</summary><div class="reply-composer__context-body">${escapeHtml(detail.postBody).replace(/\n/g, "<br>")}</div></details>`
|
|
3914
|
+
: "";
|
|
3915
|
+
const intentBlock = detail.intent
|
|
3916
|
+
? `<div class="reply-composer__intent"><span class="eyebrow-pill eyebrow-pill--quiet">${escapeHtml(L("moltbook.draft.intent"))}</span><p>${escapeHtml(detail.intent).replace(/\n/g, "<br>")}</p></div>`
|
|
3917
|
+
: "";
|
|
3918
|
+
const titleInput = isOriginalPost && enabled
|
|
3919
|
+
? `<label class="field reply-field reply-field--title"><span class="field-label">${escapeHtml(L("moltbook.draft.titleLabel"))}</span><input type="text" name="title" class="reply-field__input" value="${escapeHtml(detail.postTitle || "")}" data-moltbook-draft-title /></label>`
|
|
3920
|
+
: isOriginalPost
|
|
3921
|
+
? `<p class="reply-composer__title-display"><strong>${escapeHtml(detail.postTitle || "")}</strong></p>`
|
|
3922
|
+
: "";
|
|
3923
|
+
const submoltBadge = isOriginalPost && detail.submoltName
|
|
3924
|
+
? `<span class="eyebrow-pill eyebrow-pill--subtle">${escapeHtml(detail.submoltName)}</span>`
|
|
3925
|
+
: "";
|
|
3926
|
+
const eyebrowLabel = isOriginalPost ? L("moltbook.draft.eyebrowPost") : L("moltbook.draft.eyebrowReply");
|
|
3927
|
+
const approveLabel = isOriginalPost ? L("moltbook.draft.approvePost") : L("moltbook.draft.approveReply");
|
|
3928
|
+
const buttons = enabled
|
|
3929
|
+
? `
|
|
3930
|
+
<div class="actions actions--stack${options.mobile ? " actions--sticky" : ""}">
|
|
3931
|
+
<button type="submit" data-action="approve" class="primary primary--wide">${escapeHtml(approveLabel)}</button>
|
|
3932
|
+
<button type="submit" data-action="deny" class="danger danger--wide">Deny</button>
|
|
3933
|
+
</div>
|
|
3934
|
+
`
|
|
3935
|
+
: `<p class="muted reply-composer__description">${escapeHtml(L("moltbook.draft.resolved"))}</p>`;
|
|
3936
|
+
const buttonsWrapped = enabled && options.mobile ? `<div class="detail-action-bar">${buttons}</div>` : buttons;
|
|
3937
|
+
return `
|
|
3938
|
+
<section class="detail-card detail-card--reply ${options.mobile ? "detail-card--mobile" : ""}">
|
|
3939
|
+
<form class="reply-composer" data-moltbook-draft-form data-token="${escapeHtml(detail.token || "")}" ${isOriginalPost ? 'data-draft-type="original_post"' : ""}>
|
|
3940
|
+
<div class="reply-composer__copy">
|
|
3941
|
+
<span class="eyebrow-pill eyebrow-pill--quiet">${escapeHtml(eyebrowLabel)}</span>
|
|
3942
|
+
${submoltBadge}
|
|
3943
|
+
${postLink}
|
|
3944
|
+
${postAuthorLine}
|
|
3945
|
+
${titleInput}
|
|
3946
|
+
${postBodyBlock}
|
|
3947
|
+
${intentBlock}
|
|
3948
|
+
<p class="muted reply-composer__description">${escapeHtml(L("moltbook.draft.editHint"))}</p>
|
|
3949
|
+
</div>
|
|
3950
|
+
<textarea name="text" class="reply-composer__textarea" data-moltbook-draft-textarea rows="8" ${enabled ? "" : "readonly"}>${escapeHtml(draftText)}</textarea>
|
|
3951
|
+
${buttonsWrapped}
|
|
3952
|
+
</form>
|
|
3953
|
+
</section>
|
|
3954
|
+
`;
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3758
3957
|
function renderCompletionReplyComposer(detail, options = {}) {
|
|
3759
3958
|
if (detail.kind !== "completion" || detail.reply?.enabled !== true) {
|
|
3760
3959
|
return "";
|
|
@@ -4811,6 +5010,77 @@ function bindShellInteractions() {
|
|
|
4811
5010
|
});
|
|
4812
5011
|
}
|
|
4813
5012
|
|
|
5013
|
+
const moltbookDraftForm = document.querySelector("[data-moltbook-draft-form]");
|
|
5014
|
+
if (moltbookDraftForm) {
|
|
5015
|
+
const token = moltbookDraftForm.dataset.token || "";
|
|
5016
|
+
let submittedAction = "approve";
|
|
5017
|
+
moltbookDraftForm.querySelectorAll("button[type='submit']").forEach((btn) => {
|
|
5018
|
+
btn.addEventListener("click", () => {
|
|
5019
|
+
submittedAction = btn.dataset.action || "approve";
|
|
5020
|
+
});
|
|
5021
|
+
});
|
|
5022
|
+
moltbookDraftForm.addEventListener("submit", async (event) => {
|
|
5023
|
+
event.preventDefault();
|
|
5024
|
+
if (moltbookDraftForm.dataset.submitting === "1") return;
|
|
5025
|
+
moltbookDraftForm.dataset.submitting = "1";
|
|
5026
|
+
const buttons = moltbookDraftForm.querySelectorAll("button[type='submit']");
|
|
5027
|
+
const textarea = moltbookDraftForm.querySelector("textarea");
|
|
5028
|
+
const labelCache = new Map();
|
|
5029
|
+
buttons.forEach((btn) => {
|
|
5030
|
+
labelCache.set(btn, btn.innerHTML);
|
|
5031
|
+
btn.disabled = true;
|
|
5032
|
+
if (btn.dataset.action === submittedAction) {
|
|
5033
|
+
btn.innerHTML = submittedAction === "approve" ? "送信中…" : "処理中…";
|
|
5034
|
+
btn.classList.add("is-loading");
|
|
5035
|
+
} else {
|
|
5036
|
+
btn.classList.add("is-dimmed");
|
|
5037
|
+
}
|
|
5038
|
+
});
|
|
5039
|
+
if (textarea) textarea.readOnly = true;
|
|
5040
|
+
const editedText = normalizeClientText(new FormData(moltbookDraftForm).get("text"));
|
|
5041
|
+
const titleInput = moltbookDraftForm.querySelector("[data-moltbook-draft-title]");
|
|
5042
|
+
const editedTitle = titleInput ? normalizeClientText(titleInput.value) : "";
|
|
5043
|
+
try {
|
|
5044
|
+
const decisionBody = { action: submittedAction, editedText };
|
|
5045
|
+
if (editedTitle) decisionBody.editedTitle = editedTitle;
|
|
5046
|
+
const res = await fetch(`/api/items/moltbook-draft/${encodeURIComponent(token)}/decision`, {
|
|
5047
|
+
method: "POST",
|
|
5048
|
+
headers: { "content-type": "application/json" },
|
|
5049
|
+
credentials: "same-origin",
|
|
5050
|
+
body: JSON.stringify(decisionBody),
|
|
5051
|
+
});
|
|
5052
|
+
if (!res.ok) {
|
|
5053
|
+
const errBody = await res.json().catch(() => ({}));
|
|
5054
|
+
alert(`Moltbook draft ${submittedAction} failed: ${errBody.error || res.status}`);
|
|
5055
|
+
buttons.forEach((btn) => {
|
|
5056
|
+
btn.disabled = false;
|
|
5057
|
+
btn.classList.remove("is-loading", "is-dimmed");
|
|
5058
|
+
btn.innerHTML = labelCache.get(btn);
|
|
5059
|
+
});
|
|
5060
|
+
if (textarea) textarea.readOnly = false;
|
|
5061
|
+
moltbookDraftForm.dataset.submitting = "";
|
|
5062
|
+
return;
|
|
5063
|
+
}
|
|
5064
|
+
// Mark local detail as resolved so re-render shows "already resolved" immediately.
|
|
5065
|
+
if (state.currentDetail?.kind === "moltbook_draft") {
|
|
5066
|
+
state.currentDetail.moltbookDraftEnabled = false;
|
|
5067
|
+
state.currentDetail.readOnly = true;
|
|
5068
|
+
}
|
|
5069
|
+
await refreshAuthenticatedState();
|
|
5070
|
+
await renderShell();
|
|
5071
|
+
} catch (error) {
|
|
5072
|
+
alert(`Moltbook draft error: ${error.message}`);
|
|
5073
|
+
buttons.forEach((btn) => {
|
|
5074
|
+
btn.disabled = false;
|
|
5075
|
+
btn.classList.remove("is-loading", "is-dimmed");
|
|
5076
|
+
btn.innerHTML = labelCache.get(btn);
|
|
5077
|
+
});
|
|
5078
|
+
if (textarea) textarea.readOnly = false;
|
|
5079
|
+
moltbookDraftForm.dataset.submitting = "";
|
|
5080
|
+
}
|
|
5081
|
+
});
|
|
5082
|
+
}
|
|
5083
|
+
|
|
4814
5084
|
const replyForm = document.querySelector("[data-completion-reply-form]");
|
|
4815
5085
|
if (replyForm) {
|
|
4816
5086
|
const token = replyForm.dataset.token || "";
|
|
@@ -5289,6 +5559,9 @@ function kindMeta(kind) {
|
|
|
5289
5559
|
return { label: L("common.diff"), tone: "neutral", icon: "diff" };
|
|
5290
5560
|
case "file_event":
|
|
5291
5561
|
return { label: L("common.fileEvent"), tone: "neutral", icon: "file-event" };
|
|
5562
|
+
case "moltbook_reply":
|
|
5563
|
+
case "moltbook_draft":
|
|
5564
|
+
return { label: L("common.sns"), tone: "neutral", icon: "item" };
|
|
5292
5565
|
default:
|
|
5293
5566
|
return { label: L("common.item"), tone: "neutral", icon: "item" };
|
|
5294
5567
|
}
|
package/web/i18n.js
CHANGED
|
@@ -29,6 +29,10 @@ const translations = {
|
|
|
29
29
|
"common.unavailable": "unavailable",
|
|
30
30
|
"common.yes": "Yes",
|
|
31
31
|
"common.no": "No",
|
|
32
|
+
"common.enabled": "Enabled",
|
|
33
|
+
"common.disabled": "Disabled",
|
|
34
|
+
"common.supported": "Supported",
|
|
35
|
+
"common.notSupported": "Not supported",
|
|
32
36
|
"common.none": "none",
|
|
33
37
|
"common.settings": "Settings",
|
|
34
38
|
"common.pending": "Pending",
|
|
@@ -44,6 +48,7 @@ const translations = {
|
|
|
44
48
|
"common.assistantCommentary": "Update",
|
|
45
49
|
"common.assistantFinal": "Final reply",
|
|
46
50
|
"common.item": "Item",
|
|
51
|
+
"common.sns": "SNS",
|
|
47
52
|
"common.untitledItem": "Untitled item",
|
|
48
53
|
"common.useDeviceLanguage": "Use device language",
|
|
49
54
|
"language.option.en": "English",
|
|
@@ -339,6 +344,28 @@ const translations = {
|
|
|
339
344
|
"settings.row.currentLanguage": "Current language",
|
|
340
345
|
"settings.row.languageSource": "Language source",
|
|
341
346
|
"settings.row.defaultLanguage": "Setup default",
|
|
347
|
+
"settings.moltbook.title": "Moltbook",
|
|
348
|
+
"settings.moltbook.subtitle": "Auto-scout posting quota",
|
|
349
|
+
"settings.moltbook.copy": "View your daily auto-scout posting status and quota.",
|
|
350
|
+
"settings.moltbook.unavailable": "Moltbook is not configured.",
|
|
351
|
+
"settings.row.moltbookQuota": "Posted today",
|
|
352
|
+
"settings.row.moltbookDate": "Date",
|
|
353
|
+
"settings.row.moltbookSeenPosts": "Seen posts",
|
|
354
|
+
"settings.moltbook.batchTitle": "Current batch",
|
|
355
|
+
"settings.row.moltbookBatchCandidates": "Candidates",
|
|
356
|
+
"settings.row.moltbookBatchTopScore": "Top score",
|
|
357
|
+
"settings.row.moltbookBatchRemaining": "Time remaining",
|
|
358
|
+
"settings.row.moltbookComposed": "Original posts today",
|
|
359
|
+
"settings.row.moltbookSlots": "Slots attempted",
|
|
360
|
+
"settings.row.moltbookRecentTitles": "Recent post titles",
|
|
361
|
+
"moltbook.draft.eyebrowPost": "Moltbook new post",
|
|
362
|
+
"moltbook.draft.eyebrowReply": "Moltbook draft",
|
|
363
|
+
"moltbook.draft.intent": "Intent",
|
|
364
|
+
"moltbook.draft.titleLabel": "Title",
|
|
365
|
+
"moltbook.draft.approvePost": "Approve & publish",
|
|
366
|
+
"moltbook.draft.approveReply": "Approve & post",
|
|
367
|
+
"moltbook.draft.editHint": "Edit if needed, then approve to publish or deny to skip.",
|
|
368
|
+
"moltbook.draft.resolved": "This draft has already been resolved.",
|
|
342
369
|
"notice.notificationsEnabled": "Notifications are enabled on this device.",
|
|
343
370
|
"notice.notificationsDisabled": "Notifications are disabled on this device.",
|
|
344
371
|
"notice.testNotificationSent": "Test notification sent.",
|
|
@@ -575,6 +602,10 @@ const translations = {
|
|
|
575
602
|
"common.unavailable": "利用不可",
|
|
576
603
|
"common.yes": "はい",
|
|
577
604
|
"common.no": "いいえ",
|
|
605
|
+
"common.enabled": "有効",
|
|
606
|
+
"common.disabled": "無効",
|
|
607
|
+
"common.supported": "対応",
|
|
608
|
+
"common.notSupported": "非対応",
|
|
578
609
|
"common.none": "なし",
|
|
579
610
|
"common.settings": "設定",
|
|
580
611
|
"common.pending": "未対応",
|
|
@@ -590,6 +621,7 @@ const translations = {
|
|
|
590
621
|
"common.assistantCommentary": "途中経過",
|
|
591
622
|
"common.assistantFinal": "最終回答",
|
|
592
623
|
"common.item": "項目",
|
|
624
|
+
"common.sns": "SNS",
|
|
593
625
|
"common.untitledItem": "無題の項目",
|
|
594
626
|
"common.useDeviceLanguage": "端末の言語を使う",
|
|
595
627
|
"language.option.en": "English",
|
|
@@ -881,6 +913,28 @@ const translations = {
|
|
|
881
913
|
"settings.row.currentLanguage": "現在の言語",
|
|
882
914
|
"settings.row.languageSource": "言語ソース",
|
|
883
915
|
"settings.row.defaultLanguage": "setup 時の既定値",
|
|
916
|
+
"settings.moltbook.title": "Moltbook",
|
|
917
|
+
"settings.moltbook.subtitle": "自動スカウトの投稿枠",
|
|
918
|
+
"settings.moltbook.copy": "本日の自動スカウト投稿状況と上限を確認できます。",
|
|
919
|
+
"settings.moltbook.unavailable": "Moltbook が設定されていません。",
|
|
920
|
+
"settings.row.moltbookQuota": "本日の投稿数",
|
|
921
|
+
"settings.row.moltbookDate": "日付",
|
|
922
|
+
"settings.row.moltbookSeenPosts": "確認済み投稿数",
|
|
923
|
+
"settings.moltbook.batchTitle": "現在のバッチ",
|
|
924
|
+
"settings.row.moltbookBatchCandidates": "候補数",
|
|
925
|
+
"settings.row.moltbookBatchTopScore": "最高スコア",
|
|
926
|
+
"settings.row.moltbookBatchRemaining": "残り時間",
|
|
927
|
+
"settings.row.moltbookComposed": "本日の新規投稿数",
|
|
928
|
+
"settings.row.moltbookSlots": "提案済みスロット",
|
|
929
|
+
"settings.row.moltbookRecentTitles": "最近の投稿タイトル",
|
|
930
|
+
"moltbook.draft.eyebrowPost": "Moltbook 新規投稿",
|
|
931
|
+
"moltbook.draft.eyebrowReply": "Moltbook ドラフト",
|
|
932
|
+
"moltbook.draft.intent": "意図",
|
|
933
|
+
"moltbook.draft.titleLabel": "タイトル",
|
|
934
|
+
"moltbook.draft.approvePost": "承認して投稿",
|
|
935
|
+
"moltbook.draft.approveReply": "承認して返信",
|
|
936
|
+
"moltbook.draft.editHint": "必要に応じて編集し、承認して投稿するか、拒否してスキップします。",
|
|
937
|
+
"moltbook.draft.resolved": "このドラフトは既に処理済みです。",
|
|
884
938
|
"notice.notificationsEnabled": "この端末で通知を有効にしました。",
|
|
885
939
|
"notice.notificationsDisabled": "この端末の通知を無効にしました。",
|
|
886
940
|
"notice.testNotificationSent": "テスト通知を送信しました。",
|
|
@@ -1012,7 +1066,7 @@ const translations = {
|
|
|
1012
1066
|
"cli.setup.webPushEnabled": "Web Push: enabled (HTTPS)",
|
|
1013
1067
|
"cli.setup.webPushDisabled": "Web Push: disabled",
|
|
1014
1068
|
"cli.setup.claudeHooksSkipped": "Claude hooks はスキップしました(~/.claude ディレクトリが見つかりません)。Claude Desktop をインストール後に `viveworker setup` を再実行すると Claude 連携が有効になります。",
|
|
1015
|
-
"cli.setup.completePending": "viveworker の setup は完了しましたが、まだ health 応答は確認できていません。
|
|
1069
|
+
"cli.setup.completePending": "viveworker の setup は完了しましたが、まだ health 応答は確認できていません。",
|
|
1016
1070
|
"cli.setup.caFlow.title": "端末で viveworker を開く前に、必要ならローカルの証明書をインストールしてください:",
|
|
1017
1071
|
"cli.setup.caFlow.step1": "1. 下の CA download URL を開くか QR を読み取り、端末で rootCA.pem を取得します。",
|
|
1018
1072
|
"cli.setup.caFlow.step2": "2. rootCA.pem をインストールし、必要なら端末側でその証明書を信頼します。",
|
package/web/sw.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const CACHE_NAME = "viveworker-
|
|
1
|
+
const CACHE_NAME = "viveworker-v20";
|
|
2
2
|
const NOTIFICATION_INTENT_CACHE = "viveworker-notification-intent-v1";
|
|
3
3
|
const NOTIFICATION_INTENT_PATH = "/__viveworker_notification_intent__";
|
|
4
4
|
const APP_ASSETS = ["/app.css", "/app.js", "/i18n.js"];
|