Skip to content

Commit 89951ef

Browse files
author
Kerwin
committed
feat: custom sensitive words
1 parent a52966a commit 89951ef

File tree

11 files changed

+80
-29
lines changed

11 files changed

+80
-29
lines changed

service/src/chatgpt/index.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,6 @@ async function chatReplyProcess(options: RequestOptions) {
9090
const model = isNotEmptyString(config.apiModel) ? config.apiModel : 'gpt-3.5-turbo'
9191
const { message, lastContext, process, systemMessage, temperature, top_p } = options
9292

93-
if ((config.auditConfig?.enabled ?? false) && !await auditText(config.auditConfig, message))
94-
return sendResponse({ type: 'Fail', message: '含有敏感词 | Contains sensitive words' })
95-
9693
try {
9794
const timeoutMs = (await getCacheConfig()).timeoutMs
9895
let options: SendMessageOptions = { timeoutMs }
@@ -135,11 +132,19 @@ export function initAuditService(audit: AuditConfig) {
135132
auditService = new Service(audit.options)
136133
}
137134

138-
async function auditText(audit: AuditConfig, text: string): Promise<boolean> {
139-
if (!auditService)
140-
initAuditService(audit)
141-
142-
return await auditService.audit(text)
135+
async function containsSensitiveWords(audit: AuditConfig, text: string): Promise<boolean> {
136+
if (audit.customizeEnabled && isNotEmptyString(audit.sensitiveWords)) {
137+
const textLower = text.toLowerCase()
138+
const notSafe = audit.sensitiveWords.split('\n').filter(d => textLower.includes(d.trim().toLowerCase())).length > 0
139+
if (notSafe)
140+
return true
141+
}
142+
if (audit.enabled) {
143+
if (!auditService)
144+
initAuditService(audit)
145+
return await auditService.containsSensitiveWords(text)
146+
}
147+
return false
143148
}
144149
let cachedBanlance: number | undefined
145150
let cacheExpiration = 0
@@ -193,7 +198,7 @@ async function fetchBalance() {
193198

194199
// 计算剩余额度
195200
cachedBanlance = totalAmount - totalUsage
196-
cacheExpiration = now + 10 * 60 * 1000
201+
cacheExpiration = now + 60 * 60 * 1000
197202

198203
return Promise.resolve(cachedBanlance.toFixed(3))
199204
}
@@ -254,4 +259,4 @@ initApi()
254259

255260
export type { ChatContext, ChatMessage }
256261

257-
export { chatReplyProcess, chatConfig, currentModel, auditText }
262+
export { chatReplyProcess, chatConfig, currentModel, containsSensitiveWords }

service/src/index.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import jwt from 'jsonwebtoken'
33
import * as dotenv from 'dotenv'
44
import type { RequestProps } from './types'
55
import type { ChatContext, ChatMessage } from './chatgpt'
6-
import { auditText, chatConfig, chatReplyProcess, currentModel, initApi, initAuditService } from './chatgpt'
6+
import { chatConfig, chatReplyProcess, containsSensitiveWords, currentModel, initApi, initAuditService } from './chatgpt'
77
import { auth } from './middleware/auth'
88
import { clearConfigCache, getCacheConfig, getOriginConfig } from './storage/config'
99
import type { AuditConfig, ChatInfo, ChatOptions, Config, MailConfig, SiteConfig, UsageResponse, UserInfo } from './storage/model'
@@ -277,6 +277,16 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
277277
let result
278278
let message: ChatInfo
279279
try {
280+
const config = await getCacheConfig()
281+
if (config.auditConfig.enabled || config.auditConfig.customizeEnabled) {
282+
const userId = req.headers.userId.toString()
283+
const user = await getUserById(userId)
284+
if (user.email.toLowerCase() !== process.env.ROOT_USER && await containsSensitiveWords(config.auditConfig, prompt)) {
285+
res.send({ status: 'Fail', message: '含有敏感词 | Contains sensitive words', data: null })
286+
return
287+
}
288+
}
289+
280290
message = regenerate
281291
? await getChat(roomId, uuid)
282292
: await insertChat(uuid, prompt, roomId, options as ChatOptions)
@@ -602,7 +612,8 @@ router.post('/setting-audit', rootAuth, async (req, res) => {
602612
thisConfig.auditConfig = config
603613
const result = await updateConfig(thisConfig)
604614
clearConfigCache()
605-
initAuditService(config)
615+
if (config.enabled)
616+
initAuditService(config)
606617
res.send({ status: 'Success', message: '操作成功 | Successfully', data: result.auditConfig })
607618
}
608619
catch (error) {
@@ -614,10 +625,12 @@ router.post('/audit-test', rootAuth, async (req, res) => {
614625
try {
615626
const { audit, text } = req.body as { audit: AuditConfig; text: string }
616627
const config = await getCacheConfig()
617-
initAuditService(audit)
618-
const result = await auditText(audit, text)
619-
initAuditService(config.auditConfig)
620-
res.send({ status: 'Success', message: !result ? '含敏感词 | Contains sensitive words' : '不含敏感词 | Does not contain sensitive words.', data: null })
628+
if (audit.enabled)
629+
initAuditService(audit)
630+
const result = await containsSensitiveWords(audit, text)
631+
if (audit.enabled)
632+
initAuditService(config.auditConfig)
633+
res.send({ status: 'Success', message: result ? '含敏感词 | Contains sensitive words' : '不含敏感词 | Does not contain sensitive words.', data: null })
621634
}
622635
catch (error) {
623636
res.send({ status: 'Fail', message: error.message, data: null })

service/src/storage/model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ export class AuditConfig {
162162
public provider: TextAuditServiceProvider,
163163
public options: TextAuditServiceOptions,
164164
public textType: TextAudioType,
165+
public customizeEnabled: boolean,
166+
public sensitiveWords: string,
165167
) { }
166168
}
167169

service/src/utils/textAudit.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface TextAuditServiceOptions {
77
}
88

99
export interface TextAuditService {
10-
audit(text: string): Promise<boolean>
10+
containsSensitiveWords(text: string): Promise<boolean>
1111
}
1212

1313
/**
@@ -19,23 +19,23 @@ export class BaiduTextAuditService implements TextAuditService {
1919

2020
constructor(private options: TextAuditServiceOptions) { }
2121

22-
async audit(text: string): Promise<boolean> {
22+
async containsSensitiveWords(text: string): Promise<boolean> {
2323
if (!await this.refreshAccessToken())
24-
return
24+
throw new Error('Access Token Error')
2525
const url = `https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token=${this.accessToken}`
2626
let headers: {
2727
'Content-Type': 'application/x-www-form-urlencoded'
2828
'Accept': 'application/json'
2929
}
3030
const response = await fetch(url, { headers, method: 'POST', body: `text=${encodeURIComponent(text)}` })
31-
const data = await response.json() as { conclusionType: number; data: any }
31+
const data = await response.json() as { conclusionType: number; data: any; error_msg: string }
3232

3333
if (data.error_msg)
3434
throw new Error(data.error_msg)
3535

3636
// 审核结果类型,可取值1、2、3、4,分别代表1:合规,2:不合规,3:疑似,4:审核失败
3737
if (data.conclusionType === 1)
38-
return true
38+
return false
3939

4040
// https://ai.baidu.com/ai-doc/ANTIPORN/Nk3h6xbb2#%E7%BB%86%E5%88%86%E6%A0%87%E7%AD%BE%E5%AF%B9%E7%85%A7%E8%A1%A8
4141

@@ -46,9 +46,9 @@ export class BaiduTextAuditService implements TextAuditService {
4646
const str = JSON.stringify(data)
4747
for (const l of this.options.label.split(',')) {
4848
if (str.indexOf(l))
49-
return false
49+
return true
5050
}
51-
return true
51+
return false
5252
}
5353

5454
async refreshAccessToken() {

src/components/common/Setting/Audit.vue

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,27 @@ onMounted(() => {
6464
<NSpin :show="loading">
6565
<div class="p-4 space-y-5 min-h-[200px]">
6666
<div class="space-y-6">
67+
<div class="flex items-center space-x-4">
68+
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.auditCustomizeEnabled') }}</span>
69+
<div class="flex-1">
70+
<NSwitch
71+
:round="false" :value="config && config.customizeEnabled"
72+
@update:value="(val) => { if (config) config.customizeEnabled = val }"
73+
/>
74+
</div>
75+
</div>
76+
<div v-if="config && config.customizeEnabled" class="flex items-center space-x-4">
77+
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.auditCustomizeWords') }}</span>
78+
<div class="flex-1">
79+
<NInput
80+
:value="config && config.sensitiveWords"
81+
placeholder="One word per line"
82+
type="textarea"
83+
:autosize="{ minRows: 1, maxRows: 4 }"
84+
@input="(val) => { if (config) config.sensitiveWords = val }"
85+
/>
86+
</div>
87+
</div>
6788
<div class="flex items-center space-x-4">
6889
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.auditEnabled') }}</span>
6990
<div class="flex-1">
@@ -117,7 +138,7 @@ onMounted(() => {
117138
<a target="_blank" href="https://ai.baidu.com/ai-doc/ANTIPORN/Nk3h6xbb2#%E7%BB%86%E5%88%86%E6%A0%87%E7%AD%BE%E5%AF%B9%E7%85%A7%E8%A1%A8">{{ $t('setting.auditBaiduLabelLink') }}</a>
118139
</p>
119140
</div>
120-
<div v-if="config && config.enabled" class="flex items-center space-x-4">
141+
<div v-if="config && (config.enabled || config.customizeEnabled)" class="flex items-center space-x-4">
121142
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.auditTest') }}</span>
122143
<div class="flex-1">
123144
<NInput

src/components/common/Setting/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const show = computed({
4040
</script>
4141

4242
<template>
43-
<NModal v-model:show="show" :auto-focus="false" preset="card" style="width: 95%; max-width: 640px">
43+
<NModal v-model:show="show" :auto-focus="false" preset="card" style="width: 95%; max-width: 1024px">
4444
<div>
4545
<NTabs v-model:value="active" type="line" animated>
4646
<NTabPane name="General" tab="General">

src/components/common/Setting/model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,6 @@ export class AuditConfig {
5050
provider?: TextAuditServiceProvider
5151
options?: TextAuditServiceOptions
5252
textType?: TextAudioType
53+
customizeEnabled?: boolean
54+
sensitiveWords?: string
5355
}

src/locales/en-US.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,16 @@ export default {
103103
loginSalt: 'Login Salt',
104104
loginSaltTip: 'Changes will invalidate all logged in',
105105
monthlyUsage: 'Monthly Usage',
106-
auditEnabled: 'Audit Enabled',
106+
auditEnabled: 'Third Party',
107107
auditProvider: 'Provider',
108108
auditApiKey: 'Api Key',
109109
auditApiSecret: 'Api Secret',
110110
auditTest: 'Test Text',
111111
auditBaiduLabel: 'Label',
112112
auditBaiduLabelTip: 'English comma separated, If empty, only politics.',
113113
auditBaiduLabelLink: 'Goto Label Detail',
114+
auditCustomizeEnabled: 'Customize',
115+
auditCustomizeWords: 'Sensitive Words',
114116
},
115117
store: {
116118
siderButton: 'Prompt Store',

src/locales/ko-KR.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,16 @@ export default {
101101
loginSalt: '로그인 정보 암호화 Salt',
102102
loginSaltTip: '변경하면 모든사용자의 로그인이 풀립니다.',
103103
monthlyUsage: '월간 사용량',
104-
auditEnabled: '승인 상태',
104+
auditEnabled: '타사',
105105
auditProvider: '공급자',
106106
auditApiKey: 'Api Key',
107107
auditApiSecret: 'Api Secret',
108108
auditTest: '테스트 텍스트',
109109
auditBaiduLabel: 'Label',
110110
auditBaiduLabelTip: '영어 쉼표로 구분, If empty, only politics.',
111111
auditBaiduLabelLink: '레이블 세부 정보로 이동',
112+
auditCustomizeEnabled: '맞춤화하다',
113+
auditCustomizeWords: '단어 맞춤설정',
112114
},
113115
store: {
114116
siderButton: '프롬프트 스토어',

src/locales/zh-CN.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,16 @@ export default {
103103
loginSalt: '登录混淆盐',
104104
loginSaltTip: '变更会导致所有已登录失效',
105105
monthlyUsage: '本月使用量',
106-
auditEnabled: '审核状态',
106+
auditEnabled: '第三方',
107107
auditProvider: '提供商',
108108
auditApiKey: 'Api Key',
109109
auditApiSecret: 'Api Secret',
110110
auditTest: '测试文本',
111111
auditBaiduLabel: '二级分类',
112112
auditBaiduLabelTip: '英文逗号分隔, 如果空, 仅政治',
113113
auditBaiduLabelLink: '查看细分类型',
114+
auditCustomizeEnabled: '自定义',
115+
auditCustomizeWords: '敏感词',
114116
},
115117
store: {
116118
siderButton: '提示词商店',

0 commit comments

Comments
 (0)