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
|
Type WsMsgType `json:"type"` // 消息类别,start, end, img
|
||||||
Content interface{} `json:"content"`
|
Content interface{} `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WsMsgType string
|
type WsMsgType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -348,7 +348,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
}
|
}
|
||||||
data = append(data, gin.H{
|
data = append(data, gin.H{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"text": text,
|
"text": strings.TrimSpace(text),
|
||||||
})
|
})
|
||||||
content = data
|
content = data
|
||||||
} else {
|
} else {
|
||||||
|
@ -9,6 +9,7 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"geekai/core"
|
"geekai/core"
|
||||||
|
"geekai/core/types"
|
||||||
"geekai/service/oss"
|
"geekai/service/oss"
|
||||||
"geekai/store/model"
|
"geekai/store/model"
|
||||||
"geekai/store/vo"
|
"geekai/store/vo"
|
||||||
@ -35,6 +36,12 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
|||||||
return
|
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)
|
userId := h.GetLoginUserId(c)
|
||||||
res := h.DB.Create(&model.File{
|
res := h.DB.Create(&model.File{
|
||||||
UserId: int(userId),
|
UserId: int(userId),
|
||||||
@ -54,10 +61,24 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *UploadHandler) List(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)
|
userId := h.GetLoginUserId(c)
|
||||||
var items []model.File
|
var items []model.File
|
||||||
var files = make([]vo.File, 0)
|
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 {
|
if len(items) > 0 {
|
||||||
for _, v := range items {
|
for _, v := range items {
|
||||||
var file vo.File
|
var file vo.File
|
||||||
|
@ -255,7 +255,7 @@ func main() {
|
|||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) {
|
||||||
s.Engine.POST("/api/upload", h.Upload)
|
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)
|
s.Engine.GET("/api/upload/remove", h.Remove)
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) {
|
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_ADMIN_PASS=admin123
|
||||||
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
|
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
|
||||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
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_WS_HOST=
|
||||||
VUE_APP_KEY_PREFIX=ChatPLUS_
|
VUE_APP_KEY_PREFIX=ChatPLUS_
|
||||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
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: 2px solid #21AA93
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
padding 10px
|
padding 10px
|
||||||
|
background-color #F4F4F4
|
||||||
|
|
||||||
|
.input-inner {
|
||||||
.prompt-input::-webkit-scrollbar {
|
display flex
|
||||||
width: 0;
|
flex-flow column
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-input {
|
|
||||||
width 100%
|
width 100%
|
||||||
line-height: 24px
|
|
||||||
border none
|
.file-list {
|
||||||
font-size 14px
|
padding-bottom 10px
|
||||||
background none
|
}
|
||||||
resize: none
|
.prompt-input::-webkit-scrollbar {
|
||||||
white-space: pre-wrap; /* 保持文本换行 */
|
width: 0;
|
||||||
word-wrap: break-word; /* 允许单词换行 */
|
height: 0;
|
||||||
overflow-wrap: break-word; /* 允许长单词换行,适用于现代浏览器 */
|
}
|
||||||
|
|
||||||
|
.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 {
|
.send-btn {
|
||||||
width 32px
|
width 32px
|
||||||
margin-left 10px
|
margin-left 10px
|
||||||
|
@ -6,6 +6,25 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat-item">
|
<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="content" v-html="content"></div>
|
||||||
<div class="bar" v-if="createdAt">
|
<div class="bar" v-if="createdAt">
|
||||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
|
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
|
||||||
@ -17,50 +36,87 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import {defineComponent} from "vue"
|
import {onMounted, ref} from "vue"
|
||||||
import {Clock} from "@element-plus/icons-vue";
|
import {Clock} from "@element-plus/icons-vue";
|
||||||
import {httpPost} from "@/utils/http";
|
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({
|
const mathjaxPlugin = require('markdown-it-mathjax3')
|
||||||
name: 'ChatPrompt',
|
const md = require('markdown-it')({
|
||||||
components: {Clock},
|
breaks: true,
|
||||||
methods: {},
|
html: true,
|
||||||
props: {
|
linkify: true,
|
||||||
content: {
|
typographer: true,
|
||||||
type: String,
|
highlight: function (str, lang) {
|
||||||
default: '',
|
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
|
||||||
},
|
// 显示复制代码按钮
|
||||||
icon: {
|
const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
|
||||||
type: String,
|
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '</textarea>')}</textarea>`
|
||||||
default: 'images/user-icon.png',
|
if (lang && hl.getLanguage(lang)) {
|
||||||
},
|
const langHtml = `<span class="lang-name">${lang}</span>`
|
||||||
createdAt: {
|
// 处理代码高亮
|
||||||
type: String,
|
const preCode = hl.highlight(lang, str, true).value
|
||||||
default: '',
|
// 将代码包裹在 pre 中
|
||||||
},
|
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
|
||||||
tokens: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
model: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
finalTokens: this.tokens
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理代码高亮
|
||||||
|
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() {
|
icon: {
|
||||||
if (!this.finalTokens) {
|
type: String,
|
||||||
httpPost("/api/chat/tokens", {text: this.content, model: this.model}).then(res => {
|
default: 'images/user-icon.png',
|
||||||
this.finalTokens = res.data;
|
},
|
||||||
}).catch(() => {
|
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>
|
</script>
|
||||||
|
|
||||||
@ -92,10 +148,58 @@ export default defineComponent({
|
|||||||
|
|
||||||
.chat-item {
|
.chat-item {
|
||||||
width 100%
|
width 100%
|
||||||
position: relative;
|
|
||||||
padding: 0 5px 0 0;
|
padding: 0 5px 0 0;
|
||||||
overflow: hidden;
|
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 {
|
.content {
|
||||||
word-break break-word;
|
word-break break-word;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
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>
|
<template>
|
||||||
<el-container class="file-list-box">
|
<el-container class="file-select-box">
|
||||||
<a class="file-upload-img" @click="fetchFiles">
|
<a class="file-upload-img" @click="fetchFiles">
|
||||||
<i class="iconfont icon-attachment-st"></i>
|
<i class="iconfont icon-attachment-st"></i>
|
||||||
</a>
|
</a>
|
||||||
@ -34,8 +34,8 @@
|
|||||||
effect="dark"
|
effect="dark"
|
||||||
:content="file.name"
|
:content="file.name"
|
||||||
placement="top">
|
placement="top">
|
||||||
<el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @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.url)"/>
|
<el-image :src="GetFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file)"/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<div class="opt">
|
<div class="opt">
|
||||||
@ -55,6 +55,7 @@ import {ElMessage} from "element-plus";
|
|||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import {Delete, Plus} from "@element-plus/icons-vue";
|
import {Delete, Plus} from "@element-plus/icons-vue";
|
||||||
import {isImage, removeArrayItem} from "@/utils/libs";
|
import {isImage, removeArrayItem} from "@/utils/libs";
|
||||||
|
import {GetFileIcon} from "@/store/system";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
userId: Number,
|
userId: Number,
|
||||||
@ -65,30 +66,12 @@ const fileList = ref([])
|
|||||||
|
|
||||||
const fetchFiles = () => {
|
const fetchFiles = () => {
|
||||||
show.value = true
|
show.value = true
|
||||||
httpGet("/api/upload/list").then(res => {
|
httpPost("/api/upload/list").then(res => {
|
||||||
fileList.value = res.data
|
fileList.value = res.data
|
||||||
}).catch(() => {
|
}).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 afterRead = (file) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@ -113,19 +96,19 @@ const removeFile = (file) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertURL = (url) => {
|
const insertURL = (file) => {
|
||||||
show.value = false
|
show.value = false
|
||||||
// 如果是相对路径,处理成绝对路径
|
// 如果是相对路径,处理成绝对路径
|
||||||
if (url.indexOf("http") === -1) {
|
if (file.url.indexOf("http") === -1) {
|
||||||
url = location.protocol + "//" + location.host + url
|
file.url = location.protocol + "//" + location.host + file.url
|
||||||
}
|
}
|
||||||
emits('selected', url)
|
emits('selected', file)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
|
|
||||||
.file-list-box {
|
.file-select-box {
|
||||||
.file-upload-img {
|
.file-upload-img {
|
||||||
.iconfont {
|
.iconfont {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
@ -25,3 +25,37 @@ export function getAdminTheme() {
|
|||||||
export function setAdminTheme(theme) {
|
export function setAdminTheme(theme) {
|
||||||
Storage.set(ADMIN_THEME, 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) {
|
export function processPrompt(prompt) {
|
||||||
prompt = prompt.replace(/&/g, "&")
|
return prompt.replace(/&/g, "&")
|
||||||
.replace(/</g, "<")
|
.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
|
// on cancel
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,14 +129,18 @@
|
|||||||
|
|
||||||
<span class="tool-item" v-if="isLogin">
|
<span class="tool-item" v-if="isLogin">
|
||||||
<el-tooltip class="box-item" effect="dark" content="上传附件">
|
<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>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="input-body">
|
<div class="input-body">
|
||||||
<div ref="textHeightRef" class="hide-div">{{prompt}}</div>
|
<div ref="textHeightRef" class="hide-div">{{prompt}}</div>
|
||||||
<div class="input-border">
|
<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"
|
ref="inputRef"
|
||||||
class="prompt-input"
|
class="prompt-input"
|
||||||
:rows="row"
|
:rows="row"
|
||||||
@ -146,6 +150,8 @@
|
|||||||
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
|
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
|
||||||
autofocus>
|
autofocus>
|
||||||
</textarea>
|
</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span class="send-btn">
|
<span class="send-btn">
|
||||||
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
|
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
|
||||||
<el-icon>
|
<el-icon>
|
||||||
@ -210,6 +216,7 @@ import {checkSession} from "@/action/session";
|
|||||||
import Welcome from "@/components/Welcome.vue";
|
import Welcome from "@/components/Welcome.vue";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import {useSharedStore} from "@/store/sharedata";
|
||||||
import FileSelect from "@/components/FileSelect.vue";
|
import FileSelect from "@/components/FileSelect.vue";
|
||||||
|
import FileList from "@/components/FileList.vue";
|
||||||
|
|
||||||
const title = ref('ChatGPT-智能助手');
|
const title = ref('ChatGPT-智能助手');
|
||||||
const models = ref([])
|
const models = ref([])
|
||||||
@ -743,12 +750,17 @@ const sendMessage = function () {
|
|||||||
if (prompt.value.trim().length === 0 || canSend.value === false) {
|
if (prompt.value.trim().length === 0 || canSend.value === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// 如果携带了文件,则串上文件地址
|
||||||
|
let content = prompt.value
|
||||||
|
if (files.value.length > 0) {
|
||||||
|
content = files.value.map(file => file.url).join(" ") + " " + content
|
||||||
|
}
|
||||||
// 追加消息
|
// 追加消息
|
||||||
chatData.value.push({
|
chatData.value.push({
|
||||||
type: "prompt",
|
type: "prompt",
|
||||||
id: randString(32),
|
id: randString(32),
|
||||||
icon: loginUser.value.avatar,
|
icon: loginUser.value.avatar,
|
||||||
content: md.render(processPrompt(prompt.value)),
|
content: content,
|
||||||
created_at: new Date().getTime() / 1000,
|
created_at: new Date().getTime() / 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -758,9 +770,10 @@ const sendMessage = function () {
|
|||||||
|
|
||||||
showHello.value = false
|
showHello.value = false
|
||||||
disableInput(false)
|
disableInput(false)
|
||||||
socket.value.send(JSON.stringify({type: "chat", content: prompt.value}));
|
socket.value.send(JSON.stringify({type: "chat", content: content}));
|
||||||
tmpChatTitle.value = prompt.value
|
tmpChatTitle.value = content
|
||||||
prompt.value = '';
|
prompt.value = ''
|
||||||
|
files.value = []
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -810,9 +823,11 @@ const loadChatHistory = function (chatId) {
|
|||||||
showHello.value = false
|
showHello.value = false
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
data[i].orgContent = data[i].content;
|
data[i].orgContent = data[i].content;
|
||||||
data[i].content = md.render(processContent(data[i].content))
|
if (data[i].type === 'reply') {
|
||||||
if (i > 0 && data[i].type === 'reply') {
|
data[i].content = md.render(processContent(data[i].content))
|
||||||
data[i].prompt = data[i - 1].orgContent
|
if (i > 0) {
|
||||||
|
data[i].prompt = data[i - 1].orgContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
chatData.value.push(data[i]);
|
chatData.value.push(data[i]);
|
||||||
}
|
}
|
||||||
@ -893,9 +908,13 @@ const notShow = () => {
|
|||||||
showNotice.value = false
|
showNotice.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 插入文件路径
|
const files = ref([])
|
||||||
const insertURL = (url) => {
|
// 插入文件
|
||||||
prompt.value += " " + url + " "
|
const insertFile = (file) => {
|
||||||
|
files.value.push(file)
|
||||||
|
}
|
||||||
|
const removeFile = (file) => {
|
||||||
|
files.value = removeArrayItem(files.value, file, (v1,v2) => v1.url===v2.url)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user