Skip to content

Commit 9f2f5c3

Browse files
author
Kerwin
committed
feat: support real stop response (Close #157)
fix: access token, after the first chat, refreshing the page makes it impossible to continue talking
1 parent b826368 commit 9f2f5c3

File tree

7 files changed

+64
-8
lines changed

7 files changed

+64
-8
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chatgpt-web",
3-
"version": "2.13.0",
3+
"version": "2.13.3",
44
"private": false,
55
"description": "ChatGPT Web",
66
"author": "ChenZhaoYu <[email protected]>",

service/src/chatgpt/index.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,12 @@ export async function initApi(key: KeyConfig, chatModel: CHATMODEL) {
8181
return new ChatGPTUnofficialProxyAPI({ ...options })
8282
}
8383
}
84-
84+
const processThreads: { userId: string; abort: AbortController; messageId: string }[] = []
8585
async function chatReplyProcess(options: RequestOptions) {
8686
const model = options.chatModel
8787
const key = options.key
88+
const userId = options.userId
89+
const messageId = options.messageId
8890
if (key == null || key === undefined)
8991
throw new Error('没有可用的配置。请再试一次 | No available configuration. Please try again.')
9092

@@ -107,6 +109,10 @@ async function chatReplyProcess(options: RequestOptions) {
107109
options = { ...lastContext }
108110
}
109111
const api = await initApi(key, model)
112+
113+
const abort = new AbortController()
114+
options.abortSignal = abort.signal
115+
processThreads.push({ userId, abort, messageId })
110116
const response = await api.sendMessage(message, {
111117
...options,
112118
onProgress: (partialResponse) => {
@@ -125,9 +131,22 @@ async function chatReplyProcess(options: RequestOptions) {
125131
}
126132
finally {
127133
releaseApiKey(key)
134+
const index = processThreads.findIndex(d => d.userId === userId)
135+
if (index > -1)
136+
processThreads.splice(index, 1)
128137
}
129138
}
130139

140+
export function abortChatProcess(userId: string) {
141+
const index = processThreads.findIndex(d => d.userId === userId)
142+
if (index <= -1)
143+
return
144+
const messageId = processThreads[index].messageId
145+
processThreads[index].abort.abort()
146+
processThreads.splice(index, 1)
147+
return messageId
148+
}
149+
131150
export function initAuditService(audit: AuditConfig) {
132151
if (!audit || !audit.options || !audit.options.apiKey || !audit.options.apiSecret)
133152
return
@@ -328,10 +347,8 @@ async function randomKeyConfig(keys: KeyConfig[]): Promise<KeyConfig | null> {
328347
const thisLockedKey = _lockedKeys.filter(d => d.key === thisKey.key)
329348
if (thisLockedKey.length <= 0)
330349
_lockedKeys.push({ key: thisKey.key, count: 1 })
331-
332350
else
333351
thisLockedKey[0].count++
334-
335352
return thisKey
336353
}
337354

service/src/chatgpt/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export interface RequestOptions {
1010
top_p?: number
1111
chatModel: CHATMODEL
1212
key: KeyConfig
13+
userId: string
14+
messageId: string
1315
}
1416

1517
export interface BalanceResponse {

service/src/index.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as dotenv from 'dotenv'
44
import { ObjectId } from 'mongodb'
55
import type { RequestProps } from './types'
66
import type { ChatContext, ChatMessage } from './chatgpt'
7-
import { chatConfig, chatReplyProcess, containsSensitiveWords, getRandomApiKey, initAuditService } from './chatgpt'
7+
import { abortChatProcess, chatConfig, chatReplyProcess, containsSensitiveWords, getRandomApiKey, initAuditService } from './chatgpt'
88
import { auth, getUserId } from './middleware/auth'
99
import { clearApiKeyCache, clearConfigCache, getApiKeys, getCacheApiKeys, getCacheConfig, getOriginConfig } from './storage/config'
1010
import type { AuditConfig, CHATMODEL, ChatInfo, ChatOptions, Config, KeyConfig, MailConfig, SiteConfig, UsageResponse, UserInfo } from './storage/model'
@@ -431,6 +431,8 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
431431
top_p,
432432
chatModel: user.config.chatModel,
433433
key: await getRandomApiKey(user, user.config.chatModel),
434+
userId,
435+
messageId: message._id.toString(),
434436
})
435437
// return the whole response including usage
436438
res.write(`\n${JSON.stringify(result.data)}`)
@@ -457,13 +459,15 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
457459
await updateChat(message._id as unknown as string,
458460
result.data.text,
459461
result.data.id,
462+
result.data.conversationId,
460463
result.data.detail?.usage as UsageResponse,
461464
previousResponse as [])
462465
}
463466
else {
464467
await updateChat(message._id as unknown as string,
465468
result.data.text,
466469
result.data.id,
470+
result.data.conversationId,
467471
result.data.detail?.usage as UsageResponse)
468472
}
469473

@@ -481,6 +485,23 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
481485
}
482486
})
483487

