fix: 修复部份 `bug` (#131)
* fix: 主题模式图标不一致的问题[#125] * fix: 修复样式问题[#123][#126] * perf: 优化代码和添加类型
This commit is contained in:
parent
628187f5c3
commit
1406292405
|
@ -1,22 +1,18 @@
|
||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
import 'isomorphic-fetch'
|
import 'isomorphic-fetch'
|
||||||
import type { ChatGPTAPI, ChatMessage, SendMessageOptions } from 'chatgpt'
|
import type { ChatMessage, SendMessageOptions } from 'chatgpt'
|
||||||
import { ChatGPTUnofficialProxyAPI } from 'chatgpt'
|
import { ChatGPTAPI, ChatGPTUnofficialProxyAPI } from 'chatgpt'
|
||||||
import { SocksProxyAgent } from 'socks-proxy-agent'
|
import { SocksProxyAgent } from 'socks-proxy-agent'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { sendResponse } from './utils'
|
import { sendResponse } from './utils'
|
||||||
|
import type { ApiModel, ChatContext, ChatGPTAPIOptions, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from './types'
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
let apiModel: 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined
|
|
||||||
|
|
||||||
interface ChatContext {
|
|
||||||
conversationId?: string
|
|
||||||
parentMessageId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeoutMs: number = !isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 30 * 1000
|
const timeoutMs: number = !isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 30 * 1000
|
||||||
|
|
||||||
|
let apiModel: ApiModel
|
||||||
|
|
||||||
if (!process.env.OPENAI_API_KEY && !process.env.OPENAI_ACCESS_TOKEN)
|
if (!process.env.OPENAI_API_KEY && !process.env.OPENAI_ACCESS_TOKEN)
|
||||||
throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable')
|
throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable')
|
||||||
|
|
||||||
|
@ -25,15 +21,20 @@ let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
|
||||||
// To use ESM in CommonJS, you can use a dynamic import
|
// To use ESM in CommonJS, you can use a dynamic import
|
||||||
(async () => {
|
(async () => {
|
||||||
// More Info: https://github.com/transitive-bullshit/chatgpt-api
|
// More Info: https://github.com/transitive-bullshit/chatgpt-api
|
||||||
const { ChatGPTAPI } = await import('chatgpt')
|
|
||||||
|
|
||||||
if (process.env.OPENAI_API_KEY) {
|
if (process.env.OPENAI_API_KEY) {
|
||||||
api = new ChatGPTAPI({ apiKey: process.env.OPENAI_API_KEY })
|
const options: ChatGPTAPIOptions = {
|
||||||
|
apiKey: process.env.OPENAI_API_KEY,
|
||||||
|
debug: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
api = new ChatGPTAPI({ ...options })
|
||||||
apiModel = 'ChatGPTAPI'
|
apiModel = 'ChatGPTAPI'
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const options = {
|
const options: ChatGPTUnofficialProxyAPIOptions = {
|
||||||
debug: true,
|
accessToken: process.env.OPENAI_ACCESS_TOKEN,
|
||||||
|
debug: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT) {
|
if (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT) {
|
||||||
|
@ -41,16 +42,13 @@ let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
|
||||||
hostname: process.env.SOCKS_PROXY_HOST,
|
hostname: process.env.SOCKS_PROXY_HOST,
|
||||||
port: process.env.SOCKS_PROXY_PORT,
|
port: process.env.SOCKS_PROXY_PORT,
|
||||||
})
|
})
|
||||||
globalThis.console.log(`Using socks proxy: ${process.env.SOCKS_PROXY_HOST}:${process.env.SOCKS_PROXY_PORT}`)
|
|
||||||
options.fetch = (url, options) => {
|
options.fetch = (url, options) => {
|
||||||
return fetch(url, { agent, ...options })
|
return fetch(url, { agent, ...options })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.API_REVERSE_PROXY) {
|
if (process.env.API_REVERSE_PROXY)
|
||||||
options.apiReverseProxyUrl = process.env.API_REVERSE_PROXY
|
options.apiReverseProxyUrl = process.env.API_REVERSE_PROXY
|
||||||
globalThis.console.log(`Using api reverse proxy: ${process.env.API_REVERSE_PROXY}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
api = new ChatGPTUnofficialProxyAPI({
|
api = new ChatGPTUnofficialProxyAPI({
|
||||||
accessToken: process.env.OPENAI_ACCESS_TOKEN,
|
accessToken: process.env.OPENAI_ACCESS_TOKEN,
|
||||||
|
@ -82,7 +80,6 @@ async function chatReply(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 实验性质的函数,用于处理聊天过程中的中间结果 */
|
|
||||||
async function chatReplyProcess(
|
async function chatReplyProcess(
|
||||||
message: string,
|
message: string,
|
||||||
lastContext?: { conversationId?: string; parentMessageId?: string },
|
lastContext?: { conversationId?: string; parentMessageId?: string },
|
||||||
|
@ -119,7 +116,7 @@ async function chatConfig() {
|
||||||
reverseProxy: process.env.API_REVERSE_PROXY,
|
reverseProxy: process.env.API_REVERSE_PROXY,
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
socksProxy: (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT) ? (`${process.env.SOCKS_PROXY_HOST}:${process.env.SOCKS_PROXY_PORT}`) : '-',
|
socksProxy: (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT) ? (`${process.env.SOCKS_PROXY_HOST}:${process.env.SOCKS_PROXY_PORT}`) : '-',
|
||||||
},
|
} as ModelConfig,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ router.post('/chat', async (req, res) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 实验性质的函数,用于处理聊天过程中的中间结果 */
|
|
||||||
router.post('/chat-process', async (req, res) => {
|
router.post('/chat-process', async (req, res) => {
|
||||||
res.setHeader('Content-type', 'application/octet-stream')
|
res.setHeader('Content-type', 'application/octet-stream')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import type { FetchFn, openai } from 'chatgpt'
|
||||||
|
|
||||||
|
export interface ChatContext {
|
||||||
|
conversationId?: string
|
||||||
|
parentMessageId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatGPTAPIOptions {
|
||||||
|
apiKey: string
|
||||||
|
debug?: boolean
|
||||||
|
completionParams?: Partial<openai.CompletionParams>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatGPTUnofficialProxyAPIOptions {
|
||||||
|
accessToken: string
|
||||||
|
apiReverseProxyUrl?: string
|
||||||
|
model?: string
|
||||||
|
debug?: boolean
|
||||||
|
headers?: Record<string, string>
|
||||||
|
fetch?: FetchFn
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelConfig {
|
||||||
|
apiModel?: ApiModel
|
||||||
|
reverseProxy?: string
|
||||||
|
timeoutMs?: number
|
||||||
|
socksProxy?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiModel = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined
|
|
@ -19,7 +19,6 @@ export function fetchChatConfig<T = any>() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 实验性质的函数,用于处理聊天过程中的中间结果 */
|
|
||||||
export function fetchChatAPIProcess<T = any>(
|
export function fetchChatAPIProcess<T = any>(
|
||||||
params: {
|
params: {
|
||||||
prompt: string
|
prompt: string
|
||||||
|
|
|
@ -2,15 +2,7 @@
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
|
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||||
const props = defineProps<Props>()
|
|
||||||
|
|
||||||
marked.setOptions({
|
|
||||||
renderer: new marked.Renderer(),
|
|
||||||
highlight(code) {
|
|
||||||
return hljs.highlightAuto(code).value
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
inversion?: boolean
|
inversion?: boolean
|
||||||
|
@ -19,12 +11,23 @@ interface Props {
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { isMobile } = useBasicLayout()
|
||||||
|
|
||||||
|
marked.setOptions({
|
||||||
|
renderer: new marked.Renderer(),
|
||||||
|
highlight(code) {
|
||||||
|
return hljs.highlightAuto(code).value
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const wrapClass = computed(() => {
|
const wrapClass = computed(() => {
|
||||||
return [
|
return [
|
||||||
'text-wrap',
|
'text-wrap',
|
||||||
'p-3',
|
|
||||||
'min-w-[20px]',
|
'min-w-[20px]',
|
||||||
'rounded-md',
|
'rounded-md',
|
||||||
|
isMobile.value ? 'p-2' : 'p-3',
|
||||||
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
|
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
|
||||||
props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
|
props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
|
||||||
{ 'text-red-500': props.error },
|
{ 'text-red-500': props.error },
|
||||||
|
|
|
@ -13,7 +13,6 @@ interface Props {
|
||||||
|
|
||||||
interface Emit {
|
interface Emit {
|
||||||
(ev: 'regenerate'): void
|
(ev: 'regenerate'): void
|
||||||
(ev: 'copy'): void
|
|
||||||
(ev: 'delete'): void
|
(ev: 'delete'): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,15 +32,15 @@ function handleRegenerate() {
|
||||||
<template>
|
<template>
|
||||||
<div class="flex w-full mb-6 overflow-hidden" :class="[{ 'flex-row-reverse': inversion }]">
|
<div class="flex w-full mb-6 overflow-hidden" :class="[{ 'flex-row-reverse': inversion }]">
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-center rounded-full overflow-hidden flex-shrink-0 w-[32px] h-[32px]"
|
class="flex items-center justify-center flex-shrink-0 h-8 overflow-hidden rounded-full basis-8"
|
||||||
:class="[inversion ? 'ml-3' : 'mr-3']"
|
:class="[inversion ? 'ml-2' : 'mr-2']"
|
||||||
>
|
>
|
||||||
<AvatarComponent :image="inversion" />
|
<AvatarComponent :image="inversion" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col flex-1 text-sm" :class="[inversion ? 'items-end' : 'items-start']">
|
<div class="overflow-hidden text-sm " :class="[inversion ? 'items-end' : 'items-start']">
|
||||||
<span class="text-xs text-[#b4bbc4]">
|
<p class="text-xs text-[#b4bbc4]" :class="[inversion ? 'text-right' : 'text-left']">
|
||||||
{{ dateTime }}
|
{{ dateTime }}
|
||||||
</span>
|
</p>
|
||||||
<div
|
<div
|
||||||
class="flex items-end gap-2 mt-2"
|
class="flex items-end gap-2 mt-2"
|
||||||
:class="[inversion ? 'flex-row-reverse' : 'flex-row']"
|
:class="[inversion ? 'flex-row-reverse' : 'flex-row']"
|
||||||
|
|
|
@ -95,7 +95,6 @@ async function onConversation() {
|
||||||
if (lastIndex !== -1)
|
if (lastIndex !== -1)
|
||||||
chunk = responseText.substring(lastIndex)
|
chunk = responseText.substring(lastIndex)
|
||||||
try {
|
try {
|
||||||
globalThis.console.log(`trunk = ${chunk}`)
|
|
||||||
const data = JSON.parse(chunk)
|
const data = JSON.parse(chunk)
|
||||||
updateChat(
|
updateChat(
|
||||||
+uuid,
|
+uuid,
|
||||||
|
@ -117,23 +116,6 @@ async function onConversation() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
/*
|
|
||||||
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal)
|
|
||||||
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: 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.'
|
||||||
|
@ -206,7 +188,6 @@ async function onRegenerate(index: number) {
|
||||||
if (lastIndex !== -1)
|
if (lastIndex !== -1)
|
||||||
chunk = responseText.substring(lastIndex)
|
chunk = responseText.substring(lastIndex)
|
||||||
try {
|
try {
|
||||||
globalThis.console.log(`trunk = ${chunk}`)
|
|
||||||
const data = JSON.parse(chunk)
|
const data = JSON.parse(chunk)
|
||||||
updateChat(
|
updateChat(
|
||||||
+uuid,
|
+uuid,
|
||||||
|
@ -227,22 +208,6 @@ async function onRegenerate(index: number) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
/*
|
|
||||||
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal)
|
|
||||||
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: 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.'
|
||||||
|
@ -345,7 +310,7 @@ onUnmounted(() => {
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-full" :class="wrapClass">
|
<div class="flex flex-col h-full" :class="wrapClass">
|
||||||
<main class="flex-1 overflow-hidden">
|
<main class="flex-1 overflow-hidden">
|
||||||
<div ref="scrollRef" class="h-full p-4 overflow-hidden overflow-y-auto" :class="[{ 'p-2': isMobile }]">
|
<div ref="scrollRef" class="h-full overflow-hidden overflow-y-auto" :class="[isMobile ? 'p-2' : 'p-4']">
|
||||||
<template v-if="!dataSources.length">
|
<template v-if="!dataSources.length">
|
||||||
<div class="flex items-center justify-center mt-4 text-center text-neutral-300">
|
<div class="flex items-center justify-center mt-4 text-center text-neutral-300">
|
||||||
<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
|
<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
|
||||||
|
|
|
@ -18,7 +18,7 @@ function handleUpdateCollapsed() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="fixed top-0 left-0 right-0 z-30 border-b dark:border-neutral-800 bg-white/80 dark:bg-black/30 backdrop-blur">
|
<header class="fixed top-0 left-0 right-0 z-30 border-b dark:border-neutral-800 bg-white/80 dark:bg-black/20 backdrop-blur">
|
||||||
<div class="relative flex items-center justify-between h-14">
|
<div class="relative flex items-center justify-between h-14">
|
||||||
<button class="flex items-center justify-center w-11 h-11" @click="handleUpdateCollapsed">
|
<button class="flex items-center justify-center w-11 h-11" @click="handleUpdateCollapsed">
|
||||||
<SvgIcon v-if="collapsed" class="text-2xl" icon="ri:align-justify" />
|
<SvgIcon v-if="collapsed" class="text-2xl" icon="ri:align-justify" />
|
||||||
|
|
|
@ -46,8 +46,8 @@ function handleThemeChange(key: 'light' | 'dark' | 'auto') {
|
||||||
<NDropdown :options="options" placement="top" trigger="click" @select="handleThemeChange">
|
<NDropdown :options="options" placement="top" trigger="click" @select="handleThemeChange">
|
||||||
<HoverButton>
|
<HoverButton>
|
||||||
<span class="text-xl text-[#4f555e] dark:text-white">
|
<span class="text-xl text-[#4f555e] dark:text-white">
|
||||||
<SvgIcon v-if="theme === 'dark'" icon="ri:sun-foggy-line" />
|
<SvgIcon v-if="theme === 'dark'" icon="ri:moon-foggy-line" />
|
||||||
<SvgIcon v-if="theme === 'light'" icon="ri:moon-foggy-line" />
|
<SvgIcon v-if="theme === 'light'" icon="ri:sun-foggy-line" />
|
||||||
<SvgIcon v-if="theme === 'auto'" icon="ri:contrast-line" />
|
<SvgIcon v-if="theme === 'auto'" icon="ri:contrast-line" />
|
||||||
</span>
|
</span>
|
||||||
</HoverButton>
|
</HoverButton>
|
||||||
|
|
Loading…
Reference in New Issue