feat: 前后端统一接口类型

This commit is contained in:
ChenZhaoYu 2023-02-13 15:54:17 +08:00
parent dd522eb404
commit 847623e22a
12 changed files with 167 additions and 78 deletions

View File

@ -24,7 +24,7 @@ const api = new ChatGPTAPI({ apiKey })
async function chatReply(message: string) { async function chatReply(message: string) {
if (!message) if (!message)
return sendResponse({ type: 'fail', message: 'Message is empty' }) return sendResponse({ type: 'Fail', message: 'Message is empty' })
try { try {
// Get the last context from the chat context // Get the last context from the chat context
@ -43,16 +43,17 @@ async function chatReply(message: string) {
if (conversationId && id) if (conversationId && id)
chatContext.add({ conversationId, parentMessageId: id }) chatContext.add({ conversationId, parentMessageId: id })
return sendResponse({ type: 'success', data: response }) return sendResponse({ type: 'Success', data: response })
} }
catch (error: any) { catch (error: any) {
return sendResponse({ type: 'fail', message: error.message }) global.console.log(error)
return sendResponse({ type: 'Fail', message: error.message })
} }
} }
async function chatReplayOne(message: string, options?: ChatContext) { async function chatReplayOne(message: string, options?: ChatContext) {
if (!message) if (!message)
return sendResponse({ type: 'fail', message: 'Message is empty' }) return sendResponse({ type: 'Fail', message: 'Message is empty' })
try { try {
let messageOptions: SendMessageOptions = {} let messageOptions: SendMessageOptions = {}
@ -63,18 +64,18 @@ async function chatReplayOne(message: string, options?: ChatContext) {
const response = await api.sendMessage(message, { ...messageOptions }) const response = await api.sendMessage(message, { ...messageOptions })
return sendResponse({ type: 'success', data: response }) return sendResponse({ type: 'Success', data: response })
} }
} }
catch (error: any) { catch (error: any) {
return sendResponse({ type: 'fail', message: error.message }) return sendResponse({ type: 'Fail', message: error.message })
} }
} }
async function clearChatContext() { async function clearChatContext() {
// Clear the chat context // Clear the chat context
chatContext.clear() chatContext.clear()
return sendResponse({ type: 'success', message: 'Chat context cleared' }) return sendResponse({ type: 'Success', message: 'Chat context cleared' })
} }
export { chatReply, chatReplayOne, clearChatContext } export { chatReply, chatReplayOne, clearChatContext }

View File

