refactor: 移除通用频道管理(LangBot)模块,IM Bot 改为直接对接;迁移 _call_real_llm 至 im_llm

This commit is contained in:
QihuAI Design committed 2026-07-14 08:21:56 +08:00
1 parent 945eeb8ba0
commit ab54d7344c
15 files changed
+53 -5629

No files matched your search

-1
View File
@@ -1,6 +1,5 @@
from . import auth from . import auth
from . import chat from . import chat
from . import channels
from . import knowledge from . import knowledge
from . import market from . import market
from . import token_usage from . import token_usage
+1 -1
View File
@@ -608,7 +608,7 @@ def _qihua_reply(text: str) -> str:
自行创建 DB 会话,避免上层线程无可用 session 时崩溃。""" 自行创建 DB 会话,避免上层线程无可用 session 时崩溃。"""
db = None db = None
try: try:
from app.api.channels import _call_real_llm from app.api.im_llm import _call_real_llm
db = SessionLocal() db = SessionLocal()
return _call_real_llm(db, None, text, "个人微信") return _call_real_llm(db, None, text, "个人微信")
except Exception as e: except Exception as e:
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -134,7 +134,7 @@ def _qihua_reply(text: str, user_id: str = None) -> str:
) )
db = None db = None
try: try:
from app.api.channels import _call_real_llm from app.api.im_llm import _call_real_llm
db = SessionLocal() db = SessionLocal()
return _call_real_llm(db, None, prompt, "个人微信") return _call_real_llm(db, None, prompt, "个人微信")
except Exception as e: except Exception as e:
+45
View File
@@ -0,0 +1,45 @@
"""
IM Bot 真实 LLM 调用。
原实现位于 channels.py(通用频道管理模块),该模块已移除。
为保持 IM Bot(微信机器人等)正常生成回复,将 _call_real_llm 迁移至此独立模块。
"""
from typing import Optional
import httpx
from sqlalchemy.orm import Session
from ..capability_registry import resolve_auto_model
from ..models import AIModel, ModelProvider
def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im") -> str:
"""调用七虎AI 默认文本模型生成回复,供 IM Bot 复用。"""
try:
ai_model = resolve_auto_model("text", db)
if not ai_model:
return "(未配置默认文本模型,无法生成回复)"
provider = db.query(ModelProvider).filter(ModelProvider.id == ai_model.provider_id).first()
api_key = ai_model.api_key or (provider.api_key if provider else None)
api_url = ai_model.endpoint
if not api_url and provider and provider.api_base_url:
api_url = f"{provider.api_base_url.rstrip('/')}/v1/chat/completions"
if not (api_key and api_url):
return "(模型未配置 API Key 或地址,无法生成回复)"
messages = [
{"role": "system", "content": "你是七虎AI 助手,负责在即时通讯中回复用户消息。"},
{"role": "user", "content": prompt},
]
request_body = {"model": ai_model.name, "messages": messages, "max_tokens": 2048}
with httpx.Client(timeout=ai_model.timeout or 120) as client:
resp = client.post(
api_url,
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
json=request_body,
)
if resp.status_code != 200:
return f"(AI 模型调用失败 HTTP {resp.status_code})"
data = resp.json()
return data.get("choices", [{}])[0].get("message", {}).get("content", "")
except Exception as e:
return f"(AI 回复生成失败:{e})"
+1 -2
View File
@@ -15,7 +15,7 @@ from datetime import datetime
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.api import auth, chat, channels, knowledge, market, token_usage, payment, profile, notifications, assets, evolution from app.api import auth, chat, knowledge, market, token_usage, payment, profile, notifications, assets, evolution
from app.casdoor_auth import router as casdoor_router from app.casdoor_auth import router as casdoor_router
from app.api.admin import dashboard, users, models, settings, payment_admin, services, membership_admin, ads, market_admin, logs, skills_admin, agents_admin, knowledge_admin, workflow_admin, storage_admin, automation_admin, mcp_admin, developer_admin, apps_admin, distro_admin, code_exec_admin, privacy_admin, im_bot_admin, promotion_admin, achievement_admin, tenant_admin, billing_admin, analytics_admin from app.api.admin import dashboard, users, models, settings, payment_admin, services, membership_admin, ads, market_admin, logs, skills_admin, agents_admin, knowledge_admin, workflow_admin, storage_admin, automation_admin, mcp_admin, developer_admin, apps_admin, distro_admin, code_exec_admin, privacy_admin, im_bot_admin, promotion_admin, achievement_admin, tenant_admin, billing_admin, analytics_admin
from app.api.im_bot_user import router as im_bot_user_router from app.api.im_bot_user import router as im_bot_user_router
@@ -192,7 +192,6 @@ app.add_middleware(
# 前台 API # 前台 API
app.include_router(auth.router, prefix="/app/api/auth", tags=["认证"]) app.include_router(auth.router, prefix="/app/api/auth", tags=["认证"])
app.include_router(chat.router, prefix="/app/api/chat", tags=["AI聊天"]) app.include_router(chat.router, prefix="/app/api/chat", tags=["AI聊天"])
app.include_router(channels.router, prefix="/app/api/channels", tags=["频道"])
app.include_router(knowledge.router, prefix="/app/api/knowledge", tags=["知识库"]) app.include_router(knowledge.router, prefix="/app/api/knowledge", tags=["知识库"])
app.include_router(market.router, prefix="/app/api/market", tags=["应用市场"]) app.include_router(market.router, prefix="/app/api/market", tags=["应用市场"])
app.include_router(token_usage.router, prefix="/app/api/token", tags=["Token驾驶舱"]) app.include_router(token_usage.router, prefix="/app/api/token", tags=["Token驾驶舱"])
-2
View File
@@ -13,7 +13,6 @@ import AdminLayout from '@/components/layout/AdminLayout';
const LandingPage = React.lazy(() => import('@/pages/front/LandingPage')); const LandingPage = React.lazy(() => import('@/pages/front/LandingPage'));
const LoginPage = React.lazy(() => import('@/pages/front/LoginPage')); const LoginPage = React.lazy(() => import('@/pages/front/LoginPage'));
const ChatPage = React.lazy(() => import('@/pages/front/ChatPage')); const ChatPage = React.lazy(() => import('@/pages/front/ChatPage'));
const ChannelPage = React.lazy(() => import('@/pages/front/ChannelPage'));
const KnowledgePage = React.lazy(() => import('@/pages/front/KnowledgePage')); const KnowledgePage = React.lazy(() => import('@/pages/front/KnowledgePage'));
const MarketPage = React.lazy(() => import('@/pages/front/MarketPage')); const MarketPage = React.lazy(() => import('@/pages/front/MarketPage'));
const TokenDashboardPage = React.lazy(() => import('@/pages/front/TokenDashboardPage')); const TokenDashboardPage = React.lazy(() => import('@/pages/front/TokenDashboardPage'));
@@ -112,7 +111,6 @@ const App: React.FC = () => {
<Route path="/" element={<FrontLayout />}> <Route path="/" element={<FrontLayout />}>
<Route path="chat" element={<ChatPage />} /> <Route path="chat" element={<ChatPage />} />
<Route path="chat/:id" element={<ChatPage />} /> <Route path="chat/:id" element={<ChatPage />} />
<Route path="channels" element={<ChannelPage />} />
<Route path="knowledge" element={<KnowledgePage />} /> <Route path="knowledge" element={<KnowledgePage />} />
<Route path="market" element={<MarketPage />} /> <Route path="market" element={<MarketPage />} />
<Route path="token" element={<TokenDashboardPage />} /> <Route path="token" element={<TokenDashboardPage />} />
-57
View File
@@ -230,63 +230,6 @@ export const chatAPI = {
// ============================================================
// 频道相关API
// ============================================================
export const channelAPI = {
getChannels: () => get<any[]>('/channels/'),
createChannel: (data: any) => post<any>('/channels/', data),
getChannel: (id: string) => get<any>(`/channels/${id}`),
updateChannel: (id: string, data: any) => put<any>(`/channels/${id}`, data),
deleteChannel: (id: string) => del<any>(`/channels/${id}`),
connectChannel: (id: string) => post<any>(`/channels/${id}/connect`),
disconnectChannel: (id: string) => post<any>(`/channels/${id}/disconnect`),
getPlatformConfigs: () => get<any>('/channels/platforms/config'),
webhookMessage: (id: string, data: { from_user: string; content: string; message_type?: string }) =>
post<any>(`/channels/${id}/webhook`, data),
getMembers: (id: string) => get<any[]>(`/channels/${id}/members`),
addMember: (id: string, userId: string) => post<any>(`/channels/${id}/members`, { user_id: userId }),
removeMember: (id: string, userId: string) => del<any>(`/channels/${id}/members/${userId}`),
getMessages: (id: string, params?: { limit?: number; offset?: number }) => {
const sp = new URLSearchParams();
if (params?.limit) sp.set('limit', String(params.limit));
if (params?.offset) sp.set('offset', String(params.offset));
const qs = sp.toString();
return get<any[]>(`/channels/${id}/messages${qs ? `?${qs}` : ''}`);
},
sendMessage: (id: string, content: string) => post<any>(`/channels/${id}/messages`, { content }),
authorize: (platform: string) => post<any>('/channels/authorize', { platform }),
scanAuthorize: (platform: string, credentials?: Record<string, string>) => post<any>(`/channels/authorize/scan?platform=${platform}`, credentials ? { credentials } : {}),
scanAuthorizeStatus: (platform: string) => post<any>(`/channels/authorize/scan/status?platform=${platform}`),
};
@@ -16,7 +16,6 @@ const navGroups = [
{ {
title: 'IM Bot', title: 'IM Bot',
items: [ items: [
{ path: '/channels', icon: 'channel', label: '频道管理' },
{ path: '/wechat-bot', icon: 'wechat', label: '微信机器人' }, { path: '/wechat-bot', icon: 'wechat', label: '微信机器人' },
], ],
}, },
+2 -8
View File
@@ -2,7 +2,7 @@
// 分离的Mock数据层,便于后续接入真实后端时替换 // 分离的Mock数据层,便于后续接入真实后端时替换
import type { import type {
User, Conversation, Message, Channel, KnowledgeBase, User, Conversation, Message, KnowledgeBase,
KnowledgeDocument, MarketApp, AppReview, TokenUsageLog, KnowledgeDocument, MarketApp, AppReview, TokenUsageLog,
TokenBalance, Order, MembershipPlan, Coupon, UserCoupon, TokenBalance, Order, MembershipPlan, Coupon, UserCoupon,
Notification, ModelProvider, AIModel, SiteSettings, Notification, ModelProvider, AIModel, SiteSettings,
@@ -97,13 +97,7 @@ export const mockMessages: Record<string, Message[]> = {
], ],
}; };
// ==================== 频道数据 (IM账户绑定) ====================
export const mockChannels: Channel[] = [
{ id: 'ch1', userId: 'u1', platform: 'feishu', name: '飞书AI助手', accountName: 'zangsan@company.feishu.cn', agentId: 'a1', knowledgeBaseId: 'kb1', status: 'connected', lastActive: '2026-05-26T09:30:00Z', boundModel: 'DeepSeek-V3', features: ['AI对话', '文档问答', '任务提醒', '会议纪要'], createdAt: '2026-04-01T08:00:00Z' },
{ id: 'ch2', userId: 'u1', platform: 'dingtalk', name: '钉钉AI助理', accountName: '张经理', agentId: 'a2', knowledgeBaseId: 'kb2', status: 'connected', lastActive: '2026-05-26T08:15:00Z', boundModel: 'GPT-4o', features: ['AI对话', '审批辅助', '日程管理'], createdAt: '2026-04-15T10:00:00Z' },
{ id: 'ch3', userId: 'u1', platform: 'wechat', name: '微信AI伙伴', accountName: 'wxid_abc123', agentId: undefined, knowledgeBaseId: undefined, status: 'pending', lastActive: undefined, boundModel: 'DeepSeek-V3', features: ['AI对话', '内容创作'], createdAt: '2026-05-01T09:00:00Z' },
{ id: 'ch4', userId: 'u1', platform: 'telegram', name: 'Telegram Bot', accountName: '@aiflow_bot', agentId: undefined, knowledgeBaseId: undefined, status: 'disconnected', lastActive: '2026-05-10T14:00:00Z', boundModel: 'Qwen-Max', features: ['AI对话'], createdAt: '2026-05-10T14:00:00Z' },
];
// ==================== 知识库数据 ==================== // ==================== 知识库数据 ====================
export const mockKnowledgeBases: KnowledgeBase[] = [ export const mockKnowledgeBases: KnowledgeBase[] = [
+2 -178
View File
@@ -33,7 +33,7 @@ const statusColors: Record<string, string> = {
disconnected: '#6b7280', disconnected: '#6b7280',
}; };
type Tab = 'dashboard' | 'channels' | 'routes' | 'langbot' | 'messages' | 'setup'; type Tab = 'dashboard' | 'channels' | 'routes' | 'messages' | 'setup';
const ImBots: React.FC = () => { const ImBots: React.FC = () => {
const [activeTab, setActiveTab] = useState<Tab>('dashboard'); const [activeTab, setActiveTab] = useState<Tab>('dashboard');
@@ -46,7 +46,6 @@ const ImBots: React.FC = () => {
const [filterPlatform, setFilterPlatform] = useState(''); const [filterPlatform, setFilterPlatform] = useState('');
const [filterStatus, setFilterStatus] = useState(''); const [filterStatus, setFilterStatus] = useState('');
const [routes, setRoutes] = useState<any[]>([]); const [routes, setRoutes] = useState<any[]>([]);
const [langbotConfig, setLangbotConfig] = useState<any[]>([]);
const [messages, setMessages] = useState<any[]>([]); const [messages, setMessages] = useState<any[]>([]);
const [messagesTotal, setMessagesTotal] = useState(0); const [messagesTotal, setMessagesTotal] = useState(0);
const [messagesPage, setMessagesPage] = useState(1); const [messagesPage, setMessagesPage] = useState(1);
@@ -77,18 +76,6 @@ const ImBots: React.FC = () => {
credentials: '', credentials: '',
}); });
// LangBot form state
const [langbotForm, setLangbotForm] = useState({
id: 1,
gateway_url: '',
api_key: '',
webhook_secret: '',
bot_uuid: '',
callback_url: '',
sync_channels: 0,
status: 'inactive',
});
const showToast = (msg: string, type: 'success' | 'error' = 'success') => { const showToast = (msg: string, type: 'success' | 'error' = 'success') => {
setToast({ msg, type }); setToast({ msg, type });
setTimeout(() => setToast(null), 3000); setTimeout(() => setToast(null), 3000);
@@ -155,22 +142,6 @@ const ImBots: React.FC = () => {
} }
}, []); }, []);
// Load LangBot config
const loadLangbot = useCallback(async () => {
try {
const res = await adminAPI.getLangbotConfig();
if ((res as any)?.success) {
const configs = res.data || [];
setLangbotConfig(configs);
if (configs.length > 0) {
setLangbotForm({ ...langbotForm, ...configs[0] });
}
}
} catch (e) {
showToast('Failed to load LangBot config', 'error');
}
}, []);
// Load messages // Load messages
const loadMessages = useCallback(async () => { const loadMessages = useCallback(async () => {
try { try {
@@ -211,8 +182,6 @@ const ImBots: React.FC = () => {
} else if (activeTab === 'routes') { } else if (activeTab === 'routes') {
loadRoutes(); loadRoutes();
loadChannels(); loadChannels();
} else if (activeTab === 'langbot') {
loadLangbot();
} else if (activeTab === 'messages') { } else if (activeTab === 'messages') {
loadMessages(); loadMessages();
} }
@@ -348,51 +317,7 @@ const ImBots: React.FC = () => {
} }
}; };
// Handle LangBot config save
const handleSaveLangbot = async () => {
try {
const params = new URLSearchParams({
config_id: String(langbotForm.id || 1),
gateway_url: langbotForm.gateway_url,
status: langbotForm.status,
sync_channels: String(langbotForm.sync_channels),
});
if (langbotForm.api_key) params.append('api_key', langbotForm.api_key);
if (langbotForm.webhook_secret) params.append('webhook_secret', langbotForm.webhook_secret);
if (langbotForm.bot_uuid) params.append('bot_uuid', langbotForm.bot_uuid);
if (langbotForm.callback_url) params.append('callback_url', langbotForm.callback_url);
await adminAPI.updateLangbotConfig('?' + params.toString());
showToast('LangBot config saved');
loadLangbot();
} catch (e) {
showToast('Failed to save LangBot config', 'error');
}
};
// Handle LangBot test
const handleTestLangbot = async () => {
if (!langbotForm.gateway_url) {
showToast('Please enter gateway URL', 'error');
return;
}
try {
const params = new URLSearchParams({
gateway_url: langbotForm.gateway_url,
});
if (langbotForm.api_key) params.append('api_key', langbotForm.api_key);
const res = await adminAPI.testLangbot('?' + params.toString());
if ((res as any)?.success) {
const data = res.data;
if (data.reachable) {
showToast(`LangBot reachable! Status: ${data.status_code}`);
} else {
showToast(`LangBot unreachable: ${data.error}`, 'error');
}
}
} catch (e) {
showToast('Test failed', 'error');
}
};
// Copy to clipboard // Copy to clipboard
const copyToClipboard = (text: string) => { const copyToClipboard = (text: string) => {
@@ -411,7 +336,6 @@ const ImBots: React.FC = () => {
{ key: 'dashboard', label: '仪表盘', icon: 'M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z' }, { key: 'dashboard', label: '仪表盘', icon: 'M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z' },
{ key: 'channels', label: '频道管理', icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z' }, { key: 'channels', label: '频道管理', icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z' },
{ key: 'routes', label: '消息路由', icon: 'M3.5 18.5l6-6.01 4 4L22 6.92l-1.41-1.41-7.09 7.97-4-4L2 17l1.5 1.5z' }, { key: 'routes', label: '消息路由', icon: 'M3.5 18.5l6-6.01 4 4L22 6.92l-1.41-1.41-7.09 7.97-4-4L2 17l1.5 1.5z' },
{ key: 'langbot', label: 'LangBot 网关', icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z' },
{ key: 'messages', label: '消息日志', icon: 'M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z' }, { key: 'messages', label: '消息日志', icon: 'M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z' },
{ key: 'setup', label: '快速接入', icon: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z' }, { key: 'setup', label: '快速接入', icon: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z' },
]; ];
@@ -422,7 +346,7 @@ const ImBots: React.FC = () => {
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
<div> <div>
<h1 style={{ fontSize: '24px', fontWeight: 700, margin: 0 }}>IM 机器人管理</h1> <h1 style={{ fontSize: '24px', fontWeight: 700, margin: 0 }}>IM 机器人管理</h1>
<p style={{ color: '#888', fontSize: '14px', margin: '4px 0 0 0' }}>管理即时通讯平台接入、消息路由和 LangBot 网关</p> <p style={{ color: '#888', fontSize: '14px', margin: '4px 0 0 0' }}>管理即时通讯平台接入与消息路由</p>
</div> </div>
<div style={{ display: 'flex', gap: '8px' }}> <div style={{ display: 'flex', gap: '8px' }}>
{stats && ( {stats && (
@@ -430,7 +354,6 @@ const ImBots: React.FC = () => {
<StatBadge label="频道" value={stats.total_channels} color="#3370FF" /> <StatBadge label="频道" value={stats.total_channels} color="#3370FF" />
<StatBadge label="在线" value={stats.active_channels} color="#10b981" /> <StatBadge label="在线" value={stats.active_channels} color="#10b981" />
<StatBadge label="今日消息" value={stats.today_messages_in + stats.today_messages_out} color="#f59e0b" /> <StatBadge label="今日消息" value={stats.today_messages_in + stats.today_messages_out} color="#f59e0b" />
<StatBadge label="LangBot" value={stats.langbot_status === 'active' ? 'ON' : 'OFF'} color={stats.langbot_status === 'active' ? '#10b981' : '#6b7280'} />
</div> </div>
)} )}
</div> </div>
@@ -744,105 +667,6 @@ const ImBots: React.FC = () => {
</div> </div>
)} )}
{/* LangBot Tab */}
{activeTab === 'langbot' && (
<div style={{ maxWidth: '800px' }}>
<div style={{ background: '#1a1d27', borderRadius: '12px', padding: '24px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
<h3 style={{ margin: 0, fontSize: '16px' }}>LangBot 网关配置</h3>
<span style={{
padding: '4px 12px', borderRadius: '4px', fontSize: '12px',
background: langbotForm.status === 'active' ? '#10b98120' : '#6b728020',
color: langbotForm.status === 'active' ? '#10b981' : '#6b7280',
}}>
{langbotForm.status === 'active' ? '已启用' : '未启用'}
</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
<FormField label="网关 URL" >
<input
value={langbotForm.gateway_url}
onChange={(e) => setLangbotForm({ ...langbotForm, gateway_url: e.target.value })}
placeholder="http://localhost:5300"
style={inputStyle}
/>
</FormField>
<FormField label="Bot UUID">
<input
value={langbotForm.bot_uuid || ''}
onChange={(e) => setLangbotForm({ ...langbotForm, bot_uuid: e.target.value })}
placeholder="LangBot HTTP Bot UUID"
style={inputStyle}
/>
</FormField>
<FormField label="API Key">
<input
type="password"
value={langbotForm.api_key || ''}
onChange={(e) => setLangbotForm({ ...langbotForm, api_key: e.target.value })}
placeholder="LangBot REST API Key"
style={inputStyle}
/>
</FormField>
<FormField label="Webhook Secret">
<input
type="password"
value={langbotForm.webhook_secret || ''}
onChange={(e) => setLangbotForm({ ...langbotForm, webhook_secret: e.target.value })}
placeholder="HMAC-SHA256 签名密钥"
style={inputStyle}
/>
</FormField>
<FormField label="回调 URL">
<input
value={langbotForm.callback_url || ''}
onChange={(e) => setLangbotForm({ ...langbotForm, callback_url: e.target.value })}
placeholder="http://210.16.161.218:12888/app/api/channels/webhook/langbot"
style={inputStyle}
/>
</FormField>
<FormField label="自动同步频道">
<select
value={langbotForm.sync_channels}
onChange={(e) => setLangbotForm({ ...langbotForm, sync_channels: Number(e.target.value) })}
style={selectStyle}
>
<option value={0}>否</option>
<option value={1}>是</option>
</select>
</FormField>
<FormField label="状态">
<select
value={langbotForm.status}
onChange={(e) => setLangbotForm({ ...langbotForm, status: e.target.value })}
style={selectStyle}
>
<option value="inactive">未启用</option>
<option value="active">启用</option>
</select>
</FormField>
</div>
<div style={{ display: 'flex', gap: '12px', marginTop: '24px' }}>
<button onClick={handleSaveLangbot} style={primaryButtonStyle}>保存配置</button>
<button onClick={handleTestLangbot} style={secondaryButtonStyle}>测试连接</button>
</div>
</div>
{/* LangBot Info */}
<div style={{ background: '#1a1d27', borderRadius: '12px', padding: '24px', marginTop: '16px' }}>
<h4 style={{ marginTop: 0, fontSize: '14px', color: '#888' }}>LangBot 集成说明</h4>
<ul style={{ fontSize: '13px', color: '#aaa', lineHeight: '1.8', paddingLeft: '20px' }}>
<li>LangBot 是开源的 LLM 原生 IM 机器人平台,支持飞书、微信、钉钉等</li>
<li>配置 LangBot 作为外部网关后,IM 消息将通过 LangBot 转发到本平台</li>
<li>在 LangBot 中创建 HTTP Bot 适配器,将 Webhook URL 指向本平台</li>
<li>API Key 在 LangBot 的 config.yaml 中通过 api.global_api_key 配置</li>
<li>Webhook Secret 用于 HMAC-SHA256 双向签名验证</li>
</ul>
</div>
</div>
)}
{/* Messages Tab */} {/* Messages Tab */}
{activeTab === 'messages' && ( {activeTab === 'messages' && (
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -68,7 +68,7 @@ const features = [
{ d: <><circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" /></>, { d: <><circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" /></>,
title: 'Token 驾驶舱', desc: '实时监控 Token 消耗,费用透明可控,每一笔都有据可查。' }, title: 'Token 驾驶舱', desc: '实时监控 Token 消耗,费用透明可控,每一笔都有据可查。' },
{ d: <><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></>, { d: <><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></>,
title: 'IM 频道绑定', desc: '微信 / 企微 / 飞书 / QQ 多平台绑定,AI 助手融入日常工作流。' }, title: 'AI 微信机器人', desc: '个人微信 AI 助手直接对接,消息自动回复,AI 融入你的日常沟通。' },
{ d: <><polyline points="16 3 21 3 21 8" /><line x1="4" y1="20" x2="21" y2="3" /><polyline points="21 16 21 21 16 21" /><line x1="15" y1="15" x2="21" y2="21" /><line x1="4" y1="4" x2="9" y2="9" /></>, { d: <><polyline points="16 3 21 3 21 8" /><line x1="4" y1="20" x2="21" y2="3" /><polyline points="21 16 21 21 16 21" /><line x1="15" y1="15" x2="21" y2="21" /><line x1="4" y1="4" x2="9" y2="9" /></>,
title: '自动化任务编排', desc: '关键词触发、定时执行等自动化任务,让 AI 按你的节奏工作。' }, title: '自动化任务编排', desc: '关键词触发、定时执行等自动化任务,让 AI 按你的节奏工作。' },
]; ];
-18
View File
@@ -63,24 +63,6 @@ export interface Message {
used_skills?: string[]; used_skills?: string[];
} }
// 频道
// IM账户绑定频道
export type IMPlatform = 'feishu' | 'wechat' | 'dingtalk' | 'whatsapp' | 'telegram' | 'slack';
export interface Channel {
id: string;
userId: string;
platform: IMPlatform;
name: string;
accountName: string;
agentId?: string;
knowledgeBaseId?: string;
status: 'connected' | 'disconnected' | 'pending';
lastActive?: string;
boundModel: string;
features: string[];
createdAt: string;
}
// 知识库 // 知识库
export interface KnowledgeBase { export interface KnowledgeBase {