mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
feat: new UI for chat file manager is ready
This commit is contained in:
parent
d63536d5ef
commit
f8fed83507
@ -22,6 +22,7 @@ type WsMessage struct {
|
||||
Type WsMsgType `json:"type"` // 消息类别,start, end, img
|
||||
Content interface{} `json:"content"`
|
||||
}
|
||||
|
||||
type WsMsgType string
|
||||
|
||||
const (
|
||||
|
@ -348,7 +348,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
||||
}
|
||||
data = append(data, gin.H{
|
||||
"type": "text",
|
||||
"text": text,
|
||||
"text": strings.TrimSpace(text),
|
||||
})
|
||||
content = data
|
||||
} else {
|
||||
|
@ -9,6 +9,7 @@ package handler
|
||||
|
||||
import (
|
||||
"geekai/core"
|
||||
"geekai/core/types"
|
||||
"geekai/service/oss"
|
||||
"geekai/store/model"
|
||||
"geekai/store/vo"
|
||||
@ -35,6 +36,12 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("upload file: %s", file.Name)
|
||||
// cut the file name if it's too long
|
||||
if len(file.Name) > 100 {
|
||||
file.Name = file.Name[:90] + file.Ext
|
||||
}
|
||||
|
||||
userId := h.GetLoginUserId(c)
|
||||
res := h.DB.Create(&model.File{
|
||||
UserId: int(userId),
|
||||
@ -54,10 +61,24 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *UploadHandler) List(c *gin.Context) {
|
||||
var data struct {
|
||||
Urls []string `json:"urls"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
logger.Info(data)
|
||||
|
||||
userId := h.GetLoginUserId(c)
|
||||
var items []model.File
|
||||
var files = make([]vo.File, 0)
|
||||
h.DB.Where("user_id = ?", userId).Find(&items)
|
||||
session := h.DB.Session(&gorm.Session{})
|
||||
session = session.Where("user_id = ?", userId)
|
||||
if len(data.Urls) > 0 {
|
||||
session = session.Where("url IN ?", data.Urls)
|
||||
}
|
||||
session.Find(&items)
|
||||
if len(items) > 0 {
|
||||
for _, v := range items {
|
||||
var file vo.File
|
||||
|
@ -255,7 +255,7 @@ func main() {
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) {
|
||||
s.Engine.POST("/api/upload", h.Upload)
|
||||
s.Engine.GET("/api/upload/list", h.List)
|
||||
s.Engine.POST("/api/upload/list", h.List)
|
||||
s.Engine.GET("/api/upload/remove", h.Remove)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) {
|
||||
|
@ -6,4 +6,4 @@ VUE_APP_ADMIN_USER=admin
|
||||
VUE_APP_ADMIN_PASS=admin123
|
||||
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
|
||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
||||
VUE_APP_VERSION=v4.0.9
|
||||
VUE_APP_VERSION=v4.1.0
|
||||
|
@ -2,4 +2,4 @@ VUE_APP_API_HOST=
|
||||
VUE_APP_WS_HOST=
|
||||
VUE_APP_KEY_PREFIX=ChatPLUS_
|
||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
||||
VUE_APP_VERSION=v4.0.9
|
||||
VUE_APP_VERSION=v4.1.0
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.7 KiB |
@ -252,25 +252,35 @@ $borderColor = #4676d0;
|
||||
border: 2px solid #21AA93
|
||||
border-radius 10px
|
||||
padding 10px
|
||||
background-color #F4F4F4
|
||||
|
||||
|
||||
.prompt-input::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.prompt-input {
|
||||
.input-inner {
|
||||
display flex
|
||||
flex-flow column
|
||||
width 100%
|
||||
line-height: 24px
|
||||
border none
|
||||
font-size 14px
|
||||
background none
|
||||
resize: none
|
||||
white-space: pre-wrap; /* 保持文本换行 */
|
||||
word-wrap: break-word; /* 允许单词换行 */
|
||||
overflow-wrap: break-word; /* 允许长单词换行,适用于现代浏览器 */
|
||||
|
||||
.file-list {
|
||||
padding-bottom 10px
|
||||
}
|
||||
.prompt-input::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.prompt-input {
|
||||
width 100%
|
||||
line-height: 24px
|
||||
border none
|
||||
font-size 14px
|
||||
background none
|
||||
resize: none
|
||||
white-space: pre-wrap; /* 保持文本换行 */
|
||||
word-wrap: break-word; /* 允许单词换行 */
|
||||
overflow-wrap: break-word; /* 允许长单词换行,适用于现代浏览器 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.send-btn {
|
||||
width 32px
|
||||
margin-left 10px
|
||||
|
@ -6,6 +6,25 @@
|
||||
</div>
|
||||
|
||||
<div class="chat-item">
|
||||
<div v-if="files.length > 0" class="file-list-box">
|
||||
<div v-for="file in files">
|
||||
<div class="image" v-if="isImage(file.ext)">
|
||||
<el-image :src="file.url" fit="cover"/>
|
||||
</div>
|
||||
<div class="item" v-else>
|
||||
<div class="icon">
|
||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" />
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="title">{{file.name}}</div>
|
||||
<div class="info">
|
||||
<span>{{GetFileType(file.ext)}}</span>
|
||||
<span>{{FormatFileSize(file.size)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" v-html="content"></div>
|
||||
<div class="bar" v-if="createdAt">
|
||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
|
||||
@ -17,50 +36,87 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent} from "vue"
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue"
|
||||
import {Clock} from "@element-plus/icons-vue";
|
||||
import {httpPost} from "@/utils/http";
|
||||
import hl from "highlight.js";
|
||||
import {isImage, processPrompt, substr} from "@/utils/libs";
|
||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ChatPrompt',
|
||||
components: {Clock},
|
||||
methods: {},
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'images/user-icon.png',
|
||||
},
|
||||
createdAt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tokens: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
model: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
finalTokens: this.tokens
|
||||
const mathjaxPlugin = require('markdown-it-mathjax3')
|
||||
const md = require('markdown-it')({
|
||||
breaks: true,
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
highlight: function (str, lang) {
|
||||
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
|
||||
// 显示复制代码按钮
|
||||
const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
|
||||
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '</textarea>')}</textarea>`
|
||||
if (lang && hl.getLanguage(lang)) {
|
||||
const langHtml = `<span class="lang-name">${lang}</span>`
|
||||
// 处理代码高亮
|
||||
const preCode = hl.highlight(lang, str, true).value
|
||||
// 将代码包裹在 pre 中
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
|
||||
}
|
||||
|
||||
// 处理代码高亮
|
||||
const preCode = md.utils.escapeHtml(str)
|
||||
// 将代码包裹在 pre 中
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
|
||||
}
|
||||
});
|
||||
md.use(mathjaxPlugin)
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
mounted() {
|
||||
if (!this.finalTokens) {
|
||||
httpPost("/api/chat/tokens", {text: this.content, model: this.model}).then(res => {
|
||||
this.finalTokens = res.data;
|
||||
}).catch(() => {
|
||||
})
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'images/user-icon.png',
|
||||
},
|
||||
createdAt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tokens: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
model: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
const finalTokens = ref(props.tokens)
|
||||
const content =ref(processPrompt(props.content))
|
||||
const files = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
if (!finalTokens.value) {
|
||||
httpPost("/api/chat/tokens", {text: props.content, model: props.model}).then(res => {
|
||||
finalTokens.value = res.data;
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
const linkRegex = /(https?:\/\/\S+)/g;
|
||||
const links = props.content.match(linkRegex);
|
||||
if (links) {
|
||||
httpPost("/api/upload/list", {urls: links}).then(res => {
|
||||
files.value = res.data
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
for (let link of links) {
|
||||
content.value = content.value.replace(link,"")
|
||||
}
|
||||
}
|
||||
content.value = md.render(content.value.trim())
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -92,10 +148,58 @@ export default defineComponent({
|
||||
|
||||
.chat-item {
|
||||
width 100%
|
||||
position: relative;
|
||||
padding: 0 5px 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
.file-list-box {
|
||||
display flex
|
||||
flex-flow column
|
||||
.image {
|
||||
display flex
|
||||
flex-flow row
|
||||
margin-right 10px
|
||||
position relative
|
||||
|
||||
.el-image {
|
||||
border 1px solid #e3e3e3
|
||||
border-radius 10px
|
||||
margin-bottom 10px
|
||||
}
|
||||
}
|
||||
.item {
|
||||
display flex
|
||||
flex-flow row
|
||||
border-radius 10px
|
||||
background-color #ffffff
|
||||
border 1px solid #e3e3e3
|
||||
padding 6px
|
||||
margin-bottom 10px
|
||||
|
||||
.icon {
|
||||
.el-image {
|
||||
width 40px
|
||||
height 40px
|
||||
}
|
||||
}
|
||||
.body {
|
||||
margin-left 8px
|
||||
font-size 14px
|
||||
.title {
|
||||
font-weight bold
|
||||
line-height 24px
|
||||
color #0D0D0D
|
||||
}
|
||||
.info {
|
||||
color #B4B4B4
|
||||
|
||||
span {
|
||||
margin-right 10px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
word-break break-word;
|
||||
padding: 6px 10px;
|
||||
@ -149,4 +253,4 @@ export default defineComponent({
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
114
web/src/components/FileList.vue
Normal file
114
web/src/components/FileList.vue
Normal file
@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<el-container class="chat-file-list">
|
||||
<div v-for="file in fileList">
|
||||
<div class="image" v-if="isImage(file.ext)">
|
||||
<el-image :src="file.url" fit="cover"/>
|
||||
<div class="action">
|
||||
<el-icon @click="removeFile(file)"><CircleCloseFilled /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" v-else>
|
||||
<div class="icon">
|
||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" />
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="title">{{substr(file.name, 30)}}</div>
|
||||
<div class="info">
|
||||
<span>{{GetFileType(file.ext)}}</span>
|
||||
<span>{{FormatFileSize(file.size)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action">
|
||||
<el-icon @click="removeFile(file)"><CircleCloseFilled /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import {CircleCloseFilled} from "@element-plus/icons-vue";
|
||||
import {isImage, removeArrayItem, substr} from "@/utils/libs";
|
||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
|
||||
|
||||
const props = defineProps({
|
||||
files: {
|
||||
type: Array,
|
||||
default:[],
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(['removeFile']);
|
||||
const fileList = ref(props.files)
|
||||
|
||||
|
||||
const removeFile = (file) => {
|
||||
fileList.value = removeArrayItem(fileList.value, file, (v1,v2) => v1.url===v2.url)
|
||||
emits('removeFile', file)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
|
||||
.chat-file-list {
|
||||
display flex
|
||||
flex-flow row
|
||||
.image {
|
||||
display flex
|
||||
flex-flow row
|
||||
margin-right 10px
|
||||
position relative
|
||||
|
||||
.el-image {
|
||||
height 56px
|
||||
width 56px
|
||||
border 1px solid #e3e3e3
|
||||
border-radius 10px
|
||||
}
|
||||
}
|
||||
.item {
|
||||
position relative
|
||||
display flex
|
||||
flex-flow row
|
||||
border-radius 10px
|
||||
background-color #ffffff
|
||||
border 1px solid #e3e3e3
|
||||
padding 6px
|
||||
margin-right 10px
|
||||
|
||||
.icon {
|
||||
.el-image {
|
||||
width 40px
|
||||
height 40px
|
||||
}
|
||||
}
|
||||
.body {
|
||||
margin-left 5px
|
||||
font-size 14px
|
||||
.title {
|
||||
font-weight bold
|
||||
line-height 24px
|
||||
color #0D0D0D
|
||||
}
|
||||
.info {
|
||||
color #B4B4B4
|
||||
|
||||
span {
|
||||
margin-right 10px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
position absolute
|
||||
top -8px
|
||||
right -8px
|
||||
color #da0d54
|
||||
cursor pointer
|
||||
font-size 20px
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-container class="file-list-box">
|
||||
<el-container class="file-select-box">
|
||||
<a class="file-upload-img" @click="fetchFiles">
|
||||
<i class="iconfont icon-attachment-st"></i>
|
||||
</a>
|
||||
@ -34,8 +34,8 @@
|
||||
effect="dark"
|
||||
:content="file.name"
|
||||
placement="top">
|
||||
<el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file.url)"/>
|
||||
<el-image :src="getFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file.url)"/>
|
||||
<el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file)"/>
|
||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file)"/>
|
||||
</el-tooltip>
|
||||
|
||||
<div class="opt">
|
||||
@ -55,6 +55,7 @@ import {ElMessage} from "element-plus";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {Delete, Plus} from "@element-plus/icons-vue";
|
||||
import {isImage, removeArrayItem} from "@/utils/libs";
|
||||
import {GetFileIcon} from "@/store/system";
|
||||
|
||||
const props = defineProps({
|
||||
userId: Number,
|
||||
@ -65,30 +66,12 @@ const fileList = ref([])
|
||||
|
||||
const fetchFiles = () => {
|
||||
show.value = true
|
||||
httpGet("/api/upload/list").then(res => {
|
||||
httpPost("/api/upload/list").then(res => {
|
||||
fileList.value = res.data
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
const getFileIcon = (ext) => {
|
||||
const files = {
|
||||
".docx": "doc.png",
|
||||
".doc": "doc.png",
|
||||
".xls": "xls.png",
|
||||
".xlsx": "xls.png",
|
||||
".ppt": "ppt.png",
|
||||
".pptx": "ppt.png",
|
||||
".md": "md.png",
|
||||
".pdf": "pdf.png",
|
||||
".sql": "sql.png"
|
||||
}
|
||||
if (files[ext]) {
|
||||
return '/images/ext/' + files[ext]
|
||||
}
|
||||
|
||||
return '/images/ext/file.png'
|
||||
}
|
||||
|
||||
const afterRead = (file) => {
|
||||
const formData = new FormData();
|
||||
@ -113,19 +96,19 @@ const removeFile = (file) => {
|
||||
})
|
||||
}
|
||||
|
||||
const insertURL = (url) => {
|
||||
const insertURL = (file) => {
|
||||
show.value = false
|
||||
// 如果是相对路径,处理成绝对路径
|
||||
if (url.indexOf("http") === -1) {
|
||||
url = location.protocol + "//" + location.host + url
|
||||
if (file.url.indexOf("http") === -1) {
|
||||
file.url = location.protocol + "//" + location.host + file.url
|
||||
}
|
||||
emits('selected', url)
|
||||
emits('selected', file)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
||||
.file-list-box {
|
||||
.file-select-box {
|
||||
.file-upload-img {
|
||||
.iconfont {
|
||||
font-size: 24px;
|
||||
|
@ -24,4 +24,38 @@ export function getAdminTheme() {
|
||||
|
||||
export function setAdminTheme(theme) {
|
||||
Storage.set(ADMIN_THEME, theme)
|
||||
}
|
||||
|
||||
export function GetFileIcon(ext) {
|
||||
const files = {
|
||||
".docx": "doc.png",
|
||||
".doc": "doc.png",
|
||||
".xls": "xls.png",
|
||||
".xlsx": "xls.png",
|
||||
".csv": "xls.png",
|
||||
".ppt": "ppt.png",
|
||||
".pptx": "ppt.png",
|
||||
".md": "md.png",
|
||||
".pdf": "pdf.png",
|
||||
".sql": "sql.png"
|
||||
}
|
||||
if (files[ext]) {
|
||||
return '/images/ext/' + files[ext]
|
||||
}
|
||||
|
||||
return '/images/ext/file.png'
|
||||
}
|
||||
|
||||
// 获取文件类型
|
||||
export function GetFileType (ext) {
|
||||
return ext.replace(".", "").toUpperCase()
|
||||
}
|
||||
|
||||
// 将文件大小转成字符
|
||||
export function FormatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
@ -223,23 +223,9 @@ export function processContent(content) {
|
||||
}
|
||||
|
||||
export function processPrompt(prompt) {
|
||||
prompt = prompt.replace(/&/g, "&")
|
||||
return prompt.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
|
||||
const linkRegex = /(https?:\/\/\S+)/g;
|
||||
const links = prompt.match(linkRegex);
|
||||
if (links) {
|
||||
for (let link of links) {
|
||||
if (isImage(link)) {
|
||||
const index = prompt.indexOf(link)
|
||||
if (prompt.substring(index - 1, 2) !== "]") {
|
||||
prompt = prompt.replace(link, "\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return prompt
|
||||
}
|
||||
|
||||
// 判断是否为微信浏览器
|
||||
@ -258,3 +244,4 @@ export function showLoginDialog(router) {
|
||||
// on cancel
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -129,14 +129,18 @@
|
||||
|
||||
<span class="tool-item" v-if="isLogin">
|
||||
<el-tooltip class="box-item" effect="dark" content="上传附件">
|
||||
<file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertURL"/>
|
||||
<file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertFile"/>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
|
||||
<div class="input-body">
|
||||
<div ref="textHeightRef" class="hide-div">{{prompt}}</div>
|
||||
<div class="input-border">
|
||||
<textarea
|
||||
<div class="input-inner">
|
||||
<div class="file-list" v-if="files.length > 0">
|
||||
<file-list :files="files" @remove-file="removeFile" />
|
||||
</div>
|
||||
<textarea
|
||||
ref="inputRef"
|
||||
class="prompt-input"
|
||||
:rows="row"
|
||||
@ -146,6 +150,8 @@
|
||||
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
|
||||
autofocus>
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
<span class="send-btn">
|
||||
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
|
||||
<el-icon>
|
||||
@ -210,6 +216,7 @@ import {checkSession} from "@/action/session";
|
||||
import Welcome from "@/components/Welcome.vue";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import FileSelect from "@/components/FileSelect.vue";
|
||||
import FileList from "@/components/FileList.vue";
|
||||
|
||||
const title = ref('ChatGPT-智能助手');
|
||||
const models = ref([])
|
||||
@ -743,12 +750,17 @@ const sendMessage = function () {
|
||||
if (prompt.value.trim().length === 0 || canSend.value === false) {
|
||||
return false;
|
||||
}
|
||||
// 如果携带了文件,则串上文件地址
|
||||
let content = prompt.value
|
||||
if (files.value.length > 0) {
|
||||
content = files.value.map(file => file.url).join(" ") + " " + content
|
||||
}
|
||||
// 追加消息
|
||||
chatData.value.push({
|
||||
type: "prompt",
|
||||
id: randString(32),
|
||||
icon: loginUser.value.avatar,
|
||||
content: md.render(processPrompt(prompt.value)),
|
||||
content: content,
|
||||
created_at: new Date().getTime() / 1000,
|
||||
});
|
||||
|
||||
@ -758,9 +770,10 @@ const sendMessage = function () {
|
||||
|
||||
showHello.value = false
|
||||
disableInput(false)
|
||||
socket.value.send(JSON.stringify({type: "chat", content: prompt.value}));
|
||||
tmpChatTitle.value = prompt.value
|
||||
prompt.value = '';
|
||||
socket.value.send(JSON.stringify({type: "chat", content: content}));
|
||||
tmpChatTitle.value = content
|
||||
prompt.value = ''
|
||||
files.value = []
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -810,9 +823,11 @@ const loadChatHistory = function (chatId) {
|
||||
showHello.value = false
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i].orgContent = data[i].content;
|
||||
data[i].content = md.render(processContent(data[i].content))
|
||||
if (i > 0 && data[i].type === 'reply') {
|
||||
data[i].prompt = data[i - 1].orgContent
|
||||
if (data[i].type === 'reply') {
|
||||
data[i].content = md.render(processContent(data[i].content))
|
||||
if (i > 0) {
|
||||
data[i].prompt = data[i - 1].orgContent
|
||||
}
|
||||
}
|
||||
chatData.value.push(data[i]);
|
||||
}
|
||||
@ -893,9 +908,13 @@ const notShow = () => {
|
||||
showNotice.value = false
|
||||
}
|
||||
|
||||
// 插入文件路径
|
||||
const insertURL = (url) => {
|
||||
prompt.value += " " + url + " "
|
||||
const files = ref([])
|
||||
// 插入文件
|
||||
const insertFile = (file) => {
|
||||
files.value.push(file)
|
||||
}
|
||||
const removeFile = (file) => {
|
||||
files.value = removeArrayItem(files.value, file, (v1,v2) => v1.url===v2.url)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user