Skip to content

Commit 4ec4814

Browse files
authored
feat: AI antispam (#2406)
* Init * Fix: add AiModule dependency to CommentModule and inject AiService in CommentService * Finallize
1 parent 2f4600b commit 4ec4814

File tree

5 files changed

+76
-3
lines changed

5 files changed

+76
-3
lines changed

apps/core/src/modules/comment/comment.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { forwardRef, Module } from '@nestjs/common'
22

33
import { GatewayModule } from '~/processors/gateway/gateway.module'
44

5+
import { AiModule } from '../ai/ai.module'
56
import { ReaderModule } from '../reader/reader.module'
67
import { ServerlessModule } from '../serverless/serverless.module'
78
import { UserModule } from '../user/user.module'
@@ -17,6 +18,7 @@ import { CommentService } from './comment.service'
1718
GatewayModule,
1819
forwardRef(() => ServerlessModule),
1920
forwardRef(() => ReaderModule),
21+
forwardRef(() => AiModule),
2022
],
2123
})
2224
export class CommentModule {}

apps/core/src/modules/comment/comment.service.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { InjectModel } from '~/transformers/model.transformer'
3333
import { scheduleManager } from '~/utils/schedule.util'
3434
import { getAvatar, hasChinese } from '~/utils/tool.util'
3535

36+
import { AiService } from '../ai/ai.service'
3637
import { ConfigsService } from '../configs/configs.service'
3738
import { ReaderModel } from '../reader/reader.model'
3839
import { ReaderService } from '../reader/reader.service'
@@ -62,6 +63,8 @@ export class CommentService implements OnModuleInit {
6263
private readonly mailService: EmailService,
6364

6465
private readonly configsService: ConfigsService,
66+
@Inject(forwardRef(() => AiService))
67+
private readonly aiService: AiService,
6568
@Inject(forwardRef(() => ServerlessService))
6669
private readonly serverlessService: ServerlessService,
6770
private readonly eventManager: EventManagerService,
@@ -146,6 +149,24 @@ export class CommentService implements OnModuleInit {
146149
return true
147150
}
148151

152+
if (commentOptions.aiReview) {
153+
const openai = await this.aiService.getOpenAiChain()
154+
const { aiReviewType, aiReviewThreshold } = commentOptions
155+
const runnable = openai
156+
157+
const prompt =
158+
aiReviewType === 'score'
159+
? 'Check the comment and return a risk score directly. Higher means more risky (1-10). Outputs should only be a number'
160+
: 'Check if the comment is spam or not. Outputs should be true or false(Lowercase)'
161+
162+
const result = (await runnable.invoke([`${prompt}:${doc.text}`]))
163+
.content
164+
165+
if (aiReviewType === 'score') {
166+
return (result as any) > aiReviewThreshold
167+
}
168+
return result === 'true'
169+
}
149170
return false
150171
})()
151172
if (res) {
@@ -525,9 +546,9 @@ export class CommentService implements OnModuleInit {
525546
const location =
526547
`${result.countryName || ''}${
527548
result.regionName && result.regionName !== result.cityName
528-
? `${result.regionName}`
549+
? String(result.regionName)
529550
: ''
530-
}${result.cityName ? `${result.cityName}` : ''}` || undefined
551+
}${result.cityName ? String(result.cityName) : ''}` || undefined
531552

532553
if (location) await this.commentModel.updateOne({ _id: id }, { location })
533554
}

apps/core/src/modules/configs/configs.default.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export const generateDefaultConfig: () => IConfig = () => ({
2727
},
2828
commentOptions: {
2929
antiSpam: false,
30+
aiReview: false,
31+
aiReviewType: 'binary',
32+
aiReviewThreshold: 5,
3033
disableComment: false,
3134
blockIps: [],
3235
disableNoChinese: false,

apps/core/src/modules/configs/configs.dto.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
IsOptional,
1111
IsString,
1212
IsUrl,
13+
Max,
1314
Min,
1415
ValidateNested,
1516
} from 'class-validator'
@@ -124,6 +125,41 @@ export class CommentOptionsDto {
124125
@JSONSchemaToggleField('反垃圾评论')
125126
antiSpam: boolean
126127

128+
@IsBoolean()
129+
@IsOptional()
130+
@JSONSchemaToggleField('开启 AI 审核')
131+
aiReview: boolean
132+
133+
@IsString()
134+
@IsOptional()
135+
@JSONSchemaPlainField('AI 审核方式', {
136+
description: '默认为是非,可以选择评分',
137+
'ui:options': {
138+
type: 'select',
139+
values: [
140+
{
141+
label: '是非',
142+
value: 'binary',
143+
},
144+
{
145+
label: '评分',
146+
value: 'score',
147+
},
148+
],
149+
},
150+
})
151+
aiReviewType: string
152+
153+
@IsInt()
154+
@Transform(({ value: val }) => Number.parseInt(val))
155+
@Min(1)
156+
@Max(10)
157+
@IsOptional()
158+
@JSONSchemaNumberField('AI 审核阈值', {
159+
description: '分数大于多少时会被归类为垃圾评论, 范围为 1-10, 默认为 5',
160+
})
161+
aiReviewThreshold: number
162+
127163
@IsBoolean()
128164
@IsOptional()
129165
@JSONSchemaToggleField('全站禁止评论', { description: '敏感时期专用' })

apps/core/src/modules/configs/configs.service.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { RedisKeys } from '~/constants/cache.constant'
1313
import { EventBusEvents } from '~/constants/event-bus.constant'
1414
import { VALIDATION_PIPE_INJECTION } from '~/constants/system.constant'
1515
import { EventManagerService } from '~/processors/helper/helper.event.service'
16-
import { CacheService } from '~/processors/redis/cache.service'
1716
import { RedisService } from '~/processors/redis/redis.service'
1817
import { SubPubBridgeService } from '~/processors/redis/subpub.service'
1918
import { InjectModel } from '~/transformers/model.transformer'
@@ -192,6 +191,18 @@ export class ConfigsService {
192191
if (!dto) {
193192
throw new BadRequestException('设置不存在')
194193
}
194+
195+
// 如果是评论设置,并且尝试启用 AI 审核,就检查 AI 配置
196+
if (key === 'commentOptions' && (value as any).aiReview === true) {
197+
const aiConfig = await this.get('ai')
198+
const { openAiEndpoint, openAiKey } = aiConfig
199+
if (!openAiEndpoint || !openAiKey) {
200+
throw new BadRequestException(
201+
'OpenAI API Key/Endpoint 未设置,无法启用 AI 评论审核',
202+
)
203+
}
204+
}
205+
195206
const instanceValue = this.validWithDto(dto, value)
196207

197208
encryptObject(instanceValue)

0 commit comments

Comments
 (0)