feat(admin): LLM config, knowledge base, and WeCom AI binding
CI / verify (pull_request) Has been cancelled
CI / verify (pull_request) Has been cancelled
Add owner-scoped model API settings, uploadable knowledge bases, and WeCom bot AI/KB wiring with OpenAI-compatible URL normalization. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -25,6 +25,8 @@ import {
|
||||
WECOM_BOT_ROLE_DEFAULT_PERMISSIONS,
|
||||
WECOM_BOT_ROLE_LABELS,
|
||||
WECOM_BOT_ROLES,
|
||||
type LlmApiConfigOptionDto,
|
||||
type KnowledgeBaseOptionDto,
|
||||
type WecomBotDto,
|
||||
type WecomBotPermission,
|
||||
type WecomBotRole,
|
||||
@@ -53,6 +55,9 @@ type FormValues = {
|
||||
avatarUrl?: string;
|
||||
welcome?: string;
|
||||
permissions: WecomBotPermission[];
|
||||
aiEnabled: boolean;
|
||||
llmConfigId?: string | null;
|
||||
knowledgeBaseId?: string | null;
|
||||
enabled: boolean;
|
||||
sortOrder: number;
|
||||
};
|
||||
@@ -66,6 +71,8 @@ export default function WecomBotsPage() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [detail, setDetail] = useState<WecomBotDto | null>(null);
|
||||
const [runtime, setRuntime] = useState<ListRes['runtime']>();
|
||||
const [llmOptions, setLlmOptions] = useState<LlmApiConfigOptionDto[]>([]);
|
||||
const [kbOptions, setKbOptions] = useState<KnowledgeBaseOptionDto[]>([]);
|
||||
const roleWatch = Form.useWatch('role', form);
|
||||
|
||||
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList<WecomBotDto>(
|
||||
@@ -87,6 +94,21 @@ export default function WecomBotsPage() {
|
||||
.catch(() => {});
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
void request<{
|
||||
llmConfigs: LlmApiConfigOptionDto[];
|
||||
knowledgeBases: KnowledgeBaseOptionDto[];
|
||||
}>('/admin/wecom-bots/ai-options')
|
||||
.then((res) => {
|
||||
setLlmOptions(res.llmConfigs);
|
||||
setKbOptions(res.knowledgeBases);
|
||||
})
|
||||
.catch(() => {
|
||||
setLlmOptions([]);
|
||||
setKbOptions([]);
|
||||
});
|
||||
}, []);
|
||||
|
||||
function openCreate() {
|
||||
setEditing(null);
|
||||
form.setFieldsValue({
|
||||
@@ -97,6 +119,9 @@ export default function WecomBotsPage() {
|
||||
avatarUrl: '',
|
||||
welcome: '',
|
||||
permissions: [...WECOM_BOT_ROLE_DEFAULT_PERMISSIONS.CUSTOMER_SERVICE],
|
||||
aiEnabled: false,
|
||||
llmConfigId: null,
|
||||
knowledgeBaseId: null,
|
||||
enabled: true,
|
||||
sortOrder: 0,
|
||||
});
|
||||
@@ -113,6 +138,9 @@ export default function WecomBotsPage() {
|
||||
avatarUrl: row.avatarUrl || '',
|
||||
welcome: row.welcome || '',
|
||||
permissions: row.permissions,
|
||||
aiEnabled: row.aiEnabled,
|
||||
llmConfigId: row.llmConfigId,
|
||||
knowledgeBaseId: row.knowledgeBaseId,
|
||||
enabled: row.enabled,
|
||||
sortOrder: row.sortOrder,
|
||||
});
|
||||
@@ -123,19 +151,25 @@ export default function WecomBotsPage() {
|
||||
const values = await form.validateFields();
|
||||
setSaving(true);
|
||||
try {
|
||||
const payload = {
|
||||
name: values.name.trim(),
|
||||
role: values.role,
|
||||
botId: values.botId.trim(),
|
||||
avatarUrl: values.avatarUrl?.trim() || null,
|
||||
welcome: values.welcome?.trim() || null,
|
||||
permissions: values.permissions,
|
||||
aiEnabled: values.aiEnabled,
|
||||
llmConfigId: values.llmConfigId || null,
|
||||
knowledgeBaseId: values.knowledgeBaseId || null,
|
||||
enabled: values.enabled,
|
||||
sortOrder: values.sortOrder,
|
||||
};
|
||||
if (editing) {
|
||||
await request(`/admin/wecom-bots/${editing.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
name: values.name.trim(),
|
||||
role: values.role,
|
||||
botId: values.botId.trim(),
|
||||
...payload,
|
||||
secret: values.secret?.trim() || undefined,
|
||||
avatarUrl: values.avatarUrl?.trim() || null,
|
||||
welcome: values.welcome?.trim() || null,
|
||||
permissions: values.permissions,
|
||||
enabled: values.enabled,
|
||||
sortOrder: values.sortOrder,
|
||||
}),
|
||||
});
|
||||
message.success('已更新');
|
||||
@@ -147,15 +181,8 @@ export default function WecomBotsPage() {
|
||||
await request('/admin/wecom-bots', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
name: values.name.trim(),
|
||||
role: values.role,
|
||||
botId: values.botId.trim(),
|
||||
...payload,
|
||||
secret: values.secret.trim(),
|
||||
avatarUrl: values.avatarUrl?.trim() || null,
|
||||
welcome: values.welcome?.trim() || null,
|
||||
permissions: values.permissions,
|
||||
enabled: values.enabled,
|
||||
sortOrder: values.sortOrder,
|
||||
}),
|
||||
});
|
||||
message.success('已创建');
|
||||
@@ -225,6 +252,16 @@ export default function WecomBotsPage() {
|
||||
</Tag>
|
||||
)),
|
||||
},
|
||||
{
|
||||
title: 'AI',
|
||||
width: 100,
|
||||
render: (_, row) =>
|
||||
row.aiEnabled ? (
|
||||
<Tag color="blue">{row.llmConfigName || '已开'}</Tag>
|
||||
) : (
|
||||
<Tag>关</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '启用',
|
||||
dataIndex: 'enabled',
|
||||
@@ -401,6 +438,34 @@ export default function WecomBotsPage() {
|
||||
<Form.Item name="welcome" label="欢迎语">
|
||||
<Input.TextArea rows={3} placeholder="进入会话时的欢迎语,可空" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="aiEnabled"
|
||||
label="启用 AI 问答"
|
||||
valuePropName="checked"
|
||||
extra="开启后,未匹配指令的自然语言将调用绑定的语言模型(可挂知识库)"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="llmConfigId" label="语言模型">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="选择已生效的配置"
|
||||
options={llmOptions.map((o) => ({
|
||||
value: o.id,
|
||||
label: `${o.name}(${o.modelName})`,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="knowledgeBaseId" label="知识库">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="可选,供 AI 检索"
|
||||
options={kbOptions.map((o) => ({
|
||||
value: o.id,
|
||||
label: `${o.name}(${o.documentCount} 篇)`,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Space size="large">
|
||||
<Form.Item name="enabled" label="启用" valuePropName="checked">
|
||||
<Switch />
|
||||
@@ -432,6 +497,9 @@ export default function WecomBotsPage() {
|
||||
{detail.permissions.map((p) => WECOM_BOT_PERMISSION_LABELS[p] || p).join('、')}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="欢迎语">{detail.welcome || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="AI 问答">{detail.aiEnabled ? '开' : '关'}</Descriptions.Item>
|
||||
<Descriptions.Item label="语言模型">{detail.llmConfigName || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="知识库">{detail.knowledgeBaseName || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="启用">{detail.enabled ? '是' : '否'}</Descriptions.Item>
|
||||
<Descriptions.Item label="排序">{detail.sortOrder}</Descriptions.Item>
|
||||
<Descriptions.Item label="创建">{fmtTime(detail.createdAt)}</Descriptions.Item>
|
||||
|
||||
Reference in New Issue
Block a user