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
@@ -0,0 +1,36 @@
const { PrismaClient } = require('@prisma/client');
const p = new PrismaClient();
async function ensureColumn(table, column, ddl) {
const cols = await p.$queryRawUnsafe(`SHOW COLUMNS FROM \`${table}\` LIKE '${column}'`);
if (!cols.length) {
await p.$executeRawUnsafe(ddl);
console.log(`added ${table}.${column}`);
} else {
console.log(`${table}.${column} exists`);
}
}
(async () => {
await ensureColumn(
'wecom_bot',
'ai_enabled',
'ALTER TABLE `wecom_bot` ADD COLUMN `ai_enabled` TINYINT(1) NOT NULL DEFAULT 0 AFTER `permissions`',
);
await ensureColumn(
'wecom_bot',
'llm_config_id',
'ALTER TABLE `wecom_bot` ADD COLUMN `llm_config_id` BIGINT UNSIGNED NULL AFTER `ai_enabled`',
);
await ensureColumn(
'wecom_bot',
'knowledge_base_id',
'ALTER TABLE `wecom_bot` ADD COLUMN `knowledge_base_id` BIGINT UNSIGNED NULL AFTER `llm_config_id`',
);
await p.$disconnect();
})().catch(async (e) => {
console.error(e);
await p.$disconnect();
process.exit(1);
});
@@ -0,0 +1,50 @@
-- LLM / 知识库 / 企微 AI 字段
CREATE TABLE IF NOT EXISTS `llm_api_config` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(64) NOT NULL,
`provider` VARCHAR(32) NOT NULL,
`base_url` VARCHAR(512) NOT NULL,
`api_key` VARCHAR(512) NOT NULL,
`model_name` VARCHAR(128) NOT NULL,
`temperature` DECIMAL(3, 2) NULL,
`max_tokens` INT NULL,
`system_prompt` TEXT NULL,
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
`created_by_hq_account_id` BIGINT UNSIGNED NOT NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
INDEX `llm_api_config_created_by_hq_account_id_enabled_idx` (`created_by_hq_account_id`, `enabled`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `knowledge_base` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(128) NOT NULL,
`description` VARCHAR(512) NULL,
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
`created_by_hq_account_id` BIGINT UNSIGNED NOT NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
INDEX `knowledge_base_created_by_hq_account_id_enabled_idx` (`created_by_hq_account_id`, `enabled`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `knowledge_document` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`knowledge_base_id` BIGINT UNSIGNED NOT NULL,
`title` VARCHAR(256) NOT NULL,
`file_name` VARCHAR(256) NULL,
`file_url` VARCHAR(1024) NULL,
`mime_type` VARCHAR(128) NULL,
`size_bytes` INT NULL,
`content_text` LONGTEXT NULL,
`status` VARCHAR(16) NOT NULL DEFAULT 'READY',
`error_message` VARCHAR(512) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
INDEX `knowledge_document_knowledge_base_id_idx` (`knowledge_base_id`),
CONSTRAINT `knowledge_document_knowledge_base_id_fkey`
FOREIGN KEY (`knowledge_base_id`) REFERENCES `knowledge_base` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+80 -12
View File
@@ -352,24 +352,92 @@ model SystemConfig {
/// 企业微信智能机器人(HQ 可创建多实例,长连接)
model WecomBot {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
name String @db.VarChar(64)
role String @db.VarChar(32)
botId String @unique @map("bot_id") @db.VarChar(128)
secret String @db.VarChar(256)
avatarUrl String? @map("avatar_url") @db.VarChar(512)
welcome String? @db.VarChar(1024)
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
name String @db.VarChar(64)
role String @db.VarChar(32)
botId String @unique @map("bot_id") @db.VarChar(128)
secret String @db.VarChar(256)
avatarUrl String? @map("avatar_url") @db.VarChar(512)
welcome String? @db.VarChar(1024)
/// JSON 字符串数组,如 ["ticket.create","user.view_sms"]
permissions String @db.Text
enabled Boolean @default(true)
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
permissions String @db.Text
/// 未匹配指令时是否调用语言模型
aiEnabled Boolean @default(false) @map("ai_enabled")
llmConfigId BigInt? @map("llm_config_id") @db.UnsignedBigInt
knowledgeBaseId BigInt? @map("knowledge_base_id") @db.UnsignedBigInt
enabled Boolean @default(true)
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
llmConfig LlmApiConfig? @relation(fields: [llmConfigId], references: [id], onDelete: SetNull)
knowledgeBase KnowledgeBase? @relation(fields: [knowledgeBaseId], references: [id], onDelete: SetNull)
@@index([enabled, sortOrder])
@@index([llmConfigId])
@@index([knowledgeBaseId])
@@map("wecom_bot")
}
/// HQ 语言模型 API 配置(非超管仅可见/可开关自己创建的)
model LlmApiConfig {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
name String @db.VarChar(64)
provider String @db.VarChar(32)
baseUrl String @map("base_url") @db.VarChar(512)
apiKey String @map("api_key") @db.VarChar(512)
modelName String @map("model_name") @db.VarChar(128)
temperature Decimal? @db.Decimal(3, 2)
maxTokens Int? @map("max_tokens")
systemPrompt String? @map("system_prompt") @db.Text
enabled Boolean @default(true)
createdByHqAccountId BigInt @map("created_by_hq_account_id") @db.UnsignedBigInt
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
wecomBots WecomBot[]
@@index([createdByHqAccountId, enabled])
@@map("llm_api_config")
}
/// HQ 知识库
model KnowledgeBase {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
name String @db.VarChar(128)
description String? @db.VarChar(512)
enabled Boolean @default(true)
createdByHqAccountId BigInt @map("created_by_hq_account_id") @db.UnsignedBigInt
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
documents KnowledgeDocument[]
wecomBots WecomBot[]
@@index([createdByHqAccountId, enabled])
@@map("knowledge_base")
}
model KnowledgeDocument {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
knowledgeBaseId BigInt @map("knowledge_base_id") @db.UnsignedBigInt
title String @db.VarChar(256)
fileName String? @map("file_name") @db.VarChar(256)
fileUrl String? @map("file_url") @db.VarChar(1024)
mimeType String? @map("mime_type") @db.VarChar(128)
sizeBytes Int? @map("size_bytes")
contentText String? @map("content_text") @db.LongText
status String @default("READY") @db.VarChar(16)
errorMessage String? @map("error_message") @db.VarChar(512)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
knowledgeBase KnowledgeBase @relation(fields: [knowledgeBaseId], references: [id], onDelete: Cascade)
@@index([knowledgeBaseId])
@@map("knowledge_document")
}
model MockSmsCode {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
phone String @db.VarChar(20)