viveworker 0.4.6 → 0.4.8
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/package.json +1 -1
- package/scripts/a2a-executor.mjs +157 -19
- package/scripts/a2a-handler.mjs +2 -1
- package/scripts/a2a-relay-client.mjs +9 -0
- package/scripts/moltbook-api.mjs +209 -0
- package/scripts/moltbook-cli.mjs +12 -397
- package/scripts/viveworker-bridge.mjs +917 -47
- package/scripts/viveworker-claude-hook.mjs +74 -1
- package/web/app.css +123 -1
- package/web/app.js +367 -37
- package/web/i18n.js +75 -3
package/web/app.js
CHANGED
|
@@ -5,7 +5,7 @@ const INSTALL_BANNER_DISMISS_KEY = "viveworker-install-banner-dismissed-v2";
|
|
|
5
5
|
const PUSH_BANNER_DISMISS_KEY = "viveworker-push-banner-dismissed-v1";
|
|
6
6
|
const INITIAL_DETECTED_LOCALE = detectBrowserLocale();
|
|
7
7
|
const TIMELINE_MESSAGE_KINDS = new Set(["user_message", "assistant_commentary", "assistant_final"]);
|
|
8
|
-
const TIMELINE_OPERATIONAL_KINDS = new Set(["approval", "plan", "plan_ready", "choice"
|
|
8
|
+
const TIMELINE_OPERATIONAL_KINDS = new Set(["approval", "plan", "plan_ready", "choice"]);
|
|
9
9
|
const THREAD_FILTER_INTERACTION_DEFER_MS = 8000;
|
|
10
10
|
const MAX_COMPLETION_REPLY_IMAGE_COUNT = 4;
|
|
11
11
|
const NOTIFICATION_INTENT_CACHE = "viveworker-notification-intent-v1";
|
|
@@ -612,12 +612,19 @@ function filteredDiffEntries() {
|
|
|
612
612
|
}
|
|
613
613
|
|
|
614
614
|
function syncTimelineThreadFilter() {
|
|
615
|
-
const threads = Array.isArray(state.timeline?.threads) ? state.timeline.threads : [];
|
|
616
615
|
if (!state.timelineThreadFilter || state.timelineThreadFilter === "all") {
|
|
617
616
|
state.timelineThreadFilter = "all";
|
|
618
617
|
return;
|
|
619
618
|
}
|
|
620
|
-
|
|
619
|
+
const provider = state.providerFilter || "all";
|
|
620
|
+
let validThreadIds;
|
|
621
|
+
if (provider === "all") {
|
|
622
|
+
const threads = Array.isArray(state.timeline?.threads) ? state.timeline.threads : [];
|
|
623
|
+
validThreadIds = new Set(threads.map((t) => t.id));
|
|
624
|
+
} else {
|
|
625
|
+
validThreadIds = new Set(timelineThreadsForProvider(provider).map((t) => t.id));
|
|
626
|
+
}
|
|
627
|
+
if (!validThreadIds.has(state.timelineThreadFilter)) {
|
|
621
628
|
state.timelineThreadFilter = "all";
|
|
622
629
|
}
|
|
623
630
|
}
|
|
@@ -630,16 +637,47 @@ function syncTimelineKindFilter() {
|
|
|
630
637
|
}
|
|
631
638
|
|
|
632
639
|
function timelineKindFilterOptions() {
|
|
633
|
-
|
|
634
|
-
|
|
640
|
+
const provider = state.providerFilter || "all";
|
|
641
|
+
const allOption = { id: "all", label: L("timeline.kindFilter.all"), icon: "filter" };
|
|
642
|
+
|
|
643
|
+
if (provider === "moltbook") {
|
|
644
|
+
return [
|
|
645
|
+
allOption,
|
|
646
|
+
{ id: "moltbook_reply_drafts", label: L("timeline.kindFilter.moltbookReplyDrafts"), icon: "moltbook-reply" },
|
|
647
|
+
{ id: "moltbook_post_drafts", label: L("timeline.kindFilter.moltbookPostDrafts"), icon: "moltbook-draft" },
|
|
648
|
+
{ id: "moltbook_comments", label: L("timeline.kindFilter.moltbookComments"), icon: "moltbook-comment" },
|
|
649
|
+
];
|
|
650
|
+
}
|
|
651
|
+
if (provider === "a2a") {
|
|
652
|
+
return [
|
|
653
|
+
allOption,
|
|
654
|
+
{ id: "a2a_requests", label: L("timeline.kindFilter.a2aRequests"), icon: "item" },
|
|
655
|
+
{ id: "a2a_results", label: L("timeline.kindFilter.a2aResults"), icon: "completion-item" },
|
|
656
|
+
];
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const codexClaudeOptions = [
|
|
660
|
+
allOption,
|
|
635
661
|
{ id: "messages", label: L("timeline.kindFilter.messages"), icon: "timeline" },
|
|
636
662
|
{ id: "files", label: L("timeline.kindFilter.files"), icon: "file-event" },
|
|
637
663
|
{ id: "approvals", label: L("timeline.kindFilter.approvals"), icon: "approval" },
|
|
638
664
|
{ id: "plans", label: L("timeline.kindFilter.plans"), icon: "plan" },
|
|
639
665
|
{ id: "choices", label: L("timeline.kindFilter.choices"), icon: "choice" },
|
|
640
|
-
{ id: "
|
|
641
|
-
|
|
666
|
+
{ id: "final_answers", label: L("timeline.kindFilter.finalAnswers"), icon: "assistant-final" },
|
|
667
|
+
];
|
|
668
|
+
|
|
669
|
+
if (provider === "codex" || provider === "claude") {
|
|
670
|
+
return codexClaudeOptions;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// "all" — union of everything
|
|
674
|
+
return [
|
|
675
|
+
...codexClaudeOptions,
|
|
676
|
+
{ id: "moltbook_reply_drafts", label: L("timeline.kindFilter.moltbookReplyDrafts"), icon: "moltbook-reply" },
|
|
677
|
+
{ id: "moltbook_post_drafts", label: L("timeline.kindFilter.moltbookPostDrafts"), icon: "moltbook-draft" },
|
|
642
678
|
{ id: "moltbook_comments", label: L("timeline.kindFilter.moltbookComments"), icon: "moltbook-comment" },
|
|
679
|
+
{ id: "a2a_requests", label: L("timeline.kindFilter.a2aRequests"), icon: "item" },
|
|
680
|
+
{ id: "a2a_results", label: L("timeline.kindFilter.a2aResults"), icon: "completion-item" },
|
|
643
681
|
];
|
|
644
682
|
}
|
|
645
683
|
|
|
@@ -665,10 +703,20 @@ function timelineEntryMatchesKindFilter(entry, filterId) {
|
|
|
665
703
|
return kind === "choice";
|
|
666
704
|
case "completions":
|
|
667
705
|
return kind === "completion";
|
|
706
|
+
case "final_answers":
|
|
707
|
+
return kind === "assistant_final";
|
|
668
708
|
case "moltbook_drafts":
|
|
669
709
|
return kind === "moltbook_draft";
|
|
710
|
+
case "moltbook_reply_drafts":
|
|
711
|
+
return kind === "moltbook_draft" && entry?.draftType === "reply";
|
|
712
|
+
case "moltbook_post_drafts":
|
|
713
|
+
return kind === "moltbook_draft" && entry?.draftType !== "reply";
|
|
670
714
|
case "moltbook_comments":
|
|
671
715
|
return kind === "moltbook_reply";
|
|
716
|
+
case "a2a_requests":
|
|
717
|
+
return kind === "a2a_task";
|
|
718
|
+
case "a2a_results":
|
|
719
|
+
return kind === "a2a_task_result";
|
|
672
720
|
default:
|
|
673
721
|
return true;
|
|
674
722
|
}
|
|
@@ -689,10 +737,19 @@ function completedThreads() {
|
|
|
689
737
|
if (!items.length) {
|
|
690
738
|
return [];
|
|
691
739
|
}
|
|
740
|
+
const provider = state.providerFilter || "all";
|
|
692
741
|
const byThread = new Map();
|
|
693
742
|
for (const item of items) {
|
|
694
743
|
const threadId = normalizeClientText(item.threadId || "");
|
|
695
|
-
if (!threadId
|
|
744
|
+
if (!threadId) {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
// Skip Moltbook threads unless Moltbook tab is active
|
|
748
|
+
if (provider !== "moltbook" && isMoltbookThreadId(threadId, item)) {
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
// Filter by provider
|
|
752
|
+
if (provider !== "all" && normalizeProviderClient(item.provider) !== provider) {
|
|
696
753
|
continue;
|
|
697
754
|
}
|
|
698
755
|
const latestAtMs = Number(item.createdAtMs || 0);
|
|
@@ -993,6 +1050,9 @@ function shouldDeferRenderForActiveInteraction() {
|
|
|
993
1050
|
if (state.currentDetail?.kind === "moltbook_draft") {
|
|
994
1051
|
return true;
|
|
995
1052
|
}
|
|
1053
|
+
if (state.currentDetail?.kind === "thread_share" && state.currentDetail?.threadShareEnabled) {
|
|
1054
|
+
return true;
|
|
1055
|
+
}
|
|
996
1056
|
if (
|
|
997
1057
|
activeElement instanceof HTMLTextAreaElement &&
|
|
998
1058
|
activeElement.matches("[data-claude-question-note]")
|
|
@@ -1372,7 +1432,6 @@ function buildDetailLoadingSnapshot(itemRef = state.currentItem) {
|
|
|
1372
1432
|
readOnly:
|
|
1373
1433
|
entry?.status === "completed" ||
|
|
1374
1434
|
TIMELINE_MESSAGE_KINDS.has(itemRef.kind) ||
|
|
1375
|
-
itemRef.kind === "completion" ||
|
|
1376
1435
|
(itemRef.kind === "choice" && item.supported === false),
|
|
1377
1436
|
loading: true,
|
|
1378
1437
|
};
|
|
@@ -1776,11 +1835,13 @@ function renderProviderFilter() {
|
|
|
1776
1835
|
`;
|
|
1777
1836
|
}
|
|
1778
1837
|
|
|
1838
|
+
const COMPLETED_CARD_KINDS = new Set(["assistant_final", "approval", "moltbook_reply", "moltbook_draft", "a2a_task_result", "thread_share"]);
|
|
1839
|
+
|
|
1779
1840
|
function renderItemCard(entry, sourceTab, desktop) {
|
|
1780
|
-
if (entry.status === "completed" && entry.item.kind
|
|
1841
|
+
if (entry.status === "completed" && COMPLETED_CARD_KINDS.has(entry.item.kind)) {
|
|
1781
1842
|
return renderCompletedCompletionCard(entry, sourceTab);
|
|
1782
1843
|
}
|
|
1783
|
-
const kindInfo = kindMeta(entry.item.kind);
|
|
1844
|
+
const kindInfo = kindMeta(entry.item.kind, entry.item);
|
|
1784
1845
|
const cardTitle = cardTitleForEntry(entry);
|
|
1785
1846
|
const statusText = entry.status === "completed" ? L("common.completed") : L("common.actionNeeded");
|
|
1786
1847
|
const intentText = itemIntentText(entry.item.kind, entry.status, entry.item.provider);
|
|
@@ -1849,10 +1910,12 @@ function cardTitleForEntry(entry) {
|
|
|
1849
1910
|
|
|
1850
1911
|
function renderCompletedCompletionCard(entry, sourceTab) {
|
|
1851
1912
|
const item = entry.item;
|
|
1852
|
-
const kindInfo = kindMeta(item.kind);
|
|
1913
|
+
const kindInfo = kindMeta(item.kind, item);
|
|
1853
1914
|
const summaryText = item.summary || fallbackSummaryForKind(item.kind, entry.status, item.provider);
|
|
1854
1915
|
const threadLabel = timelineEntryThreadLabel(item, true);
|
|
1855
1916
|
const timestampLabel = formatTimelineTimestamp(item.createdAtMs);
|
|
1917
|
+
const pillLabel = item.kind === "completion" ? L("common.task") : kindInfo.label;
|
|
1918
|
+
const pillTone = item.kind === "completion" ? "completion" : kindInfo.tone;
|
|
1856
1919
|
|
|
1857
1920
|
return `
|
|
1858
1921
|
<button
|
|
@@ -1864,7 +1927,7 @@ function renderCompletedCompletionCard(entry, sourceTab) {
|
|
|
1864
1927
|
>
|
|
1865
1928
|
<div class="item-card__header">
|
|
1866
1929
|
<div class="item-card__meta">
|
|
1867
|
-
<span class="type-pill type-pill
|
|
1930
|
+
<span class="type-pill type-pill--${escapeHtml(pillTone)}">${escapeHtml(pillLabel)}</span>
|
|
1868
1931
|
${renderProviderBadge(item.provider)}
|
|
1869
1932
|
</div>
|
|
1870
1933
|
<div class="item-card__header-right">
|
|
@@ -1954,17 +2017,47 @@ function renderDiffPanel({ entries, desktop }) {
|
|
|
1954
2017
|
`;
|
|
1955
2018
|
}
|
|
1956
2019
|
|
|
2020
|
+
function timelineThreadsForProvider(provider) {
|
|
2021
|
+
const entries = Array.isArray(state.timeline?.entries) ? state.timeline.entries : [];
|
|
2022
|
+
const byThread = new Map();
|
|
2023
|
+
for (const entry of entries) {
|
|
2024
|
+
const threadId = entry.threadId || "";
|
|
2025
|
+
if (!threadId) continue;
|
|
2026
|
+
if (normalizeProviderClient(entry.provider) !== provider) continue;
|
|
2027
|
+
const latestAtMs = Number(entry.createdAtMs) || 0;
|
|
2028
|
+
const label = entry.threadLabel || "";
|
|
2029
|
+
const existing = byThread.get(threadId);
|
|
2030
|
+
if (!existing || latestAtMs > existing.latestAtMs) {
|
|
2031
|
+
byThread.set(threadId, { id: threadId, label, latestAtMs });
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
return [...byThread.values()]
|
|
2035
|
+
.sort((a, b) => b.latestAtMs - a.latestAtMs)
|
|
2036
|
+
.map((t) => ({ id: t.id, label: dropdownThreadLabel(t.id, t.label) }));
|
|
2037
|
+
}
|
|
2038
|
+
|
|
1957
2039
|
function renderTimelineThreadDropdown() {
|
|
1958
|
-
const
|
|
2040
|
+
const provider = state.providerFilter || "all";
|
|
2041
|
+
const kindFilterHtml = renderTimelineKindFilterControls();
|
|
2042
|
+
|
|
2043
|
+
let threads;
|
|
2044
|
+
if (provider === "all") {
|
|
2045
|
+
// "All" view — use the server-provided thread list
|
|
2046
|
+
threads = (Array.isArray(state.timeline?.threads) ? state.timeline.threads : []).map((thread) => ({
|
|
2047
|
+
id: thread.id,
|
|
2048
|
+
label: dropdownThreadLabel(thread.id, thread.label || ""),
|
|
2049
|
+
}));
|
|
2050
|
+
} else {
|
|
2051
|
+
// Provider-specific view — build from entries matching this provider
|
|
2052
|
+
threads = timelineThreadsForProvider(provider);
|
|
2053
|
+
}
|
|
2054
|
+
|
|
1959
2055
|
return renderThreadDropdown({
|
|
1960
2056
|
inputId: "timeline-thread-select",
|
|
1961
2057
|
dataAttribute: "data-timeline-thread-select",
|
|
1962
2058
|
selectedThreadId: state.timelineThreadFilter,
|
|
1963
|
-
controlsHtml:
|
|
1964
|
-
threads
|
|
1965
|
-
id: thread.id,
|
|
1966
|
-
label: dropdownThreadLabel(thread.id, thread.label || ""),
|
|
1967
|
-
})),
|
|
2059
|
+
controlsHtml: kindFilterHtml,
|
|
2060
|
+
threads,
|
|
1968
2061
|
});
|
|
1969
2062
|
}
|
|
1970
2063
|
|
|
@@ -2069,10 +2162,11 @@ function renderTimelineKindFilterControls() {
|
|
|
2069
2162
|
|
|
2070
2163
|
function renderTimelineEntry(entry, { desktop }) {
|
|
2071
2164
|
const item = entry.item;
|
|
2072
|
-
const kindInfo = kindMeta(item.kind);
|
|
2165
|
+
const kindInfo = kindMeta(item.kind, item);
|
|
2073
2166
|
const kindClassName = escapeHtml(kindInfo.tone || "neutral");
|
|
2074
2167
|
const kindNameClass = escapeHtml(String(item.kind || "item").replace(/_/gu, "-"));
|
|
2075
|
-
const
|
|
2168
|
+
const isMoltbookOrA2A = item.kind === "moltbook_reply" || item.kind === "moltbook_draft" || item.kind === "a2a_task" || item.kind === "a2a_task_result" || item.kind === "thread_share";
|
|
2169
|
+
const isMessageLike = TIMELINE_MESSAGE_KINDS.has(item.kind) || isMoltbookOrA2A;
|
|
2076
2170
|
const isFileEvent = item.kind === "file_event";
|
|
2077
2171
|
const imageUrls = Array.isArray(item.imageUrls) ? item.imageUrls.filter(Boolean) : [];
|
|
2078
2172
|
const fileRefs = normalizeClientFileRefs(item.fileRefs);
|
|
@@ -2929,6 +3023,9 @@ function settingsPageMeta(page) {
|
|
|
2929
3023
|
description: L("settings.a2aRelay.copy"),
|
|
2930
3024
|
icon: "link",
|
|
2931
3025
|
};
|
|
3026
|
+
case "a2aExecutor":
|
|
3027
|
+
// Executor settings integrated into a2aRelay page — redirect.
|
|
3028
|
+
return settingsPageMeta("a2aRelay");
|
|
2932
3029
|
default:
|
|
2933
3030
|
return settingsPageMeta("notifications");
|
|
2934
3031
|
}
|
|
@@ -3061,6 +3158,7 @@ function renderSettingsSubpage(context, { mobile }) {
|
|
|
3061
3158
|
content = renderSettingsMoltbookPage(context);
|
|
3062
3159
|
break;
|
|
3063
3160
|
case "a2aRelay":
|
|
3161
|
+
case "a2aExecutor":
|
|
3064
3162
|
content = renderSettingsA2aRelayPage(context);
|
|
3065
3163
|
break;
|
|
3066
3164
|
default:
|
|
@@ -3331,6 +3429,21 @@ function renderSettingsA2aRelayPage(context) {
|
|
|
3331
3429
|
const profileUrl = `${relay.relayUrl}/${relay.userId}`;
|
|
3332
3430
|
const userIdLink = `<a href="${escapeHtml(profileUrl)}" target="_blank" rel="noopener">${escapeHtml(relay.userId)}</a>`;
|
|
3333
3431
|
const relayHost = (() => { try { return new URL(relay.relayUrl).host; } catch { return relay.relayUrl; } })();
|
|
3432
|
+
const publicChecked = relay.acceptPublicTasks === true;
|
|
3433
|
+
|
|
3434
|
+
// Executor preference section
|
|
3435
|
+
const executors = state.session?.a2aExecutors || { codex: false, claude: false };
|
|
3436
|
+
const currentExec = state.session?.a2aExecutorPreference || "ask";
|
|
3437
|
+
const bothAvailable = executors.codex && executors.claude;
|
|
3438
|
+
const execOptions = [
|
|
3439
|
+
{ id: "ask", label: L("settings.a2aExecutor.ask") },
|
|
3440
|
+
];
|
|
3441
|
+
if (executors.codex) execOptions.push({ id: "codex", label: L("settings.a2aExecutor.codex"), detected: true });
|
|
3442
|
+
if (executors.claude) execOptions.push({ id: "claude", label: L("settings.a2aExecutor.claude"), detected: true });
|
|
3443
|
+
if (bothAvailable) {
|
|
3444
|
+
execOptions.push({ id: "auto", label: L("settings.a2aExecutor.auto") });
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3334
3447
|
return `
|
|
3335
3448
|
<div class="settings-page">
|
|
3336
3449
|
${renderSettingsGroup("", [
|
|
@@ -3342,6 +3455,28 @@ function renderSettingsA2aRelayPage(context) {
|
|
|
3342
3455
|
? renderSettingsInfoRow(L("settings.row.a2aLastPoll"), new Date(relay.lastPollAtMs).toLocaleString(state.locale))
|
|
3343
3456
|
: "",
|
|
3344
3457
|
].filter(Boolean))}
|
|
3458
|
+
<section class="settings-group">
|
|
3459
|
+
<p class="settings-group__title">${escapeHtml(L("settings.a2aRelay.publicTasks.title"))}</p>
|
|
3460
|
+
<label class="reply-mode-switch reply-mode-switch--settings" data-a2a-public-toggle>
|
|
3461
|
+
<input type="checkbox" class="reply-mode-switch__input" ${publicChecked ? "checked" : ""} data-a2a-public-checkbox />
|
|
3462
|
+
<span class="reply-mode-switch--settings__toggle">
|
|
3463
|
+
<span class="reply-mode-switch__track" aria-hidden="true"><span class="reply-mode-switch__thumb"></span></span>
|
|
3464
|
+
<span class="reply-mode-switch__state">${escapeHtml(publicChecked ? L("settings.claudeAway.on") : L("settings.claudeAway.off"))}</span>
|
|
3465
|
+
</span>
|
|
3466
|
+
<span class="reply-mode-switch__hint">${escapeHtml(L("settings.a2aRelay.publicTasks.description"))}</span>
|
|
3467
|
+
</label>
|
|
3468
|
+
</section>
|
|
3469
|
+
${renderSettingsGroup(L("settings.a2aExecutor.title"), [
|
|
3470
|
+
`<p class="settings-group__description">${escapeHtml(L("settings.a2aExecutor.copy"))}</p>`,
|
|
3471
|
+
...execOptions.map((opt) => `
|
|
3472
|
+
<label class="settings-radio-row" data-a2a-executor-option="${escapeHtml(opt.id)}">
|
|
3473
|
+
<input type="radio" name="a2aExecutor" value="${escapeHtml(opt.id)}"
|
|
3474
|
+
${currentExec === opt.id ? "checked" : ""} />
|
|
3475
|
+
<span class="settings-radio-row__label">${escapeHtml(opt.label)}</span>
|
|
3476
|
+
${opt.detected ? `<span class="settings-radio-row__badge">✓ ${escapeHtml(L("settings.a2aExecutor.detected"))}</span>` : ""}
|
|
3477
|
+
</label>
|
|
3478
|
+
`),
|
|
3479
|
+
])}
|
|
3345
3480
|
</div>
|
|
3346
3481
|
`;
|
|
3347
3482
|
}
|
|
@@ -3452,21 +3587,22 @@ function renderDetailContent(detail, { mobile }) {
|
|
|
3452
3587
|
}
|
|
3453
3588
|
|
|
3454
3589
|
function renderStandardDetailDesktop(detail) {
|
|
3455
|
-
const kindInfo = kindMeta(detail.kind);
|
|
3456
|
-
const spaciousBodyDetail = TIMELINE_MESSAGE_KINDS.has(detail.kind)
|
|
3590
|
+
const kindInfo = kindMeta(detail.kind, detail);
|
|
3591
|
+
const spaciousBodyDetail = TIMELINE_MESSAGE_KINDS.has(detail.kind);
|
|
3457
3592
|
const plainIntro = renderDetailPlainIntro(detail);
|
|
3458
3593
|
return `
|
|
3459
3594
|
<div class="detail-shell">
|
|
3460
3595
|
${renderDetailMetaRow(detail, kindInfo)}
|
|
3461
3596
|
<h2 class="detail-title detail-title--desktop">${renderDetailTitle(detail)}</h2>
|
|
3462
|
-
${detail.readOnly || detail.kind === "approval" || detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" ? "" : renderDetailLead(detail, kindInfo)}
|
|
3597
|
+
${detail.readOnly || detail.kind === "approval" || detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "thread_share" ? "" : renderDetailLead(detail, kindInfo)}
|
|
3463
3598
|
${renderPreviousContextCard(detail)}
|
|
3464
3599
|
${renderInterruptedDetailNotice(detail)}
|
|
3465
3600
|
${renderMoltbookReplyComposer(detail)}
|
|
3466
3601
|
${renderMoltbookDraftComposer(detail)}
|
|
3467
3602
|
${renderA2ATaskDetail(detail)}
|
|
3603
|
+
${renderThreadShareDetail(detail)}
|
|
3468
3604
|
${
|
|
3469
|
-
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task"
|
|
3605
|
+
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task" || detail.kind === "a2a_task_result" || detail.kind === "thread_share"
|
|
3470
3606
|
? ""
|
|
3471
3607
|
: plainIntro
|
|
3472
3608
|
? plainIntro
|
|
@@ -3489,8 +3625,8 @@ function renderStandardDetailDesktop(detail) {
|
|
|
3489
3625
|
}
|
|
3490
3626
|
|
|
3491
3627
|
function renderStandardDetailMobile(detail) {
|
|
3492
|
-
const kindInfo = kindMeta(detail.kind);
|
|
3493
|
-
const spaciousBodyDetail = TIMELINE_MESSAGE_KINDS.has(detail.kind)
|
|
3628
|
+
const kindInfo = kindMeta(detail.kind, detail);
|
|
3629
|
+
const spaciousBodyDetail = TIMELINE_MESSAGE_KINDS.has(detail.kind);
|
|
3494
3630
|
const plainIntro = renderDetailPlainIntro(detail, { mobile: true });
|
|
3495
3631
|
return `
|
|
3496
3632
|
<div class="mobile-detail-screen">
|
|
@@ -3502,8 +3638,9 @@ function renderStandardDetailMobile(detail) {
|
|
|
3502
3638
|
${renderMoltbookReplyComposer(detail, { mobile: true })}
|
|
3503
3639
|
${renderMoltbookDraftComposer(detail, { mobile: true })}
|
|
3504
3640
|
${renderA2ATaskDetail(detail, { mobile: true })}
|
|
3641
|
+
${renderThreadShareDetail(detail, { mobile: true })}
|
|
3505
3642
|
${
|
|
3506
|
-
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task"
|
|
3643
|
+
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task" || detail.kind === "a2a_task_result" || detail.kind === "thread_share"
|
|
3507
3644
|
? ""
|
|
3508
3645
|
: plainIntro
|
|
3509
3646
|
? plainIntro
|
|
@@ -4061,7 +4198,7 @@ function renderMoltbookDraftComposer(detail, options = {}) {
|
|
|
4061
4198
|
}
|
|
4062
4199
|
|
|
4063
4200
|
function renderA2ATaskDetail(detail, options = {}) {
|
|
4064
|
-
if (detail.kind !== "a2a_task") return "";
|
|
4201
|
+
if (detail.kind !== "a2a_task" && detail.kind !== "a2a_task_result") return "";
|
|
4065
4202
|
const enabled = detail.a2aTaskEnabled !== false && detail.readOnly !== true;
|
|
4066
4203
|
const instruction = detail.instruction || "";
|
|
4067
4204
|
const callerIp = detail.callerInfo?.ip || "";
|
|
@@ -4079,10 +4216,32 @@ function renderA2ATaskDetail(detail, options = {}) {
|
|
|
4079
4216
|
rejected: "a2a.task.statusRejected",
|
|
4080
4217
|
}[detail.taskStatus] || "a2a.task.statusSubmitted";
|
|
4081
4218
|
|
|
4082
|
-
const statusBadge = !enabled
|
|
4219
|
+
const statusBadge = !enabled && detail.kind === "a2a_task_result"
|
|
4083
4220
|
? `<span class="eyebrow-pill eyebrow-pill--subtle">${escapeHtml(L(statusKey))}</span>`
|
|
4084
4221
|
: "";
|
|
4085
4222
|
|
|
4223
|
+
// Show executor selector when "ask" mode is active and both CLIs are available.
|
|
4224
|
+
const executors = state.session?.a2aExecutors || { codex: false, claude: false };
|
|
4225
|
+
const executorPref = state.session?.a2aExecutorPreference || "auto";
|
|
4226
|
+
const showExecutorPicker = enabled && executorPref === "ask" && executors.codex && executors.claude;
|
|
4227
|
+
const executorPicker = showExecutorPicker
|
|
4228
|
+
? `
|
|
4229
|
+
<div class="reply-composer__instruction">
|
|
4230
|
+
<label class="field-label">${escapeHtml(L("a2a.task.executor"))}</label>
|
|
4231
|
+
<div class="a2a-executor-picker">
|
|
4232
|
+
<label class="a2a-executor-picker__option">
|
|
4233
|
+
<input type="radio" name="executor" value="codex" checked />
|
|
4234
|
+
<span>${escapeHtml(L("a2a.executor.codex"))}</span>
|
|
4235
|
+
</label>
|
|
4236
|
+
<label class="a2a-executor-picker__option">
|
|
4237
|
+
<input type="radio" name="executor" value="claude" />
|
|
4238
|
+
<span>${escapeHtml(L("a2a.executor.claude"))}</span>
|
|
4239
|
+
</label>
|
|
4240
|
+
</div>
|
|
4241
|
+
</div>
|
|
4242
|
+
`
|
|
4243
|
+
: "";
|
|
4244
|
+
|
|
4086
4245
|
const buttons = enabled
|
|
4087
4246
|
? `
|
|
4088
4247
|
<div class="actions actions--stack${options.mobile ? " actions--sticky" : ""}">
|
|
@@ -4106,6 +4265,63 @@ function renderA2ATaskDetail(detail, options = {}) {
|
|
|
4106
4265
|
<label class="field-label">${escapeHtml(L("a2a.task.instruction"))}</label>
|
|
4107
4266
|
<textarea name="instruction" class="reply-composer__textarea" rows="6" ${enabled ? "" : "readonly"}>${escapeHtml(instruction)}</textarea>
|
|
4108
4267
|
</div>
|
|
4268
|
+
${executorPicker}
|
|
4269
|
+
${!enabled && detail.messageText ? `
|
|
4270
|
+
<div class="reply-composer__instruction">
|
|
4271
|
+
<label class="field-label">${escapeHtml(L("a2a.task.response"))}</label>
|
|
4272
|
+
<pre class="a2a-task-response">${escapeHtml(detail.messageText)}</pre>
|
|
4273
|
+
</div>
|
|
4274
|
+
` : ""}
|
|
4275
|
+
${buttonsWrapped}
|
|
4276
|
+
</form>
|
|
4277
|
+
</section>
|
|
4278
|
+
`;
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
function renderThreadShareDetail(detail, options = {}) {
|
|
4282
|
+
if (detail.kind !== "thread_share") return "";
|
|
4283
|
+
const enabled = detail.threadShareEnabled !== false && detail.readOnly !== true;
|
|
4284
|
+
const content = detail.shareContent || "";
|
|
4285
|
+
const fromLabel = detail.sourceLabel || detail.sourceTool || "agent";
|
|
4286
|
+
const toLabel = detail.targetLabel || detail.targetConversationId?.slice(0, 8) || detail.targetTool || "thread";
|
|
4287
|
+
const contextFiles = Array.isArray(detail.contextFiles) ? detail.contextFiles : [];
|
|
4288
|
+
const shareTypeLabel = {
|
|
4289
|
+
plan_review: L("threadShare.type.planReview"),
|
|
4290
|
+
handoff: L("threadShare.type.handoff"),
|
|
4291
|
+
message: L("threadShare.type.message"),
|
|
4292
|
+
}[detail.shareType] || L("threadShare.type.message");
|
|
4293
|
+
|
|
4294
|
+
const contextFilesHtml = contextFiles.length > 0
|
|
4295
|
+
? `<div class="reply-composer__instruction">
|
|
4296
|
+
<label class="field-label">${escapeHtml(L("threadShare.contextFiles"))}</label>
|
|
4297
|
+
<ul class="context-files-list">${contextFiles.map((f) => `<li class="context-file-item"><code>${escapeHtml(f)}</code></li>`).join("")}</ul>
|
|
4298
|
+
</div>`
|
|
4299
|
+
: "";
|
|
4300
|
+
|
|
4301
|
+
const buttons = enabled
|
|
4302
|
+
? `
|
|
4303
|
+
<div class="actions actions--stack${options.mobile ? " actions--sticky" : ""}">
|
|
4304
|
+
<button type="submit" data-action="approve" class="primary primary--wide">${escapeHtml(L("threadShare.approve"))}</button>
|
|
4305
|
+
<button type="submit" data-action="deny" class="danger danger--wide">${escapeHtml(L("threadShare.deny"))}</button>
|
|
4306
|
+
</div>
|
|
4307
|
+
`
|
|
4308
|
+
: `<p class="muted reply-composer__description">${escapeHtml(L("threadShare.resolved"))}</p>`;
|
|
4309
|
+
const buttonsWrapped = enabled && options.mobile ? `<div class="detail-action-bar">${buttons}</div>` : buttons;
|
|
4310
|
+
|
|
4311
|
+
return `
|
|
4312
|
+
<section class="detail-card detail-card--reply ${options.mobile ? "detail-card--mobile" : ""}">
|
|
4313
|
+
<form class="reply-composer" data-thread-share-form data-token="${escapeHtml(detail.token || "")}">
|
|
4314
|
+
<div class="reply-composer__copy">
|
|
4315
|
+
<span class="eyebrow-pill eyebrow-pill--quiet">${escapeHtml(L("threadShare.eyebrow"))}</span>
|
|
4316
|
+
<span class="eyebrow-pill eyebrow-pill--subtle">${escapeHtml(shareTypeLabel)}</span>
|
|
4317
|
+
<p class="reply-composer__author-meta muted">${escapeHtml(fromLabel)} → ${escapeHtml(toLabel)}</p>
|
|
4318
|
+
<p class="muted reply-composer__description">${enabled ? escapeHtml(L("threadShare.editHint")) : ""}</p>
|
|
4319
|
+
</div>
|
|
4320
|
+
${contextFilesHtml}
|
|
4321
|
+
<div class="reply-composer__instruction">
|
|
4322
|
+
<label class="field-label">${escapeHtml(L("threadShare.content"))}</label>
|
|
4323
|
+
<textarea name="shareContent" class="reply-composer__textarea" rows="12" ${enabled ? "" : "readonly"}>${escapeHtml(content)}</textarea>
|
|
4324
|
+
</div>
|
|
4109
4325
|
${buttonsWrapped}
|
|
4110
4326
|
</form>
|
|
4111
4327
|
</section>
|
|
@@ -4113,7 +4329,7 @@ function renderA2ATaskDetail(detail, options = {}) {
|
|
|
4113
4329
|
}
|
|
4114
4330
|
|
|
4115
4331
|
function renderCompletionReplyComposer(detail, options = {}) {
|
|
4116
|
-
if (detail.kind !== "completion" || detail.reply?.enabled !== true) {
|
|
4332
|
+
if ((detail.kind !== "completion" && detail.kind !== "assistant_final") || detail.reply?.enabled !== true) {
|
|
4117
4333
|
return "";
|
|
4118
4334
|
}
|
|
4119
4335
|
|
|
@@ -4183,7 +4399,7 @@ function renderCompletionReplyComposer(detail, options = {}) {
|
|
|
4183
4399
|
</div>
|
|
4184
4400
|
`
|
|
4185
4401
|
: `
|
|
4186
|
-
<form class="reply-composer__form" data-completion-reply-form data-token="${escapeHtml(detail.token)}" data-provider="${escapeHtml(normalizeProviderClient(detail?.provider))}">
|
|
4402
|
+
<form class="reply-composer__form" data-completion-reply-form data-token="${escapeHtml(detail.token)}" data-provider="${escapeHtml(normalizeProviderClient(detail?.provider))}" data-reply-kind="${escapeHtml(detail.kind)}">
|
|
4187
4403
|
<label class="field reply-field">
|
|
4188
4404
|
<span class="field-label">${escapeHtml(L("reply.fieldLabel"))}</span>
|
|
4189
4405
|
<div class="reply-field__shell">
|
|
@@ -4761,6 +4977,11 @@ function bindShellInteractions() {
|
|
|
4761
4977
|
return;
|
|
4762
4978
|
}
|
|
4763
4979
|
state.providerFilter = next;
|
|
4980
|
+
// Reset thread/kind filters — each provider has its own set of valid options
|
|
4981
|
+
state.timelineThreadFilter = "all";
|
|
4982
|
+
state.timelineKindFilter = "all";
|
|
4983
|
+
state.timelineKindFilterOpen = false;
|
|
4984
|
+
state.completedThreadFilter = "all";
|
|
4764
4985
|
alignCurrentItemToVisibleEntries();
|
|
4765
4986
|
await renderShell();
|
|
4766
4987
|
});
|
|
@@ -5106,6 +5327,33 @@ function bindShellInteractions() {
|
|
|
5106
5327
|
});
|
|
5107
5328
|
}
|
|
5108
5329
|
|
|
5330
|
+
for (const checkbox of document.querySelectorAll("[data-a2a-public-checkbox]")) {
|
|
5331
|
+
checkbox.addEventListener("change", async () => {
|
|
5332
|
+
const next = checkbox.checked === true;
|
|
5333
|
+
try {
|
|
5334
|
+
await apiPost("/api/a2a/public-tasks", { accept: next });
|
|
5335
|
+
state.a2aRelayStatus = await apiGet("/api/a2a/relay-status");
|
|
5336
|
+
} catch (error) {
|
|
5337
|
+
state.pushError = error.message || String(error);
|
|
5338
|
+
}
|
|
5339
|
+
await renderShell();
|
|
5340
|
+
});
|
|
5341
|
+
}
|
|
5342
|
+
|
|
5343
|
+
for (const radio of document.querySelectorAll("[data-a2a-executor-option] input[type='radio']")) {
|
|
5344
|
+
radio.addEventListener("change", async () => {
|
|
5345
|
+
if (!radio.checked) return;
|
|
5346
|
+
const preference = radio.value || "auto";
|
|
5347
|
+
try {
|
|
5348
|
+
await apiPost("/api/settings/a2a-executor", { preference });
|
|
5349
|
+
await refreshSession();
|
|
5350
|
+
} catch (error) {
|
|
5351
|
+
state.pushError = error.message || String(error);
|
|
5352
|
+
}
|
|
5353
|
+
await renderShell();
|
|
5354
|
+
});
|
|
5355
|
+
}
|
|
5356
|
+
|
|
5109
5357
|
for (const button of document.querySelectorAll("[data-locale-option]")) {
|
|
5110
5358
|
button.addEventListener("click", async () => {
|
|
5111
5359
|
state.pushError = "";
|
|
@@ -5267,12 +5515,16 @@ function bindShellInteractions() {
|
|
|
5267
5515
|
});
|
|
5268
5516
|
if (textarea) textarea.readOnly = true;
|
|
5269
5517
|
const instruction = normalizeClientText(new FormData(a2aTaskForm).get("instruction"));
|
|
5518
|
+
const executorRadio = a2aTaskForm.querySelector("input[name='executor']:checked");
|
|
5519
|
+
const executor = executorRadio ? executorRadio.value : "";
|
|
5270
5520
|
try {
|
|
5521
|
+
const decisionBody = { action: submittedAction, instruction };
|
|
5522
|
+
if (executor) decisionBody.executor = executor;
|
|
5271
5523
|
const res = await fetch(`/api/items/a2a-task/${encodeURIComponent(token)}/decision`, {
|
|
5272
5524
|
method: "POST",
|
|
5273
5525
|
headers: { "content-type": "application/json" },
|
|
5274
5526
|
credentials: "same-origin",
|
|
5275
|
-
body: JSON.stringify(
|
|
5527
|
+
body: JSON.stringify(decisionBody),
|
|
5276
5528
|
});
|
|
5277
5529
|
if (!res.ok) {
|
|
5278
5530
|
const errBody = await res.json().catch(() => ({}));
|
|
@@ -5305,6 +5557,72 @@ function bindShellInteractions() {
|
|
|
5305
5557
|
});
|
|
5306
5558
|
}
|
|
5307
5559
|
|
|
5560
|
+
const threadShareForm = document.querySelector("[data-thread-share-form]");
|
|
5561
|
+
if (threadShareForm) {
|
|
5562
|
+
const token = threadShareForm.dataset.token || "";
|
|
5563
|
+
let submittedAction = "approve";
|
|
5564
|
+
threadShareForm.querySelectorAll("button[type='submit']").forEach((btn) => {
|
|
5565
|
+
btn.addEventListener("click", () => {
|
|
5566
|
+
submittedAction = btn.dataset.action || "approve";
|
|
5567
|
+
});
|
|
5568
|
+
});
|
|
5569
|
+
threadShareForm.addEventListener("submit", async (event) => {
|
|
5570
|
+
event.preventDefault();
|
|
5571
|
+
if (threadShareForm.dataset.submitting === "1") return;
|
|
5572
|
+
threadShareForm.dataset.submitting = "1";
|
|
5573
|
+
const buttons = threadShareForm.querySelectorAll("button[type='submit']");
|
|
5574
|
+
const textarea = threadShareForm.querySelector("textarea");
|
|
5575
|
+
const labelCache = new Map();
|
|
5576
|
+
buttons.forEach((btn) => {
|
|
5577
|
+
labelCache.set(btn, btn.innerHTML);
|
|
5578
|
+
btn.disabled = true;
|
|
5579
|
+
if (btn.dataset.action === submittedAction) {
|
|
5580
|
+
btn.innerHTML = submittedAction === "approve" ? "Sharing…" : "Denying…";
|
|
5581
|
+
btn.classList.add("is-loading");
|
|
5582
|
+
} else {
|
|
5583
|
+
btn.classList.add("is-dimmed");
|
|
5584
|
+
}
|
|
5585
|
+
});
|
|
5586
|
+
if (textarea) textarea.readOnly = true;
|
|
5587
|
+
const editedContent = normalizeClientText(new FormData(threadShareForm).get("shareContent"));
|
|
5588
|
+
try {
|
|
5589
|
+
const res = await fetch(`/api/threads/share/${encodeURIComponent(token)}/decision`, {
|
|
5590
|
+
method: "POST",
|
|
5591
|
+
headers: { "content-type": "application/json" },
|
|
5592
|
+
credentials: "same-origin",
|
|
5593
|
+
body: JSON.stringify({ decision: submittedAction, editedContent }),
|
|
5594
|
+
});
|
|
5595
|
+
if (!res.ok) {
|
|
5596
|
+
const errBody = await res.json().catch(() => ({}));
|
|
5597
|
+
alert(`Thread share ${submittedAction} failed: ${errBody.error || res.status}`);
|
|
5598
|
+
buttons.forEach((btn) => {
|
|
5599
|
+
btn.disabled = false;
|
|
5600
|
+
btn.classList.remove("is-loading", "is-dimmed");
|
|
5601
|
+
btn.innerHTML = labelCache.get(btn);
|
|
5602
|
+
});
|
|
5603
|
+
if (textarea) textarea.readOnly = false;
|
|
5604
|
+
threadShareForm.dataset.submitting = "";
|
|
5605
|
+
return;
|
|
5606
|
+
}
|
|
5607
|
+
if (state.currentDetail?.kind === "thread_share") {
|
|
5608
|
+
state.currentDetail.threadShareEnabled = false;
|
|
5609
|
+
state.currentDetail.readOnly = true;
|
|
5610
|
+
}
|
|
5611
|
+
await refreshAuthenticatedState();
|
|
5612
|
+
await renderShell();
|
|
5613
|
+
} catch (error) {
|
|
5614
|
+
alert(`Thread share error: ${error.message}`);
|
|
5615
|
+
buttons.forEach((btn) => {
|
|
5616
|
+
btn.disabled = false;
|
|
5617
|
+
btn.classList.remove("is-loading", "is-dimmed");
|
|
5618
|
+
btn.innerHTML = labelCache.get(btn);
|
|
5619
|
+
});
|
|
5620
|
+
if (textarea) textarea.readOnly = false;
|
|
5621
|
+
threadShareForm.dataset.submitting = "";
|
|
5622
|
+
}
|
|
5623
|
+
});
|
|
5624
|
+
}
|
|
5625
|
+
|
|
5308
5626
|
const replyForm = document.querySelector("[data-completion-reply-form]");
|
|
5309
5627
|
if (replyForm) {
|
|
5310
5628
|
const token = replyForm.dataset.token || "";
|
|
@@ -5361,7 +5679,8 @@ function bindShellInteractions() {
|
|
|
5361
5679
|
requestBody.append("image", attachment.file, attachment.name || attachment.file.name);
|
|
5362
5680
|
}
|
|
5363
5681
|
}
|
|
5364
|
-
|
|
5682
|
+
const replyKind = replyForm.dataset.replyKind || "completion";
|
|
5683
|
+
await apiPost(`/api/items/${encodeURIComponent(replyKind)}/${encodeURIComponent(token)}/reply`, requestBody);
|
|
5365
5684
|
setCompletionReplyDraft(token, {
|
|
5366
5685
|
text: "",
|
|
5367
5686
|
sentText: text,
|
|
@@ -5759,10 +6078,10 @@ function inboxSubtabForItemKind(kind, sourceSubtab = "") {
|
|
|
5759
6078
|
if (normalizeClientText(sourceSubtab || "") === "completed") {
|
|
5760
6079
|
return "completed";
|
|
5761
6080
|
}
|
|
5762
|
-
return kind === "completion" ? "completed" : "pending";
|
|
6081
|
+
return kind === "completion" || kind === "assistant_final" ? "completed" : "pending";
|
|
5763
6082
|
}
|
|
5764
6083
|
|
|
5765
|
-
function kindMeta(kind) {
|
|
6084
|
+
function kindMeta(kind, item) {
|
|
5766
6085
|
switch (kind) {
|
|
5767
6086
|
case "user_message":
|
|
5768
6087
|
return { label: L("common.userMessage"), tone: "neutral", icon: "user-message" };
|
|
@@ -5784,8 +6103,17 @@ function kindMeta(kind) {
|
|
|
5784
6103
|
case "file_event":
|
|
5785
6104
|
return { label: L("common.fileEvent"), tone: "neutral", icon: "file-event" };
|
|
5786
6105
|
case "moltbook_reply":
|
|
6106
|
+
return { label: L("common.moltbookReply"), tone: "neutral", icon: "moltbook-comment" };
|
|
5787
6107
|
case "moltbook_draft":
|
|
5788
|
-
return
|
|
6108
|
+
return item?.draftType === "reply"
|
|
6109
|
+
? { label: L("common.moltbookDraftReply"), tone: "neutral", icon: "moltbook-reply" }
|
|
6110
|
+
: { label: L("common.moltbookDraft"), tone: "neutral", icon: "moltbook-draft" };
|
|
6111
|
+
case "thread_share":
|
|
6112
|
+
return { label: L("common.threadShare"), tone: "neutral", icon: "link" };
|
|
6113
|
+
case "a2a_task":
|
|
6114
|
+
return { label: L("common.a2aTaskRequest"), tone: "neutral", icon: "item" };
|
|
6115
|
+
case "a2a_task_result":
|
|
6116
|
+
return { label: L("common.a2aTaskResult"), tone: "completion", icon: "completion-item" };
|
|
5789
6117
|
default:
|
|
5790
6118
|
return { label: L("common.item"), tone: "neutral", icon: "item" };
|
|
5791
6119
|
}
|
|
@@ -6140,6 +6468,8 @@ function renderIcon(name) {
|
|
|
6140
6468
|
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="m9.5 12.5 5.9-5.9a3 3 0 1 1 4.2 4.2l-7.7 7.7a5 5 0 1 1-7.1-7.1l8.1-8.1"/></svg>`;
|
|
6141
6469
|
case "moltbook-draft":
|
|
6142
6470
|
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M15.2 3.8 20.2 8.8 8.5 20.5 3.5 20.5 3.5 15.5Z"/><path d="M12.5 6.5l5 5"/></svg>`;
|
|
6471
|
+
case "moltbook-reply":
|
|
6472
|
+
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M9 4 4 9l5 5"/><path d="M4 9h11a4 4 0 0 1 4 4v3"/></svg>`;
|
|
6143
6473
|
case "moltbook-comment":
|
|
6144
6474
|
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 5.5h15a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H11l-4 3.5v-3.5H4.5a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2Z"/><path d="M8 10h8"/><path d="M8 13h5"/></svg>`;
|
|
6145
6475
|
case "filter":
|