liyongli 1 rok temu
rodzic
commit
ae2192efe5
5 zmienionych plików z 297 dodań i 62 usunięć
  1. 1 0
      package.json
  2. 51 8
      pnpm-lock.yaml
  3. 34 0
      src/api/kimigpt.js
  4. 208 54
      src/view/allMedia/kimigpt/index.vue
  5. 3 0
      src/view/allMedia/main.vue

+ 1 - 0
package.json

@@ -16,6 +16,7 @@
     "core-js": "^3.6.5",
     "dayjs": "^1.11.7",
     "element-plus": "^2.2.18",
+    "markdown-it": "^14.1.0",
     "sha256": "^0.2.0",
     "vant": "^4.0.11",
     "vue": "^3.2.4",

+ 51 - 8
pnpm-lock.yaml

@@ -29,6 +29,9 @@ dependencies:
   element-plus:
     specifier: ^2.2.18
     version: 2.2.18(vue@3.2.4)
+  markdown-it:
+    specifier: ^14.1.0
+    version: 14.1.0
   sha256:
     specifier: ^0.2.0
     version: 0.2.0
@@ -161,7 +164,7 @@ packages:
     resolution: {integrity: sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.19.4
+      '@babel/types': 7.21.2
       '@jridgewell/gen-mapping': 0.3.2
       jsesc: 2.5.2
     dev: true
@@ -323,7 +326,7 @@ packages:
     resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.19.4
+      '@babel/types': 7.21.2
     dev: true
 
   /@babel/helper-module-transforms@7.19.0:
@@ -337,7 +340,7 @@ packages:
       '@babel/helper-validator-identifier': 7.19.1
       '@babel/template': 7.18.10
       '@babel/traverse': 7.19.4
-      '@babel/types': 7.19.4
+      '@babel/types': 7.21.2
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -457,7 +460,7 @@ packages:
     dependencies:
       '@babel/template': 7.18.10
       '@babel/traverse': 7.19.4
-      '@babel/types': 7.19.4
+      '@babel/types': 7.21.2
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -1334,7 +1337,7 @@ packages:
       '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.19.3)
       '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.19.3)
       '@babel/preset-modules': 0.1.5(@babel/core@7.19.3)
-      '@babel/types': 7.19.4
+      '@babel/types': 7.21.2
       babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.19.3)
       babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.19.3)
       babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.19.3)
@@ -1373,8 +1376,8 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/code-frame': 7.18.6
-      '@babel/parser': 7.19.4
-      '@babel/types': 7.19.4
+      '@babel/parser': 7.21.2
+      '@babel/types': 7.21.2
     dev: true
 
   /@babel/template@7.20.7:
@@ -1905,7 +1908,7 @@ packages:
       '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.19.3)
       '@babel/template': 7.18.10
       '@babel/traverse': 7.19.4
-      '@babel/types': 7.19.4
+      '@babel/types': 7.21.2
       '@vue/babel-helper-vue-transform-on': 1.0.2
       camelcase: 6.3.0
       html-tags: 3.2.0
@@ -3002,6 +3005,10 @@ packages:
       sprintf-js: 1.0.3
     dev: true
 
+  /argparse@2.0.1:
+    resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+    dev: false
+
   /arr-diff@4.0.0:
     resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==}
     engines: {node: '>=0.10.0'}
@@ -4949,6 +4956,11 @@ packages:
     resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
     dev: true
 
+  /entities@4.5.0:
+    resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+    engines: {node: '>=0.12'}
+    dev: false
+
   /errno@0.1.8:
     resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
     hasBin: true
@@ -7035,6 +7047,12 @@ packages:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
     dev: true
 
+  /linkify-it@5.0.0:
+    resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+    dependencies:
+      uc.micro: 2.1.0
+    dev: false
+
   /loader-fs-cache@1.0.3:
     resolution: {integrity: sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==}
     dependencies:
@@ -7264,6 +7282,18 @@ packages:
       object-visit: 1.0.1
     dev: true
 
