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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ Point FlyBot at that server in `~/.dsh/profiles/web/cordis.patch.yml` (or `$DSH_

On first start FlyBot calls `POST <server>/v1/onboard/flybot`, registers your home (`@<name>.home`), and stores credentials in `$DSH_HOME/bot/flybot-social.json` (mode 600). No Bot is shared until you grant it. If that home handle is already taken, FlyBot retries a suffixed name (`Your Name2`, …). Sidebar **+** → **Copy my handle** copies the connected handle. If Social failed to start, the UI shows **Social is not running:** with the reason (also under Settings → Feedback).

**Change the server later** without editing YAML: Settings → **Bot Social** shows the server and its connection status, **Change server** checks the new address (`https://`, or `http://localhost` for development; FlyBot reads `<server>/v1/platform` and shows its features) and, after a clear warning, switches without a restart. A new server is a **new identity**: your friends and sharing stay on the old server, your old credentials file is kept as a backup and never deleted, and switching back restores them. The `cordis.patch.yml` value stays the default.

**Share one Bot:** that Bot's settings → **Sharing** → **Invite to this Bot** (or **Copy invite link**); send the link. Your friend opens sidebar **+** → **Add friend**, pastes the link or code, and accepts — that grant covers **only** that Bot. **People with access** lists who has it; **Revoke** removes them.

**Chat:** click the friend's Bot in the sidebar. Their messages arrive at once. What your Bots send back waits in that chat (Approve / Reject) unless **Auto-send** is on for that friend (friend Bot settings; default off). Look for the **to approve** badge if they see no reply.
Expand Down
2 changes: 2 additions & 0 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ export PORT=8787

首次启动会调用 `POST <server>/v1/onboard/flybot`,登记你家(`@<名字>.home`),凭证写在 `$DSH_HOME/bot/flybot-social.json`(权限 600)。在你授权之前,没有任何 Bot 被共享。若家句柄已被占用,会自动试 `你的名字2`…。侧栏 **+** → **复制我的 handle** 复制当前已连接的句柄。若 Social 没起来,界面会显示 **Social is not running:** 及原因(也会进 Settings → Feedback)。

**之后换服务器**不用改 YAML:设置 → **Bot Social** 显示当前服务器和连接状态,**更换服务器**会先检查新地址(`https://`;开发时可用 `http://localhost`;FlyBot 读取 `<server>/v1/platform` 并列出它的功能),经过明确的警告确认后无需重启即可切换。换服务器等于**换一个新身份**:好友和分享留在旧服务器上,旧的凭证文件作为备份保留、不会被删除,切换回去即可恢复。`cordis.patch.yml` 里的值仍是默认值。

**分享某一个 Bot:** 该 Bot 设置 → **Sharing** → **Invite to this Bot**(或 **复制邀请链接**),把链接发给对方;对方侧栏 **+** → **添加好友** 粘贴链接或码并接受——**只授权这一个 Bot**。**People with access** 可查看并 **Revoke**。

**私聊:** 点侧栏里对方的 Bot。对方来信立刻到。你这边发出去的内容停在该聊天等批准(除非对该好友开了 **Auto-send**,默认关)。对方看不到回复时,先看 **to approve / 待批准** 角标。
Expand Down
1 change: 1 addition & 0 deletions changelog.d/337-social-server-settings.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Settings → Bot Social shows the Bot Social server FlyBot uses and its connection, and switches to another server without editing `cordis.patch.yml` or restarting ([#337](https://github.com/ThinkFlowLab/FlyBot/issues/337)). The address must be `https://` (`http://` only for localhost), is checked against `<server>/v1/platform` (its features are shown) before anything is saved, and a switch needs a confirm that says a new server is a new identity: friends and sharing stay on the old server, the old credentials file is kept and never deleted, and switching back restores them. A failed switch puts the previous server back. The profile value stays the default; with nothing configured nothing changes. Plugins can add Settings pages through the new `flybot.settings.page` slot.
49 changes: 44 additions & 5 deletions core/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,35 @@ var ZH = {
"Connecting…": "正在连接…",
"Could not connect: {error}": "连接失败:{error}",
"Connected as {account}": "已连接:{account}",
// Settings → Bot Social (@flybot/social, #337)
"Bot Social": "Bot Social",
"Bot Social server": "Bot Social 服务器",
"Connected as {handle}": "已连接:{handle}",
"No server set. Friends and sharing are off until you set one.": "还没有设置服务器。设置之前,好友和分享功能不可用。",
"Set here": "在这里设置",
"Profile default": "配置文件默认值",
"Not set": "未设置",
"No server": "没有服务器",
"Change server": "更换服务器",
"Features": "功能",
"No optional features listed.": "服务器没有列出可选功能。",
"Reachability": "连通性",
"Read-only": "只读",
"Another FlyBot window holds this team. Change the server there.": "另一个 FlyBot 窗口正在使用这个团队,请在那里更换服务器。",
"Switch server": "切换服务器",
"Server address": "服务器地址",
"Used before:": "用过的服务器:",
"{host} (profile default)": "{host}(配置文件默认值)",
"Name for your home (optional)": "你的主页名称(可选)",
"Switching servers changes who you are": "换服务器,就是换一个身份",
"You used this server before. Your saved identity, friends and settings for it come back.": "你以前用过这台服务器,它上面保存的身份、好友和设置会恢复。",
"You get a new identity on this server. The friends and sharing you have now stay on the current server; they do not move.": "你会在这台服务器上得到一个新身份。现在的好友和分享留在当前服务器上,不会搬过去。",
"Your current sign-in is kept as a backup on this computer and is never deleted. Switch back any time.": "当前的登录凭据会作为备份保留在这台电脑上,不会被删除。随时可以切换回来。",
"Your Bots stop being shared with friends until you share them again on the new server.": "在新服务器上重新分享之前,你的 Bot 不再与好友共享。",
"This is the server you already use.": "这就是你正在用的服务器。",
"Switching…": "正在切换…",
"Switch to this server": "切换到这台服务器",
"Check": "检查",
Connected: "已连接",
Disconnect: "断开",
Connect: "连接",
Expand Down Expand Up @@ -1319,7 +1348,7 @@ function ungrantedLocalFix(details) {
}

// src/client/flybot-registry.js
var SLOTS = ["flybot.sidebar.section", "flybot.sidebar.structure", "flybot.composer.mention", "flybot.bot.details", "flybot.approvals", "flybot.room.overview", "flybot.session.dock", "flybot.new-chat.action", "flybot.account.header"];
var SLOTS = ["flybot.sidebar.section", "flybot.sidebar.structure", "flybot.composer.mention", "flybot.bot.details", "flybot.approvals", "flybot.room.overview", "flybot.session.dock", "flybot.new-chat.action", "flybot.account.header", "flybot.settings.page"];
function createRegistry() {
const entries2 = new Map(SLOTS.map((name) => [name, []]));
const listeners2 = /* @__PURE__ */ new Set();
Expand Down Expand Up @@ -1910,6 +1939,12 @@ var SlotBoundary = class extends import_react2.Component {
function Safe({ entry, props }) {
return (0, import_react2.createElement)(SlotBoundary, { id: entry.id }, (0, import_react2.createElement)(entry.component, props));
}
function useSettingsPages() {
return useSlot("flybot.settings.page");
}
function SettingsPageSlot({ entry, props }) {
return (0, import_react2.createElement)(Safe, { entry, props });
}
function useSidebarStructure() {
return useSlot("flybot.sidebar.structure")[0];
}
Expand Down Expand Up @@ -12349,9 +12384,12 @@ function SettingsDialog({ page, id, roster, actions, useUpdate, leaving = false
});
const bot = page === "bots" && id ? roster.byId[id] : void 0;
const room2 = page === "groups" && id ? roster.roomsById[id] : void 0;
const shown = SETTINGS_PAGES.some(([key]) => key === page) ? page : "general";
const extra = useSettingsPages().map((entry) => ({ entry, key: `plugin:${entry.id}` }));
const pages = SETTINGS_PAGES.flatMap((item) => item[0] === "connectors" ? [item, ...extra.map(({ entry, key }) => [key, entry.label ?? entry.id])] : [item]);
const shown = pages.some(([key]) => key === page) ? page : "general";
const pluginPage = extra.find(({ key }) => key === shown)?.entry;
const usageFocus = shown === "usage" && id ? id : null;
const label = t(SETTINGS_PAGES.find(([key]) => key === shown)[1]);
const label = t(pages.find(([key]) => key === shown)[1]);
const back = bot ? t("Back to Bots") : room2 ? t("Back to group chats") : usageFocus ? t("All usage") : null;
const nav = useGlide(shown, { axis: "y" });
const pageKey = `${shown}:${bot?.id ?? room2?.id ?? ""}`;
Expand All @@ -12374,13 +12412,13 @@ function SettingsDialog({ page, id, roster, actions, useUpdate, leaving = false
"nav",
{ ref: nav.box, className: "bt-settings-nav", "aria-label": t("Settings pages") },
(0, import_react29.createElement)("span", { ref: nav.thumb, className: "bt-thumb", "aria-hidden": true }),
SETTINGS_PAGES.map(([key, text]) => (0, import_react29.createElement)("button", {
pages.map(([key, text]) => (0, import_react29.createElement)("button", {
key,
type: "button",
className: "bt-settings-item",
"aria-current": shown === key ? "page" : void 0,
onClick: () => actions.openSettings(key)
}, (0, import_react29.createElement)(PAGE_ICONS[key]), t(text))),
}, (0, import_react29.createElement)(extra.find((item) => item.key === key)?.entry.icon ?? PAGE_ICONS[key] ?? ConnectorIcon), t(text))),
(0, import_react29.createElement)("button", {
type: "button",
className: "bt-settings-item bt-settings-shell",
Expand Down Expand Up @@ -12410,6 +12448,7 @@ function SettingsDialog({ page, id, roster, actions, useUpdate, leaving = false
room2 ? (0, import_react29.createElement)("div", { className: "bt-settings-form" }, (0, import_react29.createElement)(GroupSettingsForm, { key: room2.id, room: room2, roster, actions, run })) : null,
shown === "groups" && !room2 ? (0, import_react29.createElement)(GroupsPage, { roster, actions, run }) : null,
shown === "connectors" ? (0, import_react29.createElement)(ConnectorsPage, { roster, actions }) : null,
pluginPage ? (0, import_react29.createElement)(SettingsPageSlot, { entry: pluginPage, props: { roster, actions, call: call2, run, t, Section: SetSection, Row: SetRow } }) : null,
shown === "capabilities" ? (0, import_react29.createElement)(CapabilitiesPage, { roster, actions, run }) : null,
shown === "schedules" ? (0, import_react29.createElement)(SchedulesPage, { roster, actions, start: id ?? void 0 }) : null,
shown === "secrets" ? (0, import_react29.createElement)(SecretsPage, { roster, actions, run, Section: SetSection }) : null,
Expand Down
2 changes: 1 addition & 1 deletion core/src/client/flybot-registry.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The FlyBot slot registry, free of React so the "@" menu and tests can use it.
// See flybot-slots.js for what each slot carries.
export const SLOTS = ['flybot.sidebar.section', 'flybot.sidebar.structure', 'flybot.composer.mention', 'flybot.bot.details', 'flybot.approvals', 'flybot.room.overview', 'flybot.session.dock', 'flybot.new-chat.action', 'flybot.account.header']
export const SLOTS = ['flybot.sidebar.section', 'flybot.sidebar.structure', 'flybot.composer.mention', 'flybot.bot.details', 'flybot.approvals', 'flybot.room.overview', 'flybot.session.dock', 'flybot.new-chat.action', 'flybot.account.header', 'flybot.settings.page']

export function createRegistry() {
const entries = new Map(SLOTS.map(name => [name, []]))
Expand Down
13 changes: 13 additions & 0 deletions core/src/client/flybot-slots.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
// approval ids it shows: the overlay then draws no second card for them (#260)
// flybot.new-chat.action { id, order, label, hint?, visible?, run({ close, actions, roster }) } a row in the sidebar '+' menu (next to Create group chat; Face-to-face group, Add friend, Friend requests)
// flybot.account.header { id, order, component({ actions, call }) } top of the account menu (the owner's own Bot Social handle)
// flybot.settings.page { id, order, label, icon?, component({ roster, actions, call, run, t, Section, Row }) } a page in the Settings list
// (after Connectors); `label` is English and goes through the host's t(); the component gets the host's t(),
// Section(title) and Row(label, hint) so the page looks like the others and reads zh in a zh UI
//
// The same registry object also carries the host Bot look so a plugin can draw a Bot
// exactly as the sidebar does: __flybot__.BotAvatar / BotMark / lookOf (set in registry()).
Expand Down Expand Up @@ -73,6 +76,16 @@ function Safe({ entry, props }) {
return h(SlotBoundary, { id: entry.id }, h(entry.component, props))
}

// Pages plugins add to Settings (flybot.settings.page), in order.
export function useSettingsPages() {
return useSlot('flybot.settings.page')
}

// Renders a plugin's Settings page without letting its failure close the dialog.
export function SettingsPageSlot({ entry, props }) {
return h(Safe, { entry, props })
}

// The first 'flybot.sidebar.structure' entry, or undefined: the sidebar then lists Bots flat.
export function useSidebarStructure() {
return useSlot('flybot.sidebar.structure')[0]
Expand Down
29 changes: 29 additions & 0 deletions core/src/client/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,35 @@ const ZH = {
'Connecting…': '正在连接…',
'Could not connect: {error}': '连接失败:{error}',
'Connected as {account}': '已连接:{account}',
// Settings → Bot Social (@flybot/social, #337)
'Bot Social': 'Bot Social',
'Bot Social server': 'Bot Social 服务器',
'Connected as {handle}': '已连接:{handle}',
'No server set. Friends and sharing are off until you set one.': '还没有设置服务器。设置之前,好友和分享功能不可用。',
'Set here': '在这里设置',
'Profile default': '配置文件默认值',
'Not set': '未设置',
'No server': '没有服务器',
'Change server': '更换服务器',
'Features': '功能',
'No optional features listed.': '服务器没有列出可选功能。',
'Reachability': '连通性',
'Read-only': '只读',
'Another FlyBot window holds this team. Change the server there.': '另一个 FlyBot 窗口正在使用这个团队,请在那里更换服务器。',
'Switch server': '切换服务器',
'Server address': '服务器地址',
'Used before:': '用过的服务器:',
'{host} (profile default)': '{host}(配置文件默认值)',
'Name for your home (optional)': '你的主页名称(可选)',
'Switching servers changes who you are': '换服务器,就是换一个身份',
'You used this server before. Your saved identity, friends and settings for it come back.': '你以前用过这台服务器,它上面保存的身份、好友和设置会恢复。',
'You get a new identity on this server. The friends and sharing you have now stay on the current server; they do not move.': '你会在这台服务器上得到一个新身份。现在的好友和分享留在当前服务器上,不会搬过去。',
'Your current sign-in is kept as a backup on this computer and is never deleted. Switch back any time.': '当前的登录凭据会作为备份保留在这台电脑上,不会被删除。随时可以切换回来。',
'Your Bots stop being shared with friends until you share them again on the new server.': '在新服务器上重新分享之前,你的 Bot 不再与好友共享。',
'This is the server you already use.': '这就是你正在用的服务器。',
'Switching…': '正在切换…',
'Switch to this server': '切换到这台服务器',
'Check': '检查',
Connected: '已连接',
Disconnect: '断开',
Connect: '连接',
Expand Down
14 changes: 10 additions & 4 deletions core/src/client/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { allThemes } from './themes.js'
import { CloseIcon, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConnectorIcon, InfoIcon, PlugIcon, PlusIcon, SlidersIcon, SETTINGS_ICONS } from './icons.js'
import { AdminStar, BotAvatar, RoomAvatar } from './mark.js'
import { openHarnessSettings } from './sidebar.js'
import { SettingsPageSlot, useSettingsPages, call } from './flybot-slots.js'
import { useEscape } from './overlay-hooks.js'
import { BotSettingsForm } from './bot-settings.js'
import { Segmented, useGlide } from './motion.js'
Expand Down Expand Up @@ -161,9 +162,13 @@ export function SettingsDialog({ page, id, roster, actions, useUpdate, leaving =
// A Bot or group deleted while its form is open leaves its page on the list.
const bot = page === 'bots' && id ? roster.byId[id] : undefined
const room = page === 'groups' && id ? roster.roomsById[id] : undefined
const shown = SETTINGS_PAGES.some(([key]) => key === page) ? page : 'general'
// Plugin pages (flybot.settings.page) sit after Connectors; a page whose plugin is gone falls back to General.
const extra = useSettingsPages().map(entry => ({ entry, key: `plugin:${entry.id}` }))
const pages = SETTINGS_PAGES.flatMap(item => (item[0] === 'connectors' ? [item, ...extra.map(({ entry, key }) => [key, entry.label ?? entry.id])] : [item]))
const shown = pages.some(([key]) => key === page) ? page : 'general'
const pluginPage = extra.find(({ key }) => key === shown)?.entry
const usageFocus = shown === 'usage' && id ? id : null
const label = t(SETTINGS_PAGES.find(([key]) => key === shown)[1])
const label = t(pages.find(([key]) => key === shown)[1])
const back = bot ? t('Back to Bots') : room ? t('Back to group chats') : usageFocus ? t('All usage') : null
const nav = useGlide(shown, { axis: 'y' })
// A page enters from the right when it is one level in, from the left when it comes
Expand All @@ -186,9 +191,9 @@ export function SettingsDialog({ page, id, roster, actions, useUpdate, leaving =
h('div', { className: 'bt-settings', role: 'dialog', 'aria-modal': true, 'aria-label': t('FlyBot settings'), 'data-wide': shown === 'usage' ? '' : undefined },
h('nav', { ref: nav.box, className: 'bt-settings-nav', 'aria-label': t('Settings pages') },
h('span', { ref: nav.thumb, className: 'bt-thumb', 'aria-hidden': true }),
SETTINGS_PAGES.map(([key, text]) => h('button', {
pages.map(([key, text]) => h('button', {
key, type: 'button', className: 'bt-settings-item', 'aria-current': shown === key ? 'page' : undefined, onClick: () => actions.openSettings(key),
}, h(PAGE_ICONS[key]), t(text))),
}, h(extra.find(item => item.key === key)?.entry.icon ?? PAGE_ICONS[key] ?? ConnectorIcon), t(text))),
h('button', {
type: 'button', className: 'bt-settings-item bt-settings-shell', title: t('Models, providers, and the rest of DeepSeek Harness'),
onClick: () => { close(); openHarnessSettings() },
Expand All @@ -205,6 +210,7 @@ export function SettingsDialog({ page, id, roster, actions, useUpdate, leaving =
room ? h('div', { className: 'bt-settings-form' }, h(GroupSettingsForm, { key: room.id, room, roster, actions, run })) : null,
shown === 'groups' && !room ? h(GroupsPage, { roster, actions, run }) : null,
shown === 'connectors' ? h(ConnectorsPage, { roster, actions }) : null,
pluginPage ? h(SettingsPageSlot, { entry: pluginPage, props: { roster, actions, call, run, t, Section: SetSection, Row: SetRow } }) : null,
shown === 'capabilities' ? h(CapabilitiesPage, { roster, actions, run }) : null,
shown === 'schedules' ? h(SchedulesPage, { roster, actions, start: id ?? undefined }) : null,
shown === 'secrets' ? h(SecretsPage, { roster, actions, run, Section: SetSection }) : null,
Expand Down
Loading
Loading