Keep configured IM channels editable

This commit is contained in:
taohe
2026-06-11 14:37:58 +08:00
parent c966eb71a7
commit 9d51e38641
9 changed files with 110 additions and 68 deletions
@@ -60,6 +60,8 @@ export function ChannelRuntimeConfigDialog({
return null;
}
const isEditing = provider.configured;
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
onSubmit(provider, values);
@@ -71,7 +73,9 @@ export function ChannelRuntimeConfigDialog({
<form onSubmit={handleSubmit} className="space-y-4">
<DialogHeader>
<DialogTitle>
{t.channels.setupTitle(provider.display_name)}
{isEditing
? t.channels.setupEditTitle(provider.display_name)
: t.channels.setupTitle(provider.display_name)}
</DialogTitle>
<DialogDescription>{t.channels.setupDescription}</DialogDescription>
</DialogHeader>
@@ -118,7 +122,7 @@ export function ChannelRuntimeConfigDialog({
{submitting ? (
<LoaderCircleIcon className="animate-spin" />
) : null}
{t.channels.saveAndConnect}
{isEditing ? t.channels.saveChanges : t.channels.saveAndConnect}
</Button>
</DialogFooter>
</form>
@@ -37,6 +37,10 @@ function providerCanConnect(provider: ChannelProvider): boolean {
);
}
function providerCanEditRuntimeConfig(provider: ChannelProvider): boolean {
return provider.enabled && (provider.credential_fields?.length ?? 0) > 0;
}
function getProviderUnavailableReason(
provider: ChannelProvider,
t: ReturnType<typeof useI18n>["t"],
@@ -126,6 +130,7 @@ export function WorkspaceChannelsList() {
<SidebarGroupLabel>{t.sidebar.channels}</SidebarGroupLabel>
<SidebarMenu>
{visibleProviders.map((provider) => {
const canEditRuntimeConfig = providerCanEditRuntimeConfig(provider);
const isConnected = provider.connection_status === "connected";
const isPending =
(connectMutation.isPending &&
@@ -153,10 +158,13 @@ export function WorkspaceChannelsList() {
"h-8 w-24 px-2 text-xs",
isConnected && "gap-1",
)}
disabled={isConnected || isPending}
disabled={isPending}
title={unavailableReason}
onClick={() => {
if (providerNeedsRuntimeConfig(provider)) {
if (
providerNeedsRuntimeConfig(provider) ||
canEditRuntimeConfig
) {
setSetupProvider(provider);
return;
}
@@ -193,16 +201,13 @@ export function WorkspaceChannelsList() {
}
}}
onSubmit={(provider, values) => {
const connectWindow =
provider.auth_mode === "deep_link" ? prepareConnectWindow() : null;
void configureMutation
.mutateAsync({ provider: provider.provider, values })
.then((configuredProvider) => {
.then(() => {
setSetupProvider(null);
startConnect(configuredProvider, connectWindow);
toast.success(t.channels.connected);
})
.catch((error) => {
closeConnectWindow(connectWindow);
toast.error(
error instanceof Error ? error.message : t.channels.unavailable,
);
@@ -108,6 +108,10 @@ function providerNeedsRuntimeConfig(provider: ChannelProvider): boolean {
);
}
function providerCanEditRuntimeConfig(provider: ChannelProvider): boolean {
return provider.enabled && (provider.credential_fields?.length ?? 0) > 0;
}
function ChannelProviderItem({
provider,
connection,
@@ -120,7 +124,10 @@ function ChannelProviderItem({
const configureMutation = useConfigureChannelProvider();
const disconnectMutation = useDisconnectChannelConnection();
const [setupOpen, setSetupOpen] = useState(false);
const isConnected = connection?.status === "connected";
const isConnected =
connection?.status === "connected" ||
provider.connection_status === "connected";
const canEditRuntimeConfig = providerCanEditRuntimeConfig(provider);
const canConnect =
(provider.connectable ?? (provider.enabled && provider.configured)) &&
!isConnected;
@@ -195,21 +202,41 @@ function ChannelProviderItem({
</ItemDescription>
</ItemContent>
<ItemActions className="ml-auto">
{isConnected && connection ? (
<Button
type="button"
variant="outline"
size="sm"
disabled={isDisconnecting}
onClick={() => disconnectMutation.mutate(connection.id)}
>
{isDisconnecting ? (
<LoaderCircleIcon className="animate-spin" />
) : (
<UnplugIcon />
)}
{t.channels.disconnect}
</Button>
{isConnected ? (
<>
{canEditRuntimeConfig ? (
<Button
type="button"
variant="outline"
size="sm"
disabled={isConnecting}
onClick={() => setSetupOpen(true)}
>
{isConnecting ? (
<LoaderCircleIcon className="animate-spin" />
) : (
<PlugIcon />
)}
{t.channels.modify}
</Button>
) : null}
{connection ? (
<Button
type="button"
variant="outline"
size="sm"
disabled={isDisconnecting}
onClick={() => disconnectMutation.mutate(connection.id)}
>
{isDisconnecting ? (
<LoaderCircleIcon className="animate-spin" />
) : (
<UnplugIcon />
)}
{t.channels.disconnect}
</Button>
) : null}
</>
) : (
<Button
type="button"
@@ -217,7 +244,10 @@ function ChannelProviderItem({
disabled={isConnecting}
title={unavailableReason}
onClick={() => {
if (providerNeedsRuntimeConfig(provider)) {
if (
providerNeedsRuntimeConfig(provider) ||
canEditRuntimeConfig
) {
setSetupOpen(true);
return;
}
@@ -248,18 +278,13 @@ function ChannelProviderItem({
submitting={configureMutation.isPending}
onOpenChange={setSetupOpen}
onSubmit={(submitProvider, values) => {
const connectWindow =
submitProvider.auth_mode === "deep_link"
? prepareConnectWindow()
: null;
void configureMutation
.mutateAsync({ provider: submitProvider.provider, values })
.then((configuredProvider) => {
.then(() => {
setSetupOpen(false);
startConnect(configuredProvider, connectWindow);
toast.success(t.channels.connected);
})
.catch((error) => {
closeConnectWindow(connectWindow);
toast.error(
error instanceof Error ? error.message : t.channels.unavailable,
);
+3
View File
@@ -259,6 +259,7 @@ export const enUS: Translations = {
channels: {
title: "Channels",
connect: "Connect",
modify: "Modify",
reconnect: "Reconnect",
disconnect: "Disconnect",
connected: "Connected",
@@ -270,9 +271,11 @@ export const enUS: Translations = {
unavailable: "Channel connections are unavailable right now.",
unavailableShort: "Unavailable",
setupTitle: (name: string) => `Connect ${name}`,
setupEditTitle: (name: string) => `Modify ${name}`,
setupDescription:
"Enter the values needed by this server process. They are not written to config.yaml.",
saveAndConnect: "Save and connect",
saveChanges: "Save changes",
descriptions: {
telegram: "Telegram direct messages through your DeerFlow bot.",
slack: "Slack workspace messages and mentions.",
+3
View File
@@ -190,6 +190,7 @@ export interface Translations {
channels: {
title: string;
connect: string;
modify: string;
reconnect: string;
disconnect: string;
connected: string;
@@ -201,8 +202,10 @@ export interface Translations {
unavailable: string;
unavailableShort: string;
setupTitle: (name: string) => string;
setupEditTitle: (name: string) => string;
setupDescription: string;
saveAndConnect: string;
saveChanges: string;
descriptions: Record<string, string>;
connectedAs: (name: string) => string;
};
+3
View File
@@ -247,6 +247,7 @@ export const zhCN: Translations = {
channels: {
title: "渠道",
connect: "连接",
modify: "修改",
reconnect: "重新连接",
disconnect: "断开连接",
connected: "已连接",
@@ -258,9 +259,11 @@ export const zhCN: Translations = {
unavailable: "当前无法使用渠道连接。",
unavailableShort: "不可用",
setupTitle: (name: string) => `连接 ${name}`,
setupEditTitle: (name: string) => `修改 ${name}`,
setupDescription:
"填写当前服务进程需要的配置值。这些内容不会写入 config.yaml。",
saveAndConnect: "保存并连接",
saveChanges: "保存修改",
descriptions: {
telegram: "通过 DeerFlow Bot 接收 Telegram 私聊消息。",
slack: "接收 Slack 工作区消息和提及。",