mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
feat: chat with file function is ready
This commit is contained in:
parent
3fdcc895ed
commit
a27ce36a32
@ -323,20 +323,46 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
reqMgs = append(reqMgs, m)
|
reqMgs = append(reqMgs, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fullPrompt := prompt
|
||||||
|
text := prompt
|
||||||
|
// extract files in prompt
|
||||||
|
files := utils.ExtractFileURLs(prompt)
|
||||||
|
logger.Debugf("detected FILES: %+v", files)
|
||||||
|
if len(files) > 0 {
|
||||||
|
contents := make([]string, 0)
|
||||||
|
var file model.File
|
||||||
|
for _, v := range files {
|
||||||
|
h.DB.Where("url = ?", v).First(&file)
|
||||||
|
content, err := utils.ReadFileContent(v)
|
||||||
|
if err == nil {
|
||||||
|
contents = append(contents, fmt.Sprintf("%s 文件内容:%s", file.Name, content))
|
||||||
|
}
|
||||||
|
text = strings.Replace(text, v, "", 1)
|
||||||
|
}
|
||||||
|
if len(contents) > 0 {
|
||||||
|
fullPrompt = fmt.Sprintf("请根据提供的文件内容信息回答问题(其中Excel 已转成 HTML):\n\n %s\n\n 问题:%s", strings.Join(contents, "\n"), text)
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens, _ := utils.CalcTokens(fullPrompt, req.Model)
|
||||||
|
if tokens > session.Model.MaxContext {
|
||||||
|
return fmt.Errorf("文件的长度超出模型允许的最大上下文长度,请减少文件内容数量或文件大小。")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Debug("最终Prompt:", fullPrompt)
|
||||||
|
|
||||||
if session.Model.Platform == types.QWen.Value {
|
if session.Model.Platform == types.QWen.Value {
|
||||||
req.Input = make(map[string]interface{})
|
req.Input = make(map[string]interface{})
|
||||||
reqMgs = append(reqMgs, types.Message{
|
reqMgs = append(reqMgs, types.Message{
|
||||||
Role: "user",
|
Role: "user",
|
||||||
Content: prompt,
|
Content: fullPrompt,
|
||||||
})
|
})
|
||||||
req.Input["messages"] = reqMgs
|
req.Input["messages"] = reqMgs
|
||||||
} else if session.Model.Platform == types.OpenAI.Value || session.Model.Platform == types.Azure.Value { // extract image for gpt-vision model
|
} else if session.Model.Platform == types.OpenAI.Value || session.Model.Platform == types.Azure.Value { // extract image for gpt-vision model
|
||||||
imgURLs := utils.ExtractImgURL(prompt)
|
imgURLs := utils.ExtractImgURLs(prompt)
|
||||||
logger.Debugf("detected IMG: %+v", imgURLs)
|
logger.Debugf("detected IMG: %+v", imgURLs)
|
||||||
var content interface{}
|
var content interface{}
|
||||||
if len(imgURLs) > 0 {
|
if len(imgURLs) > 0 {
|
||||||
data := make([]interface{}, 0)
|
data := make([]interface{}, 0)
|
||||||
text := prompt
|
|
||||||
for _, v := range imgURLs {
|
for _, v := range imgURLs {
|
||||||
text = strings.Replace(text, v, "", 1)
|
text = strings.Replace(text, v, "", 1)
|
||||||
data = append(data, gin.H{
|
data = append(data, gin.H{
|
||||||
@ -352,7 +378,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
})
|
})
|
||||||
content = data
|
content = data
|
||||||
} else {
|
} else {
|
||||||
content = prompt
|
content = fullPrompt
|
||||||
}
|
}
|
||||||
req.Messages = append(reqMgs, map[string]interface{}{
|
req.Messages = append(reqMgs, map[string]interface{}{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
@ -361,7 +387,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
} else {
|
} else {
|
||||||
req.Messages = append(reqMgs, map[string]interface{}{
|
req.Messages = append(reqMgs, map[string]interface{}{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": prompt,
|
"content": fullPrompt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -454,7 +480,7 @@ func (h *ChatHandler) StopGenerate(c *gin.Context) {
|
|||||||
func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, session *types.ChatSession, apiKey *model.ApiKey) (*http.Response, error) {
|
func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, session *types.ChatSession, apiKey *model.ApiKey) (*http.Response, error) {
|
||||||
// if the chat model bind a KEY, use it directly
|
// if the chat model bind a KEY, use it directly
|
||||||
if session.Model.KeyId > 0 {
|
if session.Model.KeyId > 0 {
|
||||||
h.DB.Debug().Where("id", session.Model.KeyId).Where("enabled", true).Find(apiKey)
|
h.DB.Where("id", session.Model.KeyId).Find(apiKey)
|
||||||
}
|
}
|
||||||
// use the last unused key
|
// use the last unused key
|
||||||
if apiKey.Id == 0 {
|
if apiKey.Id == 0 {
|
||||||
|
@ -79,7 +79,7 @@ func (h *ChatHandler) sendXunFeiMessage(
|
|||||||
var res *gorm.DB
|
var res *gorm.DB
|
||||||
// use the bind key
|
// use the bind key
|
||||||
if session.Model.KeyId > 0 {
|
if session.Model.KeyId > 0 {
|
||||||
res = h.DB.Where("id", session.Model.KeyId).Where("enabled", true).Find(&apiKey)
|
res = h.DB.Where("id", session.Model.KeyId).Find(&apiKey)
|
||||||
}
|
}
|
||||||
// use the last unused key
|
// use the last unused key
|
||||||
if apiKey.Id == 0 {
|
if apiKey.Id == 0 {
|
||||||
|
@ -215,7 +215,7 @@ func (h *MarkMapHandler) doRequest(req types.ApiRequest, chatModel model.ChatMod
|
|||||||
// if the chat model bind a KEY, use it directly
|
// if the chat model bind a KEY, use it directly
|
||||||
var res *gorm.DB
|
var res *gorm.DB
|
||||||
if chatModel.KeyId > 0 {
|
if chatModel.KeyId > 0 {
|
||||||
res = h.DB.Where("id", chatModel.KeyId).Where("enabled", true).Find(apiKey)
|
res = h.DB.Where("id", chatModel.KeyId).Find(apiKey)
|
||||||
}
|
}
|
||||||
// use the last unused key
|
// use the last unused key
|
||||||
if apiKey.Id == 0 {
|
if apiKey.Id == 0 {
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
file := "http://nk.img.r9it.com/chatgpt-plus/1719389335351828.xlsx"
|
file := "http://nk.img.r9it.com/chatgpt-plus/1719389335351828.xlsx"
|
||||||
content, err := utils.ReadPdf(file)
|
content, err := utils.ReadFileContent(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ import (
|
|||||||
"github.com/google/go-tika/tika"
|
"github.com/google/go-tika/tika"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReadPdf(filePath string) (string, error) {
|
func ReadFileContent(filePath string) (string, error) {
|
||||||
|
// for remote file, download it first
|
||||||
if strings.HasPrefix(filePath, "http") {
|
if strings.HasPrefix(filePath, "http") {
|
||||||
file, err := downloadFile(filePath)
|
file, err := downloadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -31,22 +32,34 @@ func ReadPdf(filePath string) (string, error) {
|
|||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// 使用 Tika 提取 PDF 文件的文本内容
|
// 使用 Tika 提取 PDF 文件的文本内容
|
||||||
html, err := client.Parse(context.TODO(), file)
|
content, err := client.Parse(context.TODO(), file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error with parse file: %v", err)
|
return "", fmt.Errorf("error with parse file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(html)
|
ext := filepath.Ext(filePath)
|
||||||
|
switch ext {
|
||||||
return cleanBlankLine(html), nil
|
case ".doc", ".docx", ".pdf", ".pptx", "ppt":
|
||||||
|
return cleanBlankLine(cleanHtml(content, false)), nil
|
||||||
|
case ".xls", ".xlsx":
|
||||||
|
return cleanBlankLine(cleanHtml(content, true)), nil
|
||||||
|
default:
|
||||||
|
return cleanBlankLine(content), nil
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理文本内容
|
// 清理文本内容
|
||||||
func cleanHtml(html string) string {
|
func cleanHtml(html string, keepTable bool) string {
|
||||||
// 清理 HTML 标签
|
// 清理 HTML 标签
|
||||||
p := bluemonday.StrictPolicy()
|
var policy *bluemonday.Policy
|
||||||
return p.Sanitize(html)
|
if keepTable {
|
||||||
|
policy = bluemonday.NewPolicy()
|
||||||
|
policy.AllowElements("table", "thead", "tbody", "tfoot", "tr", "td", "th")
|
||||||
|
} else {
|
||||||
|
policy = bluemonday.StrictPolicy()
|
||||||
|
}
|
||||||
|
return policy.Sanitize(html)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanBlankLine(content string) string {
|
func cleanBlankLine(content string) string {
|
||||||
@ -57,6 +70,12 @@ func cleanBlankLine(content string) string {
|
|||||||
if len(line) < 2 {
|
if len(line) < 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// discard image
|
||||||
|
if strings.HasSuffix(line, ".png") ||
|
||||||
|
strings.HasSuffix(line, ".jpg") ||
|
||||||
|
strings.HasSuffix(line, ".jpeg") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
texts = append(texts, line)
|
texts = append(texts, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ func GetImgExt(filename string) string {
|
|||||||
return ext
|
return ext
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractImgURL(text string) []string {
|
func ExtractImgURLs(text string) []string {
|
||||||
re := regexp.MustCompile(`(http[s]?:\/\/.*?\.(?:png|jpg|jpeg|gif))`)
|
re := regexp.MustCompile(`(http[s]?:\/\/.*?\.(?:png|jpg|jpeg|gif))`)
|
||||||
matches := re.FindAllStringSubmatch(text, 10)
|
matches := re.FindAllStringSubmatch(text, 10)
|
||||||
urls := make([]string, 0)
|
urls := make([]string, 0)
|
||||||
@ -99,3 +99,15 @@ func ExtractImgURL(text string) []string {
|
|||||||
}
|
}
|
||||||
return urls
|
return urls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExtractFileURLs(text string) []string {
|
||||||
|
re := regexp.MustCompile(`(http[s]?:\/\/.*?\.(?:docx?|pdf|pptx?|xlsx?|txt))`)
|
||||||
|
matches := re.FindAllStringSubmatch(text, 10)
|
||||||
|
urls := make([]string, 0)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
for _, m := range matches {
|
||||||
|
urls = append(urls, m[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urls
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [
|
presets: [
|
||||||
'@vue/cli-plugin-babel/preset'
|
'@vue.css/cli-plugin-babel/preset'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
237
web/src/assets/css/markdown/vue.css
Normal file
237
web/src/assets/css/markdown/vue.css
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
.chat-line {
|
||||||
|
ol, ul {
|
||||||
|
margin: 0.8em 0;
|
||||||
|
list-style: normal;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #42b983;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0 2px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.4;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:hover a.anchor,
|
||||||
|
h2:hover a.anchor,
|
||||||
|
h3:hover a.anchor,
|
||||||
|
h4:hover a.anchor,
|
||||||
|
h5:hover a.anchor,
|
||||||
|
h6:hover a.anchor {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 tt,
|
||||||
|
h1 code {
|
||||||
|
font-size: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 tt,
|
||||||
|
h2 code {
|
||||||
|
font-size: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 tt,
|
||||||
|
h3 code {
|
||||||
|
font-size: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 tt,
|
||||||
|
h4 code {
|
||||||
|
font-size: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 tt,
|
||||||
|
h5 code {
|
||||||
|
font-size: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 tt,
|
||||||
|
h6 code {
|
||||||
|
font-size: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 a,
|
||||||
|
h3 a {
|
||||||
|
color: #34495e;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
padding-bottom: .4rem;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
line-height: 1.225;
|
||||||
|
margin: 35px 0 15px;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1.43;
|
||||||
|
margin: 20px 0 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
blockquote,
|
||||||
|
ul,
|
||||||
|
ol,
|
||||||
|
dl,
|
||||||
|
table {
|
||||||
|
margin: 0.8em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li > ol,
|
||||||
|
li > ul {
|
||||||
|
margin: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 2px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 16px 0;
|
||||||
|
background-color: #e7e7e7;
|
||||||
|
border: 0 none;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > h2:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > h1:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > h1:first-child + h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > h3:first-child,
|
||||||
|
body > h4:first-child,
|
||||||
|
body > h5:first-child,
|
||||||
|
body > h6:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:first-child h1,
|
||||||
|
a:first-child h2,
|
||||||
|
a:first-child h3,
|
||||||
|
a:first-child h4,
|
||||||
|
a:first-child h5,
|
||||||
|
a:first-child h6 {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 p,
|
||||||
|
h2 p,
|
||||||
|
h3 p,
|
||||||
|
h4 p,
|
||||||
|
h5 p,
|
||||||
|
h6 p {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li p.first {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul:first-child,
|
||||||
|
ol:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul:last-child,
|
||||||
|
ol:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid #42b983;
|
||||||
|
padding: 10px 15px;
|
||||||
|
color: #777;
|
||||||
|
background-color: rgba(66, 185, 131, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
padding: 0;
|
||||||
|
word-break: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr {
|
||||||
|
border-top: 1px solid #dfe2e5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:nth-child(2n),
|
||||||
|
thead {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr th {
|
||||||
|
font-weight: bold;
|
||||||
|
border: 1px solid #dfe2e5;
|
||||||
|
border-bottom: 0;
|
||||||
|
text-align: left;
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr td {
|
||||||
|
border: 1px solid #dfe2e5;
|
||||||
|
text-align: left;
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr th:first-child,
|
||||||
|
table tr td:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr th:last-child,
|
||||||
|
table tr td:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,9 @@
|
|||||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" />
|
<el-image :src="GetFileIcon(file.ext)" fit="cover" />
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="title">{{file.name}}</div>
|
<div class="title">
|
||||||
|
<el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{file.name}}</el-link>
|
||||||
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span>{{GetFileType(file.ext)}}</span>
|
<span>{{GetFileType(file.ext)}}</span>
|
||||||
<span>{{FormatFileSize(file.size)}}</span>
|
<span>{{FormatFileSize(file.size)}}</span>
|
||||||
@ -121,6 +123,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
|
@import '@/assets/css/markdown/vue.css';
|
||||||
.chat-line-prompt {
|
.chat-line-prompt {
|
||||||
background-color #ffffff;
|
background-color #ffffff;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -214,11 +217,6 @@ onMounted(() => {
|
|||||||
margin 10px 0
|
margin 10px 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
a {
|
|
||||||
color #20a0ff
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
line-height 1.5
|
line-height 1.5
|
||||||
}
|
}
|
||||||
|
@ -97,6 +97,7 @@ const reGenerate = (prompt) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
|
@import '@/assets/css/markdown/vue.css';
|
||||||
.common-layout {
|
.common-layout {
|
||||||
.chat-line-reply {
|
.chat-line-reply {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -132,18 +133,12 @@ const reGenerate = (prompt) => {
|
|||||||
.content {
|
.content {
|
||||||
min-height 20px;
|
min-height 20px;
|
||||||
word-break break-word;
|
word-break break-word;
|
||||||
padding: 6px 10px;
|
padding: 0 10px;
|
||||||
color #374151;
|
color #374151;
|
||||||
font-size: var(--content-font-size);
|
font-size: var(--content-font-size);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
overflow auto;
|
overflow auto;
|
||||||
|
|
||||||
a {
|
|
||||||
color #20a0ff
|
|
||||||
}
|
|
||||||
|
|
||||||
// control the image size in content
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@ -170,10 +165,11 @@ const reGenerate = (prompt) => {
|
|||||||
|
|
||||||
.code-container {
|
.code-container {
|
||||||
position relative
|
position relative
|
||||||
|
display flex
|
||||||
|
|
||||||
.hljs {
|
.hljs {
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
line-height 1.5
|
width 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-code-btn {
|
.copy-code-btn {
|
||||||
@ -194,7 +190,7 @@ const reGenerate = (prompt) => {
|
|||||||
.lang-name {
|
.lang-name {
|
||||||
position absolute;
|
position absolute;
|
||||||
right 10px
|
right 10px
|
||||||
bottom 50px
|
bottom 20px
|
||||||
padding 2px 6px 4px 6px
|
padding 2px 6px 4px 6px
|
||||||
background-color #444444
|
background-color #444444
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
:auto-upload="true"
|
:auto-upload="true"
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
:http-request="afterRead"
|
:http-request="afterRead"
|
||||||
|
accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf"
|
||||||
>
|
>
|
||||||
<el-icon class="avatar-uploader-icon">
|
<el-icon class="avatar-uploader-icon">
|
||||||
<Plus/>
|
<Plus/>
|
||||||
|
@ -188,38 +188,7 @@ export function processContent(content) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return content
|
||||||
const lines = content.split("\n")
|
|
||||||
if (lines.length <= 1) {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
const texts = []
|
|
||||||
// 定义匹配数学公式的正则表达式
|
|
||||||
const formulaRegex = /^\s*[a-z|A-Z]+[^=]+\s*=\s*[^=]+$/;
|
|
||||||
let hasCode = false
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
// 处理引用块换行
|
|
||||||
if (lines[i].startsWith(">")) {
|
|
||||||
texts.push(lines[i])
|
|
||||||
texts.push("\n")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 如果包含代码块则跳过公式检测
|
|
||||||
if (lines[i].indexOf("```") !== -1) {
|
|
||||||
texts.push(lines[i])
|
|
||||||
hasCode = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 识别并处理数学公式,需要排除那些已经被识别出来的公式
|
|
||||||
if (i > 0 && formulaRegex.test(lines[i]) && lines[i - 1].indexOf("$$") === -1 && !hasCode) {
|
|
||||||
texts.push("$$")
|
|
||||||
texts.push(lines[i])
|
|
||||||
texts.push("$$")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
texts.push(lines[i])
|
|
||||||
}
|
|
||||||
return texts.join("\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processPrompt(prompt) {
|
export function processPrompt(prompt) {
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<div class="content" :style="{height: leftBoxHeight+'px'}">
|
<div class="content" :style="{height: leftBoxHeight+'px'}">
|
||||||
<el-row v-for="chat in chatList" :key="chat.chat_id">
|
<el-row v-for="chat in chatList" :key="chat.chat_id">
|
||||||
<div :class="chat.chat_id === activeChat.chat_id?'chat-list-item active':'chat-list-item'"
|
<div :class="chat.chat_id === activeChat.chat_id?'chat-list-item active':'chat-list-item'"
|
||||||
@click="changeChat(chat)">
|
@click="loadChat(chat)">
|
||||||
<el-image :src="chat.icon" class="avatar"/>
|
<el-image :src="chat.icon" class="avatar"/>
|
||||||
<span class="chat-title-input" v-if="chat.edit">
|
<span class="chat-title-input" v-if="chat.edit">
|
||||||
<el-input v-model="tmpChatTitle" size="small" @keydown="titleKeydown($event, chat)"
|
<el-input v-model="tmpChatTitle" size="small" @keydown="titleKeydown($event, chat)"
|
||||||
@ -424,12 +424,8 @@ const newChat = () => {
|
|||||||
connect(null, roleId.value)
|
connect(null, roleId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换会话
|
|
||||||
const changeChat = (chat) => {
|
|
||||||
localStorage.setItem("chat_id", chat.chat_id)
|
|
||||||
loadChat(chat)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 切换会话
|
||||||
const loadChat = function (chat) {
|
const loadChat = function (chat) {
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
store.setShowLoginDialog(true)
|
store.setShowLoginDialog(true)
|
||||||
@ -753,7 +749,7 @@ const sendMessage = function () {
|
|||||||
// 如果携带了文件,则串上文件地址
|
// 如果携带了文件,则串上文件地址
|
||||||
let content = prompt.value
|
let content = prompt.value
|
||||||
if (files.value.length > 0) {
|
if (files.value.length > 0) {
|
||||||
content = files.value.map(file => file.url).join(" ") + " " + content
|
content += files.value.map(file => file.url).join(" ")
|
||||||
}
|
}
|
||||||
// 追加消息
|
// 追加消息
|
||||||
chatData.value.push({
|
chatData.value.push({
|
||||||
|
@ -162,7 +162,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="绑定API-KEY:" prop="apikey">
|
<el-form-item label="绑定API-KEY:" prop="apikey">
|
||||||
<el-select v-model="item.key_id" placeholder="请选择 API KEY" clearable>
|
<el-select v-model="item.key_id" placeholder="请选择 API KEY" filterable clearable>
|
||||||
<el-option v-for="v in apiKeys" :value="v.id" :label="v.name" :key="v.id">
|
<el-option v-for="v in apiKeys" :value="v.id" :label="v.name" :key="v.id">
|
||||||
{{ v.name }}
|
{{ v.name }}
|
||||||
<el-text type="info" size="small">{{ substr(v.api_url, 50) }}</el-text>
|
<el-text type="info" size="small">{{ substr(v.api_url, 50) }}</el-text>
|
||||||
@ -229,7 +229,7 @@ const platforms = ref([])
|
|||||||
|
|
||||||
// 获取 API KEY
|
// 获取 API KEY
|
||||||
const apiKeys = ref([])
|
const apiKeys = ref([])
|
||||||
httpGet('/api/admin/apikey/list?status=true&type=chat').then(res => {
|
httpGet('/api/admin/apikey/list?type=chat').then(res => {
|
||||||
apiKeys.value = res.data
|
apiKeys.value = res.data
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
ElMessage.error("获取 API KEY 失败:" + e.message)
|
ElMessage.error("获取 API KEY 失败:" + e.message)
|
||||||
|
Loading…
Reference in New Issue
Block a user