Browse Source

feat:实现对markdown文本渲染

master^2
dashan 2 years ago
parent
commit
658b1a48d7
  1. 22
      README.md
  2. 10
      package-lock.json
  3. 2
      package.json
  4. 2
      quasar.conf.js
  5. 7
      src/api/chatbot/chatbot.js
  6. 49
      src/components/EssentialLink.vue
  7. 131
      src/layouts/MainLayout.vue
  8. 61
      src/pages/Index.vue
  9. 2
      src/utils/request.js

22
README.md

@ -2,27 +2,27 @@
chatbot web chatbot web
## Install the dependencies ## 效果展示
### PC端效果展示
![](https://raw.githubusercontent.com/DaiShuaishuai/pic/main/picgo/pc1.jpg)
![](https://raw.githubusercontent.com/DaiShuaishuai/pic/main/picgo/pc2.jpg)
### 移动端效果展示
![](https://raw.githubusercontent.com/DaiShuaishuai/pic/main/picgo/yd1.jpg)
![](https://raw.githubusercontent.com/DaiShuaishuai/pic/main/picgo/yd2.jpg)
## 下载依赖
```bash ```bash
yarn yarn
# or # or
npm install npm install
``` ```
### Start the app in development mode (hot-code reloading, error reporting, etc.) ### 启动项目
```bash ```bash
quasar dev quasar dev
``` ```
### 打包
### Lint the files
```bash
yarn lint
# or
npm run lint
```
### Build the app for production
```bash ```bash
quasar build quasar build
``` ```

10
package-lock.json generated

@ -6186,6 +6186,11 @@
"integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==",
"dev": true "dev": true
}, },
"github-markdown-css": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.2.0.tgz",
"integrity": "sha512-hq5RaCInSUZ48bImOZpkppW2/MT44StRgsbsZ8YA4vJFwLKB/Vo3k7R2t+pUGqO+ThG0QDMi96TewV/B3vyItg=="
},
"glob": { "glob": {
"version": "7.2.3", "version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@ -7825,6 +7830,11 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"marked": {
"version": "4.2.12",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz",
"integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw=="
},
"matcher": { "matcher": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",

2
package.json

@ -12,6 +12,8 @@
"dependencies": { "dependencies": {
"@quasar/extras": "^1.0.0", "@quasar/extras": "^1.0.0",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"github-markdown-css": "^5.2.0",
"marked": "^4.2.12",
"moment": "^2.29.4", "moment": "^2.29.4",
"quasar": "^1.0.0" "quasar": "^1.0.0"
}, },

2
quasar.conf.js

@ -81,7 +81,7 @@ module.exports = function (/* ctx */) {
proxy: { proxy: {
// 接口地址代理 // 接口地址代理
'/': { '/': {
target: 'http://localhost:8090', // 接口的域名 target: 'http://localhost:8092', // 接口的域名
secure: false, // 如果是https接口,需要配置这个参数 secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true // 如果接口跨域,需要进行这个参数配置 changeOrigin: true // 如果接口跨域,需要进行这个参数配置
} }

7
src/api/chatbot/chatbot.js

@ -1,7 +1,8 @@
import service from "../../utils/request"; import service from "../../utils/request";
export const sendQuestion= (param)=>{ export const sendQuestion= (params)=>{
return service({//获取新闻列表 return service({//获取新闻列表
url:'/bot?question='+param, url:'/bot',
method:'get' method:'get',
params
}) })
} }

49
src/components/EssentialLink.vue

@ -1,49 +0,0 @@
<template>
<q-item
clickable
tag="a"
target="_blank"
:href="link"
>
<q-item-section
v-if="icon"
avatar
>
<q-icon :name="icon" />
</q-item-section>
<q-item-section>
<q-item-label>{{ title }}</q-item-label>
<q-item-label caption>
{{ caption }}
</q-item-label>
</q-item-section>
</q-item>
</template>
<script>
export default {
name: 'EssentialLink',
props: {
title: {
type: String,
required: true
},
caption: {
type: String,
default: ''
},
link: {
type: String,
default: '#'
},
icon: {
type: String,
default: ''
}
}
}
</script>

131
src/layouts/MainLayout.vue

@ -14,15 +14,38 @@
<q-footer elevated class="no-shadow" ref="footer"> <q-footer elevated class="no-shadow" ref="footer">
<q-toolbar <q-toolbar
class="bg-grey-3 text-black no-shadow row justify-center q-pt-md q-pb-sm" class="bg-grey-3 text-black no-shadow row justify-center q-pt-md q-pb-sm"
>
<q-select
bg-color="white"
rounded
outlined
v-model="select_model"
:options="options"
option-value="value"
emit-value
map-options
><q-tooltip>机器人型号</q-tooltip>
<template v-slot:option="{ itemProps, itemEvents, opt }">
<q-item
v-bind="itemProps"
v-on="itemEvents"
>
<q-item-section>
<q-item-label >{{opt.label}}
<q-tooltip >{{opt.value}}</q-tooltip>
</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select
> >
<q-input <q-input
class="WAL__field col-grow q-mr-sm" class="WAL__field col-grow q-mr-sm"
style="max-width: 1000px" style="max-width: 1000px"
bg-color="white" bg-color="white"
bottom-slots
v-model="search_text" v-model="search_text"
label="请输入你的问题" label="请输入你的问题"
counter
rounded rounded
outlined outlined
@keyup.enter.native="sendMsg" @keyup.enter.native="sendMsg"
@ -36,7 +59,11 @@
icon="send" icon="send"
@click="sendMsg" @click="sendMsg"
> >
<q-tooltip anchor="top middle" self="bottom middle" :offset="[10, 10]"> <q-tooltip
anchor="top middle"
self="bottom middle"
:offset="[10, 10]"
>
发送问题给机器人 发送问题给机器人
</q-tooltip> </q-tooltip>
</q-btn> </q-btn>
@ -48,7 +75,11 @@
icon="close" icon="close"
@click="clearMsgs" @click="clearMsgs"
> >
<q-tooltip anchor="top middle" self="bottom middle" :offset="[10, 10]"> <q-tooltip
anchor="top middle"
self="bottom middle"
:offset="[10, 10]"
>
清空聊天记录 清空聊天记录
</q-tooltip> </q-tooltip>
</q-btn> </q-btn>
@ -62,70 +93,100 @@
<script> <script>
import { sendQuestion } from "../api/chatbot/chatbot"; import { sendQuestion } from "../api/chatbot/chatbot";
import moment from "moment"; import moment from "moment";
import {dta} from "../utils/date_utils"; import { dta } from "../utils/date_utils";
const bot_message_records="bot_message_records" const bot_message_records = "bot_message_records";
export default { export default {
name: "MainLayout", name: "MainLayout",
data() { data() {
return { return {
select_model: "TEXT_DEVINCI_003",
options: [
{
value: "TEXT_DEVINCI_003",
label: "聪明"
},
{
value: "TEXT_CURIE_001",
label: "还行"
},
{
value: "TEXT_BABBAGE_001",
label: "有点呆"
},
{
value: "TEXT_ADA_001",
label: "憨批"
}
],
search_text: "", search_text: "",
disable: false, disable: false,
messages: [ messages: [
{ {
name: "bot", name: "bot",
text: ["请告诉我你的问题"], text: "请告诉我你的问题",
sent: false, sent: false,
datetime: moment().format(dta) datetime: moment().format(dta)
}, }
], ]
}; };
}, },
created(){ created() {
let records = localStorage.getItem(bot_message_records) //localStorage
if(records&&records!="[]"){ let records = localStorage.getItem(bot_message_records);
this.messages=JSON.parse(records) if (records && records != "[]") {
localStorage.removeItem(bot_message_records) this.messages = JSON.parse(records);
localStorage.removeItem(bot_message_records);
} }
window.addEventListener("pagehide",()=>{ //pagehide localStorage
localStorage.setItem(bot_message_records,JSON.stringify(this.messages)) window.addEventListener("pagehide", () => {
}) localStorage.setItem(bot_message_records, JSON.stringify(this.messages));
});
}, },
methods: { methods: {
// //
async sendMsg() { async sendMsg() {
if (!this.search_text||this.disable) { if (!this.search_text || this.disable) {
return; return;
} }
this.disable = true; this.disable = true;
//msg //msg
let text = this.search_text let text = this.search_text;
this.search_text = "" this.search_text = "";
this.addMessage("me", true, text,moment().format(dta)); this.addMessage("me", true, text, moment().format(dta));
await sendQuestion(text).then((res) => { await sendQuestion({question:text,model:this.select_model})
// .then((res) => {
this.addMessage("bot", false, res.data,moment().format(dta)); if(res.code==1){
// this.addMessage("bot", false, res.data, moment().format(dta));
}).catch(()=>{ }else{
this.addMessage("bot", false, "出错了!!!",moment().format(dta)); this.$q.notify({
color:"red",
message:"请求服务器错误",
position:"top",
timeout: 3000
}) })
this.addMessage("bot", false,"出错了!!!", moment().format(dta));
}
})
.catch(() => {
this.addMessage("bot", false, "出错了!!!", moment().format(dta));
});
this.disable = false; this.disable = false;
}, },
addMessage(name, sent, text,datetime) { addMessage(name, sent, text, datetime) {
this.messages.push({ this.messages.push({
name: name, name: name,
text: [text], text: text,
sent: sent, sent: sent,
datetime datetime
}); });
}, },
clearMsgs(){ clearMsgs() {
this.messages=[]; this.messages = [];
localStorage.removeItem(bot_message_records) localStorage.removeItem(bot_message_records);
}
} }
},
}; };
</script> </script>
<style lang="css"> <style lang="css">

61
src/pages/Index.vue

@ -12,15 +12,30 @@
:key="index" :key="index"
> >
<q-chat-message <q-chat-message
style="white-space: pre-wrap"
:text="message.text"
:name="message.name" :name="message.name"
:sent="message.sent" :sent="message.sent"
:stamp="fromNow(message.datetime)" :stamp="fromNow(message.datetime)"
/> :bg-color="message.sent ? 'green-7' : 'white'"
>
<div
v-if="message.sent"
:style="{ 'max-width': width * 0.8 + 'px' }"
>
{{ message.text }}
</div> </div>
<div class="col-xs-11 col-sm-10 col-md-9 col-lg-8 col-xl-7" v-show="disable"> <div
<q-chat-message name="bot"> v-else
class="markdown-body"
:style="{ 'max-width': width * 0.8 + 'px' }"
v-html="markdownToHtml(message.text)"
></div>
</q-chat-message>
</div>
<div
class="col-xs-11 col-sm-10 col-md-9 col-lg-8 col-xl-7"
v-show="disable"
>
<q-chat-message name="bot" bg-color="white">
<q-spinner-dots size="2rem" /> <q-spinner-dots size="2rem" />
</q-chat-message> </q-chat-message>
<q-tooltip <q-tooltip
@ -38,22 +53,54 @@
<script> <script>
import { dta, fromNow } from "../utils/date_utils"; import { dta, fromNow } from "../utils/date_utils";
import { marked } from "marked"; //markdown
import "github-markdown-css";
export default { export default {
props: ["messages", "disable"], props: ["messages", "disable"],
watch: { watch: {
messages: function (val) { messages: function (val) {
// //
this.$refs.scrollArea.setScrollPosition("vertical", 9999999, 300); this.$refs.scrollArea.setScrollPosition("vertical", 9999999, 300);
},
width(val) {
// resize使
if (!this.timer) {
// screenWidthdatascreenWidth
this.width = val;
this.timer = true;
const that = this;
setTimeout(function () {
that.timer = false;
}, 10);
}
} }
}, },
data() { data() {
return {}; return { width: document.body.clientWidth };
}, },
mounted() { mounted() {
this.$refs.scrollArea.setScrollPosition("vertical", 9999999, 300); this.$refs.scrollArea.setScrollPosition("vertical", 9999999, 300);
//
window.onresize = () =>
(() => {
window.screenWidth = document.body.clientWidth;
this.width = window.screenWidth;
})();
}, },
methods: { methods: {
markdownToHtml(text) {
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true, //true Git Hubmarkdown.
tables: true, //true gfm true
breaks: false, //false gfm true
pedantic: false, //false markdown.pl
sanitize: false, //
smartLists: true,
smartypants: false //使
});
return marked(text);
},
fromNow(date) { fromNow(date) {
return fromNow(date, dta); return fromNow(date, dta);
} }

2
src/utils/request.js

@ -27,7 +27,7 @@ service.interceptors.request.use(
// response拦截器 // response拦截器
service.interceptors.response.use( service.interceptors.response.use(
(response) => { (response) => {
return response return response.data
}, },
(error) => { (error) => {
if (error && error.response) { if (error && error.response) {

Loading…
Cancel
Save