488+
router.post('/chat-abort', [auth, limiter], async (req, res) => {
489+
try {
490+
const userId = req.headers.userId.toString()
491+
const { text, messageId, conversationId } = req.body as { text: string; messageId: string; conversationId: string }
492+
const msgId = await abortChatProcess(userId)
493+
await updateChat(msgId,
494+
text,
495+
messageId,
496+
conversationId,
497+
null)
498+
res.send({ status: 'Success', message: 'OK', data: null })
499+
}
500+
catch (error) {
501+
res.send({ status: 'Fail', message: '重置邮件已发送 | Reset email has been sent', data: null })
502+
}
503+
})
504+
484505
router.post('/user-register', async (req, res) => {
485506
try {
486507
const { username, password } = req.body as { username: string; password: string }

service/src/storage/mongo.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@ export async function getChatByMessageId(messageId: string) {
3939
return await chatCol.findOne({ 'options.messageId': messageId }) as ChatInfo
4040
}
4141

42-
export async function updateChat(chatId: string, response: string, messageId: string, usage: UsageResponse, previousResponse?: []) {
42+
export async function updateChat(chatId: string, response: string, messageId: string, conversationId: string, usage: UsageResponse, previousResponse?: []) {
4343
const query = { _id: new ObjectId(chatId) }
4444
const update = {
4545
$set: {
4646
'response': response,
4747
'options.messageId': messageId,
48+
'options.conversationId': conversationId,
4849
'options.prompt_tokens': usage?.prompt_tokens,
4950
'options.completion_tokens': usage?.completion_tokens,
5051
'options.total_tokens': usage?.total_tokens,

src/api/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ export function fetchChatAPIProcess<T = any>(
5959
})
6060
}
6161

62+
export function fetchChatStopResponding<T = any>(text: string, messageId: string, conversationId: string) {
63+
return post<T>({
64+
url: '/chat-abort',
65+
data: { text, messageId, conversationId },
66+
})
67+
}
68+
6269
export function fetchChatResponseoHistory<T = any>(roomId: number, uuid: number, index: number) {
6370
return get<T>({
6471
url: '/chat-response-history',

src/views/chat/index.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import HeaderComponent from './components/Header/index.vue'
1313
import { HoverButton, SvgIcon } from '@/components/common'
1414
import { useBasicLayout } from '@/hooks/useBasicLayout'
1515
import { useAuthStore, useChatStore, usePromptStore, useUserStore } from '@/store'
16-
import { fetchChatAPIProcess, fetchChatResponseoHistory, fetchUpdateUserChatModel } from '@/api'
16+
import { fetchChatAPIProcess, fetchChatResponseoHistory, fetchChatStopResponding, fetchUpdateUserChatModel } from '@/api'
1717
import { t } from '@/locales'
1818
import { debounce } from '@/utils/functions/debounce'
1919
import IconPrompt from '@/icons/Prompt.vue'
@@ -22,6 +22,7 @@ import type { CHATMODEL } from '@/components/common/Setting/model'
2222
const Prompt = defineAsyncComponent(() => import('@/components/common/Setting/Prompt.vue'))
2323
2424
let controller = new AbortController()
25+
let lastChatInfo: any = {}
2526
2627
const openLongReply = import.meta.env.VITE_GLOB_OPEN_LONG_REPLY === 'true'
2728
@@ -138,6 +139,7 @@ async function onConversation() {
138139
chunk = responseText.substring(lastIndex)
139140
try {
140141
const data = JSON.parse(chunk)
142+
lastChatInfo = data
141143
const usage = (data.detail && data.detail.usage)
142144
? {
143145
completion_tokens: data.detail.usage.completion_tokens || null,
@@ -284,6 +286,7 @@ async function onRegenerate(index: number) {
284286
chunk = responseText.substring(lastIndex)
285287
try {
286288
const data = JSON.parse(chunk)
289+
lastChatInfo = data
287290
const usage = (data.detail && data.detail.usage)
288291
? {
289292
completion_tokens: data.detail.usage.completion_tokens || null,
@@ -464,10 +467,11 @@ function handleEnter(event: KeyboardEvent) {
464467
}
465468
}
466469
467-
function handleStop() {
470+
async function handleStop() {
468471
if (loading.value) {
469472
controller.abort()
470473
loading.value = false
474+
await fetchChatStopResponding(lastChatInfo.text, lastChatInfo.id, lastChatInfo.conversationId)
471475
}
472476
}
473477
@@ -581,6 +585,10 @@ async function handleSyncChatModel(chatModel: CHATMODEL) {
581585
onMounted(() => {
582586
firstLoading.value = true
583587
handleSyncChat()
588+
589+
const chatModels = authStore.session?.chatModels
590+
if (chatModels != null && chatModels.filter(d => d.value === userStore.userInfo.config.chatModel).length <= 0)
591+
ms.error('你选择的模型已不存在,请重新选择 | The selected model not exists, please choose again.', { duration: 7000 })
584592
})
585593
586594
watch(() => chatStore.active, (newVal, oldVal) => {

0 commit comments

Comments
 (0)