+  /markdown-it@14.1.0:
+    resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
+    hasBin: true
+    dependencies:
+      argparse: 2.0.1
+      entities: 4.5.0
+      linkify-it: 5.0.0
+      mdurl: 2.0.0
+      punycode.js: 2.3.1
+      uc.micro: 2.1.0
+    dev: false
+
   /md5.js@1.3.5:
     resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==}
     dependencies:
@@ -7280,6 +7310,10 @@ packages:
     resolution: {integrity: sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==}
     dev: true
 
+  /mdurl@2.0.0:
+    resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
+    dev: false
+
   /media-typer@0.3.0:
     resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
     engines: {node: '>= 0.6'}
@@ -8756,6 +8790,11 @@ packages:
       pump: 2.0.1
     dev: true
 
+  /punycode.js@2.3.1:
+    resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+    engines: {node: '>=6'}
+    dev: false
+
   /punycode@1.3.2:
     resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==}
     dev: true
@@ -10153,6 +10192,10 @@ packages:
     resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
     dev: true
 
+  /uc.micro@2.1.0:
+    resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+    dev: false
+
   /ufo@1.1.0:
     resolution: {integrity: sha512-LQc2s/ZDMaCN3QLpa+uzHUOQ7SdV0qgv3VBXOolQGXTaaZpIur6PwUclF5nN2hNkiTRcUugXd1zFOW3FLJ135Q==}
     dev: true

+ 34 - 0
src/api/kimigpt.js

@@ -0,0 +1,34 @@
+import ajax from '../utils/request.js';
+
+/**
+ * 获取分组列表
+ * @param {Object data}
+ * @returns {Promise}
+ */
+export function getGroupList(data) {
+  return ajax({
+    api: '/chat/group/list',
+    base: 'origin',
+    method: 'get',
+    data,
+    headers: {
+      Authorization: localStorage.getItem('token')
+    }
+  });
+}
+/**
+   * 创建分组
+   * @param {Object data}
+   * @returns {Promise}
+   */
+export function createGroup(data) {
+  return ajax({
+    api: '/chat/group/create',
+    base: 'origin',
+    method: 'POST',
+    data,
+    headers: {
+      Authorization: localStorage.getItem('token')
+    }
+  });
+}

+ 208 - 54
src/view/allMedia/kimigpt/index.vue

@@ -1,57 +1,91 @@
 <template>
   <div class="kimigpt">
     <header_local />
-    <el-image class="logo" v-if="!list.length" style="width: 300px" :src="logo" fit="contain" />
-
-    <div class="chatList">
-      <div
-        :class="item.role"
-        :style="item.role === 'user' ? 'padding-bottom: 12px;' : ''"
-        v-for="(item, index) in list"
-        :key="index"
-      >
-        <div v-html="item.content"></div>
-        <div v-if="item.role === 'system'">
-          <div style="display: inline-block; width: 25px; text-align: center">
-            <el-icon @click="() => copy(item.content)" :size="15"><DocumentCopy /></el-icon>
+    <el-container>
+      <el-aside width="200px">
+        <br />
+        <el-button
+          type="warning"
+          @click="newChat"
+          style="width: 100%; display: block; margin: 0 auto"
+          round
+          block
+        >
+          新建对话
+        </el-button>
+        <br />
+        <br />
+        <el-menu @select="select" :default-active="selectMneu" v-if="menu && menu.length">
+          <el-menu-item :index="item.id + ''" v-for="item in menu" :key="item.id">
+            <span class="ellipsis" v-text="item.title"></span>
+          </el-menu-item>
+        </el-menu>
+      </el-aside>
+      <el-main style="position: relative; padding-bottom: 60px; box-sizing: border-box">
+        <el-image class="logo" v-if="!list.length" style="width: 300px" :src="logo" fit="contain" />
+
+        <div class="chatList" id="chatList">
+          <div
+            :class="item.role"
+            :style="item.role === 'user' ? 'padding-bottom: 12px;' : ''"
+            v-for="(item, index) in list"
+            :key="index"
+          >
+            <div v-if="item.content && item.content.length" style="padding-left: 10px">
+              <div
+                v-for="(item2, index2) in item.content"
+                :key="index2"
+                v-html="toMarkdown(item2)"
+              />
+            </div>
+            <div v-if="item.role === 'system'">
+              <div style="display: inline-block; width: 25px; text-align: center">
+                <el-icon @click="() => copy(item.content)" :size="15"><DocumentCopy /></el-icon>
+              </div>
+            </div>
           </div>
         </div>
