mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	feat: chat export function is ready
This commit is contained in:
		@@ -10,44 +10,6 @@ import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// List 获取会话列表
 | 
			
		||||
func (h *ChatHandler) List(c *gin.Context) {
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	if userId == 0 {
 | 
			
		||||
		resp.ERROR(c, "The parameter 'user_id' is needed.")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var items = make([]vo.ChatItem, 0)
 | 
			
		||||
	var chats []model.ChatItem
 | 
			
		||||
	res := h.db.Where("user_id = ?", userId).Order("id DESC").Find(&chats)
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		var roleIds = make([]uint, 0)
 | 
			
		||||
		for _, chat := range chats {
 | 
			
		||||
			roleIds = append(roleIds, chat.RoleId)
 | 
			
		||||
		}
 | 
			
		||||
		var roles []model.ChatRole
 | 
			
		||||
		res = h.db.Find(&roles, roleIds)
 | 
			
		||||
		if res.Error == nil {
 | 
			
		||||
			roleMap := make(map[uint]model.ChatRole)
 | 
			
		||||
			for _, role := range roles {
 | 
			
		||||
				roleMap[role.Id] = role
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, chat := range chats {
 | 
			
		||||
				var item vo.ChatItem
 | 
			
		||||
				err := utils.CopyObject(chat, &item)
 | 
			
		||||
				if err == nil {
 | 
			
		||||
					item.Id = chat.Id
 | 
			
		||||
					item.Icon = roleMap[chat.RoleId].Icon
 | 
			
		||||
					items = append(items, item)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c, items)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Update 更新会话标题
 | 
			
		||||
func (h *ChatHandler) Update(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										71
									
								
								api/handler/chat_item_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								api/handler/chat_item_handler.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
package handler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
	"chatplus/store/vo"
 | 
			
		||||
	"chatplus/utils"
 | 
			
		||||
	"chatplus/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// List 获取会话列表
 | 
			
		||||
func (h *ChatHandler) List(c *gin.Context) {
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	if userId == 0 {
 | 
			
		||||
		resp.ERROR(c, "The parameter 'user_id' is needed.")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var items = make([]vo.ChatItem, 0)
 | 
			
		||||
	var chats []model.ChatItem
 | 
			
		||||
	res := h.db.Where("user_id = ?", userId).Order("id DESC").Find(&chats)
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		var roleIds = make([]uint, 0)
 | 
			
		||||
		for _, chat := range chats {
 | 
			
		||||
			roleIds = append(roleIds, chat.RoleId)
 | 
			
		||||
		}
 | 
			
		||||
		var roles []model.ChatRole
 | 
			
		||||
		res = h.db.Find(&roles, roleIds)
 | 
			
		||||
		if res.Error == nil {
 | 
			
		||||
			roleMap := make(map[uint]model.ChatRole)
 | 
			
		||||
			for _, role := range roles {
 | 
			
		||||
				roleMap[role.Id] = role
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, chat := range chats {
 | 
			
		||||
				var item vo.ChatItem
 | 
			
		||||
				err := utils.CopyObject(chat, &item)
 | 
			
		||||
				if err == nil {
 | 
			
		||||
					item.Id = chat.Id
 | 
			
		||||
					item.Icon = roleMap[chat.RoleId].Icon
 | 
			
		||||
					items = append(items, item)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c, items)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *ChatHandler) Detail(c *gin.Context) {
 | 
			
		||||
	chatId := h.GetTrim(c, "chat_id")
 | 
			
		||||
	if utils.IsEmptyValue(chatId) {
 | 
			
		||||
		resp.ERROR(c, "Invalid chatId")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var chatItem model.ChatItem
 | 
			
		||||
	res := h.db.Where("chat_id = ?", chatId).First(&chatItem)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "No chat found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var chatItemVo vo.ChatItem
 | 
			
		||||
	err := utils.CopyObject(chatItem, &chatItemVo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, chatItemVo)
 | 
			
		||||
}
 | 
			
		||||
@@ -169,6 +169,7 @@ func main() {
 | 
			
		||||
			group := s.Engine.Group("/api/chat/")
 | 
			
		||||
			group.Any("new", h.ChatHandle)
 | 
			
		||||
			group.GET("list", h.List)
 | 
			
		||||
			group.GET("detail", h.Detail)
 | 
			
		||||
			group.POST("update", h.Update)
 | 
			
		||||
			group.GET("remove", h.Remove)
 | 
			
		||||
			group.GET("history", h.History)
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,12 @@ const routes = [
 | 
			
		||||
        meta: {title: 'ChatGPT-智能助手V3'},
 | 
			
		||||
        component: () => import('@/views/ChatPlus.vue'),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: 'chat-export',
 | 
			
		||||
        path: '/chat/export',
 | 
			
		||||
        meta: {title: '导出会话记录'},
 | 
			
		||||
        component: () => import('@/views/ChatExport.vue'),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: '/admin/login',
 | 
			
		||||
        name: 'admin-login',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										155
									
								
								web/src/views/ChatExport.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								web/src/views/ChatExport.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="chat-export" v-loading="loading">
 | 
			
		||||
    <div class="chat-box" id="chat-box">
 | 
			
		||||
      <div class="title">
 | 
			
		||||
        <h2>{{ chatTitle }}</h2>
 | 
			
		||||
        <el-button type="success" @click="exportChat" :icon="Promotion">
 | 
			
		||||
          导出 PDF 文档
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div v-for="item in chatData" :key="item.id">
 | 
			
		||||
        <chat-prompt
 | 
			
		||||
            v-if="item.type==='prompt'"
 | 
			
		||||
            :icon="item.icon"
 | 
			
		||||
            :created-at="dateFormat(item['created_at'])"
 | 
			
		||||
            :tokens="item['tokens']"
 | 
			
		||||
            :model="model"
 | 
			
		||||
            :content="item.content"/>
 | 
			
		||||
        <chat-reply v-else-if="item.type==='reply'"
 | 
			
		||||
                    :icon="item.icon"
 | 
			
		||||
                    :org-content="item.orgContent"
 | 
			
		||||
                    :created-at="dateFormat(item['created_at'])"
 | 
			
		||||
                    :tokens="item['tokens']"
 | 
			
		||||
                    :content="item.content"/>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div><!-- end chat box -->
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import ChatReply from "@/components/ChatReply.vue";
 | 
			
		||||
import ChatPrompt from "@/components/ChatPrompt.vue";
 | 
			
		||||
import {nextTick, ref} from "vue";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import 'highlight.js/styles/a11y-dark.css'
 | 
			
		||||
import hl from "highlight.js";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {Promotion} from "@element-plus/icons-vue";
 | 
			
		||||
 | 
			
		||||
const chatData = ref([])
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const chatId = router.currentRoute.value.query['chat_id']
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const chatTitle = ref('')
 | 
			
		||||
 | 
			
		||||
httpGet('/api/chat/history?chat_id=' + chatId).then(res => {
 | 
			
		||||
  const data = res.data
 | 
			
		||||
  if (!data) {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const md = require('markdown-it')({breaks: true});
 | 
			
		||||
  for (let i = 0; i < data.length; i++) {
 | 
			
		||||
    if (data[i].type === "prompt") {
 | 
			
		||||
      chatData.value.push(data[i]);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data[i].orgContent = data[i].content;
 | 
			
		||||
    data[i].content = md.render(data[i].content);
 | 
			
		||||
    chatData.value.push(data[i]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  nextTick(() => {
 | 
			
		||||
    hl.configure({ignoreUnescapedHTML: true})
 | 
			
		||||
    const blocks = document.querySelector("#chat-box").querySelectorAll('pre code');
 | 
			
		||||
    blocks.forEach((block) => {
 | 
			
		||||
      hl.highlightElement(block)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
  loading.value = false
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  ElMessage.error('加载聊天记录失败:' + e.message);
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
httpGet('/api/chat/detail?chat_id=' + chatId).then(res => {
 | 
			
		||||
  chatTitle.value = res.data.title
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  ElMessage.error("加载会失败: " + e.message)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const exportChat = () => {
 | 
			
		||||
  window.print()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
.chat-export {
 | 
			
		||||
  display flex
 | 
			
		||||
  justify-content center
 | 
			
		||||
 | 
			
		||||
  .chat-box {
 | 
			
		||||
    max-width 800px;
 | 
			
		||||
    // 变量定义
 | 
			
		||||
    --content-font-size: 16px;
 | 
			
		||||
    --content-color: #c1c1c1;
 | 
			
		||||
 | 
			
		||||
    font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
 | 
			
		||||
    padding: 0 0 50px 0;
 | 
			
		||||
 | 
			
		||||
    .title {
 | 
			
		||||
      text-align center
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    .chat-line {
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: flex-start;
 | 
			
		||||
 | 
			
		||||
      .chat-line-inner {
 | 
			
		||||
        .content {
 | 
			
		||||
          padding-top: 0
 | 
			
		||||
          font-size 16px;
 | 
			
		||||
 | 
			
		||||
          p:first-child {
 | 
			
		||||
            margin-top 0
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .chat-line-reply {
 | 
			
		||||
      padding-top: 1.5rem;
 | 
			
		||||
 | 
			
		||||
      .chat-line-inner {
 | 
			
		||||
        display flex
 | 
			
		||||
 | 
			
		||||
        .copy-reply {
 | 
			
		||||
          display none
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .bar-item {
 | 
			
		||||
          background-color: #f7f7f8;
 | 
			
		||||
          color: #888;
 | 
			
		||||
          padding: 3px 5px;
 | 
			
		||||
          margin-right: 10px;
 | 
			
		||||
          border-radius: 5px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .chat-icon {
 | 
			
		||||
          margin-right: 20px
 | 
			
		||||
 | 
			
		||||
          img {
 | 
			
		||||
            width 30px
 | 
			
		||||
            height 30px
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -779,8 +779,10 @@ const exportChat = () => {
 | 
			
		||||
  if (!activeChat.value['chat_id']) {
 | 
			
		||||
    return ElMessage.error("请先选中一个会话")
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  window.open(location.protocol + location.host + '/chat/export?chat_id=' + activeChat.value['chat_id'], '_blank');
 | 
			
		||||
 | 
			
		||||
  const url = location.protocol + '//' + location.host + '/chat/export?chat_id=' + activeChat.value['chat_id']
 | 
			
		||||
  // console.log(url)
 | 
			
		||||
  window.open(url, '_blank');
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user