feat: v2.7.2 消息样式美化和优化代码 (#111)

* perf: 优化代码

* feat: 美化消息,支持 markdown 全语法

* chore: version 2.7.2
This commit is contained in:
Redon 2023-02-24 15:03:49 +08:00 committed by GitHub
parent 1e2f893ef6
commit b6fd9ae766
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 65 additions and 130 deletions

View File

@ -1,3 +1,10 @@
## v2.7.2
`2023-02-24`
### Enhancement
- 消息使用 [github-markdown-css](https://www.npmjs.com/package/github-markdown-css) 进行美化,现在支持全语法
- 移除测试无用函数
## v2.7.1 ## v2.7.1
`2023-02-23` `2023-02-23`

View File

@ -6,9 +6,9 @@ export function createViteProxy(isOpenProxy: boolean, viteEnv: ImportMetaEnv) {
const proxy: Record<string, string | ProxyOptions> = { const proxy: Record<string, string | ProxyOptions> = {
'/api': { '/api': {
target: viteEnv.VITE_GLOB_API_URL, target: viteEnv.VITE_APP_API_BASE_URL,
changeOrigin: true, changeOrigin: true,
rewrite: path => path.replace(/^\/api/, ''), rewrite: path => path.replace('/api/', '/'),
}, },
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "chatgpt-web", "name": "chatgpt-web",
"version": "2.7.1", "version": "2.7.2",
"private": false, "private": false,
"description": "ChatGPT Web", "description": "ChatGPT Web",
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>", "author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
@ -24,6 +24,7 @@
}, },
"dependencies": { "dependencies": {
"@vueuse/core": "^9.13.0", "@vueuse/core": "^9.13.0",
"github-markdown-css": "^5.2.0",
"highlight.js": "^11.7.0", "highlight.js": "^11.7.0",
"marked": "^4.2.12", "marked": "^4.2.12",
"naive-ui": "^2.34.3", "naive-ui": "^2.34.3",

View File

@ -15,6 +15,7 @@ specifiers:
axios: ^1.3.3 axios: ^1.3.3
crypto-js: ^4.1.1 crypto-js: ^4.1.1
eslint: ^8.34.0 eslint: ^8.34.0
github-markdown-css: ^5.2.0
highlight.js: ^11.7.0 highlight.js: ^11.7.0
husky: ^8.0.3 husky: ^8.0.3
less: ^4.1.3 less: ^4.1.3
@ -34,6 +35,7 @@ specifiers:
dependencies: dependencies:
'@vueuse/core': 9.13.0_vue@3.2.47 '@vueuse/core': 9.13.0_vue@3.2.47
github-markdown-css: 5.2.0
highlight.js: 11.7.0 highlight.js: 11.7.0
marked: 4.2.12 marked: 4.2.12
naive-ui: 2.34.3_vue@3.2.47 naive-ui: 2.34.3_vue@3.2.47
@ -2524,6 +2526,10 @@ packages:
through2: 4.0.2 through2: 4.0.2
dev: true dev: true
/github-markdown-css/5.2.0:
resolution: {integrity: sha512-hq5RaCInSUZ48bImOZpkppW2/MT44StRgsbsZ8YA4vJFwLKB/Vo3k7R2t+pUGqO+ThG0QDMi96TewV/B3vyItg==}
dev: false
/glob-parent/5.1.2: /glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'} engines: {node: '>= 6'}

View File

@ -65,35 +65,6 @@ 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',
@ -107,4 +78,4 @@ async function chatConfig() {
export type { ChatContext, ChatMessage } export type { ChatContext, ChatMessage }
export { chatReply, chatReplyProcess, chatConfig } export { chatReply, chatConfig }

View File

@ -1,6 +1,6 @@
import express from 'express' import express from 'express'
import type { ChatContext, ChatMessage } from './chatgpt' import type { ChatContext } from './chatgpt'
import { chatConfig, chatReply, chatReplyProcess } from './chatgpt' import { chatConfig, chatReply } from './chatgpt'
const app = express() const app = express()
const router = express.Router() const router = express.Router()
@ -26,24 +26,6 @@ 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()

View File

@ -1,4 +1,4 @@
import type { AxiosProgressEvent, GenericAbortSignal } from 'axios' import type { GenericAbortSignal } from 'axios'
import { post } from '@/utils/request' import { post } from '@/utils/request'
export function fetchChatAPI<T = any>( export function fetchChatAPI<T = any>(
@ -13,22 +13,6 @@ 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',

View File

@ -1,23 +0,0 @@
import type { App, Directive } from 'vue'
import hljs from 'highlight.js'
import includeCode from '@/utils/functions/includeCode'
hljs.configure({ ignoreUnescapedHTML: true })
function highlightCode(el: HTMLElement) {
if (includeCode(el.textContent))
hljs.highlightBlock(el)
}
export default function setupHighlightDirective(app: App) {
const highLightDirective: Directive<HTMLElement> = {
mounted(el: HTMLElement) {
highlightCode(el)
},
updated(el: HTMLElement) {
highlightCode(el)
},
}
app.directive('highlight', highLightDirective)
}

View File

@ -1,6 +1 @@
import type { App } from 'vue' export function setupDirectives() {}
import setupHighlightDirective from './highlight'
export function setupDirectives(app: App) {
setupHighlightDirective(app)
}

View File

@ -1,6 +1,5 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import { setupDirectives } from './directives'
import { setupAssets } from '@/plugins' import { setupAssets } from '@/plugins'
import { setupStore } from '@/store' import { setupStore } from '@/store'
import { setupRouter } from '@/router' import { setupRouter } from '@/router'
@ -11,8 +10,6 @@ async function bootstrap() {
setupStore(app) setupStore(app)
setupDirectives(app)
await setupRouter(app) await setupRouter(app)
app.mount('#app') app.mount('#app')

View File

@ -1,4 +1,5 @@
import 'highlight.js/styles/xcode.css' import 'highlight.js/styles/xcode.css'
import 'github-markdown-css/github-markdown.css'
import '@/styles/global.css' import '@/styles/global.css'
/** Tailwind's Preflight Style Override */ /** Tailwind's Preflight Style Override */

View File

@ -1,7 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import { marked } from 'marked' import { marked } from 'marked'
import includeCode from '@/utils/functions/includeCode' import hljs from 'highlight.js'
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
@ -10,8 +19,6 @@ interface Props {
loading?: boolean loading?: boolean
} }
const props = defineProps<Props>()
const wrapClass = computed(() => { const wrapClass = computed(() => {
return [ return [
'text-wrap', 'text-wrap',
@ -24,11 +31,8 @@ const wrapClass = computed(() => {
}) })
const text = computed(() => { const text = computed(() => {
if (props.text) { if (props.text)
if (!includeCode(props.text)) return marked(props.text)
return marked.parse(props.text)
return props.text
}
return '' return ''
}) })
</script> </script>
@ -39,25 +43,13 @@ const text = computed(() => {
<span class="w-[5px] h-[20px] block animate-blink" /> <span class="w-[5px] h-[20px] block animate-blink" />
</template> </template>
<template v-else> <template v-else>
<code v-if="includeCode(text)" v-highlight class="leading-relaxed" v-text="text" /> <div class="leading-relaxed break-all">
<div v-else class="leading-relaxed break-all" v-html="text" /> <div :class="[{ 'markdown-body': !inversion }]" v-html="text" />
</div>
</template> </template>
</div> </div>
</template> </template>
<style lang="less"> <style lang="less">
.text-wrap{ @import url(./style.less);
img{
max-width: 100%;
vertical-align: middle;
}
a {
color: #2d5cf6
}
}
.hljs {
background-color: #fff0 !important;
white-space: break-spaces;
}
</style> </style>

View File

@ -1,6 +1,6 @@
<script setup lang='ts'> <script setup lang='ts'>
import Avatar from './Avatar.vue' import AvatarComponent from './Avatar.vue'
import Text from './Text.vue' import TextComponent from './Text.vue'
import { SvgIcon } from '@/components/common' import { SvgIcon } from '@/components/common'
interface Props { interface Props {
@ -36,14 +36,14 @@ function handleRegenerate() {
class="flex items-center justify-center rounded-full overflow-hidden w-[32px] h-[32px]" class="flex items-center justify-center rounded-full overflow-hidden w-[32px] h-[32px]"
:class="[inversion ? 'ml-3' : 'mr-3']" :class="[inversion ? 'ml-3' : 'mr-3']"
> >
<Avatar :image="inversion" /> <AvatarComponent :image="inversion" />
</div> </div>
<div class="flex flex-col flex-1 text-sm" :class="[inversion ? 'items-end' : 'items-start']"> <div class="flex flex-col flex-1 text-sm" :class="[inversion ? 'items-end' : 'items-start']">
<span class="text-xs text-[#b4bbc4]"> <span class="text-xs text-[#b4bbc4]">
{{ dateTime }} {{ dateTime }}
</span> </span>
<div class="flex items-end gap-2 mt-2" :class="[inversion ? 'flex-row-reverse' : 'flex-row']"> <div class="flex items-end gap-2 mt-2" :class="[inversion ? 'flex-row-reverse' : 'flex-row']">
<Text <TextComponent
:inversion="inversion" :inversion="inversion"
:error="error" :error="error"
:text="text" :text="text"

View File

@ -0,0 +1,22 @@
.markdown-body {
background-color: transparent;
font-size: 14px;
ol {
list-style-type: decimal;
}
ul {
list-style-type: disc;
}
pre code,
pre tt {
line-height: 1.65;
}
.highlight pre,
pre {
background-color: #fff;
}
}