@ -1,13 +1,13 @@
interface SendResponseOptions { interface SendResponseOptions {
type: 'success' | 'fail' type: 'Success' | 'Fail'
message?: string message?: string
data?: any data?: any
} }
export function sendResponse(options: SendResponseOptions) { export function sendResponse(options: SendResponseOptions) {
if (options.type === 'success') { if (options.type === 'Success') {
return Promise.resolve({ return Promise.resolve({
message: options.message ?? 'Success', message: options.message ?? null,
data: options.data ?? null, data: options.data ?? null,
status: options.type, status: options.type,
}) })

14
src/api/index.ts Normal file
View File

@ -0,0 +1,14 @@
import { post } from '@/utils/request'
export function fetchChatAPI<T = any>(prompt: string) {
return post<T>({
url: '/chat',
data: { prompt },
})
}
export function clearConversations<T = any>() {
return post<T>({
url: '/clear',
})
}

View File

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
interface Props { interface Props {
reversal?: boolean reversal?: boolean
error?: boolean
} }
defineProps<Props>() defineProps<Props>()
@ -8,7 +9,7 @@ defineProps<Props>()
<template> <template>
<div class="p-2 mt-2 rounded-md" :class="[reversal ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]']"> <div class="p-2 mt-2 rounded-md" :class="[reversal ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]']">
<span class="leading-relaxed whitespace-pre-wrap"> <span class="leading-relaxed whitespace-pre-wrap" :class="[{ 'text-red-400': error }]">
<slot /> <slot />
</span> </span>
</div> </div>

View File

@ -6,6 +6,7 @@ interface Props {
message?: string message?: string
dateTime?: string dateTime?: string
reversal?: boolean reversal?: boolean
error?: boolean
} }
defineProps<Props>() defineProps<Props>()
@ -23,7 +24,7 @@ defineProps<Props>()
<span class="text-xs text-[#b4bbc4]"> <span class="text-xs text-[#b4bbc4]">
{{ dateTime }} {{ dateTime }}
</span> </span>
<Text :reversal="reversal"> <Text :reversal="reversal" :error="error">
{{ message }} {{ message }}
</Text> </Text>
</div> </div>

View File

@ -2,14 +2,15 @@
import { nextTick, onMounted, ref } from 'vue' import { nextTick, onMounted, ref } from 'vue'
import { NButton, NInput, useMessage } from 'naive-ui' import { NButton, NInput, useMessage } from 'naive-ui'
import { Message } from './components' import { Message } from './components'
import { clearChatContext, fetchChatAPI } from './request'
import { Layout } from './layout' import { Layout } from './layout'
import { clearConversations, fetchChatAPI } from '@/api'
import { HoverButton, SvgIcon } from '@/components/common' import { HoverButton, SvgIcon } from '@/components/common'
interface ListProps { interface ListProps {
dateTime: string dateTime: string
message: string message: string
reversal?: boolean reversal?: boolean
error?: boolean
} }
const scrollRef = ref<HTMLDivElement>() const scrollRef = ref<HTMLDivElement>()
@ -30,13 +31,13 @@ function initChat() {
async function handleClear() { async function handleClear() {
try { try {
const { message } = await clearChatContext() const { message } = await clearConversations()
ms.success(message) ms.success(message ?? 'Success')
list.value = []
setTimeout(initChat, 100)
} }
catch (error) { catch (error) {
ms.error('Clear failed, please try again later.') ms.error('Clear failed, please try again later.')
list.value = []
setTimeout(initChat, 100)
} }
} }
@ -46,6 +47,9 @@ function handleEnter(event: KeyboardEvent) {
} }
async function handleSubmit() { async function handleSubmit() {
if (loading.value)
return
const message = prompt.value.trim() const message = prompt.value.trim()
if (!message || !message.length) { if (!message || !message.length) {
@ -58,19 +62,19 @@ async function handleSubmit() {
try { try {
loading.value = true loading.value = true
const { text } = await fetchChatAPI(message) const { data } = await fetchChatAPI(message)
addMessage(text, false) addMessage(data?.text ?? '', false)
} }
catch (error: any) { catch (error: any) {
addMessage(error.message ?? 'Request failed, please try again later.', false) addMessage(`Error: ${error.message ?? 'Request failed, please try again later.'}`, false, true)
} }
finally { finally {
loading.value = false loading.value = false
} }
} }
function addMessage(message: string, reversal = false) { function addMessage(message: string, reversal = false, error = false) {
list.value.push({ dateTime: new Date().toLocaleString(), message, reversal }) list.value.push({ dateTime: new Date().toLocaleString(), message, reversal, error })
nextTick(() => scrollRef.value && (scrollRef.value.scrollTop = scrollRef.value.scrollHeight)) nextTick(() => scrollRef.value && (scrollRef.value.scrollTop = scrollRef.value.scrollHeight))
} }
</script> </script>
@ -82,8 +86,12 @@ function addMessage(message: string, reversal = false) {
<div ref="scrollRef" class="h-full p-4 overflow-hidden overflow-y-auto"> <div ref="scrollRef" class="h-full p-4 overflow-hidden overflow-y-auto">
<div> <div>
<Message <Message
v-for="(item, index) of list" :key="index" :date-time="item.dateTime" :message="item.message" v-for="(item, index) of list"
:key="index"
:date-time="item.dateTime"
:message="item.message"
:reversal="item.reversal" :reversal="item.reversal"
:error="item.error"
/> />
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
<script setup lang='ts'> <script setup lang='ts'>
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { NButton, NLayoutSider } from 'naive-ui' import { NButton, NLayoutSider, useMessage } from 'naive-ui'
import List from './List.vue' import List from './List.vue'
import Footer from './Footer.vue' import Footer from './Footer.vue'
@ -18,6 +18,8 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<Emit>() const emit = defineEmits<Emit>()
const ms = useMessage()
const collapsed = ref(props.collapsed) const collapsed = ref(props.collapsed)
watch( watch(
@ -28,6 +30,10 @@ watch(
{ immediate: true }, { immediate: true },
) )
function handleAdd() {
ms.info('Coming soon...')
}
function handleCollapsed() { function handleCollapsed() {
collapsed.value = !collapsed.value collapsed.value = !collapsed.value
emit('update:collapsed', collapsed.value) emit('update:collapsed', collapsed.value)
@ -47,8 +53,8 @@ function handleCollapsed() {
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<main class="flex-1 min-h-0 overflow-hidden"> <main class="flex-1 min-h-0 overflow-hidden">
<div class="p-4"> <div class="p-4">
<NButton dashed block> <NButton dashed block @click="handleAdd">
Add chat New chat
</NButton> </NButton>
</div> </div>
<List /> <List />

View File

@ -1,41 +0,0 @@
import axios from 'axios'
const BASE_URL = import.meta.env.VITE_GLOB_API_URL
async function fetchChatAPI(message: string) {
if (!message || message.trim() === '')
return
try {
const { status, data } = await axios.post(`${BASE_URL}/chat`, { message })
if (status === 200) {
if (data.text)
return Promise.resolve(data)
if (data.statusText)
return Promise.reject(new Error(data.statusText))
}
return Promise.reject(new Error('Request failed'))
}
catch (error) {
return Promise.reject(error)
}
}
async function clearChatContext() {
try {
const { status, data } = await axios.post(`${BASE_URL}/clear`)
if (status === 200)
return Promise.resolve(data)
return Promise.reject(new Error('Request failed'))
}
catch (error) {
return Promise.reject(error)
}
}
export { fetchChatAPI, clearChatContext }

View File

@ -1,5 +1,5 @@
<script setup lang='ts'> <script setup lang='ts'>
import { GithubSite } from '@/components/custom'
</script> </script>
<template> <template>
@ -8,11 +8,11 @@
<img class="object-cover" src="@/assets/avatar.jpg" alt="avatar"> <img class="object-cover" src="@/assets/avatar.jpg" alt="avatar">
</div> </div>
<div class="ml-2"> <div class="ml-2">
<h2 class="text-sm font-bold"> <h2 class="font-bold text-md">
Redon ChenZhaoYu
</h2> </h2>
<p class="text-xs text-gray-500"> <p class="text-xs text-gray-500">
#0827 <GithubSite />
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,10 +1,8 @@
<template> <template>
<div class="fixed bottom-0 left-0 right-0 p-4 text-sm text-center text-neutral-500"> <div class="text-center text-neutral-500">
<span class="text-center"> <span> Star on</span>
Made By ChenZhaoYu - <a href="https://github.com/Chanzhaoyu/chatgpt-bot" target="_blank" class="text-blue-500">
<a href="https://github.com/Chanzhaoyu/chatgpt-bot" target="_blank" class="text-blue-500"> GitHub
Github </a>
</a>
</span>
</div> </div>
</template> </template>

View File

@ -0,0 +1,29 @@
import axios, { type AxiosResponse } from 'axios'
const service = axios.create({
baseURL: import.meta.env.VITE_GLOB_API_URL,
timeout: 10 * 1000,
})
service.interceptors.request.use(
(config) => {
return config
},
(error) => {
return Promise.reject(error.response)
},
)
service.interceptors.response.use(
(response: AxiosResponse): AxiosResponse => {
if (response.status === 200)
return response
throw new Error(response.status.toString())
},
(error) => {
return Promise.reject(error)
},
)
export default service

View File

@ -0,0 +1,72 @@
import type { AxiosResponse } from 'axios'
import request from './axios'
export interface HttpOption {
url: string
data?: any
method?: string
headers?: any
beforeRequest?: () => void
afterRequest?: () => void
}
export interface ExtraOption {
notification?: boolean
}
export interface Response<T = any> {
data: T
message: string | null
status: string
}
function http<T = any>({ url, data, method, headers, beforeRequest, afterRequest }: HttpOption) {
const successHandler = (res: AxiosResponse<Response<T>>) => {
if (res.data.status === 'Success')
return res.data
return Promise.reject(res.data)
}
const failHandler = (error: Response<Error>) => {
afterRequest?.()
throw new Error(error?.message || 'Error')
}
beforeRequest?.()
method = method || 'GET'
const params = Object.assign(typeof data === 'function' ? data() : data ?? {}, {})
return method === 'GET'
? request.get(url, { params }).then(successHandler, failHandler)
: request.post(url, params, { headers }).then(successHandler, failHandler)
}
export function get<T = any>(
{ url, data, method = 'GET', beforeRequest, afterRequest }: HttpOption,
): Promise<Response<T>> {
return http<T>({
url,
method,
data,
beforeRequest,
afterRequest,
})
}
export function post<T = any>(
{ url, data, method = 'POST', headers, beforeRequest, afterRequest }: HttpOption,
): Promise<Response<T>> {
return http<T>({
url,
method,
data,
headers,
beforeRequest,
afterRequest,
})
}
export default post