-      </div>
-    </div>
-
-    <div class="input">
-      <input type="text" placeholder="请输入内容" />
-      <div class="icon">
-        <el-icon :size="25"><UploadFilled /></el-icon>
-      </div>
-      <div class="icon">
-        <el-icon :size="25"><Search /></el-icon>
-      </div>
-    </div>
+        <div class="input">
+          <input type="text" v-model="inputText" placeholder="请输入内容" />
+          <div class="icon">
+            <el-icon :size="25" color="#fdba74"><UploadFilled /></el-icon>
+          </div>
+          <div class="icon" v-loading="loading">
+            <el-icon @click="() => pushW()" :size="25" color="#fdba74"><Position /></el-icon>
+          </div>
+        </div>
+      </el-main>
+    </el-container>
   </div>
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, nextTick } from 'vue';
+import MarkdownIt from 'markdown-it';
 import { ElMessage, ElNotification } from 'element-plus';
 import header_local from '../components/header.vue';
 import config from '../../../config/index';
+import { getGroupList, createGroup } from '@/api/kimigpt.js';
 
 import logo from '../../../assets/img/logo.png';
 
 import { fetchEventSource } from '@microsoft/fetch-event-source';
 
-const list = ref([
-  { role: 'user', content: '你好,我叫李雷,1+1等于多少?' },
-  {
-    role: 'system',
-    content: '```123```'
-  }
-]);
+const selectMneu = ref('');
+const inputText = ref('');
+const loading = ref(false);
+const list = ref([]);
+const menu = ref([]);
+let oldIndex = 0;
+
+const toMarkdown = text => {
+  const mk = new MarkdownIt();
+  return mk.render(text);
+};
 
 const copy = async text => {
   try {
-    await navigator.clipboard.writeText(text);
+    await navigator.clipboard.writeText(text.join('\n'));
     ElMessage({
       message: '复制成功',
       type: 'success'
@@ -63,40 +97,154 @@ const copy = async text => {
     });
   }
 };
-sse();
-function sse() {
+
+const select = (index, item) => {
+  menu.value[oldIndex].kimiChats = list.value;
+  selectMenu(menu.value[index]);
+  oldIndex = index;
+};
+
+getGroupList({}).then(r => {
+  menu.value = (r || []).map(v => {
+    return {
+      ...v,
+      kimiChats: v.kimiChats.map(o => {
+        o.content = o.content.split('\n');
+        return o;
+      })
+    };
+  });
+  selectMneu.value = ((r || [])[0].id || '').toString();
+  selectMenu(menu.value[0]);
+});
+
+function selectMenu(item) {
+  const l = [];
+  const ori = item || {};
+  selectMneu.value = ori.id + '';
+  for (let i = 0; i < (ori.kimiChats || []).length; i++) {
+    const v = (ori.kimiChats || [])[i];
+    l.push(v);
+  }
+  list.value = l;
+  toBottom();
+}
+
+const toBottom = () => {
+  nextTick(() => {
+    var scrollableContainer = document.getElementById('chatList');
+    scrollableContainer.scrollTo({
+      top: scrollableContainer.scrollHeight,
+      behavior: 'smooth'
+    });
+  });
+};
+
+function pushW() {
+  list.value.push({
+    role: 'user',
+    content: [inputText.value]
+  });
+  loading.value = true;
+  if (!selectMneu.value) {
+    createGroup({ title: inputText.value }).then(r => {
+      if (r) {
+        ElNotification({
+          title: '提示',
+          message: '创建成功',
+          duration: 1000
+        });
+        menu.value.push({
+          ...r,
+          kimiChats: []
+        });
+        selectMneu.value = r.id.toString();
+        selectMenu(r);
+        sse(inputText.value);
+      }
+    });
+  } else {
+    sse(inputText.value);
+  }
+}
+function sse(question) {
   const controller = new AbortController();
+  const questionItem = {
+    role: 'system',
+    content: []
+  };
+  const index = list.value.length;
+  const L = JSON.parse(JSON.stringify(list.value));
+  let t = undefined;
   fetchEventSource(config.base.videoProcessing + '/user/chat', {
     method: 'POST',
     headers: {
-      'Content-Type': 'application/json'
+      'Content-Type': 'application/json',
+      Authorization: localStorage.getItem('token')
     },
     body: JSON.stringify({
-      groupId: 1,
-      content: '1+1等于几,回答不上来是傻逼'
+      groupId: selectMneu.value,
+      content: question
     }),
-    signal: controller.signal ,
+    signal: controller.signal,
     onerror: e => {
+      console.log('连接报错', e);
+      controller.abort();
+      inputText.value = '';
+      loading.value = false;
+      if (t) window.clearTimeout();
+    },
+    onclose: e => {
+      console.log('连接关闭');
+      loading.value = false;
       controller.abort();
+      inputText.value = '';
+      if (t) window.clearTimeout();
     },
     onmessage: e => {
-        console.log('返回数据:',e);
+      if (e.event !== 'data') return;
+      let { data } = e;
+      if (data === '[DONE]') {
+        controller.abort();
+        return;
+      }
+      try {
+        const { choices } = JSON.parse(data);
+        for (let i = 0; i < choices.length; i++) {
+          const v = choices[i];
+          if (!questionItem.content[i]) questionItem.content[i] = '';
+          let text = v && v.delta ? v.delta.content || '' : '';
+          questionItem.content[i] += text;
+          if (v && v.finish_reason === 'stop') {
+            controller.abort();
+            loading.value = false;
+            inputText.value = '';
+          }
+        }
+        L[index] = questionItem;
+        t = setTimeout(() => {
+          if (t) clearTimeout(t);
+          list.value = JSON.parse(JSON.stringify(L));
+          toBottom();
+        }, 100);
+      } catch (error) {
+        inputText.value = '';
+        ElNotification({
+          title: '提示',
+          message: '服务繁忙',
+          duration: 1000
+        });
+      }
     },
     onopen: () => {
-        console.log('连接成功');
-    },
-    onclose: () => {
-        console.log('连接关闭');
-        controller.abort();
+      console.log('连接成功');
     }
   });
+}
 
-
-  ElNotification({
-    title: '提示',
-    message: '测试消息',
-    duration: 0
-  });
+function newChat() {
+  list.value = [];
+  selectMneu.value = '';
 }
 </script>
 
@@ -117,7 +265,7 @@ function sse() {
   width: 90%;
   left: 5%;
   bottom: 10px;
-  padding: 1em;
+  padding: 0.5em 1em;
   box-shadow: 0 0 10px #999;
   border-radius: 2em;
   display: flex;
@@ -139,7 +287,7 @@ function sse() {
   box-sizing: border-box;
   width: 100%;
   height: calc(100vh - 122px);
-  overflow: auto;
+  overflow-y: auto;
 }
 
 .chatList .system,
@@ -166,4 +314,10 @@ function sse() {
   border-color: #fdba74;
   background-color: #fff7ed;
 }
+
+.ellipsis {
+  white-space: nowrap; /* 确保文本在一行内显示 */
+  overflow: hidden; /* 超出容器的文本会被隐藏 */
+  text-overflow: ellipsis; /* 使用省略号表示被截断的文本 */
+}
 </style>

+ 3 - 0
src/view/allMedia/main.vue

@@ -84,6 +84,9 @@ function sse() {
       '/user/sse?token=' +
       localStorage.getItem('token')
   );
+  eventSource.addEventListener('error', err => {
+    console.log(err)
+  });
   eventSource.addEventListener('message', event => {
     // 处理来自服务器的消息
     const data = JSON.parse(event.data);