mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	finished refactor chat page UI
This commit is contained in:
		@@ -6,4 +6,4 @@ VUE_APP_ADMIN_USER=admin
 | 
			
		||||
VUE_APP_ADMIN_PASS=admin123
 | 
			
		||||
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
 | 
			
		||||
VUE_APP_TITLE="Geek-AI 创作系统"
 | 
			
		||||
VUE_APP_VERSION=v4.0.6
 | 
			
		||||
VUE_APP_VERSION=v4.0.7
 | 
			
		||||
 
 | 
			
		||||
@@ -2,4 +2,4 @@ VUE_APP_API_HOST=
 | 
			
		||||
VUE_APP_WS_HOST=
 | 
			
		||||
VUE_APP_KEY_PREFIX=ChatPLUS_
 | 
			
		||||
VUE_APP_TITLE="Geek-AI 创作系统"
 | 
			
		||||
VUE_APP_VERSION=v4.0.6
 | 
			
		||||
VUE_APP_VERSION=v4.0.7
 | 
			
		||||
 
 | 
			
		||||
@@ -114,10 +114,13 @@ $borderColor = #4676d0;
 | 
			
		||||
 | 
			
		||||
      .tool-box {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        justify-content: flex-end;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        padding 0 20px 10px 20px;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        padding-top 12px
 | 
			
		||||
        border-top 1px solid #3c3c3c;
 | 
			
		||||
 | 
			
		||||
        .iconfont {
 | 
			
		||||
          margin-right 5px
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -126,52 +129,6 @@ $borderColor = #4676d0;
 | 
			
		||||
      --el-main-padding: 0;
 | 
			
		||||
      margin: 0;
 | 
			
		||||
 | 
			
		||||
      .chat-head {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        height: 50px;
 | 
			
		||||
        background-color: #28292A
 | 
			
		||||
 | 
			
		||||
        .chat-config {
 | 
			
		||||
          display flex
 | 
			
		||||
          flex-direction row
 | 
			
		||||
          align-items: center;
 | 
			
		||||
          justify-content center;
 | 
			
		||||
          padding-top 10px;
 | 
			
		||||
 | 
			
		||||
          .role-select-label {
 | 
			
		||||
            color #ffffff
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .el-select {
 | 
			
		||||
            max-width 150px;
 | 
			
		||||
            margin-right 10px;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .role-select {
 | 
			
		||||
            max-width 130px;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .el-button {
 | 
			
		||||
            .el-icon {
 | 
			
		||||
              margin-right 5px;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .iconfont {
 | 
			
		||||
          margin-right 5px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .is-circle {
 | 
			
		||||
          margin-left 5px
 | 
			
		||||
 | 
			
		||||
          .iconfont {
 | 
			
		||||
            margin-right 0
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      .chat-box {
 | 
			
		||||
        min-width: 0;
 | 
			
		||||
        flex: 1;
 | 
			
		||||
@@ -207,23 +164,6 @@ $borderColor = #4676d0;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .re-generate {
 | 
			
		||||
            position: relative;
 | 
			
		||||
            display: flex;
 | 
			
		||||
            justify-content: center;
 | 
			
		||||
 | 
			
		||||
            .btn-box {
 | 
			
		||||
              position: absolute
 | 
			
		||||
              bottom: 10px;
 | 
			
		||||
 | 
			
		||||
              .el-button {
 | 
			
		||||
                .el-icon {
 | 
			
		||||
                  margin-right 5px;
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .input-box {
 | 
			
		||||
            background-color: #ffffff
 | 
			
		||||
            display: flex;
 | 
			
		||||
@@ -232,6 +172,26 @@ $borderColor = #4676d0;
 | 
			
		||||
            box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
 | 
			
		||||
            padding 0 15px;
 | 
			
		||||
 | 
			
		||||
            .tool-item {
 | 
			
		||||
              margin-right 15px
 | 
			
		||||
              border-radius: 6px;
 | 
			
		||||
              color: #19c37d;
 | 
			
		||||
              display flex
 | 
			
		||||
              justify-content center
 | 
			
		||||
              justify-items center
 | 
			
		||||
              padding 6px
 | 
			
		||||
              cursor pointer
 | 
			
		||||
              background #F2F2F2
 | 
			
		||||
 | 
			
		||||
              &:hover {
 | 
			
		||||
                background #D5FAD3
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .iconfont {
 | 
			
		||||
                font-size: 24px;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .input-container {
 | 
			
		||||
              width 100%
 | 
			
		||||
              margin: 0;
 | 
			
		||||
@@ -242,7 +202,6 @@ $borderColor = #4676d0;
 | 
			
		||||
              position relative
 | 
			
		||||
 | 
			
		||||
              .el-textarea {
 | 
			
		||||
 | 
			
		||||
                .el-textarea__inner::-webkit-scrollbar {
 | 
			
		||||
                  width: 0;
 | 
			
		||||
                  height: 0;
 | 
			
		||||
@@ -263,8 +222,6 @@ $borderColor = #4676d0;
 | 
			
		||||
                .el-button {
 | 
			
		||||
                  padding 8px 5px;
 | 
			
		||||
                  border-radius 6px;
 | 
			
		||||
                  background: rgb(25, 195, 125)
 | 
			
		||||
                  color #ffffff;
 | 
			
		||||
                  font-size 20px;
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,23 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .navbar {
 | 
			
		||||
      display flex
 | 
			
		||||
      flex-flow row
 | 
			
		||||
 | 
			
		||||
      .link-button {
 | 
			
		||||
        margin-right 15px
 | 
			
		||||
        color #e1e1e1
 | 
			
		||||
        padding 0 10px
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          background-color #414141
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .iconfont {
 | 
			
		||||
          font-size 24px
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .user-info {
 | 
			
		||||
        width 100%
 | 
			
		||||
        padding 5px 0;
 | 
			
		||||
@@ -153,4 +170,18 @@
 | 
			
		||||
      background-color #f1f1f1
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .user-info-menu {
 | 
			
		||||
    li {
 | 
			
		||||
      a {
 | 
			
		||||
        width 100%
 | 
			
		||||
        justify-content left
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          text-decoration none !important
 | 
			
		||||
          color var(--el-primary-text-color)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: "iconfont"; /* Project id 4125778 */
 | 
			
		||||
  src: url('iconfont.woff2?t=1713766977199') format('woff2'),
 | 
			
		||||
       url('iconfont.woff?t=1713766977199') format('woff'),
 | 
			
		||||
       url('iconfont.ttf?t=1713766977199') format('truetype');
 | 
			
		||||
  src: url('iconfont.woff2?t=1715938850931') format('woff2'),
 | 
			
		||||
       url('iconfont.woff?t=1715938850931') format('woff'),
 | 
			
		||||
       url('iconfont.ttf?t=1715938850931') format('truetype');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.iconfont {
 | 
			
		||||
@@ -13,6 +13,38 @@
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-mic-bold:before {
 | 
			
		||||
  content: "\e683";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-mic-thin:before {
 | 
			
		||||
  content: "\e8c2";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-attachment-cl:before {
 | 
			
		||||
  content: "\e66a";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-attachment-st:before {
 | 
			
		||||
  content: "\e63b";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-speaker:before {
 | 
			
		||||
  content: "\e607";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-clear:before {
 | 
			
		||||
  content: "\e900";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-bbs:before {
 | 
			
		||||
  content: "\e623";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-license:before {
 | 
			
		||||
  content: "\e65a";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-more:before {
 | 
			
		||||
  content: "\e63c";
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -5,6 +5,62 @@
 | 
			
		||||
  "css_prefix_text": "icon-",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "glyphs": [
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "6539424",
 | 
			
		||||
      "name": "麦克风",
 | 
			
		||||
      "font_class": "mic-bold",
 | 
			
		||||
      "unicode": "e683",
 | 
			
		||||
      "unicode_decimal": 59011
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "1727442",
 | 
			
		||||
      "name": "213麦克风",
 | 
			
		||||
      "font_class": "mic-thin",
 | 
			
		||||
      "unicode": "e8c2",
 | 
			
		||||
      "unicode_decimal": 59586
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "3730725",
 | 
			
		||||
      "name": "attach-attachment-cl",
 | 
			
		||||
      "font_class": "attachment-cl",
 | 
			
		||||
      "unicode": "e66a",
 | 
			
		||||
      "unicode_decimal": 58986
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "15748474",
 | 
			
		||||
      "name": "st-attachment",
 | 
			
		||||
      "font_class": "attachment-st",
 | 
			
		||||
      "unicode": "e63b",
 | 
			
		||||
      "unicode_decimal": 58939
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "1010",
 | 
			
		||||
      "name": "扬声器",
 | 
			
		||||
      "font_class": "speaker",
 | 
			
		||||
      "unicode": "e607",
 | 
			
		||||
      "unicode_decimal": 58887
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "8094805",
 | 
			
		||||
      "name": "clear",
 | 
			
		||||
      "font_class": "clear",
 | 
			
		||||
      "unicode": "e900",
 | 
			
		||||
      "unicode_decimal": 59648
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "34803640",
 | 
			
		||||
      "name": "论坛",
 | 
			
		||||
      "font_class": "bbs",
 | 
			
		||||
      "unicode": "e623",
 | 
			
		||||
      "unicode_decimal": 58915
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "310708",
 | 
			
		||||
      "name": "license",
 | 
			
		||||
      "font_class": "license",
 | 
			
		||||
      "unicode": "e65a",
 | 
			
		||||
      "unicode_decimal": 58970
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "1421807",
 | 
			
		||||
      "name": "更多",
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -7,9 +7,9 @@
 | 
			
		||||
 | 
			
		||||
      <div class="chat-item">
 | 
			
		||||
        <div class="content" v-html="content"></div>
 | 
			
		||||
        <div class="bar" v-if="createdAt !== ''">
 | 
			
		||||
        <div class="bar" v-if="createdAt">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
 | 
			
		||||
          <span class="bar-item">Tokens: {{ finalTokens }}</span>
 | 
			
		||||
          <!--          <span class="bar-item">Tokens: {{ finalTokens }}</span>-->
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,38 +2,61 @@
 | 
			
		||||
  <div class="chat-line chat-line-reply">
 | 
			
		||||
    <div class="chat-line-inner">
 | 
			
		||||
      <div class="chat-icon">
 | 
			
		||||
        <img :src="icon" alt="ChatGPT">
 | 
			
		||||
        <img :src="data.icon" alt="ChatGPT">
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="chat-item">
 | 
			
		||||
        <div class="content" v-html="content"></div>
 | 
			
		||||
        <div class="bar" v-if="createdAt !== ''">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
 | 
			
		||||
          <span class="bar-item">Tokens: {{ tokens }}</span>
 | 
			
		||||
        <div class="content" v-html="data.content"></div>
 | 
			
		||||
        <div class="bar" v-if="data.created_at">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
          <!--          <span class="bar-item">Tokens: {{ tokens }}</span>-->
 | 
			
		||||
          <span class="bar-item">
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
                  effect="dark"
 | 
			
		||||
                  content="复制回答"
 | 
			
		||||
                  placement="bottom"
 | 
			
		||||
              >
 | 
			
		||||
                <el-icon class="copy-reply" :data-clipboard-text="data.orgContent">
 | 
			
		||||
                  <DocumentCopy/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </span>
 | 
			
		||||
          <span v-if="!readOnly">
 | 
			
		||||
            <span class="bar-item" @click="reGenerate(data.prompt)">
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                content="复制回答"
 | 
			
		||||
                content="重新生成"
 | 
			
		||||
                placement="bottom"
 | 
			
		||||
            >
 | 
			
		||||
              <el-icon class="copy-reply" :data-clipboard-text="orgContent">
 | 
			
		||||
                <DocumentCopy/>
 | 
			
		||||
              </el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
              <el-icon><Refresh/></el-icon>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
          <span class="bar-item">
 | 
			
		||||
            <el-dropdown trigger="click">
 | 
			
		||||
              <span class="el-dropdown-link">
 | 
			
		||||
                <el-icon><More/></el-icon>
 | 
			
		||||
              </span>
 | 
			
		||||
              <template #dropdown>
 | 
			
		||||
                <el-dropdown-menu>
 | 
			
		||||
                  <el-dropdown-item :icon="Headset" @click="synthesis(orgContent)">生成语音</el-dropdown-item>
 | 
			
		||||
                </el-dropdown-menu>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-dropdown>
 | 
			
		||||
 | 
			
		||||
          <span class="bar-item" @click="synthesis(data.orgContent)">
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                content="生成语音朗读"
 | 
			
		||||
                placement="bottom"
 | 
			
		||||
            >
 | 
			
		||||
              <i class="iconfont icon-speaker"></i>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
          </span>
 | 
			
		||||
          <!--          <span class="bar-item">-->
 | 
			
		||||
          <!--            <el-dropdown trigger="click">-->
 | 
			
		||||
          <!--              <span class="el-dropdown-link">-->
 | 
			
		||||
          <!--                <el-icon><More/></el-icon>-->
 | 
			
		||||
          <!--              </span>-->
 | 
			
		||||
          <!--              <template #dropdown>-->
 | 
			
		||||
          <!--                <el-dropdown-menu>-->
 | 
			
		||||
          <!--                  <el-dropdown-item :icon="Headset" @click="synthesis(orgContent)">生成语音</el-dropdown-item>-->
 | 
			
		||||
          <!--                </el-dropdown-menu>-->
 | 
			
		||||
          <!--              </template>-->
 | 
			
		||||
          <!--            </el-dropdown>-->
 | 
			
		||||
          <!--          </span>-->
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -41,36 +64,36 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {Clock, DocumentCopy, Headset, More} from "@element-plus/icons-vue";
 | 
			
		||||
import {Clock, DocumentCopy, Refresh} from "@element-plus/icons-vue";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
// eslint-disable-next-line no-undef,no-unused-vars
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  content: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  data: {
 | 
			
		||||
    type: Object,
 | 
			
		||||
    default: {},
 | 
			
		||||
  },
 | 
			
		||||
  orgContent: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  createdAt: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  tokens: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 0,
 | 
			
		||||
  },
 | 
			
		||||
  icon: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'images/gpt-icon.png',
 | 
			
		||||
  readOnly: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['regen']);
 | 
			
		||||
 | 
			
		||||
if (!props.data.icon) {
 | 
			
		||||
  props.data.icon = "images/gpt-icon.png"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const synthesis = (text) => {
 | 
			
		||||
  console.log(text)
 | 
			
		||||
  ElMessage.info("语音合成功能暂不可用")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 重新生成
 | 
			
		||||
const reGenerate = (prompt) => {
 | 
			
		||||
  emits('regen', prompt)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@@ -226,6 +249,7 @@ const synthesis = (text) => {
 | 
			
		||||
            padding 3px 5px;
 | 
			
		||||
            margin-right 10px;
 | 
			
		||||
            border-radius 5px;
 | 
			
		||||
            cursor pointer
 | 
			
		||||
 | 
			
		||||
            .el-icon {
 | 
			
		||||
              position relative
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-container class="file-list-box">
 | 
			
		||||
    <el-tooltip class="box-item" effect="dark" content="打开文件管理中心">
 | 
			
		||||
      <el-button class="file-upload-img" @click="fetchFiles">
 | 
			
		||||
        <el-icon>
 | 
			
		||||
          <PictureFilled/>
 | 
			
		||||
        </el-icon>
 | 
			
		||||
      </el-button>
 | 
			
		||||
    </el-tooltip>
 | 
			
		||||
 | 
			
		||||
    <a class="file-upload-img" @click="fetchFiles">
 | 
			
		||||
      <i class="iconfont icon-attachment-st"></i>
 | 
			
		||||
    </a>
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="show"
 | 
			
		||||
        :close-on-click-modal="true"
 | 
			
		||||
@@ -58,7 +53,7 @@
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {Delete, PictureFilled, Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import {Delete, Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import {isImage, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
@@ -132,11 +127,9 @@ const insertURL = (url) => {
 | 
			
		||||
 | 
			
		||||
.file-list-box {
 | 
			
		||||
  .file-upload-img {
 | 
			
		||||
    padding: 8px 5px;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    background: #19c37d;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
    .iconfont {
 | 
			
		||||
      font-size: 24px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-dialog {
 | 
			
		||||
 
 | 
			
		||||
@@ -221,7 +221,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {computed, ref} from "vue"
 | 
			
		||||
import {ref, watch} from "vue"
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {setUserToken} from "@/store/session";
 | 
			
		||||
@@ -234,8 +234,9 @@ import {arrayContains} from "@/utils/libs";
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  show: Boolean,
 | 
			
		||||
});
 | 
			
		||||
const showDialog = computed(() => {
 | 
			
		||||
  return props.show
 | 
			
		||||
const showDialog = ref(false)
 | 
			
		||||
watch(() => props.show, (newValue) => {
 | 
			
		||||
  showDialog.value = newValue
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const login = ref(true)
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
import {createApp} from 'vue'
 | 
			
		||||
import ElementPlus from "element-plus"
 | 
			
		||||
import "element-plus/dist/index.css"
 | 
			
		||||
import '@/assets/iconfont/iconfont.css';
 | 
			
		||||
import 'vant/lib/index.css';
 | 
			
		||||
import App from './App.vue'
 | 
			
		||||
import {createPinia} from "pinia";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								web/src/store/sharedata.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								web/src/store/sharedata.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import {defineStore} from 'pinia';
 | 
			
		||||
 | 
			
		||||
export const useSharedStore = defineStore('shared', {
 | 
			
		||||
    state: () => ({
 | 
			
		||||
        showLoginDialog: false
 | 
			
		||||
    }),
 | 
			
		||||
    getters: {},
 | 
			
		||||
    actions: {
 | 
			
		||||
        setShowLoginDialog(value) {
 | 
			
		||||
            this.showLoginDialog = value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
@@ -3,9 +3,6 @@
 | 
			
		||||
    <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">
 | 
			
		||||
@@ -14,17 +11,10 @@
 | 
			
		||||
            :icon="item.icon"
 | 
			
		||||
            :created-at="dateFormat(item['created_at'])"
 | 
			
		||||
            :tokens="item['tokens']"
 | 
			
		||||
            :model="item['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"/>
 | 
			
		||||
        <chat-mid-journey v-else-if="item.type==='mj'"
 | 
			
		||||
                          :content="item.content"
 | 
			
		||||
                          :icon="item.icon"
 | 
			
		||||
                          :created-at="dateFormat(item['created_at'])"/>
 | 
			
		||||
                    :data="item" :read-only="true"/>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div><!-- end chat box -->
 | 
			
		||||
  </div>
 | 
			
		||||
@@ -34,14 +24,13 @@
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import ChatReply from "@/components/ChatReply.vue";
 | 
			
		||||
import ChatPrompt from "@/components/ChatPrompt.vue";
 | 
			
		||||
import {nextTick, ref} from "vue";
 | 
			
		||||
import {nextTick, onMounted, 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";
 | 
			
		||||
import ChatMidJourney from "@/components/ChatMidJourney.vue";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
 | 
			
		||||
const chatData = ref([])
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
@@ -91,9 +80,16 @@ httpGet('/api/chat/detail?chat_id=' + chatId).then(res => {
 | 
			
		||||
  ElMessage.error("加载会失败: " + e.message)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const exportChat = () => {
 | 
			
		||||
  window.print()
 | 
			
		||||
}
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  const clipboard = new Clipboard('.copy-reply');
 | 
			
		||||
  clipboard.on('success', () => {
 | 
			
		||||
    ElMessage.success('复制成功!');
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  clipboard.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
.chat-export {
 | 
			
		||||
@@ -115,12 +111,15 @@ const exportChat = () => {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    .chat-line {
 | 
			
		||||
    .chat-line-prompt {
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: flex-start;
 | 
			
		||||
 | 
			
		||||
      .chat-line-inner {
 | 
			
		||||
        .chat-icon {
 | 
			
		||||
          margin-right: 0
 | 
			
		||||
        }
 | 
			
		||||
        .content {
 | 
			
		||||
          padding-top: 0
 | 
			
		||||
          font-size 16px;
 | 
			
		||||
@@ -138,10 +137,6 @@ const exportChat = () => {
 | 
			
		||||
      .chat-line-inner {
 | 
			
		||||
        display flex
 | 
			
		||||
 | 
			
		||||
        .copy-reply {
 | 
			
		||||
          display none
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .bar-item {
 | 
			
		||||
          background-color: #f7f7f8;
 | 
			
		||||
          color: #888;
 | 
			
		||||
 
 | 
			
		||||
@@ -59,92 +59,12 @@
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="tool-box">
 | 
			
		||||
          <!--          <el-dropdown :hide-on-click="true" class="user-info" trigger="click" v-if="isLogin">-->
 | 
			
		||||
          <!--                        <span class="el-dropdown-link">-->
 | 
			
		||||
          <!--                          <el-image :src="loginUser.avatar"/>-->
 | 
			
		||||
          <!--                          <span class="username">{{ loginUser.nickname }}</span>-->
 | 
			
		||||
          <!--                          <el-icon><ArrowDown/></el-icon>-->
 | 
			
		||||
          <!--                        </span>-->
 | 
			
		||||
          <!--            <template #dropdown>-->
 | 
			
		||||
          <!--              <el-dropdown-menu style="width: 296px;">-->
 | 
			
		||||
          <!--                <el-dropdown-item @click="showConfig">-->
 | 
			
		||||
          <!--                  <el-icon>-->
 | 
			
		||||
          <!--                    <Tools/>-->
 | 
			
		||||
          <!--                  </el-icon>-->
 | 
			
		||||
          <!--                  <span>账户信息</span>-->
 | 
			
		||||
          <!--                </el-dropdown-item>-->
 | 
			
		||||
 | 
			
		||||
          <!--                <el-dropdown-item @click="clearAllChats">-->
 | 
			
		||||
          <!--                  <el-icon>-->
 | 
			
		||||
          <!--                    <Delete/>-->
 | 
			
		||||
          <!--                  </el-icon>-->
 | 
			
		||||
          <!--                  <span>清除所有会话</span>-->
 | 
			
		||||
          <!--                </el-dropdown-item>-->
 | 
			
		||||
 | 
			
		||||
          <!--                <el-dropdown-item @click="logout">-->
 | 
			
		||||
          <!--                  <i class="iconfont icon-logout"></i>-->
 | 
			
		||||
          <!--                  <span>注销</span>-->
 | 
			
		||||
          <!--                </el-dropdown-item>-->
 | 
			
		||||
 | 
			
		||||
          <!--                <el-dropdown-item>-->
 | 
			
		||||
          <!--                  <i class="iconfont icon-github"></i>-->
 | 
			
		||||
          <!--                  <span>-->
 | 
			
		||||
          <!--                    powered by-->
 | 
			
		||||
          <!--                    <el-link type="primary" href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">chatgpt-plus-v3</el-link>-->
 | 
			
		||||
          <!--                 </span>-->
 | 
			
		||||
          <!--                </el-dropdown-item>-->
 | 
			
		||||
          <!--              </el-dropdown-menu>-->
 | 
			
		||||
          <!--            </template>-->
 | 
			
		||||
          <!--          </el-dropdown>-->
 | 
			
		||||
          <el-button type="danger" size="small" @click="clearAllChats">
 | 
			
		||||
            <i class="iconfont icon-clear"></i> 清空聊天记录
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-aside>
 | 
			
		||||
      <el-main v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.3)">
 | 
			
		||||
        <!--        <div class="chat-head">-->
 | 
			
		||||
        <!--          <div class="chat-config">-->
 | 
			
		||||
        <!--            <el-select v-model="roleId" filterable placeholder="角色" class="role-select" @change="_newChat"-->
 | 
			
		||||
        <!--                       style="width:150px">-->
 | 
			
		||||
        <!--              <el-option-->
 | 
			
		||||
        <!--                  v-for="item in roles"-->
 | 
			
		||||
        <!--                  :key="item.id"-->
 | 
			
		||||
        <!--                  :label="item.name"-->
 | 
			
		||||
        <!--                  :value="item.id"-->
 | 
			
		||||
        <!--              >-->
 | 
			
		||||
        <!--                <div class="role-option">-->
 | 
			
		||||
        <!--                  <el-image :src="item.icon"></el-image>-->
 | 
			
		||||
        <!--                  <span>{{ item.name }}</span>-->
 | 
			
		||||
        <!--                </div>-->
 | 
			
		||||
        <!--              </el-option>-->
 | 
			
		||||
        <!--            </el-select>-->
 | 
			
		||||
 | 
			
		||||
        <!--            <el-select v-model="modelID" placeholder="模型" @change="_newChat" :disabled="disableModel"-->
 | 
			
		||||
        <!--                       style="width:150px">-->
 | 
			
		||||
        <!--              <el-option-->
 | 
			
		||||
        <!--                  v-for="item in models"-->
 | 
			
		||||
        <!--                  :key="item.id"-->
 | 
			
		||||
        <!--                  :label="item.name"-->
 | 
			
		||||
        <!--                  :value="item.id"-->
 | 
			
		||||
        <!--              >-->
 | 
			
		||||
        <!--                <span>{{ item.name }}</span>-->
 | 
			
		||||
        <!--                <el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{-->
 | 
			
		||||
        <!--                    item.power-->
 | 
			
		||||
        <!--                  }}算力-->
 | 
			
		||||
        <!--                </el-tag>-->
 | 
			
		||||
        <!--              </el-option>-->
 | 
			
		||||
        <!--            </el-select>-->
 | 
			
		||||
        <!--            <el-button type="primary" @click="newChat">-->
 | 
			
		||||
        <!--              <el-icon>-->
 | 
			
		||||
        <!--                <Plus/>-->
 | 
			
		||||
        <!--              </el-icon>-->
 | 
			
		||||
        <!--              新建对话-->
 | 
			
		||||
        <!--            </el-button>-->
 | 
			
		||||
 | 
			
		||||
        <!--            <el-button type="success" @click="exportChat" plain>-->
 | 
			
		||||
        <!--              <i class="iconfont icon-export"></i>-->
 | 
			
		||||
        <!--              <span>导出会话</span>-->
 | 
			
		||||
        <!--            </el-button>-->
 | 
			
		||||
        <!--          </div>-->
 | 
			
		||||
        <!--        </div>-->
 | 
			
		||||
 | 
			
		||||
        <div class="chat-box" :style="{height: mainWinHeight+'px'}">
 | 
			
		||||
          <div>
 | 
			
		||||
            <div id="container">
 | 
			
		||||
@@ -160,63 +80,102 @@
 | 
			
		||||
                      :tokens="item['tokens']"
 | 
			
		||||
                      :model="getModelValue(modelID)"
 | 
			
		||||
                      :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"/>
 | 
			
		||||
                  <chat-mid-journey v-else-if="item.type==='mj'"
 | 
			
		||||
                                    :content="item.content"
 | 
			
		||||
                                    :role-id="item.role_id"
 | 
			
		||||
                                    :chat-id="item.chat_id"
 | 
			
		||||
                                    :icon="item.icon"
 | 
			
		||||
                                    @disable-input="disableInput(true)"
 | 
			
		||||
                                    @enable-input="enableInput"
 | 
			
		||||
                                    :created-at="dateFormat(item['created_at'])"/>
 | 
			
		||||
                  <chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false"/>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div><!-- end chat box -->
 | 
			
		||||
 | 
			
		||||
              <div class="re-generate">
 | 
			
		||||
                <div class="btn-box">
 | 
			
		||||
                  <el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
 | 
			
		||||
                    <el-icon>
 | 
			
		||||
                      <VideoPause/>
 | 
			
		||||
                    </el-icon>
 | 
			
		||||
                    停止生成
 | 
			
		||||
                  </el-button>
 | 
			
		||||
              <el-affix position="bottom" :offset="0">
 | 
			
		||||
                <div class="input-box">
 | 
			
		||||
                  <span class="tool-item">
 | 
			
		||||
                    <el-tooltip effect="dark" content="聊天设置">
 | 
			
		||||
                      <el-popover
 | 
			
		||||
                          :width="300"
 | 
			
		||||
                          trigger="click"
 | 
			
		||||
                          placement="top-start"
 | 
			
		||||
                      >
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                          <i class="iconfont icon-config"></i>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                  <el-button type="primary" v-if="showReGenerate" @click="reGenerate" plain>
 | 
			
		||||
                    <el-icon>
 | 
			
		||||
                      <RefreshRight/>
 | 
			
		||||
                    </el-icon>
 | 
			
		||||
                    重新生成
 | 
			
		||||
                  </el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
                        <template #default>
 | 
			
		||||
                          <div class="chat-config">
 | 
			
		||||
                            <el-select v-model="roleId" filterable placeholder="角色" @change="newChat"
 | 
			
		||||
                                       class="role-select"
 | 
			
		||||
                                       style="width:150px">
 | 
			
		||||
                              <el-option
 | 
			
		||||
                                  v-for="item in roles"
 | 
			
		||||
                                  :key="item.id"
 | 
			
		||||
                                  :label="item.name"
 | 
			
		||||
                                  :value="item.id"
 | 
			
		||||
                              >
 | 
			
		||||
                                <div class="role-option">
 | 
			
		||||
                                  <el-image :src="item.icon"></el-image>
 | 
			
		||||
                                  <span>{{ item.name }}</span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                              </el-option>
 | 
			
		||||
                            </el-select>
 | 
			
		||||
 | 
			
		||||
              <div class="input-box">
 | 
			
		||||
                <div class="input-container">
 | 
			
		||||
                  <el-input
 | 
			
		||||
                      ref="textInput"
 | 
			
		||||
                      v-model="prompt"
 | 
			
		||||
                      v-on:keydown="inputKeyDown"
 | 
			
		||||
                      autofocus
 | 
			
		||||
                      type="textarea"
 | 
			
		||||
                      :rows="2"
 | 
			
		||||
                      placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
 | 
			
		||||
                  />
 | 
			
		||||
                  <span class="select-file">
 | 
			
		||||
                    <file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertURL"/>
 | 
			
		||||
                            <el-select v-model="modelID" filterable placeholder="模型" @change="newChat"
 | 
			
		||||
                                       :disabled="disableModel"
 | 
			
		||||
                                       style="width:150px">
 | 
			
		||||
                              <el-option
 | 
			
		||||
                                  v-for="item in models"
 | 
			
		||||
                                  :key="item.id"
 | 
			
		||||
                                  :label="item.name"
 | 
			
		||||
                                  :value="item.id"
 | 
			
		||||
                              >
 | 
			
		||||
                                <span>{{ item.name }}</span>
 | 
			
		||||
                                <el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{
 | 
			
		||||
                                    item.power
 | 
			
		||||
                                  }}算力
 | 
			
		||||
                                </el-tag>
 | 
			
		||||
                              </el-option>
 | 
			
		||||
                            </el-select>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-popover>
 | 
			
		||||
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                  </span>
 | 
			
		||||
                  <span class="send-btn">
 | 
			
		||||
                    <el-button @click="sendMessage">
 | 
			
		||||
 | 
			
		||||
                  <span class="tool-item" @click="ElMessage.info('暂时不支持语音输入')">
 | 
			
		||||
                    <el-tooltip class="box-item" effect="dark" content="语音输入">
 | 
			
		||||
                      <i class="iconfont icon-mic-bold"></i>
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                  </span>
 | 
			
		||||
 | 
			
		||||
                  <span class="tool-item">
 | 
			
		||||
                    <el-tooltip class="box-item" effect="dark" content="上传附件">
 | 
			
		||||
                      <file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertURL"/>
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                  </span>
 | 
			
		||||
 | 
			
		||||
                  <div class="input-container">
 | 
			
		||||
                    <el-input
 | 
			
		||||
                        ref="textInput"
 | 
			
		||||
                        v-model="prompt"
 | 
			
		||||
                        v-on:keydown="inputKeyDown"
 | 
			
		||||
                        autofocus
 | 
			
		||||
                        type="textarea"
 | 
			
		||||
                        :rows="2"
 | 
			
		||||
                        style="--el-input-focus-border-color:#21AA93;
 | 
			
		||||
                        border: 1px solid #21AA93;--el-input-border-color:#21AA93;
 | 
			
		||||
                        border-radius: 5px; --el-input-hover-border-color:#21AA93;"
 | 
			
		||||
                        placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
 | 
			
		||||
                    />
 | 
			
		||||
                    <span class="send-btn">
 | 
			
		||||
                    <el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
 | 
			
		||||
                      <el-icon>
 | 
			
		||||
                        <VideoPause/>
 | 
			
		||||
                      </el-icon>
 | 
			
		||||
                    </el-button>
 | 
			
		||||
                    <el-button @click="sendMessage" color="#19c37d" style="color:#ffffff" v-else>
 | 
			
		||||
                      <el-icon><Promotion/></el-icon>
 | 
			
		||||
                    </el-button>
 | 
			
		||||
                  </span>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div><!-- end input box -->
 | 
			
		||||
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div><!-- end input box -->
 | 
			
		||||
              </el-affix>
 | 
			
		||||
            </div><!-- end container -->
 | 
			
		||||
          </div><!-- end loading -->
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -239,10 +198,6 @@
 | 
			
		||||
        </p>
 | 
			
		||||
      </div>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
 | 
			
		||||
    <config-dialog v-if="isLogin" :show="showConfigDialog" :models="models" @hide="showConfigDialog = false"/>
 | 
			
		||||
 | 
			
		||||
    <login-dialog :show="showLoginDialog" @hide="showLoginDialog =  false" @success="initData"/>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -251,7 +206,7 @@
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref} from 'vue'
 | 
			
		||||
import ChatPrompt from "@/components/ChatPrompt.vue";
 | 
			
		||||
import ChatReply from "@/components/ChatReply.vue";
 | 
			
		||||
import {Delete, Edit, More, Plus, Promotion, RefreshRight, Search, Share, VideoPause} from '@element-plus/icons-vue'
 | 
			
		||||
import {Delete, Edit, More, Plus, Promotion, Search, Share, VideoPause} from '@element-plus/icons-vue'
 | 
			
		||||
import 'highlight.js/styles/a11y-dark.css'
 | 
			
		||||
import {dateFormat, escapeHTML, isMobile, processContent, randString, removeArrayItem, UUID} from "@/utils/libs";
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
@@ -260,12 +215,10 @@ import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import ConfigDialog from "@/components/ConfigDialog.vue";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import Welcome from "@/components/Welcome.vue";
 | 
			
		||||
import ChatMidJourney from "@/components/ChatMidJourney.vue";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import FileSelect from "@/components/FileSelect.vue";
 | 
			
		||||
import LoginDialog from "@/components/LoginDialog.vue";
 | 
			
		||||
 | 
			
		||||
const title = ref('ChatGPT-智能助手');
 | 
			
		||||
const models = ref([])
 | 
			
		||||
@@ -283,14 +236,13 @@ const roles = ref([]);
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const roleId = ref(0)
 | 
			
		||||
const newChatItem = ref(null);
 | 
			
		||||
const showConfigDialog = ref(false);
 | 
			
		||||
const showLoginDialog = ref(false)
 | 
			
		||||
const isLogin = ref(false)
 | 
			
		||||
const showHello = ref(true)
 | 
			
		||||
const textInput = ref(null)
 | 
			
		||||
const showNotice = ref(false)
 | 
			
		||||
const notice = ref("")
 | 
			
		||||
const noticeKey = ref("SYSTEM_NOTICE")
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
 | 
			
		||||
if (isMobile()) {
 | 
			
		||||
  router.replace("/mobile/chat")
 | 
			
		||||
@@ -426,22 +378,17 @@ const resizeElement = function () {
 | 
			
		||||
  leftBoxHeight.value = window.innerHeight - 90 - 45 - 82;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const _newChat = () => {
 | 
			
		||||
  if (isLogin.value) {
 | 
			
		||||
    newChat()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const disableModel = ref(false)
 | 
			
		||||
// 新建会话
 | 
			
		||||
const newChat = () => {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    showLoginDialog.value = true
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  const role = getRoleById(roleId.value)
 | 
			
		||||
  showHello.value = role.key === 'gpt';
 | 
			
		||||
  // if the role bind a model, disable model change
 | 
			
		||||
  disableModel.value = false
 | 
			
		||||
  if (role.model_id > 0) {
 | 
			
		||||
    modelID.value = role.model_id
 | 
			
		||||
    disableModel.value = true
 | 
			
		||||
@@ -469,7 +416,6 @@ const newChat = () => {
 | 
			
		||||
  };
 | 
			
		||||
  activeChat.value = {} //取消激活的会话高亮
 | 
			
		||||
  showStopGenerate.value = false;
 | 
			
		||||
  showReGenerate.value = false;
 | 
			
		||||
  connect(null, roleId.value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -481,7 +427,7 @@ const changeChat = (chat) => {
 | 
			
		||||
 | 
			
		||||
const loadChat = function (chat) {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    showLoginDialog.value = true
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -494,7 +440,6 @@ const loadChat = function (chat) {
 | 
			
		||||
  roleId.value = chat.role_id;
 | 
			
		||||
  modelID.value = chat.model_id;
 | 
			
		||||
  showStopGenerate.value = false;
 | 
			
		||||
  showReGenerate.value = false;
 | 
			
		||||
  connect(chat.chat_id, chat.role_id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -600,8 +545,6 @@ md.use(mathjaxPlugin)
 | 
			
		||||
// 创建 socket 连接
 | 
			
		||||
const prompt = ref('');
 | 
			
		||||
const showStopGenerate = ref(false); // 停止生成
 | 
			
		||||
const showReGenerate = ref(false); // 重新生成
 | 
			
		||||
const previousText = ref(''); // 上一次提问
 | 
			
		||||
const lineBuffer = ref(''); // 输出缓冲行
 | 
			
		||||
const socket = ref(null);
 | 
			
		||||
const activelyClose = ref(false); // 主动关闭
 | 
			
		||||
@@ -646,7 +589,6 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    chatData.value = []; // 初始化聊天数据
 | 
			
		||||
    previousText.value = '';
 | 
			
		||||
    enableInput()
 | 
			
		||||
    activelyClose.value = false;
 | 
			
		||||
 | 
			
		||||
@@ -686,7 +628,7 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
          } else if (data.type === 'end') { // 消息接收完毕
 | 
			
		||||
            // 追加当前会话到会话列表
 | 
			
		||||
            if (isNewChat && newChatItem.value !== null) {
 | 
			
		||||
              newChatItem.value['title'] = previousText.value;
 | 
			
		||||
              newChatItem.value['title'] = tmpChatTitle.value;
 | 
			
		||||
              newChatItem.value['chat_id'] = chat_id;
 | 
			
		||||
              chatList.value.unshift(newChatItem.value);
 | 
			
		||||
              activeChat.value = newChatItem.value;
 | 
			
		||||
@@ -751,13 +693,11 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
 | 
			
		||||
const disableInput = (force) => {
 | 
			
		||||
  canSend.value = false;
 | 
			
		||||
  showReGenerate.value = false;
 | 
			
		||||
  showStopGenerate.value = !force;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const enableInput = () => {
 | 
			
		||||
  canSend.value = true;
 | 
			
		||||
  showReGenerate.value = previousText.value !== "";
 | 
			
		||||
  showStopGenerate.value = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -782,7 +722,7 @@ const autofillPrompt = (text) => {
 | 
			
		||||
// 发送消息
 | 
			
		||||
const sendMessage = function () {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    showLoginDialog.value = true
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -800,7 +740,7 @@ const sendMessage = function () {
 | 
			
		||||
    id: randString(32),
 | 
			
		||||
    icon: loginUser.value.avatar,
 | 
			
		||||
    content: md.render(escapeHTML(processContent(prompt.value))),
 | 
			
		||||
    created_at: new Date().getTime(),
 | 
			
		||||
    created_at: new Date().getTime() / 1000,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  nextTick(() => {
 | 
			
		||||
@@ -810,15 +750,11 @@ const sendMessage = function () {
 | 
			
		||||
  showHello.value = false
 | 
			
		||||
  disableInput(false)
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: prompt.value}));
 | 
			
		||||
  previousText.value = prompt.value;
 | 
			
		||||
  tmpChatTitle.value = prompt.value
 | 
			
		||||
  prompt.value = '';
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const showConfig = function () {
 | 
			
		||||
  showConfigDialog.value = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const clearAllChats = function () {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '确认要清空所有的会话历史记录吗?<br/>此操作不可以撤销!',
 | 
			
		||||
@@ -866,6 +802,9 @@ const loadChatHistory = function (chatId) {
 | 
			
		||||
    for (let i = 0; i < data.length; i++) {
 | 
			
		||||
      data[i].orgContent = data[i].content;
 | 
			
		||||
      data[i].content = md.render(processContent(data[i].content))
 | 
			
		||||
      if (i > 0 && data[i].type === 'reply') {
 | 
			
		||||
        data[i].prompt = data[i - 1].orgContent
 | 
			
		||||
      }
 | 
			
		||||
      chatData.value.push(data[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -887,9 +826,9 @@ const stopGenerate = function () {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 重新生成
 | 
			
		||||
const reGenerate = function () {
 | 
			
		||||
const reGenerate = function (prompt) {
 | 
			
		||||
  disableInput(false)
 | 
			
		||||
  const text = '重新生成上述问题的答案:' + previousText.value;
 | 
			
		||||
  const text = '重新生成下面问题的答案:' + prompt;
 | 
			
		||||
  // 追加消息
 | 
			
		||||
  chatData.value.push({
 | 
			
		||||
    type: "prompt",
 | 
			
		||||
@@ -897,7 +836,7 @@ const reGenerate = function () {
 | 
			
		||||
    icon: loginUser.value.avatar,
 | 
			
		||||
    content: md.render(text)
 | 
			
		||||
  });
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: previousText.value}));
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: prompt}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const chatName = ref('')
 | 
			
		||||
@@ -961,4 +900,37 @@ const insertURL = (url) => {
 | 
			
		||||
    padding 0 20px
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.input-container {
 | 
			
		||||
  .el-textarea {
 | 
			
		||||
    .el-textarea__inner {
 | 
			
		||||
      padding-right 40px
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chat-config {
 | 
			
		||||
  display flex
 | 
			
		||||
  flex-direction row
 | 
			
		||||
  padding-top 10px;
 | 
			
		||||
 | 
			
		||||
  .role-select-label {
 | 
			
		||||
    color #ffffff
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-select {
 | 
			
		||||
    max-width 150px;
 | 
			
		||||
    margin-right 10px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .role-select {
 | 
			
		||||
    max-width 130px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-button {
 | 
			
		||||
    .el-icon {
 | 
			
		||||
      margin-right 5px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -11,13 +11,34 @@
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="navbar">
 | 
			
		||||
 | 
			
		||||
        <el-tooltip
 | 
			
		||||
            class="box-item"
 | 
			
		||||
            effect="dark"
 | 
			
		||||
            content="部署文档"
 | 
			
		||||
            placement="bottom">
 | 
			
		||||
          <a href="https://ai.r9it.com/docs/install/" class="link-button" target="_blank">
 | 
			
		||||
            <i class="iconfont icon-book"></i>
 | 
			
		||||
          </a>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
 | 
			
		||||
        <el-tooltip
 | 
			
		||||
            class="box-item"
 | 
			
		||||
            effect="dark"
 | 
			
		||||
            content="项目源码"
 | 
			
		||||
            placement="bottom">
 | 
			
		||||
          <a href="https://github.com/yangjian102621/chatgpt-plus" class="link-button" target="_blank">
 | 
			
		||||
            <i class="iconfont icon-github"></i>
 | 
			
		||||
          </a>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
 | 
			
		||||
        <el-dropdown :hide-on-click="true" class="user-info" trigger="click" v-if="loginUser.id">
 | 
			
		||||
                        <span class="el-dropdown-link">
 | 
			
		||||
                          <el-image :src="loginUser.avatar"/>
 | 
			
		||||
                        </span>
 | 
			
		||||
          <template #dropdown>
 | 
			
		||||
            <el-dropdown-menu>
 | 
			
		||||
              <el-dropdown-item>
 | 
			
		||||
            <el-dropdown-menu class="user-info-menu">
 | 
			
		||||
              <el-dropdown-item @click="showConfigDialog = true">
 | 
			
		||||
                <el-icon>
 | 
			
		||||
                  <UserFilled/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
@@ -26,22 +47,18 @@
 | 
			
		||||
 | 
			
		||||
              <el-dropdown-item>
 | 
			
		||||
                <i class="iconfont icon-book"></i>
 | 
			
		||||
                <span>
 | 
			
		||||
                    <el-link type="primary" href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
 | 
			
		||||
                      用户手册
 | 
			
		||||
                    </el-link>
 | 
			
		||||
                 </span>
 | 
			
		||||
                <a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
 | 
			
		||||
                  用户手册
 | 
			
		||||
                </a>
 | 
			
		||||
              </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
              <el-dropdown-item>
 | 
			
		||||
                <i class="iconfont icon-github"></i>
 | 
			
		||||
                <span>
 | 
			
		||||
                    <el-link type="primary" href="https://ai.r9it.com/docs/" target="_blank">
 | 
			
		||||
                      Geek-AI {{ version }}
 | 
			
		||||
                    </el-link>
 | 
			
		||||
                 </span>
 | 
			
		||||
                <a href="https://ai.r9it.com/docs/" target="_blank">
 | 
			
		||||
                  Geek-AI {{ version }}
 | 
			
		||||
                </a>
 | 
			
		||||
              </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
              <el-divider style="margin: 2px 0"/>
 | 
			
		||||
              <el-dropdown-item @click="logout">
 | 
			
		||||
                <i class="iconfont icon-logout"></i>
 | 
			
		||||
                <span>退出登录</span>
 | 
			
		||||
@@ -49,6 +66,11 @@
 | 
			
		||||
            </el-dropdown-menu>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-dropdown>
 | 
			
		||||
 | 
			
		||||
        <div v-else>
 | 
			
		||||
          <el-button size="small" color="#21aa93" @click="show = true" round>登录</el-button>
 | 
			
		||||
          <el-button size="small" @click="router.push('/register')" round>注册</el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="main">
 | 
			
		||||
@@ -92,25 +114,31 @@
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="content" :style="{height: mainWinHeight+'px'}">
 | 
			
		||||
        <router-view v-slot="{ Component }">
 | 
			
		||||
        <router-view :key="routerViewKey" v-slot="{ Component }">
 | 
			
		||||
          <transition name="move" mode="out-in">
 | 
			
		||||
            <component :is="Component"></component>
 | 
			
		||||
          </transition>
 | 
			
		||||
        </router-view>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <login-dialog :show="show" @hide="store.setShowLoginDialog(false)" @success="loginCallback"/>
 | 
			
		||||
    <config-dialog v-if="loginUser.id" :show="showConfigDialog" @hide="showConfigDialog = false"/>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {onMounted, ref, watch} from "vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {UserFilled} from "@element-plus/icons-vue";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {removeUserToken} from "@/store/session";
 | 
			
		||||
import LoginDialog from "@/components/LoginDialog.vue";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import ConfigDialog from "@/components/ConfigDialog.vue";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const logo = ref('/images/logo.png');
 | 
			
		||||
@@ -121,6 +149,14 @@ const title = ref("")
 | 
			
		||||
const mainWinHeight = window.innerHeight - 50
 | 
			
		||||
const loginUser = ref({})
 | 
			
		||||
const version = ref(process.env.VUE_APP_VERSION)
 | 
			
		||||
const routerViewKey = ref(0)
 | 
			
		||||
const showConfigDialog = ref(false)
 | 
			
		||||
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
const show = ref(false)
 | 
			
		||||
watch(() => store.showLoginDialog, (newValue) => {
 | 
			
		||||
  show.value = newValue
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
if (curPath.value === "/external") {
 | 
			
		||||
  curPath.value = router.currentRoute.value.query.url
 | 
			
		||||
@@ -154,23 +190,35 @@ onMounted(() => {
 | 
			
		||||
    ElMessage.error("获取系统菜单失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  init()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const init = () => {
 | 
			
		||||
  checkSession().then(user => {
 | 
			
		||||
    loginUser.value = user
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const logout = function () {
 | 
			
		||||
  httpGet('/api/user/logout').then(() => {
 | 
			
		||||
    removeUserToken()
 | 
			
		||||
    router.push("/login")
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
    loginUser.value = {}
 | 
			
		||||
    // 刷新组件
 | 
			
		||||
    routerViewKey.value += 1
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.error('注销失败!');
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const loginCallback = () => {
 | 
			
		||||
  init()
 | 
			
		||||
  // 刷新组件
 | 
			
		||||
  routerViewKey.value += 1
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '@/assets/iconfont/iconfont.css';
 | 
			
		||||
@import "@/assets/css/home.styl"
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -36,8 +36,12 @@
 | 
			
		||||
 | 
			
		||||
          <el-row class="opt" :gutter="20">
 | 
			
		||||
            <el-col :span="8"><el-link type="primary" @click="router.push('/register')">注册</el-link></el-col>
 | 
			
		||||
            <el-col :span="8"><el-link @click="showResetPass = true">重置密码</el-link></el-col>
 | 
			
		||||
            <el-col :span="8"><el-link @click="router.push('/')">首页</el-link></el-col>
 | 
			
		||||
            <el-col :span="8">
 | 
			
		||||
              <el-link type="info" @click="showResetPass = true">重置密码</el-link>
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="8">
 | 
			
		||||
              <el-link type="info" @click="router.push('/')">首页</el-link>
 | 
			
		||||
            </el-col>
 | 
			
		||||
          </el-row>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user