Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/158-face-wait-code.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The face-to-face waiting room shows the 6-digit room number clearly. Setup copy no longer says there is no preview: you wait together until someone enters the chat. ([#158](https://github.com/ThinkFlowLab/FlyBot/issues/158))
1 change: 1 addition & 0 deletions changelog.d/159-face-human-approval.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A face-to-face owner line held for approval shows the human text from the trusted sender, never a flash of the raw `FLYBOT-FACE-HUMAN` envelope. The card stays until Approve, Always approve, or Reject unless face Auto-send is on. ([#159](https://github.com/ThinkFlowLab/FlyBot/issues/159))
1 change: 1 addition & 0 deletions changelog.d/160-remote-bot-settings.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A friend's Bot settings page is a remote-friend surface, not the local editor: Auto-send and Remove this Bot stay there, without rename, model, Hide, Duplicate, or Delete. Remove closes the overlay. ([#160](https://github.com/ThinkFlowLab/FlyBot/issues/160))
1 change: 1 addition & 0 deletions changelog.d/161-invite-joiner-chief.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Accepting a friend invite no longer lists the joiner's talk-through Bot (Chief) on the inviter's Friends list. Reverse-friends of invites this home created stay out of `list_remote_bots` even on a fresh install, before inbound backfill. ([#161](https://github.com/ThinkFlowLab/FlyBot/issues/161))
1 change: 1 addition & 0 deletions changelog.d/162-talk-through-wrap.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
In Add friend, **Talk through** stays on one line instead of wrapping mid-word. ([#162](https://github.com/ThinkFlowLab/FlyBot/issues/162))
58 changes: 45 additions & 13 deletions core/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -8901,6 +8901,25 @@ function BotSettingsForm({ bot, roster, actions, run }) {
const timer = setTimeout(() => setConfirming(false), 4e3);
return () => clearTimeout(timer);
}, [confirming]);
if (bot.remote) {
const who = bot.remote.homeName || bot.remote.owner || bot.remote.home || t("Friend's Bot");
return (0, import_react19.createElement)(
"div",
{ className: "bt-bs", "data-flybot": "remote-bot-settings" },
(0, import_react19.createElement)(
"div",
{ className: "bt-bs-hero" },
(0, import_react19.createElement)(BotMark, { bot, size: 64, live: true }),
(0, import_react19.createElement)(
"div",
{ className: "bt-bs-who" },
(0, import_react19.createElement)("span", { className: "bt-bs-name" }, bot.name),
(0, import_react19.createElement)("span", { className: "bt-bs-sub" }, who)
)
),
(0, import_react19.createElement)(FlyBotDetails, { bot, roster, actions })
);
}
const isMainBot = isMainOf(roster, bot.id);
const onlyMain = isMainBot && (roster.mainBotIds ?? []).length === 1;
const isTile = roster.mainBotId === bot.id;
Expand Down Expand Up @@ -11705,8 +11724,8 @@ function BotsPage({ roster, actions, run }) {
),
(0, import_react29.createElement)(
SetSection,
{ key: "bots", title: t("Bots · {count}", { count: roster.bots.length }) },
roster.bots.map((bot) => (0, import_react29.createElement)(
{ key: "bots", title: t("Bots · {count}", { count: roster.bots.filter((bot) => !bot.remote).length }) },
roster.bots.filter((bot) => !bot.remote).map((bot) => (0, import_react29.createElement)(
"button",
{ key: bot.id, type: "button", className: "bt-set-row", onClick: () => actions.openSettings("bots", bot.id) },
(0, import_react29.createElement)(
Expand Down Expand Up @@ -11873,7 +11892,16 @@ function ContextMenu({ menu, roster, actions, leaving = false }) {
const isMainBot = isMainOf(roster, menu.id);
const isTile = roster.mainBotId === menu.id;
const mainCount = (roster.mainBotIds ?? []).length;
const groups = [
const groups = bot?.remote ? [
[
[t("Mark as Unread"), UnreadIcon, () => actions.markUnread(menu.id)]
],
[
[t("Bot settings"), GearIcon, () => actions.openSettings("bots", bot.id)]
],
pluginItems(structure, bot, actions).map(([label, run]) => [label, LinkIcon, run]),
[[t("Copy conversation ID"), CopyIcon, () => navigator.clipboard?.writeText(currentPart(bot))]]
] : [
[
isTile ? null : [entry.pinned ? t("Unpin") : t("Pin"), PinIcon, () => actions.setFlags(menu.id, { pinned: !entry.pinned })],
[t("Mark as Unread"), UnreadIcon, () => actions.markUnread(menu.id)]
Expand All @@ -11896,11 +11924,12 @@ function ContextMenu({ menu, roster, actions, leaving = false }) {
isMainBot ? null : [t("Hide from sidebar"), HideIcon, () => actions.setFlags(menu.id, { hidden: true })],
isMainBot ? null : [room2 ? t("Delete group") : t("Delete"), TrashIcon, () => bot ? actions.deleteBot(bot.id) : actions.deleteRoom(menu.id), true]
]
].map((group) => group.filter(Boolean)).filter((group) => group.length > 0);
];
const shown = groups.map((group) => group.filter(Boolean)).filter((group) => group.length > 0);
return (0, import_react30.createElement)(
"div",
{ ref, className: "bt-menu", role: "menu", "aria-label": t("{name} actions", { name: bot?.name ?? room2.name }), style: place, "data-leaving": leaving || void 0 },
groups.map((group, index) => (0, import_react30.createElement)(
shown.map((group, index) => (0, import_react30.createElement)(
import_react30.Fragment,
{ key: index },
index > 0 ? (0, import_react30.createElement)("div", { className: "bt-menu-sep", role: "separator" }) : null,
Expand Down Expand Up @@ -12559,7 +12588,7 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
"div",
{ key: "head", className: "bt-subhead", "data-move": move },
(0, import_react32.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Back to details"), onClick: () => setView("overview") }, (0, import_react32.createElement)(ChevronLeftIcon)),
(0, import_react32.createElement)("span", { className: "bt-subhead-title" }, bot ? t("Settings") : t("Group settings")),
(0, import_react32.createElement)("span", { className: "bt-subhead-title" }, bot ? bot.remote ? t("Bot settings") : t("Settings") : t("Group settings")),
hasStrip ? null : (0, import_react32.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Close details"), onClick: close }, (0, import_react32.createElement)(CloseIcon))
),
(0, import_react32.createElement)(
Expand All @@ -12573,8 +12602,8 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
const top = (0, import_react32.createElement)(
"div",
{ className: "bt-drawer-top", "data-move": side },
bot || room2 ? (0, import_react32.createElement)("button", { type: "button", className: "bt-round", "aria-label": bot ? t("Edit Bot") : t("Group settings"), title: bot ? t("Edit Bot") : t("Group settings"), "data-info-row": "settings", onClick: () => setView("settings") }, (0, import_react32.createElement)(GearIcon)) : null,
bot && roster.browser === true && actions.openBrowser ? (0, import_react32.createElement)("button", { type: "button", className: "bt-round", "aria-label": t("Open browser"), title: t("Open this Bot's browser"), onClick: () => actions.openBrowser(sessionId) }, (0, import_react32.createElement)(GlobeIcon)) : null,
bot || room2 ? (0, import_react32.createElement)("button", { type: "button", className: "bt-round", "aria-label": bot ? bot.remote ? t("Bot settings") : t("Edit Bot") : t("Group settings"), title: bot ? bot.remote ? t("Bot settings") : t("Edit Bot") : t("Group settings"), "data-info-row": "settings", onClick: () => setView("settings") }, (0, import_react32.createElement)(GearIcon)) : null,
bot && !bot.remote && roster.browser === true && actions.openBrowser ? (0, import_react32.createElement)("button", { type: "button", className: "bt-round", "aria-label": t("Open browser"), title: t("Open this Bot's browser"), onClick: () => actions.openBrowser(sessionId) }, (0, import_react32.createElement)(GlobeIcon)) : null,
bot || room2 ? (0, import_react32.createElement)("button", { type: "button", className: "bt-round", "aria-label": copied ? t("Copied conversation") : t("Share conversation"), title: copied ? t("Copied") : t("Copy conversation as Markdown"), onClick: share }, copied ? (0, import_react32.createElement)("span", { key: "copied", className: "bt-pop-in" }, "✓") : (0, import_react32.createElement)(ShareIcon)) : null,
hasStrip ? null : (0, import_react32.createElement)("button", { type: "button", className: "bt-round", "aria-label": t("Close details"), onClick: close }, (0, import_react32.createElement)(CloseIcon))
);
Expand All @@ -12600,7 +12629,7 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
"div",
{ key: "id", className: "bt-drawer-id", "data-move": side },
bot ? (0, import_react32.createElement)(BotAvatar, { bot, size: 64, state: botState(running, doing, roster.questions?.[part] !== void 0), main: isMainBot, badge: false, gaze: true, pokeable: true }) : (0, import_react32.createElement)(RoomAvatar, { room: room2, roster, size: 64 }),
editing ? (0, import_react32.createElement)("input", {
bot?.remote ? (0, import_react32.createElement)("div", { className: "bt-drawer-name" }, current) : editing ? (0, import_react32.createElement)("input", {
className: "bt-drawer-name-input",
autoFocus: true,
value: draftName,
Expand All @@ -12619,9 +12648,9 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
setDraftName(room2 && !room2.named ? "" : current);
setEditing(true);
} }, current),
subtitle ? (0, import_react32.createElement)("span", { className: "bt-drawer-role" }, subtitle) : null
subtitle ? (0, import_react32.createElement)("span", { className: "bt-drawer-role" }, subtitle) : bot?.remote ? (0, import_react32.createElement)("span", { className: "bt-drawer-role" }, t("Friend's Bot")) : null
),
(0, import_react32.createElement)(
bot?.remote ? null : (0, import_react32.createElement)(
"div",
{ key: "tabs", ref: glide.box, className: "bt-tabs", role: "tablist", "data-move": side },
(0, import_react32.createElement)("span", { ref: glide.thumb, className: "bt-thumb", "aria-hidden": true }),
Expand All @@ -12632,7 +12661,10 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
{ key: `body:${tab}`, className: "bt-drawer-body", "data-move": move },
errorLine,
room2 && tab === "details" ? (0, import_react32.createElement)(GroupOverview, { room: room2, roster, actions, usage, useWork, openSettings: () => setView("settings") }) : null,
bot && tab === "details" ? [
bot && bot.remote && tab === "details" ? [
bot.remote.handle ? (0, import_react32.createElement)("div", { key: "handle", className: "bt-muted" }, bot.remote.handle) : null
] : null,
bot && !bot.remote && tab === "details" ? [
(0, import_react32.createElement)(
"div",
{ key: "model", className: "bt-drawer-model" },
Expand All @@ -12657,7 +12689,7 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
) : null,
(0, import_react32.createElement)(ScheduleSummary, { key: "routines", bot, actions })
] : null,
bot && tab === "browser" ? (0, import_react32.createElement)(BrowserCards, { bot, sessionId, roster, actions, useBrowserTabs, useBrowserPages }) : null
bot && !bot.remote && tab === "browser" ? (0, import_react32.createElement)(BrowserCards, { bot, sessionId, roster, actions, useBrowserTabs, useBrowserPages }) : null
)
];
}
Expand Down
10 changes: 10 additions & 0 deletions core/src/client/bot-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ export function BotSettingsForm({ bot, roster, actions, run }) {
const timer = setTimeout(() => setConfirming(false), 4000)
return () => clearTimeout(timer)
}, [confirming])
if (bot.remote) {
const who = bot.remote.homeName || bot.remote.owner || bot.remote.home || t("Friend's Bot")
return h('div', { className: 'bt-bs', 'data-flybot': 'remote-bot-settings' },
h('div', { className: 'bt-bs-hero' },
h(BotMark, { bot, size: 64, live: true }),
h('div', { className: 'bt-bs-who' },
h('span', { className: 'bt-bs-name' }, bot.name),
h('span', { className: 'bt-bs-sub' }, who))),
h(FlyBotDetails, { bot, roster, actions }))
}
const isMainBot = isMainOf(roster, bot.id)
const onlyMain = isMainBot && (roster.mainBotIds ?? []).length === 1
const isTile = roster.mainBotId === bot.id
Expand Down
56 changes: 34 additions & 22 deletions core/src/client/context-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,41 @@ export function ContextMenu({ menu, roster, actions, leaving = false }) {
const isMainBot = isMainOf(roster, menu.id)
const isTile = roster.mainBotId === menu.id
const mainCount = (roster.mainBotIds ?? []).length
const groups = [
[
isTile ? null : [entry.pinned ? t('Unpin') : t('Pin'), PinIcon, () => actions.setFlags(menu.id, { pinned: !entry.pinned })],
[t('Mark as Unread'), UnreadIcon, () => actions.markUnread(menu.id)],
],
[
bot ? [t('Rename Bot'), PencilIcon, () => actions.renameBot(bot.id)] : null,
bot ? [t('Edit Bot'), GearIcon, () => actions.openSettings('bots', bot.id)] : null,
room ? [t('Group settings'), GearIcon, () => actions.openSettings('groups', room.id)] : null,
room ? [t('Rename group'), PencilIcon, () => actions.renameBot(room.id)] : null,
bot ? [t('Duplicate'), DuplicateIcon, async () => { const copy = await actions.duplicateBot(bot.id); actions.openSession(copy.id) }] : null,
bot && !isMainBot ? [t('Make Main Bot'), StarLineIcon, () => actions.setMain(bot.id, true)] : null,
bot && isMainBot && mainCount > 1 ? [t('Remove Main Bot role'), StarLineIcon, () => actions.setMain(bot.id, false)] : null,
],
bot ? pluginItems(structure, bot, actions).map(([label, run]) => [label, LinkIcon, run]) : [],
[[t('Copy conversation ID'), CopyIcon, () => navigator.clipboard?.writeText(bot ? currentPart(bot) : menu.id)]],
[
isMainBot ? null : [t('Hide from sidebar'), HideIcon, () => actions.setFlags(menu.id, { hidden: true })],
isMainBot ? null : [room ? t('Delete group') : t('Delete'), TrashIcon, () => (bot ? actions.deleteBot(bot.id) : actions.deleteRoom(menu.id)), true],
],
].map(group => group.filter(Boolean)).filter(group => group.length > 0)
const groups = bot?.remote
? [
[
[t('Mark as Unread'), UnreadIcon, () => actions.markUnread(menu.id)],
],
[
[t('Bot settings'), GearIcon, () => actions.openSettings('bots', bot.id)],
],
pluginItems(structure, bot, actions).map(([label, run]) => [label, LinkIcon, run]),
[[t('Copy conversation ID'), CopyIcon, () => navigator.clipboard?.writeText(currentPart(bot))]],
]
: [
[
isTile ? null : [entry.pinned ? t('Unpin') : t('Pin'), PinIcon, () => actions.setFlags(menu.id, { pinned: !entry.pinned })],
[t('Mark as Unread'), UnreadIcon, () => actions.markUnread(menu.id)],
],
[
bot ? [t('Rename Bot'), PencilIcon, () => actions.renameBot(bot.id)] : null,
bot ? [t('Edit Bot'), GearIcon, () => actions.openSettings('bots', bot.id)] : null,
room ? [t('Group settings'), GearIcon, () => actions.openSettings('groups', room.id)] : null,
room ? [t('Rename group'), PencilIcon, () => actions.renameBot(room.id)] : null,
bot ? [t('Duplicate'), DuplicateIcon, async () => { const copy = await actions.duplicateBot(bot.id); actions.openSession(copy.id) }] : null,
bot && !isMainBot ? [t('Make Main Bot'), StarLineIcon, () => actions.setMain(bot.id, true)] : null,
bot && isMainBot && mainCount > 1 ? [t('Remove Main Bot role'), StarLineIcon, () => actions.setMain(bot.id, false)] : null,
],
bot ? pluginItems(structure, bot, actions).map(([label, run]) => [label, LinkIcon, run]) : [],
[[t('Copy conversation ID'), CopyIcon, () => navigator.clipboard?.writeText(bot ? currentPart(bot) : menu.id)]],
[
isMainBot ? null : [t('Hide from sidebar'), HideIcon, () => actions.setFlags(menu.id, { hidden: true })],
isMainBot ? null : [room ? t('Delete group') : t('Delete'), TrashIcon, () => (bot ? actions.deleteBot(bot.id) : actions.deleteRoom(menu.id)), true],
],
]
const shown = groups.map(group => group.filter(Boolean)).filter(group => group.length > 0)
return h('div', { ref, className: 'bt-menu', role: 'menu', 'aria-label': t('{name} actions', { name: bot?.name ?? room.name }), style: place, 'data-leaving': leaving || undefined },
groups.map((group, index) => h(Fragment, { key: index },
shown.map((group, index) => h(Fragment, { key: index },
index > 0 ? h('div', { className: 'bt-menu-sep', role: 'separator' }) : null,
group.map(([label, Icon, run, danger]) => h('button', {
key: label, type: 'button', role: 'menuitem', className: danger ? 'bt-danger' : undefined,
Expand Down
Loading
Loading