parent
ba83856173
commit
09359c3c46
|
@ -15,6 +15,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "esno ./src/index.ts",
|
"start": "esno ./src/index.ts",
|
||||||
|
"dev": "esno watch ./src/index.ts",
|
||||||
"prod": "esno ./build/index.js",
|
"prod": "esno ./build/index.js",
|
||||||
"build": "pnpm clean && tsup",
|
"build": "pnpm clean && tsup",
|
||||||
"clean": "rimraf build",
|
"clean": "rimraf build",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
import 'isomorphic-fetch'
|
import 'isomorphic-fetch'
|
||||||
import type { ChatGPTAPI, SendMessageOptions } from 'chatgpt'
|
import type { ChatGPTAPI, ChatMessage, SendMessageOptions } from 'chatgpt'
|
||||||
import { ChatGPTUnofficialProxyAPI } from 'chatgpt'
|
import { ChatGPTUnofficialProxyAPI } from 'chatgpt'
|
||||||
import { sendResponse } from './utils'
|
import { sendResponse } from './utils'
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ dotenv.config()
|
||||||
|
|
||||||
let apiModel: 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined
|
let apiModel: 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined
|
||||||
|
|
||||||
export interface ChatContext {
|
interface ChatContext {
|
||||||
conversationId?: string
|
conversationId?: string
|
||||||
parentMessageId?: string
|
parentMessageId?: string
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,34 @@ async function chatReply(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function chatReplyProcess(
|
||||||
|
message: string,
|
||||||
|
lastContext?: { conversationId?: string; parentMessageId?: string },
|
||||||
|
process?: (chat: ChatMessage) => void,
|
||||||
|
) {
|
||||||
|
if (!message)
|
||||||
|
return sendResponse({ type: 'Fail', message: 'Message is empty' })
|
||||||
|
|
||||||
|
try {
|
||||||
|
let options: SendMessageOptions = { timeoutMs }
|
||||||
|
|
||||||
|
if (lastContext)
|
||||||
|
options = { ...lastContext }
|
||||||
|
|
||||||
|
const response = await api.sendMessage(message, {
|
||||||
|
...options,
|
||||||
|
onProgress: (partialResponse) => {
|
||||||
|
process?.(partialResponse)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return sendResponse({ type: 'Success', data: response })
|
||||||
|
}
|
||||||
|
catch (error: any) {
|
||||||
|
return sendResponse({ type: 'Fail', message: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function chatConfig() {
|
async function chatConfig() {
|
||||||
return sendResponse({
|
return sendResponse({
|
||||||
type: 'Success',
|
type: 'Success',
|
||||||
|
@ -76,4 +104,6 @@ async function chatConfig() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export { chatReply, chatConfig }
|
export type { ChatContext, ChatMessage }
|
||||||
|
|
||||||
|
export { chatReply, chatReplyProcess, chatConfig }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import type { ChatContext } from './chatgpt'
|
import type { ChatContext, ChatMessage } from './chatgpt'
|
||||||
import { chatConfig, chatReply } from './chatgpt'
|
import { chatConfig, chatReply, chatReplyProcess } from './chatgpt'
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
@ -26,6 +26,23 @@ router.post('/chat', async (req, res) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.post('/chat-process', async (req, res) => {
|
||||||
|
res.setHeader('Content-type', 'application/octet-stream')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { prompt, options = {} } = req.body as { prompt: string; options?: ChatContext }
|
||||||
|
await chatReplyProcess(prompt, options, (chat: ChatMessage) => {
|
||||||
|
res.write(JSON.stringify(chat))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
res.write(JSON.stringify(error))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
router.post('/config', async (req, res) => {
|
router.post('/config', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const response = await chatConfig()
|
const response = await chatConfig()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { GenericAbortSignal } from 'axios'
|
import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'
|
||||||
import { post } from '@/utils/request'
|
import { post } from '@/utils/request'
|
||||||
|
|
||||||
export function fetchChatAPI<T = any>(
|
export function fetchChatAPI<T = any>(
|
||||||
|
@ -13,6 +13,21 @@ export function fetchChatAPI<T = any>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchChatAPIProcess<T = any>(
|
||||||
|
params: {
|
||||||
|
prompt: string
|
||||||
|
options?: { conversationId?: string; parentMessageId?: string }
|
||||||
|
signal?: GenericAbortSignal
|
||||||
|
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void },
|
||||||
|
) {
|
||||||
|
return post<T>({
|
||||||
|
url: '/chat-process',
|
||||||
|
data: { prompt: params.prompt, options: params.options },
|
||||||
|
signal: params.signal,
|
||||||
|
onDownloadProgress: params.onDownloadProgress,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function fetchChatConfig<T = any>() {
|
export function fetchChatConfig<T = any>() {
|
||||||
return post<T>({
|
return post<T>({
|
||||||
url: '/config',
|
url: '/config',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { AxiosResponse, GenericAbortSignal } from 'axios'
|
import type { AxiosProgressEvent, AxiosResponse, GenericAbortSignal } from 'axios'
|
||||||
import request from './axios'
|
import request from './axios'
|
||||||
|
|
||||||
export interface HttpOption {
|
export interface HttpOption {
|
||||||
|
@ -6,6 +6,7 @@ export interface HttpOption {
|
||||||
data?: any
|
data?: any
|
||||||
method?: string
|
method?: string
|
||||||
headers?: any
|
headers?: any
|
||||||
|
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void
|
||||||
signal?: GenericAbortSignal
|
signal?: GenericAbortSignal
|
||||||
beforeRequest?: () => void
|
beforeRequest?: () => void
|
||||||
afterRequest?: () => void
|
afterRequest?: () => void
|
||||||
|
@ -17,9 +18,11 @@ export interface Response<T = any> {
|
||||||
status: string
|
status: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function http<T = any>({ url, data, method, headers, signal, beforeRequest, afterRequest }: HttpOption) {
|
function http<T = any>(
|
||||||
|
{ url, data, method, headers, onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption,
|
||||||
|
) {
|
||||||
const successHandler = (res: AxiosResponse<Response<T>>) => {
|
const successHandler = (res: AxiosResponse<Response<T>>) => {
|
||||||
if (res.data.status === 'Success')
|
if (res.data.status === 'Success' || typeof res.data === 'string')
|
||||||
return res.data
|
return res.data
|
||||||
|
|
||||||
return Promise.reject(res.data)
|
return Promise.reject(res.data)
|
||||||
|
@ -37,17 +40,18 @@ function http<T = any>({ url, data, method, headers, signal, beforeRequest, afte
|
||||||
const params = Object.assign(typeof data === 'function' ? data() : data ?? {}, {})
|
const params = Object.assign(typeof data === 'function' ? data() : data ?? {}, {})
|
||||||
|
|
||||||
return method === 'GET'
|
return method === 'GET'
|
||||||
? request.get(url, { params, signal }).then(successHandler, failHandler)
|
? request.get(url, { params, signal, onDownloadProgress }).then(successHandler, failHandler)
|
||||||
: request.post(url, params, { headers, signal }).then(successHandler, failHandler)
|
: request.post(url, params, { headers, signal, onDownloadProgress }).then(successHandler, failHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get<T = any>(
|
export function get<T = any>(
|
||||||
{ url, data, method = 'GET', signal, beforeRequest, afterRequest }: HttpOption,
|
{ url, data, method = 'GET', onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption,
|
||||||
): Promise<Response<T>> {
|
): Promise<Response<T>> {
|
||||||
return http<T>({
|
return http<T>({
|
||||||
url,
|
url,
|
||||||
method,
|
method,
|
||||||
data,
|
data,
|
||||||
|
onDownloadProgress,
|
||||||
signal,
|
signal,
|
||||||
beforeRequest,
|
beforeRequest,
|
||||||
afterRequest,
|
afterRequest,
|
||||||
|
@ -55,13 +59,14 @@ export function get<T = any>(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post<T = any>(
|
export function post<T = any>(
|
||||||
{ url, data, method = 'POST', headers, signal, beforeRequest, afterRequest }: HttpOption,
|
{ url, data, method = 'POST', headers, onDownloadProgress, signal, beforeRequest, afterRequest }: HttpOption,
|
||||||
): Promise<Response<T>> {
|
): Promise<Response<T>> {
|
||||||
return http<T>({
|
return http<T>({
|
||||||
url,
|
url,
|
||||||
method,
|
method,
|
||||||
data,
|
data,
|
||||||
headers,
|
headers,
|
||||||
|
onDownloadProgress,
|
||||||
signal,
|
signal,
|
||||||
beforeRequest,
|
beforeRequest,
|
||||||
afterRequest,
|
afterRequest,
|
||||||
|
|
|
@ -15,7 +15,7 @@ const props = defineProps<Props>()
|
||||||
const wrapClass = computed(() => {
|
const wrapClass = computed(() => {
|
||||||
return [
|
return [
|
||||||
'text-wrap',
|
'text-wrap',
|
||||||
'p-2',
|
'p-3',
|
||||||
'min-w-[20px]',
|
'min-w-[20px]',
|
||||||
'rounded-md',
|
'rounded-md',
|
||||||
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
|
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
|
||||||
|
@ -51,6 +51,9 @@ const text = computed(() => {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
a {
|
||||||
|
color: #2d5cf6
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs {
|
.hljs {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { useChat } from './hooks/useChat'
|
||||||
import { HoverButton, SvgIcon } from '@/components/common'
|
import { HoverButton, SvgIcon } from '@/components/common'
|
||||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||||
import { useChatStore } from '@/store'
|
import { useChatStore } from '@/store'
|
||||||
import { fetchChatAPI } from '@/api'
|
import { fetchChatAPIProcess } from '@/api'
|
||||||
|
|
||||||
let controller = new AbortController()
|
let controller = new AbortController()
|
||||||
|
|
||||||
|
@ -80,22 +80,39 @@ async function onConversation() {
|
||||||
)
|
)
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
|
|
||||||
|
let offset = 0
|
||||||
try {
|
try {
|
||||||
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal)
|
await fetchChatAPIProcess<Chat.ConversationResponse>({
|
||||||
updateChat(
|
prompt: message,
|
||||||
+uuid,
|
options,
|
||||||
dataSources.value.length - 1,
|
signal: controller.signal,
|
||||||
{
|
onDownloadProgress: ({ event }) => {
|
||||||
dateTime: new Date().toLocaleString(),
|
const xhr = event.target
|
||||||
text: data.text ?? '',
|
const { responseText } = xhr
|
||||||
inversion: false,
|
const chunk = responseText.substring(offset)
|
||||||
error: false,
|
offset = responseText.length
|
||||||
loading: false,
|
try {
|
||||||
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
|
const data = JSON.parse(chunk)
|
||||||
requestOptions: { prompt: message, options: { ...options } },
|
updateChat(
|
||||||
|
+uuid,
|
||||||
|
dataSources.value.length - 1,
|
||||||
|
{
|
||||||
|
dateTime: new Date().toLocaleString(),
|
||||||
|
text: data.text ?? '',
|
||||||
|
inversion: false,
|
||||||
|
error: false,
|
||||||
|
loading: false,
|
||||||
|
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
|
||||||
|
requestOptions: { prompt: message, options: { ...options } },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
scrollToBottom()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
})
|
||||||
scrollToBottom()
|
|
||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
let errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
let errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
||||||
|
@ -119,6 +136,7 @@ async function onConversation() {
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
offset = 0
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,24 +172,41 @@ async function onRegenerate(index: number) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let offset = 0
|
||||||
try {
|
try {
|
||||||
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal)
|
await fetchChatAPIProcess<Chat.ConversationResponse>({
|
||||||
updateChat(
|
prompt: message,
|
||||||
+uuid,
|
options,
|
||||||
index,
|
signal: controller.signal,
|
||||||
{
|
onDownloadProgress: ({ event }) => {
|
||||||
dateTime: new Date().toLocaleString(),
|
const xhr = event.target
|
||||||
text: data.text ?? '',
|
const { responseText } = xhr
|
||||||
inversion: false,
|
const chunk = responseText.substring(offset)
|
||||||
error: false,
|
offset = responseText.length
|
||||||
loading: false,
|
try {
|
||||||
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
|
const data = JSON.parse(chunk)
|
||||||
requestOptions: { prompt: message, ...options },
|
updateChat(
|
||||||
|
+uuid,
|
||||||
|
index,
|
||||||
|
{
|
||||||
|
dateTime: new Date().toLocaleString(),
|
||||||
|
text: data.text ?? '',
|
||||||
|
inversion: false,
|
||||||
|
error: false,
|
||||||
|
loading: false,
|
||||||
|
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
|
||||||
|
requestOptions: { prompt: message, ...options },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
let errorMessage = 'Something went wrong, please try again later.'
|
let errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
||||||
|
|
||||||
if (error.message === 'canceled')
|
if (error.message === 'canceled')
|
||||||
errorMessage = 'Request canceled. Please try again.'
|
errorMessage = 'Request canceled. Please try again.'
|
||||||
|
@ -192,6 +227,7 @@ async function onRegenerate(index: number) {
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
offset = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue