Browse Source

feat:增加知识库(暂停开发);优化会话、角色、文生图;

dxj
李朋徽 1 year ago
parent
commit
343af5a5c8
  1. 13
      src/api/base/file.ts
  2. 41
      src/api/base/message.ts
  3. 9
      src/api/base/role.ts
  4. BIN
      src/assets/images/conversation/default_img2.png
  5. BIN
      src/assets/images/conversation/default_img3.png
  6. 22
      src/assets/svg/file.svg
  7. 11
      src/assets/svg/image.svg
  8. 14
      src/assets/svg/repository.svg
  9. 12
      src/assets/svg/visual_analysis.svg
  10. 17
      src/components/AppConversationDefault/index.d.ts
  11. 102
      src/components/AppConversationDefault/index.vue
  12. 70
      src/components/AppMessage/index.vue
  13. 2
      src/components/AppRoleDefault/index.d.ts
  14. 6
      src/components/AppRoleDefault/index.vue
  15. 1
      src/components/AppSubMenuList/index.vue
  16. 3
      src/components/AppTextToPictureDefault/index.d.ts
  17. 3
      src/components/AppTextToPictureDefault/index.ts
  18. 104
      src/components/AppTextToPictureDefault/index.vue
  19. 79
      src/components/AppTextarea/index.vue
  20. 4
      src/components/AppTopPicks/index.vue
  21. 11
      src/enums/messageEnum.ts
  22. 2
      src/hooks/useMqtt.ts
  23. 13
      src/layout/AppMenu/index.vue
  24. 16
      src/router/index.ts
  25. 7
      src/utils/axios/index.ts
  26. 114
      src/views/conversation/index.vue
  27. 360
      src/views/repository/index.vue
  28. 128
      src/views/role/index.vue
  29. 109
      src/views/textToImage/index.vue

13
src/api/base/file.ts

@ -0,0 +1,13 @@
import { defHttp } from '@/utils/axios/index'
import type { AxiosProgressEvent } from 'axios'
import type { UploadFileParams } from '/#/axios'
export function uploadApi(params: UploadFileParams, onUploadProgress: (progressEvent: AxiosProgressEvent) => void) {
return defHttp.uploadFile(
{
url: '/qn/iot-system/oss/endpoint/put-file',
onUploadProgress,
},
params,
)
}

41
src/api/base/message.ts

@ -1,5 +1,6 @@
import { defHttp } from '@/utils/axios/index'
import type { MenuTypeEnum } from '@/enums/menuEnum'
import type { ModelTypeEnum } from '@/enums/messageEnum'
/**
* @description
@ -7,6 +8,8 @@ import type { MenuTypeEnum } from '@/enums/menuEnum'
export async function addMessage(data: {
type: MenuTypeEnum
title: string
sort: number
roleId?: string
}) {
return defHttp.post({
url: `/open-chat/chat/conversation/save`,
@ -42,6 +45,15 @@ export async function conversationList(type: number) {
})
}
/**
* @description
*/
export async function conversationToTop(conversationId: string) {
return defHttp.post({
url: `/open-chat/chat/conversation/top?id=${conversationId}`,
})
}
/**
* @description
*/
@ -56,17 +68,28 @@ export async function historyMessage(params: {
})
}
/**
* @description
*/
export async function stopMessage(data: { conversationId: string }) {
return defHttp.post({
url: `/open-chat/chat/stopGenerate`,
data,
})
}
/**
* @description
*/
export async function sendTextToText(data: {
conversationId: string
question: string
modelType: ModelTypeEnum
}) {
return defHttp.post({
url: `/open-chat/chat/session`,
data,
timeout: 30 * 1000,
timeout: 60 * 1000,
})
}
@ -80,7 +103,21 @@ export async function sendTextToImage(data: {
return defHttp.post({
url: `/open-gpts/gpts/getDallEImages`,
data,
timeout: 30 * 1000,
timeout: 60 * 1000,
})
}
/**
* @description
*/
export async function sendRepository(data: {
conversationId: string
question: string
}) {
return defHttp.post({
url: `/open-gpts/gpts/getQanythingStreamChat`,
data,
timeout: 60 * 1000,
})
}

9
src/api/base/role.ts

@ -12,3 +12,12 @@ export function getAppList() {
url: `/open-chat/roleInfo/app/group/list`,
})
}
/**
* @description:
*/
export function getRoleDecs(roleId: string) {
return defHttp.get({
url: `/open-chat/roleInfo/getRoleDecs?id=${roleId}`,
})
}

BIN
src/assets/images/conversation/default_img2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

BIN
src/assets/images/conversation/default_img3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

22
src/assets/svg/file.svg

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="28px" height="24px" viewBox="0 0 28 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>wenjianjia 2</title>
<defs>
<path d="M2.5,21 C0.575000002,21 0,20.3914894 0,18.4255319 L0,2.57446807 C0,0.608510639 0.575000002,0 2.5,0 L12.25,0 C13.3,0 14.2625,0.625531901 14.7875,1.60851064 L15.4875,2.68085107 C16.0125,3.3957447 16.8875,3.84255318 17.7625,3.84255318 L25.5,3.84255318 C27.425,3.84255318 28,4.45106382 28,6.4170213 L28,18.4255319 C28,20.3914894 27.425,21 25.5,21 L2.5,21 Z" id="path-1"></path>
</defs>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="知识库" transform="translate(-143.000000, -809.000000)">
<g id="wenjianjia" transform="translate(143.000000, 809.000000)">
<path d="M8.43749999,22 C6.546875,22 5,20.3902439 5,18.4227642 L5,3.57723576 C5,1.6097561 5.546875,0 7.43749999,0 L22.84375,0 C25.6796875,0 27,0.414634146 27,3.36585366 L27,18.3333333 C27,20.3902439 25.453125,22 23.5625,22 L8.43749999,22 Z" id="路径" fill="#CBD9FF" fill-rule="nonzero" opacity="0.321847098"></path>
<path d="M22.699381,23 L5.64068582,23 C3.75006083,23 3,22.4401437 3,20.5544294 L3,4.65757479 C3,2.77186052 3.75006083,2 5.64068582,2 L22.4833096,2 C24.3739345,2 25,2.54068725 25,4.42640152 L25,20.162779 C25,22.0484933 24.590006,23 22.699381,23 Z" id="路径" fill="#C9D7FF" fill-rule="nonzero" opacity="0.602841332"></path>
<g id="编组-6" transform="translate(0.000000, 3.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="路径" fill="#FFFFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
<circle id="椭圆形" fill="#4D76E5" opacity="0.0949590774" mask="url(#mask-2)" cx="0" cy="0" r="23"></circle>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

11
src/assets/svg/image.svg

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="62px" height="62px" viewBox="0 0 62 62" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>shangchuantupian</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.500395275">
<g id="图像分析" transform="translate(-1116.000000, -850.000000)" fill="#4670E3" fill-rule="nonzero">
<g id="shangchuantupian" transform="translate(1116.000000, 850.000000)">
<path d="M59.0944836,45.424465 L51.7830083,45.424465 L51.7830083,38.1439162 C51.8016901,37.3875691 51.508173,36.656663 50.9709206,36.1216831 C50.4336681,35.5867031 49.6996573,35.2944277 48.9400973,35.3130304 C48.1805374,35.2944277 47.4465265,35.5867032 46.9092741,36.1216831 C46.3720217,36.656663 46.0785047,37.3875691 46.0971864,38.1439162 L46.0971864,45.8271851 L37.9808145,45.8271851 C37.2212545,45.8085824 36.4872436,46.1008579 35.9499912,46.6358378 C35.4127387,47.1708177 35.1192217,47.9017238 35.1379035,48.6580709 C35.1192217,49.4144181 35.4127387,50.1453242 35.9499912,50.6803041 C36.4872436,51.215284 37.2212545,51.5075595 37.9808145,51.4889567 L45.6927555,51.4889567 L45.6927555,59.1682774 C45.6740738,59.9246246 45.9675908,60.6555307 46.5048432,61.1905106 C47.0420957,61.7254906 47.7761065,62.017766 48.5356665,61.9991633 C49.2952265,62.017766 50.0292373,61.7254905 50.5664898,61.1905106 C51.1037422,60.6555306 51.3972592,59.9246246 51.3785775,59.1682774 L51.3785775,51.4889567 L59.0944836,51.4889567 C59.8540436,51.5075595 60.5880544,51.215284 61.1253068,50.6803041 C61.6625593,50.1453241 61.9560763,49.4144181 61.9373945,48.6580709 C62.3418254,47.0392939 60.716172,45.4205167 59.0944836,45.4205167 L59.0944836,45.424465 Z M30.609864,52.7721337 L5.18623087,52.7721337 C4.74512045,52.7819846 4.31919592,52.6115568 4.00758008,52.3005132 C3.69596424,51.9894696 3.52583569,51.5649387 3.53678741,51.125719 L3.53678741,5.16034558 C3.53678741,4.22066518 4.24652388,3.51787903 5.18623087,3.51787903 L50.2406201,3.51787903 C51.1803271,3.51787903 51.8860985,4.22066518 51.8860985,5.16034558 L51.8860985,30.0105483 C51.8860985,30.9502287 52.5998,31.6530149 53.539507,31.6530149 C53.9799128,31.6628245 54.4052033,31.4929313 54.7166951,31.1827571 C55.0281869,30.8725829 55.1988017,30.4490912 55.1893545,30.0105483 L55.1893545,3.51787903 C55.1388646,1.59451933 53.5837478,0.0477164592 51.652163,0 L3.53678741,0 C1.60520263,0.0477164592 0.0500857924,1.59451933 0,3.51787903 L0,52.7721337 C0,54.6514945 1.64944347,56.2944568 3.53678741,56.2944568 L30.609864,56.2944568 C31.0509494,56.3048664 31.4772837,56.1354575 31.7896486,55.8251597 C32.1020135,55.514862 32.2731652,55.090739 32.2632725,54.6514945 C32.2632725,53.4749199 31.553536,52.7721337 30.609864,52.7721337 L30.609864,52.7721337 Z M30.2649083,44.6150764 C31.4821659,42.1869107 34.3250768,40.5720819 37.1640227,40.5720819 L40.4153295,40.5720819 L40.4153295,36.9318075 C40.4153295,33.6942533 42.4454137,30.8673157 45.2922898,29.655207 L41.2281563,17.5222751 L27.0175665,35.7196988 L22.953433,29.2485386 L7.9300166,44.6190247 L30.2649083,44.6190247 L30.2649083,44.6150764 Z M22.953433,18.7343839 C22.953433,16.3101664 20.9233488,14.2886692 18.4888337,14.2886692 C16.0503536,14.2886692 14.0202693,16.3101664 14.0202693,18.7343839 C14.0202693,21.1625495 16.0503536,23.1840468 18.4888337,23.1840468 C20.9233488,23.1840468 22.953433,21.1625495 22.953433,18.7343839 L22.953433,18.7343839 Z" id="形状"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

14
src/assets/svg/repository.svg

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>zhishiku07</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="知识库" transform="translate(-40.000000, -689.000000)">
<g id="形状-3" transform="translate(40.000000, 136.000000)">
<g id="zhishiku07" transform="translate(0.000000, 553.000000)">
<rect id="矩形" x="0" y="0" width="28" height="28"></rect>
<path d="M5.65967513,3.70661898 L1.11172191,3.70661898 C0.505328145,3.70661898 0,4.20751344 0,4.80858676 L0,26.8980322 C0,27.4991055 0.505328145,28 1.11172191,28 L5.65967513,28 C6.26606889,28 6.77139701,27.4991055 6.77139701,26.8980322 L6.77139701,4.80858676 C6.77139701,4.20751342 6.26606886,3.70661898 5.65967513,3.70661898 Z M4.39635478,8.21466907 L2.37504223,8.21466907 C1.81918129,8.21466907 1.36438597,7.76386406 1.36438597,7.21288014 C1.36438597,6.66189622 1.81918129,6.21109124 2.37504223,6.21109124 L4.39635478,6.21109124 C4.95221572,6.21109124 5.40701104,6.66189625 5.40701104,7.21288014 C5.40701104,7.76386403 4.95221572,8.21466907 4.39635478,8.21466907 Z M14.7050487,0 L10.1570955,0 C9.55070176,0 9.04537362,0.500894464 9.04537362,1.10196781 L9.04537362,26.8980322 C9.04537362,27.4991055 9.55070176,28 10.1570955,28 L14.7050487,28 C15.3114425,28 15.8167707,27.4991055 15.8167707,26.8980322 L15.8167707,1.10196781 C15.8673034,0.500894464 15.3619753,0 14.7050487,0 Z M13.4922612,4.55813954 L11.4709487,4.55813954 C10.9150877,4.55813954 10.4602924,4.10733453 10.4602924,3.55635064 C10.4602924,3.00536675 10.9150877,2.55456172 11.4709487,2.55456172 L13.4922612,2.55456172 C14.0481222,2.55456172 14.5029175,3.00536673 14.5029175,3.55635064 C14.5029175,4.10733456 14.0481222,4.55813954 13.4922612,4.55813954 Z M26.9845224,25.745975 L22.7397661,4.05724508 C22.5881677,3.50626119 22.1333723,3.15563506 21.5775114,3.15563506 L21.3753801,3.15563506 L16.9284925,4.00715565 C16.3220988,4.10733453 15.9178363,4.70840788 16.0189019,5.30948122 L20.314191,27.0483005 C20.4152567,27.5992844 20.870052,27.9499105 21.425913,27.9499105 L21.6280442,27.9499105 L26.0749318,27.09839 C26.6813256,26.9481216 27.0855881,26.3470483 26.9845224,25.745975 L26.9845224,25.745975 Z M21.1732489,7.46332739 L19.1519363,7.86404294 L18.9498051,7.86404294 C18.4950098,7.86404294 18.0402144,7.51341681 17.9391488,7.0626118 C17.8380832,6.51162791 18.1918129,5.960644 18.7476738,5.86046511 L20.7689864,5.45974956 C21.3248473,5.35957068 21.8807083,5.71019678 21.9817739,6.2611807 C22.0828395,6.81216459 21.7291098,7.36314851 21.1732489,7.46332739 L21.1732489,7.46332739 Z" id="形状" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

12
src/assets/svg/visual_analysis.svg

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>tuxiangtianjia</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="知识库" transform="translate(-40.000000, -789.000000)">
<g id="tuxiangtianjia" transform="translate(40.000000, 789.000000)">
<rect id="矩形" x="0" y="0" width="28" height="28"></rect>
<path d="M23.8099666,1 C24.4964071,1 25.0525743,1.54430677 25.0525743,2.21459913 L25.0525743,13.6499337 C24.2479264,13.3723933 23.4006882,13.2309238 22.5473169,13.2316126 L22.5473169,3.4463225 L2.50525745,3.4463225 L2.50651006,20.5705801 L14.1459361,9.20374253 C14.591881,8.76763483 15.3004457,8.72360508 15.799406,9.10099699 L15.9159005,9.2049657 L20.3577219,13.5471882 C18.3951254,14.1311606 16.7621187,15.4728487 15.8372965,17.2611969 C14.9124744,19.0495451 14.7761554,21.1292395 15.4599436,23.0181258 L1.2426077,23.0169026 C0.556131847,23.0162276 0,22.4726307 0,21.8023035 L0,2.21459913 C0.00476979396,1.54620943 0.558120835,1.00533101 1.2426077,1 L23.8099666,1 L23.8099666,1 Z M7.51577229,5.89264503 C8.89938775,5.89264503 10.0210297,6.98790092 10.0210297,8.33896753 C10.0210297,9.69003414 8.89938775,10.78529 7.51577229,10.78529 C6.13215684,10.78529 5.01051492,9.69003412 5.01051492,8.33896753 C5.01051492,6.98790094 6.13215684,5.89264503 7.51577229,5.89264503 L7.51577229,5.89264503 Z M22.2240578,14.9789858 C25.1698198,14.9789858 27.5578317,17.3259627 27.5578317,20.2211054 C27.5578317,21.6002915 27.0159029,22.8550784 26.1299115,23.7909998 L27.6896941,25.2712407 C28.0886203,25.6498594 28.1050828,26.2801841 27.726464,26.6791103 L27.7144814,26.6915186 L27.7144814,26.6915186 L27.6931555,26.7132227 C27.3108115,27.102348 26.687146,27.1132325 26.2914566,26.7376858 L24.5419389,25.077228 C24.5106323,25.0475151 24.4816812,25.016252 24.4550916,24.9836723 C23.7769191,25.2914929 23.0208942,25.4632251 22.2240578,25.4632251 C19.2782959,25.4632251 16.890284,23.1162482 16.890284,20.2211054 C16.890284,17.3259627 19.2782959,14.9789858 22.2240578,14.9789858 Z M22.2240578,16.726359 C20.2602165,16.726359 18.6682086,18.2910103 18.6682086,20.2211054 C18.6682086,22.1512006 20.2602165,23.7158519 22.2240578,23.7158519 C24.1878992,23.7158519 25.7799071,22.1512006 25.7799071,20.2211054 C25.7799071,18.2910103 24.1878992,16.726359 22.2240578,16.726359 Z" id="形状" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

