feat(admin): LLM config, knowledge base, and WeCom AI binding
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:
2026-07-26 09:31:22 +08:00
parent 745c192693
commit 2b7ef65cce
30 changed files with 2507 additions and 56 deletions
@@ -12,6 +12,7 @@ import {
} from '@dukang/shared-types';
import { PrismaService } from '../../common/prisma/prisma.module';
import { WecomBotActionsService } from './wecom-bot-actions.service';
import { WecomBotAiService } from './wecom-bot-ai.service';
import type { WecomBotRuntimeConfig } from './wecom-bot.types';
const DEFAULT_WELCOMES: Record<WecomBotRole, string> = {
@@ -62,6 +63,7 @@ export class WecomAibotService implements OnModuleInit, OnModuleDestroy {
constructor(
private readonly prisma: PrismaService,
private readonly actions: WecomBotActionsService,
private readonly ai: WecomBotAiService,
) {}
async onModuleInit() {
@@ -150,6 +152,9 @@ export class WecomAibotService implements OnModuleInit, OnModuleDestroy {
avatarUrl: string | null;
welcome: string | null;
permissions: string;
aiEnabled: boolean;
llmConfigId: bigint | null;
knowledgeBaseId: bigint | null;
enabled: boolean;
}): WecomBotRuntimeConfig {
const role = (isWecomRole(row.role) ? row.role : 'CUSTOM') as WecomBotRole;
@@ -164,6 +169,9 @@ export class WecomAibotService implements OnModuleInit, OnModuleDestroy {
avatarUrl: row.avatarUrl,
welcome: row.welcome?.trim() || DEFAULT_WELCOMES[role],
permissions: resolveWecomBotPermissions(role, row.permissions),
aiEnabled: row.aiEnabled,
llmConfigId: row.llmConfigId?.toString() ?? null,
knowledgeBaseId: row.knowledgeBaseId?.toString() ?? null,
};
}
@@ -258,8 +266,24 @@ export class WecomAibotService implements OnModuleInit, OnModuleDestroy {
await this.replyText(client, frame, this.formatStatusMarkdown());
return;
}
const reply = await this.actions.handleCommand(cfg, wecomUserId, content);
await this.replyText(client, frame, reply);
const useAi = cfg.aiEnabled && !!cfg.llmConfigId;
const reply = await this.actions.handleCommand(cfg, wecomUserId, content, {
skipNaturalFallback: useAi,
});
if (reply != null) {
await this.replyText(client, frame, reply);
return;
}
if (useAi) {
const aiReply = await this.ai.replyIfConfigured(cfg, content);
await this.replyText(
client,
frame,
aiReply ?? `未识别指令。\n\n${this.actions.buildHelp(cfg)}`,
);
return;
}
await this.replyText(client, frame, `未识别指令。\n\n${this.actions.buildHelp(cfg)}`);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
this.logger.error(`[${cfg.key}] handle text failed: ${msg}`);