mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-11 01:45:58 +00:00
Add user-owned IM channel connections
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { fetch } from "@/core/api/fetcher";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
|
||||
import type {
|
||||
ChannelConnectResponse,
|
||||
ChannelConnection,
|
||||
ChannelConnectionsResponse,
|
||||
ChannelProviderId,
|
||||
ChannelProvidersResponse,
|
||||
} from "./types";
|
||||
|
||||
function channelsUrl(path: string): string {
|
||||
return `${getBackendBaseURL()}/api/channels${path}`;
|
||||
}
|
||||
|
||||
async function throwChannelApiError(
|
||||
response: Response,
|
||||
fallback: string,
|
||||
): Promise<never> {
|
||||
const body = (await response.json().catch(() => ({}))) as {
|
||||
detail?: unknown;
|
||||
};
|
||||
throw new Error(typeof body.detail === "string" ? body.detail : fallback);
|
||||
}
|
||||
|
||||
export async function listChannelProviders(): Promise<ChannelProvidersResponse> {
|
||||
const response = await fetch(channelsUrl("/providers"));
|
||||
if (!response.ok) {
|
||||
await throwChannelApiError(
|
||||
response,
|
||||
`Failed to load channel providers: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
return response.json() as Promise<ChannelProvidersResponse>;
|
||||
}
|
||||
|
||||
export async function listChannelConnections(): Promise<ChannelConnection[]> {
|
||||
const response = await fetch(channelsUrl("/connections"));
|
||||
if (!response.ok) {
|
||||
await throwChannelApiError(
|
||||
response,
|
||||
`Failed to load channel connections: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
const data = (await response.json()) as ChannelConnectionsResponse;
|
||||
return data.connections;
|
||||
}
|
||||
|
||||
export async function connectChannelProvider(
|
||||
provider: ChannelProviderId,
|
||||
): Promise<ChannelConnectResponse> {
|
||||
const response = await fetch(
|
||||
channelsUrl(`/${encodeURIComponent(provider)}/connect`),
|
||||
{ method: "POST" },
|
||||
);
|
||||
if (!response.ok) {
|
||||
await throwChannelApiError(
|
||||
response,
|
||||
`Failed to connect ${provider}: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
return response.json() as Promise<ChannelConnectResponse>;
|
||||
}
|
||||
|
||||
export async function disconnectChannelConnection(
|
||||
connectionId: string,
|
||||
): Promise<void> {
|
||||
const response = await fetch(
|
||||
channelsUrl(`/connections/${encodeURIComponent(connectionId)}`),
|
||||
{ method: "DELETE" },
|
||||
);
|
||||
if (!response.ok) {
|
||||
await throwChannelApiError(
|
||||
response,
|
||||
`Failed to disconnect channel: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import {
|
||||
connectChannelProvider,
|
||||
disconnectChannelConnection,
|
||||
listChannelConnections,
|
||||
listChannelProviders,
|
||||
} from "./api";
|
||||
import type { ChannelProviderId } from "./types";
|
||||
|
||||
export const channelProviderQueryKey = ["channelProviders"] as const;
|
||||
export const channelConnectionsQueryKey = ["channelConnections"] as const;
|
||||
|
||||
export function useChannelProviders() {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: channelProviderQueryKey,
|
||||
queryFn: () => listChannelProviders(),
|
||||
});
|
||||
return {
|
||||
enabled: data?.enabled ?? false,
|
||||
providers: data?.providers ?? [],
|
||||
isLoading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useChannelConnections() {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: channelConnectionsQueryKey,
|
||||
queryFn: () => listChannelConnections(),
|
||||
});
|
||||
return { connections: data ?? [], isLoading, error };
|
||||
}
|
||||
|
||||
export function useConnectChannelProvider() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (provider: ChannelProviderId) =>
|
||||
connectChannelProvider(provider),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: channelProviderQueryKey });
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: channelConnectionsQueryKey,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDisconnectChannelConnection() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (connectionId: string) =>
|
||||
disconnectChannelConnection(connectionId),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: channelProviderQueryKey });
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: channelConnectionsQueryKey,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
export type ChannelProviderId = "telegram" | "slack" | "discord" | string;
|
||||
|
||||
export interface ChannelProvider {
|
||||
provider: ChannelProviderId;
|
||||
display_name: string;
|
||||
enabled: boolean;
|
||||
configured: boolean;
|
||||
auth_mode: string;
|
||||
connection_status: string;
|
||||
}
|
||||
|
||||
export interface ChannelProvidersResponse {
|
||||
enabled: boolean;
|
||||
providers: ChannelProvider[];
|
||||
}
|
||||
|
||||
export interface ChannelConnection {
|
||||
id: string;
|
||||
provider: ChannelProviderId;
|
||||
status: string;
|
||||
external_account_id?: string | null;
|
||||
external_account_name?: string | null;
|
||||
workspace_id?: string | null;
|
||||
workspace_name?: string | null;
|
||||
scopes: string[];
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ChannelConnectionsResponse {
|
||||
connections: ChannelConnection[];
|
||||
}
|
||||
|
||||
export interface ChannelConnectResponse {
|
||||
provider: ChannelProviderId;
|
||||
mode: string;
|
||||
url: string;
|
||||
expires_in: number;
|
||||
}
|
||||
@@ -170,6 +170,7 @@ export const enUS: Translations = {
|
||||
sidebar: {
|
||||
newChat: "New chat",
|
||||
chats: "Chats",
|
||||
channels: "Channels",
|
||||
recentChats: "Recent chats",
|
||||
demoChats: "Demo chats",
|
||||
agents: "Agents",
|
||||
@@ -254,6 +255,27 @@ export const enUS: Translations = {
|
||||
searchChats: "Search chats",
|
||||
},
|
||||
|
||||
// Channels
|
||||
channels: {
|
||||
title: "Channels",
|
||||
connect: "Connect",
|
||||
reconnect: "Reconnect",
|
||||
disconnect: "Disconnect",
|
||||
connected: "Connected",
|
||||
notConnected: "Not connected",
|
||||
pending: "Pending",
|
||||
revoked: "Disconnected",
|
||||
disabled: "Disabled",
|
||||
unconfigured: "Not configured",
|
||||
unavailable: "Channel connections are unavailable right now.",
|
||||
descriptions: {
|
||||
telegram: "Telegram direct messages through your DeerFlow bot.",
|
||||
slack: "Slack workspace messages and mentions.",
|
||||
discord: "Discord server messages through your DeerFlow bot.",
|
||||
},
|
||||
connectedAs: (name: string) => `Connected as ${name}.`,
|
||||
},
|
||||
|
||||
// Page titles (document title)
|
||||
pages: {
|
||||
appName: "DeerFlow",
|
||||
@@ -354,6 +376,7 @@ export const enUS: Translations = {
|
||||
sections: {
|
||||
account: "Account",
|
||||
appearance: "Appearance",
|
||||
channels: "Channels",
|
||||
memory: "Memory",
|
||||
tools: "Tools",
|
||||
skills: "Skills",
|
||||
@@ -456,6 +479,13 @@ export const enUS: Translations = {
|
||||
title: "Tools",
|
||||
description: "Manage the configuration and enabled status of MCP tools.",
|
||||
},
|
||||
channels: {
|
||||
title: "Channels",
|
||||
description:
|
||||
"Connect IM accounts that can send messages to DeerFlow from outside the browser.",
|
||||
disabled:
|
||||
"Channel connections are not enabled on this server. Ask an administrator to enable channel_connections.",
|
||||
},
|
||||
skills: {
|
||||
title: "Agent Skills",
|
||||
description:
|
||||
|
||||
@@ -117,6 +117,7 @@ export interface Translations {
|
||||
chats: string;
|
||||
demoChats: string;
|
||||
agents: string;
|
||||
channels: string;
|
||||
};
|
||||
|
||||
// Agents
|
||||
@@ -185,6 +186,23 @@ export interface Translations {
|
||||
searchChats: string;
|
||||
};
|
||||
|
||||
// Channels
|
||||
channels: {
|
||||
title: string;
|
||||
connect: string;
|
||||
reconnect: string;
|
||||
disconnect: string;
|
||||
connected: string;
|
||||
notConnected: string;
|
||||
pending: string;
|
||||
revoked: string;
|
||||
disabled: string;
|
||||
unconfigured: string;
|
||||
unavailable: string;
|
||||
descriptions: Record<string, string>;
|
||||
connectedAs: (name: string) => string;
|
||||
};
|
||||
|
||||
// Page titles (document title)
|
||||
pages: {
|
||||
appName: string;
|
||||
@@ -281,6 +299,7 @@ export interface Translations {
|
||||
sections: {
|
||||
account: string;
|
||||
appearance: string;
|
||||
channels: string;
|
||||
memory: string;
|
||||
tools: string;
|
||||
skills: string;
|
||||
@@ -376,6 +395,11 @@ export interface Translations {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
channels: {
|
||||
title: string;
|
||||
description: string;
|
||||
disabled: string;
|
||||
};
|
||||
skills: {
|
||||
title: string;
|
||||
description: string;
|
||||
|
||||
@@ -164,6 +164,7 @@ export const zhCN: Translations = {
|
||||
sidebar: {
|
||||
newChat: "新对话",
|
||||
chats: "对话",
|
||||
channels: "渠道",
|
||||
recentChats: "最近的对话",
|
||||
demoChats: "演示对话",
|
||||
agents: "智能体",
|
||||
@@ -242,6 +243,27 @@ export const zhCN: Translations = {
|
||||
searchChats: "搜索对话",
|
||||
},
|
||||
|
||||
// Channels
|
||||
channels: {
|
||||
title: "渠道",
|
||||
connect: "连接",
|
||||
reconnect: "重新连接",
|
||||
disconnect: "断开连接",
|
||||
connected: "已连接",
|
||||
notConnected: "未连接",
|
||||
pending: "待完成",
|
||||
revoked: "已断开",
|
||||
disabled: "已停用",
|
||||
unconfigured: "未配置",
|
||||
unavailable: "当前无法使用渠道连接。",
|
||||
descriptions: {
|
||||
telegram: "通过 DeerFlow Bot 接收 Telegram 私聊消息。",
|
||||
slack: "接收 Slack 工作区消息和提及。",
|
||||
discord: "通过 DeerFlow Bot 接收 Discord 服务器消息。",
|
||||
},
|
||||
connectedAs: (name: string) => `已连接为 ${name}。`,
|
||||
},
|
||||
|
||||
// Page titles (document title)
|
||||
pages: {
|
||||
appName: "DeerFlow",
|
||||
@@ -338,6 +360,7 @@ export const zhCN: Translations = {
|
||||
sections: {
|
||||
account: "账号",
|
||||
appearance: "外观",
|
||||
channels: "渠道",
|
||||
memory: "记忆",
|
||||
tools: "工具",
|
||||
skills: "技能",
|
||||
@@ -437,6 +460,12 @@ export const zhCN: Translations = {
|
||||
title: "工具",
|
||||
description: "管理 MCP 工具的配置和启用状态。",
|
||||
},
|
||||
channels: {
|
||||
title: "渠道",
|
||||
description: "连接可在浏览器外向 DeerFlow 发送消息的即时通讯账号。",
|
||||
disabled:
|
||||
"当前服务器未启用渠道连接。请联系管理员开启 channel_connections。",
|
||||
},
|
||||
skills: {
|
||||
title: "技能",
|
||||
description: "管理 Agent Skill 配置和启用状态。",
|
||||
|
||||
Reference in New Issue
Block a user