17
src/components/AppConversationDefault/index.d.ts vendored

@ -1,3 +1,18 @@
import type { TopPickItem } from '@/components/AppTopPicks/index.d'
import type { PictureType } from '@/components/AppPicture/index.d'
export interface LeadData {
title: string
subTitles: string[]
image: string
}
export interface Props {
height?: string
isPick?: boolean
isHot?: boolean
height: string
leadData?: LeadData
topPickList?: TopPickItem[]
roleList?: PictureType[]
applyList?: PictureType[]
}

102
src/components/AppConversationDefault/index.vue

@ -1,9 +1,7 @@
<!-- 会话默认对话组件 -->
<script setup lang="ts">
import { ref } from 'vue'
import type { Props } from './index.d'
import DefaultImage from '@/assets/images/conversation/default_img.png'
import TestImg from '@/assets/images/a1.png'
import { SvgIcon } from '@/components/SvgIcon'
import { AppContentDefaultBox } from '@/components/AppContentDefaultBox'
import { AppDefaultLead } from '@/components/AppDefaultLead'
@ -11,64 +9,48 @@ import { AppModelSelect } from '@/components/AppModelSelect'
import { AppTopPicks } from '@/components/AppTopPicks'
import type { TopPickItem } from '@/components/AppTopPicks/index.d'
import { AppPicture } from '@/components/AppPicture'
import type { PictureType } from '@/components/AppPicture/index.d'
import { getAppList, getRole } from '@/api/base/role'
withDefaults(defineProps<Props>(), {
const props = withDefaults(defineProps<Props>(), {
isPick: false,
isHot: false,
height: '100%',
})
const leadData = {
title: '你好,我是青鸟语言大模型-同聪~',
subTitles: [
'我可以自由的跟你对话~陪你聊天~帮你想方案~答疑解惑。',
'你可以试着问我',
leadData: () => ({
title: '你好,我是青鸟语言大模型-同聪~',
subTitles: [
'我可以自由的跟你对话~陪你聊天~帮你想方案~答疑解惑。',
'你可以试着问我',
],
image: DefaultImage,
}),
topPickList: () => [
{
id: '1',
label: '写一首赞美祖国的诗',
},
{
id: '2',
label: '帮我指定一个路旅游计划',
},
{
id: '3',
label: 'AI会代替人类工作吗?',
},
{
id: '4',
label: '历史上的今天发生了什么?',
},
{
id: '5',
label: '红烧牛肉的做法',
},
],
image: DefaultImage,
}
const topPickList = ref<TopPickItem[]>([
{
id: '1',
label: '写一首赞美祖国的诗',
},
{
id: '2',
label: '帮我指定一个路旅游计划',
},
{
id: '3',
label: 'AI会代替人类工作吗?',
},
{
id: '4',
label: '历史上的今天发生了什么?',
},
{
id: '5',
label: '红烧牛肉的做法',
},
])
const roleList = ref<PictureType[]>([])
const applyList = ref<PictureType[]>([])
})
//
function getRoleData() {
getRole(1).then((res) => {
roleList.value = res
})
}
const emit = defineEmits(['handleHot', 'handlePick'])
//
function getAppData() {
getAppList().then((res) => {
applyList.value = res[0].roleInfoAppModelList
})
function handlePick(index: number, item: TopPickItem) {
emit('handlePick', index, item)
}
getRoleData()
getAppData()
</script>
<template>
@ -80,9 +62,15 @@ getAppData()
:sub-titles="leadData.subTitles"
:image="leadData.image"
></AppDefaultLead>
<AppTopPicks class="pl-20 mt-10" :list="topPickList"></AppTopPicks>
<AppTopPicks
v-if="props.isPick"
class="pl-20 mt-10"
:list="props.topPickList"
@change="handlePick"
>
</AppTopPicks>
<div class="pl-20 mt-10 flex justify-between items-end">
<div v-if="props.isHot" class="pl-20 mt-10 flex justify-between items-end">
<div class="category-box">
<div class="top flex items-center">
<p class="title">
@ -100,6 +88,7 @@ getAppData()
class="picture-item-role"
:name="item.roleName"
:image="item.roleImg"
@click="$emit('handleHot', index, item)"
></AppPicture>
</div>
</div>
@ -121,6 +110,7 @@ getAppData()
:name="item.roleName"
:image="item.roleImg"
type="entire"
@click="$emit('handleHot', index, item)"
></AppPicture>
</div>
</div>

70
src/components/AppMessage/index.vue

@ -15,6 +15,10 @@ const props = defineProps({
type: Array as PropType<MessageItem[]>,
default: () => [],
},
aiWidthType: {
type: String as PropType<'auto' | 'full'>,
default: 'full',
},
})
const emit = defineEmits(['onScrollTop', 'reloadMessage'])
@ -32,7 +36,9 @@ const conversationData = computed(() => messageStore.getConversationData)
watch(
() => props.list[props.list.length - 1],
() => {
isAutoScroll.value && scrollToBottom()
if (isAutoScroll.value) {
scrollToBottom()
}
},
{ immediate: true },
)
@ -60,8 +66,6 @@ function onScroll() {
}
// - > -1px
console.log(messageRef.value.scrollTop - defaultScrollTop.value)
if (messageRef.value.scrollTop - defaultScrollTop.value > -1) {
isAutoScroll.value = true
}
@ -121,13 +125,18 @@ onMounted(async () => {
</div>
<img class="icon-user" src="@/assets/images/conversation/user.png" alt="">
</div>
<div v-if="item.messageType === MessageTypeEnum.AI" class="ai">
<div
v-if="item.messageType === MessageTypeEnum.AI || item.messageType === MessageTypeEnum.DESCRIBE"
class="ai"
>
<img class="icon-ai" src="@/assets/images/conversation/logo.png" alt="">
<div class="content">
<div class="content" :class="[aiWidthType === 'auto' ? 'width-auto' : 'width-full']">
<MdPreview
v-if="
conversationData?.type === MenuTypeEnum.TEXT_TO_TEXT
|| conversationData?.type === MenuTypeEnum.ROLE"
|| conversationData?.type === MenuTypeEnum.ROLE
|| conversationData?.type === MenuTypeEnum.REPOSITORY
"
editor-id="preview-only-ai"
:model-value="item.content"
/>
@ -143,24 +152,20 @@ onMounted(async () => {
</p>
</div>
</div>
<div v-if="item.messageStatus === MessageStatusEnum.ERROR" class="error">
哎呀出问题了...
</div>
<div
v-if="item.messageStatus === MessageStatusEnum.ACTICON"
class="stop"
>
<SvgIcon class="icon" name="stop"></SvgIcon>
停止回答
</div>
<div v-if="item.messageStatus === MessageStatusEnum.END" class="btns">
<div class="copy" @click="copyText(item.content)">
<SvgIcon class="icon" name="copy"></SvgIcon>
复制
<div v-if="item.messageType !== MessageTypeEnum.DESCRIBE" class="w-full">
<div v-if="item.messageStatus === MessageStatusEnum.ERROR" class="error">
哎呀出问题了...
</div>
<div v-if="index === list.length - 1" class="reload" @click="reloadMessage">
<SvgIcon class="icon" name="again"></SvgIcon>
重新回答
<div v-if="item.messageStatus === MessageStatusEnum.END" class="btns">
<div class="copy" @click="copyText(item.content)">
<SvgIcon class="icon" name="copy"></SvgIcon>
复制
</div>
<div v-if="index === list.length - 1" class="reload" @click="reloadMessage">
<SvgIcon class="icon" name="again"></SvgIcon>
重新回答
</div>
</div>
</div>
</div>
@ -194,7 +199,7 @@ onMounted(async () => {
justify-content: flex-end;
.content {
max-width: calc(100% - 110px);
padding: 10px 15px;
padding: 10px 10px;
background: #edf3ff;
border-radius: 10px;
}
@ -218,13 +223,18 @@ onMounted(async () => {
margin-right: 5px;
}
.content {
max-width: calc(100% - 110px);
padding: 10px 15px;
padding: 10px 10px;
background: #ffffff;
box-shadow: 0px 2px 10px 0px rgba(128, 135, 152, 0.4);
border-radius: 10px;
border: 1px solid #e3ebfc;
}
.width-auto {
max-width: calc(100% - 110px);
}
.width-full {
width: calc(100% - 110px);
}
.error {
width: 100%;
color: red;
@ -232,14 +242,6 @@ onMounted(async () => {
padding-left: 60px;
margin-top: 10px;
}
.stop {
width: 100%;
height: 30px;
color: #4670e3;
padding-left: 60px;
margin-top: 10px;
cursor: pointer;
}
.btns {
width: 100%;
height: 30px;

2
src/components/AppRoleDefault/index.d.ts vendored

@ -1,8 +1,10 @@
export interface RoleData {
id: string
roleImg: string
roleName: string
roleInfo: string
remark: string
type: number
}
interface RoleInfoAppModel {

6
src/components/AppRoleDefault/index.vue

@ -4,6 +4,8 @@ import { ref } from 'vue'
import type { AppGroup, RoleData } from './index.d'
import { getAppList, getRole } from '@/api/base/role'
defineEmits(['handleRole', 'handleApply'])
const roleList = ref<RoleData[]>([])
const application = ref<AppGroup[]>([])
const typeIndex = ref(0)
@ -12,6 +14,7 @@ const typeIndex = ref(0)
function getRoleData() {
getRole(1).then((res) => {
roleList.value = res
console.log('角色', res)
})
}
getRoleData()
@ -19,6 +22,7 @@ getRoleData()
function getAppData() {
getAppList().then((res) => {
application.value = res
console.log('应用', res)
})
}
@ -42,6 +46,7 @@ function handleChange(index: number) {
v-for="(item, index) in roleList"
:key="index"
class="role-card"
@click="$emit('handleRole', item, index)"
>
<div class="top">
<div class="avatar">
@ -87,6 +92,7 @@ function handleChange(index: number) {
v-for="(item, index) in application[typeIndex].roleInfoAppModelList"
:key="index"
class="applyList"
@click="$emit('handleApply', item, index)"
>
<div class="img">
<img class="apple-img" :src="item.roleImg" />

1
src/components/AppSubMenuList/index.vue

@ -79,6 +79,7 @@ function handleBlur(index: number, item: SubMenuItem, inputValue?: string) {
</p>
<div class="actions absolute">
<SvgIcon
v-if="index !== 0"
class-name="icon"
name="toTop"
@click.stop="$emit('handleAction', SubMenuActionEnum.TO_TOP, item, index)"

3
src/components/AppTextToPictureDefault/index.d.ts vendored

@ -1,3 +0,0 @@
export interface Props {
height?: string
}

3
src/components/AppTextToPictureDefault/index.ts

@ -1,3 +0,0 @@
import AppTextToPictureDefault from './index.vue'
export { AppTextToPictureDefault }

104
src/components/AppTextToPictureDefault/index.vue

@ -1,104 +0,0 @@
<!-- 会话默认对话组件 -->
<script setup lang="ts">
import { ref } from 'vue'
import type { Props } from './index.d'
import DefaultImage from '@/assets/images/conversation/default_img.png'
import { AppContentDefaultBox } from '@/components/AppContentDefaultBox'
import { AppDefaultLead } from '@/components/AppDefaultLead'
import { AppModelSelect } from '@/components/AppModelSelect'
import { AppTopPicks } from '@/components/AppTopPicks'
import type { TopPickItem } from '@/components/AppTopPicks/index.d'
withDefaults(defineProps<Props>(), {
height: '100%',
})
const leadData = {
title: '你好,我是青鸟语言大模型-同聪~',
subTitles: [
'我现在可是一个超级无敌大画家~支持多种风格',
'你可以试着问我',
],
image: DefaultImage,
}
const topPickList = ref<TopPickItem[]>([
{
id: '1',
label: '啦啦啦啦啦啦',
},
{
id: '2',
label: '啦啦啦啦啦啦',
},
{
id: '3',
label: '啦啦啦啦啦啦啦啦啦啦啦啦',
},
{
id: '4',
label: '啦啦啦啦啦啦',
},
{
id: '5',
label: '啦啦啦啦啦啦',
},
])
</script>
<template>
<AppContentDefaultBox class="content-default-style">
<AppModelSelect></AppModelSelect>
<AppDefaultLead
class="mt-20"
:title="leadData.title"
:sub-titles="leadData.subTitles"
:image="leadData.image"
></AppDefaultLead>
<AppTopPicks class="pl-20 mt-10" :list="topPickList"></AppTopPicks>
</AppContentDefaultBox>
</template>
<style lang="scss" scoped>
.content-default-style {
height: v-bind(height);
overflow-y: auto;
}
.category-box {
width: calc(50% - 10px);
padding: 15px 20px;
background-color: #edf3ff;
border-radius: 8px;
.top {
position: relative;
.title {
font-size: 18px;
font-weight: bold;
margin-bottom: 0;
}
.sub-title {
color: #6d7278;
margin-left: 30px;
margin-bottom: 0;
}
.icon {
width: 45px;
height: 18px;
cursor: pointer;
position: absolute;
right: 0;
top: 6px;
}
}
.picture-box {
margin-top: 20px;
.picture-item-role {
margin-left: -10px;
}
.picture-item + .picture-item {
margin-left: 15px;
}
}
}
</style>

79
src/components/AppTextarea/index.vue

@ -1,9 +1,12 @@
<!-- 公共输入框组件 -->
<script setup lang="ts">
import { ref } from 'vue'
import { Button, Textarea } from 'ant-design-vue'
import { computed, ref } from 'vue'
import { Button, Textarea, message } from 'ant-design-vue'
import { SvgIcon } from '@/components/SvgIcon'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MessageStatusEnum } from '@/enums/messageEnum'
defineProps({
const props = defineProps({
placeholder: {
type: String,
default: '在此输入你想了解的内容(Shift + Enter = 换行)',
@ -12,28 +15,70 @@ defineProps({
type: Boolean,
default: false,
},
})
const emit = defineEmits(['pressEnter', 'send'])
isStop: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['pressEnter', 'send', 'stopMessage'])
const messageStore = useMessageStore()
const value = ref('')
const stopShow = computed(() => {
if (!props.isStop) {
return false
}
if (
messageStore.getMessageList.length
&& messageStore.getMessageList[messageStore.getMessageList.length - 1].messageStatus === MessageStatusEnum.ACTICON
) {
return true
}
else {
return false
}
})
function pressEnter(event: KeyboardEvent) {
if (event.shiftKey)
if (event.shiftKey) {
return false
}
if (value.value === '') {
message.warning('请输入内容')
setTimeout(() => {
value.value = ''
}, 0)
return false
}
emit('pressEnter', value.value)
handeleSend()
}
function handeleSend() {
emit('send', value.value)
value.value = ''
setTimeout(() => {
value.value = ''
}, 0)
}
function stopMessage() {
emit('stopMessage')
}
</script>
<template>
<div class="absolute right-0 bottom-5 w-full">
<div class="app-textarea absolute right-0 bottom-5 w-full">
<div class="relative">
<div
v-if="stopShow"
class="stop flex justify-center items-center cursor-pointer"
@click="stopMessage"
>
<SvgIcon class="mr-2" name="stop"></SvgIcon>
停止回答
</div>
<Textarea
v-model:value="value"
class="pt-4 pr-34 pb-6 pl-4"
@ -54,4 +99,18 @@ function handeleSend() {
</div>
</template>
<style scoped></style>
<style lang="scss" scoped>
@include app('textarea') {
min-height: 108px;
.stop {
width: 100px;
height: 30px;
border-radius: 20px;
background-color: #edf3ff;
position: absolute;
top: -38px;
left: calc(50% - 40px);
z-index: 1;
}
}
</style>

4
src/components/AppTopPicks/index.vue

@ -1,13 +1,12 @@
<!-- 精选推荐组件 -->
<script setup lang="ts">
import type { TopPickItem } from './index.d'
import { SvgIcon } from '@/components/SvgIcon'
defineProps<{
list: TopPickItem[]
}>()
const emit = defineEmits(['change', 'refresh'])
const emit = defineEmits(['change'])
function handelChange(index: number, item: TopPickItem) {
emit('change', index, item)
@ -24,7 +23,6 @@ function handelChange(index: number, item: TopPickItem) {
>
{{ item.label }}
</div>
<SvgIcon class="icon" name="refresh" @click="emit('refresh')" />
</div>
</template>

11
src/enums/messageEnum.ts

@ -1,9 +1,10 @@
/**
* @description: ai/user
* @description: ai/user/describe
*/
export enum MessageTypeEnum {
AI = 'bot',
USER = 'user',
DESCRIBE = 'describe', // 描述,例如新建角色时没有历史记录即展示角色描述
}
export enum MessageStatusEnum {
@ -24,3 +25,11 @@ export enum MessageStatusEnum {
*/
ERROR = 'error',
}
export enum ModelTypeEnum {
// GPT4
GPT4 = 1,
// GPT3.5
GPT3 = 2,
}

2
src/hooks/useMqtt.ts

@ -34,6 +34,8 @@ export function useMqtt() {
.subscribe(topicKey)
.then(() => {
mqttService.onMessage(topicKey, (message: any) => {
console.log(message)
if (message.message_type === MessageStatusEnum.ACTICON) {
messageStore.setMessageStatus(MessageStatusEnum.ACTICON)
messageStore.setMessageLastItem({

13
src/layout/AppMenu/index.vue

@ -26,12 +26,25 @@ const menu = ref<MenuItem[]>([
path: '/role',
key: MenuTypeEnum.ROLE,
},
// {
// name: '',
// icon: 'repository',
// path: '/repository',
// key: MenuTypeEnum.REPOSITORY,
// },
{
name: '图像分析',
icon: 'visual_analysis',
path: '/visualAnalysis',
key: MenuTypeEnum.VISUAL_ANALYSIS,
},
{
name: '任务',
icon: 'ren_wu',
path: '/task',
key: MenuTypeEnum.TASK,
},
])
const footMenu = ref([

16
src/router/index.ts

@ -63,6 +63,22 @@ export const constantRoutes: Array<RouteRecordRaw> = [
title: '任务',
},
},
{
name: 'Repository',
path: '/repository',
component: () => import('@/views/repository/index.vue'),
meta: {
title: '知识库',
},
},
{
name: 'VisualAnalysis',
path: '/visualAnalysis',
component: () => import('@/views/visualAnalysis/index.vue'),
meta: {
title: '图像分析',
},
},
],
},

7
src/utils/axios/index.ts

@ -22,7 +22,12 @@ const { createMessage, createErrorModal, createSuccessModal } = useMessage()
// 请求白名单,无须token的接口
const whiteList: string[] = ['/login', '/refresh-token']
// 不需要解密接口白名单
const notDecryptWhiteList = ['/hulk-auth/oauth/token', '/open-chat/chat/session', '/open-gpts/gpts/getDallEImages']
const notDecryptWhiteList = [
'/hulk-auth/oauth/token',
'/open-chat/chat/session',
'/open-gpts/gpts/getDallEImages',
'/open-chat/chat/stopGenerate',
]
/**
* @description: 便

114
src/views/conversation/index.vue

@ -9,14 +9,17 @@ import { AppConversationDefault } from '@/components/AppConversationDefault'
import { AppUserInfo } from '@/components/AppUserInfo'
import { SubMenuActionEnum } from '@/components/AppSubMenuList/index.d'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
import type { PictureType } from '@/components/AppPicture/index.d'
import { AppTextarea } from '@/components/AppTextarea'
import { AppMessage } from '@/components/AppMessage'
import type { MessageItem } from '@/components/AppMessage/index.d'
import type { TopPickItem } from '@/components/AppTopPicks/index.d'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MenuTypeEnum } from '@/enums/menuEnum'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
import { addMessage, conversationList, historyMessage, removeMessage, sendTextToText, updateMessage } from '@/api/base/message'
import { addMessage, conversationList, conversationToTop, historyMessage, removeMessage, sendTextToText, stopMessage, updateMessage } from '@/api/base/message'
import { getAppList, getRole } from '@/api/base/role'
import { useMqtt } from '@/hooks/useMqtt'
import { useMessage } from '@/hooks/useMessage'
@ -30,6 +33,8 @@ const subMenuActionIndex = ref(-1) // 会话操作索引
const subMenuList = ref<SubMenuItem[]>([])
const subMenuInputValue = ref<string>('')
const appMessageRef = ref()
const roleList = ref<PictureType[]>([])
const applyList = ref<PictureType[]>([])
const messageList = computed(() => messageStore.getMessageList)
const messageStatus = computed(() => messageStore.getMessageStatus)
const conversationData = computed(() => messageStore.getConversationData)
@ -58,6 +63,7 @@ watch(
*/
function handlesubMenuActionIndex(type: SubMenuActionEnum, item: SubMenuItem, index: number) {
subMenuActionIndex.value = index
const subMenuActionData = subMenuList.value[index]
if (type === SubMenuActionEnum.EDIT) {
subMenuList.value.forEach((item) => {
item.actionType = SubMenuActionEnum.NOT
@ -72,19 +78,31 @@ function handlesubMenuActionIndex(type: SubMenuActionEnum, item: SubMenuItem, in
content: `确定要删除${item.title}会话吗?`,
iconType: 'warning',
onOk: () => {
removeMessage(item.id).then(() => {
removeMessage(item.id).then(async () => {
message.success('删除成功')
getConversationList()
subMenuActiveIndex.value = -1
conversationDefaultShow.value = true
await getConversationList()
})
},
})
}
if (type === SubMenuActionEnum.TO_TOP) {
conversationToTop(item.id).then(async () => {
message.success('置顶成功')
await getConversationList()
const index = subMenuList.value.findIndex(item => item.id === subMenuActionData.id)
await handleSubMenuChange(index)
getHistoryMessage()
})
}
}
/**
* @description: 切换会话
*/
async function handleSubMenuChange(index: number) {
async function handleSubMenuChange(index: number, item?: SubMenuItem) {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
@ -96,13 +114,11 @@ async function handleSubMenuChange(index: number) {
messageStore.setMessageClear()
useMqtt().end()
useMqtt().connect()
getHistoryMessage()
//
appMessageShow.value = false
setTimeout(() => {
appMessageShow.value = true
}, 0)
// item
if (item) {
getHistoryMessage()
}
}
/**
@ -117,10 +133,15 @@ async function handleSend(value: string) {
if (subMenuActiveIndex.value === -1) {
try {
await addMessage({ type: MenuTypeEnum.TEXT_TO_TEXT, title: '新的对话' })
spinning.value = true
messageStore.setMessageClear()
await addMessage({ type: MenuTypeEnum.TEXT_TO_TEXT, title: '新的对话', sort: subMenuList.value.length + 1 })
await getConversationList()
await nextTick()
subMenuActiveIndex.value = subMenuList.value.length - 1
await handleSubMenuChange(subMenuActiveIndex.value)
sendMessage(conversationData.value.id, value)
spinning.value = false
}
catch (error) {
message.error('新建对话失败,请稍后重试!')
@ -140,10 +161,6 @@ async function getConversationList() {
item.actionType = SubMenuActionEnum.NOT
})
subMenuList.value = res
subMenuActiveIndex.value = 0
if (subMenuList.value.length) {
await handleSubMenuChange(0)
}
}
/**
@ -156,11 +173,12 @@ async function getHistoryMessage() {
spinning.value = true
historyMessageParams.value.conversationId = conversationData.value.id
const res = await historyMessage(historyMessageParams.value)
messageStore.setMessageClear()
spinning.value = false
if (!res || !res.records || !res.records.length) {
conversationDefaultShow.value = true
return
}
console.log(res)
res.records.forEach((item: any) => {
const itemData: MessageItem = {
@ -175,6 +193,12 @@ async function getHistoryMessage() {
})
conversationDefaultShow.value = false
//
appMessageShow.value = false
setTimeout(() => {
appMessageShow.value = true
}, 0)
}
/**
@ -202,6 +226,10 @@ async function onScrollTop(scrollTop: number) {
* @description: 发送消息hook
*/
async function sendMessage(conversationId: string, question: string): Promise<void> {
if (!messageStore.getConversationData) {
return
}
conversationDefaultShow.value = false
messageStore.setMessageStatus(MessageStatusEnum.LOADING)
messageStore.setMessagePushItem({
@ -217,9 +245,6 @@ async function sendMessage(conversationId: string, question: string): Promise<vo
avatar: '',
messageStatus: MessageStatusEnum.LOADING,
})
if (!messageStore.getConversationData) {
return
}
sendTextToText({
conversationId,
@ -241,14 +266,49 @@ function reloadMessage() {
sendMessage(conversationData.value.id, question)
}
/**
* @description: 停止回答
*/
async function stopMessageFun() {
if (!conversationData.value) {
return
}
await stopMessage({ conversationId: conversationData.value.id })
}
/**
* @description: 点击新建会话按钮
*/
function handleAddMessage() {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
}
conversationDefaultShow.value = true
subMenuActiveIndex.value = -1
}
/**
* @description: 点击默认看板的精选话题
*/
function handlePick(_index: number, item: TopPickItem) {
handleSend(item.label)
}
//
function getRoleData() {
getRole(1).then((res) => {
roleList.value = res
})
}
//
function getAppData() {
getAppList().then((res) => {
applyList.value = res[0].roleInfoAppModelList
})
}
//
function handleSubMenuInputAffirm(index: number, item: SubMenuItem, inputValue: string) {
updateMessage({ ...item, title: inputValue }).then(() => {
@ -265,11 +325,16 @@ function handleSubMenuInputBlur(index: number, item: SubMenuItem, inputValue: st
handleSubMenuInputAffirm(index, item, inputValue)
}
onMounted(() => {
getConversationList()
onMounted(async () => {
await getConversationList()
await handleSubMenuChange(subMenuActiveIndex.value)
getHistoryMessage()
getRoleData()
getAppData()
})
onUnmounted(() => {
messageStore.setMessageClear()
useMqtt().end()
})
@ -315,7 +380,12 @@ onBeforeRouteLeave(() => {
<!-- 默认导语 -->
<AppConversationDefault
v-if="conversationDefaultShow"
:is-pick="true"
:is-hot="false"
:role-list="roleList"
:apply-list="applyList"
height="calc(100% - 120px)"
@handle-pick="handlePick"
>
</AppConversationDefault>
@ -335,7 +405,9 @@ onBeforeRouteLeave(() => {
<AppTextarea
class="pl-52 pr-32 mt-10"
:btn-loading="sendBtnLoading"
:is-stop="false"
@send="handleSend"
@stop-message="stopMessageFun"
></AppTextarea>
</template>
</AppContainerBox>

360
src/views/repository/index.vue

@ -0,0 +1,360 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { Button, Spin, message } from 'ant-design-vue'
import DefaultImage from '@/assets/images/conversation/default_img3.png'
import { AppContainerBox } from '@/components/AppContainerBox'
import { AppSubMenuTitle } from '@/components/AppSubMenuTitle'
import { AppSubMenuList } from '@/components/AppSubMenuList'
import { AppConversationDefault } from '@/components/AppConversationDefault'
import { AppUserInfo } from '@/components/AppUserInfo'
import { SubMenuActionEnum } from '@/components/AppSubMenuList/index.d'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
import { AppTextarea } from '@/components/AppTextarea'
import { AppMessage } from '@/components/AppMessage'
import type { MessageItem } from '@/components/AppMessage/index.d'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MenuTypeEnum } from '@/enums/menuEnum'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
import { addMessage, conversationList, historyMessage, removeMessage, sendRepository, updateMessage } from '@/api/base/message'
import { useMqtt } from '@/hooks/useMqtt'
import { useMessage } from '@/hooks/useMessage'
const { createConfirm } = useMessage()
const messageStore = useMessageStore()
const sendBtnLoading = ref(false)
const subMenuActiveIndex = ref(0) //
const subMenuActionIndex = ref(-1) //
const subMenuList = ref<SubMenuItem[]>([])
const subMenuInputValue = ref<string>('')
const appMessageRef = ref()
const messageList = computed(() => messageStore.getMessageList)
const messageStatus = computed(() => messageStore.getMessageStatus)
const conversationData = computed(() => messageStore.getConversationData)
const historyMessageParams = ref({
conversationId: '',
current: 1,
size: 10,
total: 0,
})
const leadData = ref({
title: '你好,我是青鸟语言大模型-同聪~',
subTitles: [
'我可以自由的跟你对话~陪你聊天~帮你想方案~答疑解惑。',
'这里是你的知识库,你可以试着问我知识库里的问题',
],
image: DefaultImage,
})
const conversationDefaultShow = ref(false)
const appMessageShow = ref(true)
const spinning = ref(true)
watch(
() => messageStatus.value,
(val) => {
if (val === MessageStatusEnum.END) {
sendBtnLoading.value = false
}
},
)
/**
* @description: 点击会话操作项
*/
function handlesubMenuActionIndex(type: SubMenuActionEnum, item: SubMenuItem, index: number) {
subMenuActionIndex.value = index
if (type === SubMenuActionEnum.EDIT) {
subMenuList.value.forEach((item) => {
item.actionType = SubMenuActionEnum.NOT
})
subMenuList.value[index].actionType = SubMenuActionEnum.EDIT
subMenuInputValue.value = item.title
}
if (type === SubMenuActionEnum.DELETE) {
createConfirm({
title: '删除',
content: `确定要删除${item.title}会话吗?`,
iconType: 'warning',
onOk: () => {
removeMessage(item.id).then(() => {
message.success('删除成功')
getConversationList()
})
},
})
}
}
/**
* @description: 切换会话
*/
async function handleSubMenuChange(index: number) {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
}
subMenuActiveIndex.value = index
historyMessageParams.value.current = 1
historyMessageParams.value.total = 0
messageStore.setConversationData(subMenuList.value[subMenuActiveIndex.value])
messageStore.setMessageClear()
useMqtt().end()
useMqtt().connect()
getHistoryMessage()
//
appMessageShow.value = false
setTimeout(() => {
appMessageShow.value = true
}, 0)
}
/**
* @description: 发送消息
*/
async function handleSend(value: string) {
sendBtnLoading.value = true
conversationDefaultShow.value = false
if (!conversationData.value) {
return
}
if (subMenuActiveIndex.value === -1) {
try {
await addMessage({ type: MenuTypeEnum.REPOSITORY, title: '新的对话' })
await getConversationList()
await nextTick()
sendMessage(conversationData.value.id, value)
}
catch (error) {
message.error('新建对话失败,请稍后重试!')
}
}
else {
sendMessage(conversationData.value.id, value)
}
}
/**
* @description: 获取会话列表
*/
async function getConversationList() {
const res = await conversationList(MenuTypeEnum.REPOSITORY)
res.forEach((item: SubMenuItem) => {
item.actionType = SubMenuActionEnum.NOT
})
subMenuList.value = res
subMenuActiveIndex.value = 0
if (subMenuList.value.length) {
await handleSubMenuChange(0)
}
}
/**
* @description: 获取历史对话记录
*/
async function getHistoryMessage() {
if (!conversationData.value) {
return
}
spinning.value = true
historyMessageParams.value.conversationId = conversationData.value.id
const res = await historyMessage(historyMessageParams.value)
spinning.value = false
if (!res || !res.records || !res.records.length) {
return
}
console.log(res)
res.records.forEach((item: any) => {
const itemData: MessageItem = {
messageType: item.roleType === MessageTypeEnum.USER ? MessageTypeEnum.USER : MessageTypeEnum.AI,
content: item.messageContent,
time: item.messageTime,
avatar: '',
messageStatus: MessageStatusEnum.END,
}
historyMessageParams.value.total = res.total
messageStore.setMessageUnshiftItem(itemData)
})
conversationDefaultShow.value = false
}
/**
* @description: 滚动监听
*/
async function onScrollTop(scrollTop: number) {
if (scrollTop !== 0) {
return
}
if (historyMessageParams.value.current * historyMessageParams.value.size > historyMessageParams.value.total) {
return
}
if (historyMessageParams.value.current < historyMessageParams.value.total) {
historyMessageParams.value.current++
await getHistoryMessage()
//
appMessageRef.value.seamlessScrollToTop()
}
}
/**
* @description: 发送消息hook
*/
async function sendMessage(conversationId: string, question: string): Promise<void> {
conversationDefaultShow.value = false
messageStore.setMessageStatus(MessageStatusEnum.LOADING)
messageStore.setMessagePushItem({
messageType: MessageTypeEnum.USER,
content: question,
time: String(new Date().getTime()),
avatar: '',
})
messageStore.setMessagePushItem({
messageType: MessageTypeEnum.AI,
content: '正在思考中...',
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.LOADING,
})
if (!messageStore.getConversationData) {
return
}
sendRepository({
conversationId,
question,
}).catch(() => {
messageStore.getMessageList.splice(-2)
messageStore.setMessageStatus(MessageStatusEnum.END)
})
}
/**
* @description: 重新回答
*/
function reloadMessage() {
if (!conversationData.value) {
return
}
const question = messageList.value[messageList.value.length - 2]?.content
sendMessage(conversationData.value.id, question)
}
/**
* @description: 点击新建会话按钮
*/
function handleAddMessage() {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
}
conversationDefaultShow.value = true
subMenuActiveIndex.value = -1
}
//
function handleSubMenuInputAffirm(index: number, item: SubMenuItem, inputValue: string) {
updateMessage({ ...item, title: inputValue }).then(() => {
message.success('修改成功')
subMenuList.value[index].title = inputValue
subMenuList.value[index].actionType = SubMenuActionEnum.NOT
})
}
function handleSubMenuInputClose(index: number) {
subMenuList.value[index].actionType = SubMenuActionEnum.NOT
}
function handleSubMenuInputBlur(index: number, item: SubMenuItem, inputValue: string) {
subMenuActionIndex.value = -1
handleSubMenuInputAffirm(index, item, inputValue)
}
onMounted(() => {
getConversationList()
})
onUnmounted(() => {
useMqtt().end()
})
//
onBeforeRouteLeave(() => {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return false
}
})
</script>
<template>
<AppContainerBox>
<template #subMenu>
<!-- 标题 -->
<AppSubMenuTitle title="知识库会话"></AppSubMenuTitle>
<!-- 按钮 -->
<div class="px-5 mb-5">
<Button type="primary" class="w-full" @click="handleAddMessage">
新建会话
</Button>
</div>
<!-- 会话列表 -->
<AppSubMenuList
v-model:input-value="subMenuInputValue"
:list="subMenuList"
:active-index="subMenuActiveIndex"
:action-index="subMenuActionIndex"
@change="handleSubMenuChange"
@handle-action="handlesubMenuActionIndex"
@input-affirm="handleSubMenuInputAffirm"
@input-close="handleSubMenuInputClose"
@input-blur="handleSubMenuInputBlur"
>
</AppSubMenuList>
<AppUserInfo />
</template>
<template #content>
<Spin :spinning="spinning" wrapper-class-name="app-content-spin">
<!-- 默认导语 -->
<AppConversationDefault
v-if="conversationDefaultShow"
height="calc(100% - 120px)"
:lead-data="leadData"
>
</AppConversationDefault>
<!-- 消息列表 -->
<AppMessage
v-if="!conversationDefaultShow && appMessageShow"
ref="appMessageRef"
class="pl-27 pr-5"
:list="messageList"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"
>
</AppMessage>
</Spin>
<!-- 发送框 -->
<AppTextarea
class="pl-52 pr-32 mt-10"
:btn-loading="sendBtnLoading"
@send="handleSend"
></AppTextarea>
</template>
</AppContainerBox>
</template>
<style lang="scss" scoped>
</style>

128
src/views/role/index.vue

@ -12,11 +12,13 @@ import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
import { AppTextarea } from '@/components/AppTextarea'
import { AppMessage } from '@/components/AppMessage'
import type { RoleData, RoleInfoAppModel } from '@/components/AppRoleDefault/index.d'
import type { MessageItem } from '@/components/AppMessage/index.d'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MenuTypeEnum } from '@/enums/menuEnum'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
import { addMessage, conversationList, historyMessage, removeMessage, sendTextToText, updateMessage } from '@/api/base/message'
import { addMessage, conversationList, conversationToTop, historyMessage, removeMessage, sendTextToText, updateMessage } from '@/api/base/message'
import { getRoleDecs } from '@/api/base/role'
import { useMqtt } from '@/hooks/useMqtt'
import { useMessage } from '@/hooks/useMessage'
@ -58,6 +60,7 @@ watch(
*/
function handlesubMenuActionIndex(type: SubMenuActionEnum, item: SubMenuItem, index: number) {
subMenuActionIndex.value = index
const subMenuActionData = subMenuList.value[index]
if (type === SubMenuActionEnum.EDIT) {
subMenuList.value.forEach((item) => {
item.actionType = SubMenuActionEnum.NOT
@ -72,19 +75,32 @@ function handlesubMenuActionIndex(type: SubMenuActionEnum, item: SubMenuItem, in
content: `确定要删除${item.title}会话吗?`,
iconType: 'warning',
onOk: () => {
removeMessage(item.id).then(() => {
removeMessage(item.id).then(async () => {
message.success('删除成功')
getConversationList()
subMenuActiveIndex.value = -1
conversationDefaultShow.value = true
await getConversationList()
await handleSubMenuChange(subMenuActiveIndex.value)
})
},
})
}
if (type === SubMenuActionEnum.TO_TOP) {
conversationToTop(item.id).then(async () => {
message.success('置顶成功')
await getConversationList()
const index = subMenuList.value.findIndex(item => item.id === subMenuActionData.id)
await handleSubMenuChange(index)
getHistoryMessage()
})
}
}
/**
* @description: 切换会话
*/
async function handleSubMenuChange(index: number) {
async function handleSubMenuChange(index: number, item?: SubMenuItem) {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
@ -96,13 +112,11 @@ async function handleSubMenuChange(index: number) {
messageStore.setMessageClear()
useMqtt().end()
useMqtt().connect()
getHistoryMessage()
//
appMessageShow.value = false
setTimeout(() => {
appMessageShow.value = true
}, 0)
// item
if (item) {
getHistoryMessage()
}
}
/**
@ -117,10 +131,12 @@ async function handleSend(value: string) {
if (subMenuActiveIndex.value === -1) {
try {
await addMessage({ type: MenuTypeEnum.ROLE, title: '新的对话' })
spinning.value = true
await addMessage({ type: MenuTypeEnum.ROLE, title: '新的对话', roleId: String(conversationData.value.roleId), sort: subMenuList.value.length + 1 })
await getConversationList()
await nextTick()
sendMessage(conversationData.value.id, value)
spinning.value = false
}
catch (error) {
message.error('新建对话失败,请稍后重试!')
@ -139,13 +155,8 @@ async function getConversationList() {
res.forEach((item: SubMenuItem) => {
item.actionType = SubMenuActionEnum.NOT
})
console.log(res)
subMenuList.value = res
subMenuActiveIndex.value = 0
if (subMenuList.value.length) {
await handleSubMenuChange(0)
}
}
/**
@ -158,10 +169,19 @@ async function getHistoryMessage() {
spinning.value = true
historyMessageParams.value.conversationId = conversationData.value.id
const res = await historyMessage(historyMessageParams.value)
console.log(res)
messageStore.setMessageClear()
spinning.value = false
if (!res || !res.records || !res.records.length) {
getRoleDecs(String(conversationData.value.roleId)).then((res) => {
conversationDefaultShow.value = false
messageStore.setMessagePushItem({
messageType: MessageTypeEnum.DESCRIBE,
content: res,
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.END,
})
})
return
}
res.records.forEach((item: any) => {
@ -177,6 +197,12 @@ async function getHistoryMessage() {
})
conversationDefaultShow.value = false
//
appMessageShow.value = false
setTimeout(() => {
appMessageShow.value = true
}, 0)
}
/**
@ -204,6 +230,10 @@ async function onScrollTop(scrollTop: number) {
* @description: 发送消息hook
*/
async function sendMessage(conversationId: string, question: string): Promise<void> {
if (!messageStore.getConversationData) {
return
}
conversationDefaultShow.value = false
messageStore.setMessageStatus(MessageStatusEnum.LOADING)
messageStore.setMessagePushItem({
messageType: MessageTypeEnum.USER,
@ -218,9 +248,6 @@ async function sendMessage(conversationId: string, question: string): Promise<vo
avatar: '',
messageStatus: MessageStatusEnum.LOADING,
})
if (!messageStore.getConversationData) {
return
}
sendTextToText({
conversationId,
@ -246,10 +273,57 @@ function reloadMessage() {
* @description: 点击新建会话按钮
*/
function handleAddMessage() {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
}
conversationDefaultShow.value = true
subMenuActiveIndex.value = -1
}
/**
* @description: 选择角色
*/
async function handleRole(item: RoleData) {
const obj = subMenuList.value.find(v => String(v.roleId) === item.id)
if (obj) {
const index = subMenuList.value.findIndex(v => String(v.roleId) === item.id)
await handleSubMenuChange(index)
getHistoryMessage()
}
else {
await addMessage({ type: MenuTypeEnum.ROLE, title: item.roleName, roleId: item.id, sort: subMenuList.value.length + 1 })
await getConversationList()
await nextTick()
const index = subMenuList.value.findIndex(v => String(v.roleId) === item.id)
subMenuActiveIndex.value = index
await handleSubMenuChange(index)
getHistoryMessage()
}
}
/**
* @description: 选择应用
*/
async function handleApply(item: RoleInfoAppModel) {
const obj = subMenuList.value.find(v => v.roleId === item.id)
if (obj) {
const index = subMenuList.value.findIndex(v => v.roleId === item.id)
await handleSubMenuChange(index)
getHistoryMessage()
}
else {
await addMessage({ type: MenuTypeEnum.ROLE, title: item.roleName, roleId: String(item.id), sort: subMenuList.value.length + 1 })
await getConversationList()
await nextTick()
const index = subMenuList.value.findIndex(v => v.roleId === item.id)
subMenuActiveIndex.value = index
await handleSubMenuChange(index)
getHistoryMessage()
}
}
//
function handleSubMenuInputAffirm(index: number, item: SubMenuItem, inputValue: string) {
updateMessage({ ...item, title: inputValue }).then(() => {
@ -266,11 +340,14 @@ function handleSubMenuInputBlur(index: number, item: SubMenuItem, inputValue: st
handleSubMenuInputAffirm(index, item, inputValue)
}
onMounted(() => {
getConversationList()
onMounted(async () => {
await getConversationList()
await handleSubMenuChange(subMenuActiveIndex.value)
getHistoryMessage()
})
onUnmounted(() => {
messageStore.setMessageClear()
useMqtt().end()
})
@ -287,7 +364,7 @@ onBeforeRouteLeave(() => {
<AppContainerBox>
<template #subMenu>
<!-- 标题 -->
<AppSubMenuTitle></AppSubMenuTitle>
<AppSubMenuTitle title="角色会话"></AppSubMenuTitle>
<!-- 按钮 -->
<div class="px-5 mb-5">
@ -317,6 +394,8 @@ onBeforeRouteLeave(() => {
<AppRoleDefault
v-if="conversationDefaultShow"
height="calc(100% - 120px)"
@handle-role="handleRole"
@handle-apply="handleApply"
>
</AppRoleDefault>
@ -334,6 +413,7 @@ onBeforeRouteLeave(() => {
<!-- 发送框 -->
<AppTextarea
v-if="!conversationDefaultShow"
class="pl-52 pr-32 mt-10"
:btn-loading="sendBtnLoading"
@send="handleSend"

109
src/views/textToImage/index.vue

@ -2,10 +2,11 @@
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { Button, Spin, message } from 'ant-design-vue'
import DefaultImage from '@/assets/images/conversation/default_img2.png'
import { AppContainerBox } from '@/components/AppContainerBox'
import { AppSubMenuTitle } from '@/components/AppSubMenuTitle'
import { AppSubMenuList } from '@/components/AppSubMenuList'
import { AppTextToPictureDefault } from '@/components/AppTextToPictureDefault'
import { AppConversationDefault } from '@/components/AppConversationDefault'
import { AppUserInfo } from '@/components/AppUserInfo'
import { SubMenuActionEnum } from '@/components/AppSubMenuList/index.d'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
@ -13,10 +14,11 @@ import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
import { AppTextarea } from '@/components/AppTextarea'
import { AppMessage } from '@/components/AppMessage'
import type { MessageItem } from '@/components/AppMessage/index.d'
import type { TopPickItem } from '@/components/AppTopPicks/index.d'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MenuTypeEnum } from '@/enums/menuEnum'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
import { addMessage, conversationList, historyMessage, removeMessage, sendTextToImage, updateMessage } from '@/api/base/message'
import { addMessage, conversationList, conversationToTop, historyMessage, removeMessage, sendTextToImage, updateMessage } from '@/api/base/message'
import { useMqtt } from '@/hooks/useMqtt'
import { useMessage } from '@/hooks/useMessage'
@ -39,6 +41,25 @@ const historyMessageParams = ref({
size: 10,
total: 0,
})
const leadData = ref({
title: '你好,我是青鸟语言大模型-同聪~',
subTitles: [
'我现在可是一个超级无敌大画家~支持多种风格',
'你可以试着问我',
],
image: DefaultImage,
})
const topPickList = ref([
{
id: '1',
label: '画一张海边风景',
},
{
id: '2',
label: '画一张猛虎下山图',
},
])
const conversationDefaultShow = ref(false)
const appMessageShow = ref(true)
@ -58,6 +79,7 @@ watch(
*/
function handlesubMenuActionIndex(type: SubMenuActionEnum, item: SubMenuItem, index: number) {
subMenuActionIndex.value = index
const subMenuActionData = subMenuList.value[index]
if (type === SubMenuActionEnum.EDIT) {
subMenuList.value.forEach((item) => {
item.actionType = SubMenuActionEnum.NOT
@ -72,19 +94,30 @@ function handlesubMenuActionIndex(type: SubMenuActionEnum, item: SubMenuItem, in
content: `确定要删除${item.title}会话吗?`,
iconType: 'warning',
onOk: () => {
removeMessage(item.id).then(() => {
removeMessage(item.id).then(async () => {
message.success('删除成功')
getConversationList()
subMenuActiveIndex.value = -1
conversationDefaultShow.value = true
await getConversationList()
})
},
})
}
if (type === SubMenuActionEnum.TO_TOP) {
conversationToTop(item.id).then(async () => {
message.success('置顶成功')
const index = subMenuList.value.findIndex(item => item.id === subMenuActionData.id)
await handleSubMenuChange(index)
getHistoryMessage()
})
}
}
/**
* @description: 切换会话
*/
async function handleSubMenuChange(index: number) {
async function handleSubMenuChange(index: number, item?: SubMenuItem) {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
@ -96,13 +129,11 @@ async function handleSubMenuChange(index: number) {
messageStore.setMessageClear()
useMqtt().end()
useMqtt().connect()
getHistoryMessage()
//
appMessageShow.value = false
setTimeout(() => {
appMessageShow.value = true
}, 0)
// item
if (item) {
getHistoryMessage()
}
}
/**
@ -117,10 +148,15 @@ async function handleSend(value: string) {
if (subMenuActiveIndex.value === -1) {
try {
await addMessage({ type: MenuTypeEnum.TEXT_TO_IMAGE, title: '新的对话' })
await getConversationList()
spinning.value = true
messageStore.setMessageClear()
await addMessage({ type: MenuTypeEnum.TEXT_TO_IMAGE, title: '新的对话', sort: subMenuList.value.length + 1 })
await nextTick()
await getConversationList()
subMenuActiveIndex.value = subMenuList.value.length - 1
await handleSubMenuChange(subMenuActiveIndex.value)
sendMessage(conversationData.value.id, value)
spinning.value = false
}
catch (error) {
message.error('新建对话失败,请稍后重试!')
@ -141,10 +177,6 @@ async function getConversationList() {
})
subMenuList.value = res
subMenuActiveIndex.value = 0
if (subMenuList.value.length) {
await handleSubMenuChange(0)
}
}
/**
@ -157,9 +189,10 @@ async function getHistoryMessage() {
spinning.value = true
historyMessageParams.value.conversationId = conversationData.value.id
const res = await historyMessage(historyMessageParams.value)
messageStore.setMessageClear()
spinning.value = false
if (!res || !res.records || !res.records.length) {
conversationDefaultShow.value = true
return
}
res.records.forEach((item: any) => {
@ -175,6 +208,12 @@ async function getHistoryMessage() {
})
conversationDefaultShow.value = false
//
appMessageShow.value = false
setTimeout(() => {
appMessageShow.value = true
}, 0)
}
/**
@ -202,6 +241,10 @@ async function onScrollTop(scrollTop: number) {
* @description: 发送消息hook
*/
async function sendMessage(conversationId: string, question: string): Promise<void> {
if (!messageStore.getConversationData) {
return
}
messageStore.setMessageStatus(MessageStatusEnum.LOADING)
messageStore.setMessagePushItem({
messageType: MessageTypeEnum.USER,
@ -216,9 +259,6 @@ async function sendMessage(conversationId: string, question: string): Promise<vo
avatar: '',
messageStatus: MessageStatusEnum.LOADING,
})
if (!messageStore.getConversationData) {
return
}
sendTextToImage({
conversationId,
@ -244,10 +284,21 @@ function reloadMessage() {
* @description: 点击新建会话按钮
*/
function handleAddMessage() {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
}
conversationDefaultShow.value = true
subMenuActiveIndex.value = -1
}
/**
* @description: 点击默认看板的精选话题
*/
function handlePick(_index: number, item: TopPickItem) {
handleSend(item.label)
}
//
function handleSubMenuInputAffirm(index: number, item: SubMenuItem, inputValue: string) {
updateMessage({ ...item, title: inputValue }).then(() => {
@ -264,11 +315,14 @@ function handleSubMenuInputBlur(index: number, item: SubMenuItem, inputValue: st
handleSubMenuInputAffirm(index, item, inputValue)
}
onMounted(() => {
getConversationList()
onMounted(async () => {
await getConversationList()
await handleSubMenuChange(subMenuActiveIndex.value)
getHistoryMessage()
})
onUnmounted(() => {
messageStore.setMessageClear()
useMqtt().end()
})
@ -312,11 +366,15 @@ onBeforeRouteLeave(() => {
<template #content>
<Spin :spinning="spinning" wrapper-class-name="app-content-spin">
<!-- 默认导语 -->
<AppTextToPictureDefault
<AppConversationDefault
v-if="conversationDefaultShow"
:is-pick="true"
:lead-data="leadData"
:top-pick-list="topPickList"
height="calc(100% - 120px)"
@handle-pick="handlePick"
>
</AppTextToPictureDefault>
</AppConversationDefault>
<!-- 消息列表 -->
<AppMessage
@ -324,6 +382,7 @@ onBeforeRouteLeave(() => {
ref="appMessageRef"
class="pl-27 pr-5"
:list="messageList"
ai-width-type="auto"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"
>

Loading…
Cancel
Save