sunxiao преди 8 месеца
родител
ревизия
b8deb90b91
променени са 3 файла, в които са добавени 328 реда и са изтрити 4 реда
  1. 316 0
      src/components/Chat/ChatInputCopy.vue
  2. 3 1
      src/components/Chat/index.js
  3. 9 3
      src/views/work/WorkView.vue

+ 316 - 0
src/components/Chat/ChatInputCopy.vue

@@ -0,0 +1,316 @@
+<script setup>
+import { ref, unref, onMounted, computed, watch } from 'vue';
+import { useMessage, NInput, NSwitch, NPopover, NScrollbar } from 'naive-ui';
+import SvgIcon from '@/components/SvgIcon';
+
+import 'load-awesome/css/ball-running-dots.min.css';
+
+const props = defineProps({
+  options: {
+    type: Array,
+    default: []
+  },
+});
+
+const emit = defineEmits(['onClick', 'onEnter']);
+
+const modelLoading = defineModel('loading');
+const switchStatus = defineModel('switch');
+
+const message = useMessage();
+
+const inpVal = ref('');
+const inpRef = ref(null);
+const isFocusState = ref(false);
+
+const isOpen = ref(false);
+const selectedOption = ref(null);
+const highlightedIndex = ref(0);
+
+const agentOptions = computed(() => props.options.filter(({ tools }) => tools));
+
+const focusInput = _ => isFocusState.value = true;
+
+const blurInput = _ => isFocusState.value = false;
+
+watch(inpVal, (curVal) => {
+  if (curVal === "@" && curVal.length === 1) {
+    if ( !unref(agentOptions).length ) {
+      return message.warning('当前未配置智能体');
+    }
+    isOpen.value = true;
+  } else {
+    isOpen.value = false;
+  }
+  // isOpen.value = (curVal === "@" && curVal.length === 1);
+})
+
+const handleInpFocus = () => {
+  inpRef.value?.focus();
+}
+
+const commonEmitEvent = (eventName) => {
+  const val = unref(inpVal);
+  const len = val.trim().length;
+
+  if ( !len ) {
+    return message.warning('请输入您的问题或需求');
+  }
+
+  if ( len > 2000 ) {
+    return message.warning('问题限制2000个字以内');
+  }
+
+  if ( modelLoading.value ) {
+    return message.warning('当前对话进行中');
+  }
+
+  emit(eventName, val);
+
+  inpVal.value = '';
+}
+
+const handleInpEnter = (event) => {
+  console.log( !unref(isOpen) );
+  if (event.key === 'Enter' && !event.shiftKey) {
+    event.preventDefault();
+    // commonEmitEvent('onEnter');
+  }
+}
+
+const handleBtnClick = () => {
+  commonEmitEvent("onClick")
+}
+
+const clearInpVal = () => {
+  inpVal.value = '';
+}
+
+// 键盘上下事件
+const handleKeyDown = (event) => {
+
+  const len = unref(agentOptions).length;
+
+  switch (event.key) {
+    case 'ArrowUp':
+      event.preventDefault();
+      highlightedIndex.value = (unref(highlightedIndex) - 1 + len) % len;
+      break;
+    case 'ArrowDown':
+      event.preventDefault();
+      highlightedIndex.value = (unref(highlightedIndex) + 1) % len;
+      break;
+    case 'Enter':
+      selectOption(unref(highlightedIndex));
+      break;
+    default:
+      break;
+  }
+}
+
+const selectOption = (index) => {
+  selectedOption.value = agentOptions.value[index];
+  isOpen.value = false;
+  highlightedIndex.value = index;
+  clearInpVal();
+}
+
+onMounted(() => {
+  document.addEventListener('keydown', handleKeyDown);
+})
+
+defineExpose({
+  clearInpVal,
+  handleInpFocus,
+  inpVal,
+})
+
+</script>
+<template>
+  <NPopover
+    trigger="hover"
+    width="trigger"
+    content-style="padding: 0;"
+    :show-arrow="false"
+    :show="isOpen"
+  >
+    <template #trigger>
+      <div>
+        <div class="chat-inp-outer border-[1px]" :class="[{ 'border-[#2454FF]': isFocusState }]">
+          <div class="helper-tools py-[10px] px-[10px] bg-[#fcfcfc] space-x-[10px]" v-show="selectedOption">
+            <span>与</span>            
+            <p class="agent-name space-x-[5px]" @click="isOpen = true">
+              <img src="https://static.fuxicarbon.com/userupload/db77ffe0cef843278a23b0d2db9505fa.png" alt="">
+              <span>{{ selectedOption?.title }}</span>
+            </p>
+            <span>对话中</span>
+          </div>
+          <div class="chat-inp-inner">
+            <div class="inp-wrapper flex-1" @click="handleInpFocus">
+              <NInput 
+                class="flex-1"
+                ref="inpRef" 
+                type="textarea" 
+                size="medium"
+                placeholder="输入您的问题或需求,Enter发送,Shift+Enter换行"
+                v-model:value="inpVal" 
+                :autosize="{ minRows: 1, maxRows: 5 }"
+                @focus="focusInput"
+                @blur="blurInput"
+                @keypress="handleInpEnter"
+              />
+            </div>
+            <div class="submit-btn">
+              <button class="btn bg-[#1A2029] hover:bg-[#3C4148]" @click="handleBtnClick">
+                <SvgIcon name="tool-send-plane" size="22" v-show="!modelLoading"></SvgIcon>
+                <div style="color: #fff" class="la-ball-running-dots la-sm" v-show="modelLoading">
+                  <div v-for="item in 5" :key="item"></div>
+                </div>
+              </button>
+            </div>
+          </div>
+        </div>
+        <div class="switch-inner pt-[8px] space-x-[6px]">
+          <NSwitch size="small" v-model:value="switchStatus"></NSwitch>
+          <span class="text-[12px] text-[#9E9E9E]">使用搜索增强</span>
+        </div>
+        <div class="masking-inner text-center text-[#2454FF]"></div>
+      </div>
+    </template>
+  
+    <div class="popover-inner">
+      <div class="header">
+        <span>选择智能体</span>
+        <p class="close" @click="isOpen = false">
+          <svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <path d="M3.14905 3.14777L12.8508 12.8496" stroke="#838A95" stroke-linecap="round"></path>
+            <path d="M13.0016 2.99786L2.99725 13.0022" stroke="#838A95" stroke-linecap="round"></path>
+            <g opacity="0.01" style="mix-blend-mode: darken;">
+              <rect width="16" height="16" fill="white"></rect>
+            </g>
+          </svg>
+        </p>
+      </div>
+      <div class="content">
+        <NScrollbar style="max-height: 240px;">
+          <div class="item" v-for="item, index in agentOptions" :class="['item', { active: highlightedIndex === index }]" @click="selectOption(index)">
+            <p class="icon">
+              <img :src="item.banner" alt="">
+            </p>
+            <p class="ml-[10px] space-x-[5px] text">
+              <span class="text-[15px]">{{item.title}}</span>
+              <!-- <span class="text-[#888] text-[14px]">/span> -->
+            </p>
+          </div>
+        </NScrollbar>
+      </div>
+    </div>
+  </NPopover>
+</template>
+
+<style scoped lang="scss">
+.popover-inner {
+  .header {
+    @include flex(x, center, between);
+    padding-bottom: 8px;
+    font-size: 14px;
+    color: #666;
+    .close {
+      @include flex(x, center, center);
+      width: 28px;
+      height: 28px;
+      border-radius: 6px;
+      background: #fff;
+      cursor: pointer;
+      &:hover {
+        background: #e9eef8;
+      }
+    }
+  }
+  .content {
+    .item {
+      @include flex(x, center, start);
+      padding: 8px 10px;
+      cursor: pointer;
+      &:hover {
+        background: #f0fafe;
+      }
+      .icon {
+        @include flex(x, center, center);
+        width: 24px;
+        height: 24px;
+        border-radius: 100%;
+        background: #e9eef8;
+        img {
+          width: 16px;
+          height: 16px;
+        }
+      }
+      .text {
+        text-align: left;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+      }
+    }
+    .active {
+      background: #f0fafe;
+    }
+  }
+}
+
+.chat-inp-outer {
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0px 3px 12px 0px #97D3FF40;
+
+  .helper-tools {
+    @include flex(x, center, start);
+    color: #666;
+    font-size: 14px;
+    
+    .agent-name {
+      @include flex(x, center, start);
+      font-weight: bold;
+      color: #333;
+      cursor: pointer;
+
+      img {
+        width: 14px;
+        height: 14px;
+      }
+    }
+  }
+}
+.chat-inp-inner {
+  position: relative;
+  @include flex(x, center, between);
+  background: #fff;
+
+  .inp-wrapper {
+    padding: 17px 0px 17px 34px;
+  }
+
+  .submit-btn {
+    @include flex(x, center, center);
+    width: 84px;
+
+    .btn {
+      @include flex(x, center, center);
+      width: 50px;
+      height: 32px;
+      border-radius: 32px;
+      transition: all .3s;
+    }
+  }
+}
+
+.masking-inner {
+  position: absolute;
+  top: -30px;
+  left: 0;
+  width: 100%;
+  height: 30px;
+  background: linear-gradient(180deg, rgba(232, 241, 250, 0) 0%, #E7F0FA 95%);
+}
+</style>

+ 3 - 1
src/components/Chat/index.js

@@ -2,11 +2,13 @@ import ChatAsk from './ChatAsk';
 import ChatAnswer from './ChatAnswer';
 import ChatInput from './ChatInput';
 import ChatBaseCard from './ChatBaseCard';
+import ChatInputCopy from './ChatInputCopy';
 
 
 export { 
   ChatAsk,
   ChatAnswer,
   ChatInput,
-  ChatBaseCard
+  ChatBaseCard,
+  ChatInputCopy
 };

+ 9 - 3
src/views/work/WorkView.vue

@@ -2,7 +2,7 @@
 import { ref, unref, computed, onMounted, onUnmounted } from 'vue';
 import { useMessage } from 'naive-ui';
 import { BaseButton, RecodeCardItem, TheSubMenu, TheChatView, ChatWelcome, SvgIcon } from '@/components';
-import { ChatAsk, ChatAnswer, ChatInput } from '@/components/Chat';
+import { ChatAsk, ChatAnswer, ChatInputCopy } from '@/components/Chat';
 import { chatApi } from '@/api/chat';
 import { helperApi } from '@/api/helper';
 
@@ -243,8 +243,14 @@ onUnmounted(() => {
       </div>
 
       <template #footer>
-        <ChatInput ref="inputRef" v-model:loading="isLoading" v-model:switch="switchActive" @on-click="handleSubmit"
-          @on-enter="handleSubmit"></ChatInput>
+        <ChatInputCopy
+          :options="helperList"
+          ref="inputRef"
+          v-model:loading="isLoading"
+          v-model:switch="switchActive"
+          @on-click="handleSubmit"
+          @on-enter="handleSubmit"
+        ></ChatInputCopy>
       </template>
     </TheChatView>
   </section>