Newer
Older
dxCard-admin / src / views / super / airag / aiapp / chat / slide.vue
YFJ on 23 Sep 9 KB 项目推送
<template>
  <div class="slide-wrap">
    <div class="header">
      <img class="header-image" :src="getImage()" />
      <div class="header-name">{{ appData.name || 'AI助手' }}</div>
    </div>
    <div class="createArea">
      <a-button type="dashed" @click="handleCreate">新建聊天</a-button>
    </div>
    <div class="historyArea">
      <ul>
        <li
          v-for="(item, index) in dataSource.history"
          :key="item.id"
          class="list"
          :class="[item.id == dataSource.active ? 'active' : 'normal', dataSource.history.length == 1 ? 'last' : '']"
          @click="handleToggleChat(item, index)"
        >
          <i class="icon message">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              xmlns:xlink="http://www.w3.org/1999/xlink"
              aria-hidden="true"
              role="img"
              class="iconify iconify--ri"
              width="1em"
              height="1em"
              viewBox="0 0 24 24"
            >
              <path
                fill="currentColor"
                d="M2 8.994A5.99 5.99 0 0 1 8 3h8c3.313 0 6 2.695 6 5.994V21H8c-3.313 0-6-2.695-6-5.994zM20 19V8.994A4.004 4.004 0 0 0 16 5H8a3.99 3.99 0 0 0-4 3.994v6.012A4.004 4.004 0 0 0 8 19zm-6-8h2v2h-2zm-6 0h2v2H8z"
              ></path>
            </svg>
          </i>
          <a-input
            class="title"
            ref="inputRef"
            v-if="item.isEdit"
            :defaultValue="item.title"
            placeholder="请输入标题"
            @change="handleInputChange"
            @keyup.enter="inputBlur(item)"
          />
          <span class="title" v-else>{{ item.title }}</span>
          <span class="icon edit" @click.stop="handleEdit(item)" v-if="!item.isEdit && !item.disabled">
            <svg xmlns="http://www.w3.org/2000/svg" role="img" class="iconify iconify--ri" width="1em" height="1em" viewBox="0 0 24 24">
              <path
                fill="currentColor"
                d="M6.414 15.89L16.556 5.748l-1.414-1.414L5 14.476v1.414zm.829 2H3v-4.243L14.435 2.212a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414zM3 19.89h18v2H3z"
              ></path>
            </svg>
          </span>
          <span class="icon del">
            <a-popconfirm
              :overlayStyle="{ 'z-index': 9999 }"
              title="确定删除此记录?"
              placement="bottom"
              ok-text="确定"
              cancel-text="取消"
              @confirm.stop="handleDel(item)"
            >
              <svg xmlns="http://www.w3.org/2000/svg" role="img" class="iconify iconify--ri" width="1em" height="1em" viewBox="0 0 24 24">
                <path
                  fill="currentColor"
                  d="M17 6h5v2h-2v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V8H2V6h5V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1zm1 2H6v12h12zm-9 3h2v6H9zm4 0h2v6h-2zM9 4v2h6V4z"
                ></path>
              </svg>
            </a-popconfirm>
          </span>
        </li>
      </ul>
    </div>
    <div class="left-footer" v-if="source!='chatJs'">
      AI客服由
      <a style="color: #4183c4;margin-left: 2px;margin-right: 2px" href="https://www.qiaoqiaoyun.com/aiCustomerService" target="_blank">
        敲敲云
      </a>
      提供
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref, watch } from 'vue';
  import { useRouter } from 'vue-router';
  import { defHttp } from '@/utils/http/axios';
  import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
  import defaultImg from '../img/ailogo.png';
  const props = defineProps(['dataSource', 'appData','source']);
  const emit = defineEmits(['save', 'click', 'reloadRight', 'prologue']);
  const inputRef = ref(null);
  const router = useRouter();
  let inputValue = '';
  //新建聊天
  const handleCreate = () => {
    const uuid = getUuid();
    props.dataSource.history.unshift({ title: '新建聊天', id: uuid, isEdit: false, disabled: true });
    // 新建第一个(需要高亮选中)
    props.dataSource.active = uuid;
    emit('click', "新建聊天", 0);
  };
  // 切换聊天
  const handleToggleChat = (item, index) => {
    if (item.id != props.dataSource.active) {
      props.dataSource.active = item.id;
      emit('click', item.title, index);
    }
  };
  const handleInputChange = (e) => {
    inputValue = e.target.value.trim();
  };
  // 失去焦点
  const inputBlur = (item) => {
    item.isEdit = false;
    item.title = inputValue;
    defHttp
      .put(
        {
          url: '/airag/chat/conversation/update/title',
          params: { id: item.id, title: inputValue },
        },
        { joinParamsToUrl: true }
      )
      .then((res) => {});
  };
  // 编辑
  const handleEdit = (item) => {
    console.log(item);
    item.isEdit = true;
    inputValue = item.title;
  };
  // 保存
  const handleSave = (item) => {
    item.isEdit = false;
    item.title = inputValue;
  };

  /**
   * 删除
   * @param data
   */
  function handleDel(data) {
    const findIndex = props.dataSource.history.findIndex((item) => item.id == data.id);
    if (findIndex != -1) {
      props.dataSource.history.splice(findIndex, 1);
      // 删除的是当前active的,active往前移,前面没了往后移。
      if (props.dataSource.history.length) {
        if (props.dataSource.active == data.id) {
          if (findIndex > 0) {
            props.dataSource.active = props.dataSource.history[findIndex - 1].id;
          } else {
            props.dataSource.active = props.dataSource.history[0].id;
          }
        }
        emit('click', props.dataSource.history[0].title, findIndex);
      } else {
        //  删没了(删除了最后一个)
        handleCreate();
      }
    }
    //update-begin---author:wangshuai---date:2025-03-12---for:【QQYUN-11560】新建聊天内容为空,无法删除---
    if(data.disabled){
      return;
    }
    //update-end---author:wangshuai---date:2025-03-12---for:【QQYUN-11560】新建聊天内容为空,无法删除---
    defHttp.delete({
      url: '/airag/chat/conversation/' + data.id,
    },{ isTransformResponse: false });
  }

  /**
   * 获取图片
   */
  function getImage() {
    return props.appData.icon ? getFileAccessHttpUrl(props.appData.icon) : defaultImg;
  }

  watch(
    () => inputRef.value,
    (newVal: any) => {
      if (newVal?.length) {
        newVal[0].focus();
      }
    },
    { deep: true }
  );

  // 指定长度和基数
  const getUuid = (len = 10, radix = 16) => {
    var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
    var uuid: any = [],
      i;
    radix = radix || chars.length;

    if (len) {
      for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
    } else {
      var r;
      uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
      uuid[14] = '4';
      for (i = 0; i < 36; i++) {
        if (!uuid[i]) {
          r = 0 | (Math.random() * 16);
          uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
        }
      }
    }
    return uuid.join('');
  };
</script>

<style scoped lang="less">
  .slide-wrap {
    border-right: 1px solid #e5e7eb;
    height: 100%;
    display: flex;
    flex-direction: column;
    .historyArea {
      padding: 20px;
      padding-top: 0;
      flex: 1;
      min-height: 0;
      overflow: auto;
      margin-bottom: 20px;
      &::-webkit-scrollbar {
        width: 8px;
        height: 8px;
      }
    }
    .historyArea ul li:hover {
      .del {
        display: block;
      }
    }
    .createArea {
      padding: 20px;
      padding-bottom: 0;
    }
    .ant-btn {
      width: 100%;
      margin-bottom: 10px;
    }
  }
  ul {
    margin-bottom: 0;
  }
  .list {
    width: 100%;
    padding-top: 0.75rem;
    padding-bottom: 0.75rem;
    padding-left: 0.75rem;
    padding-right: 0.75rem;
    border-radius: 0.375rem;
    border-width: 1px;
    cursor: pointer;
    margin-bottom: 10px;
    color: #333;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    &:hover,
    &.active {
      border-color: @primary-color;
      color: @primary-color;
    }
    .edit,
    .save,
    .del {
      display: none;
    }
    &.active {
      .edit,
      .save,
      .del {
        display: block;
      }
      &.last {
        .del {
          display: none;
        }
      }
    }
    .message {
      margin-right: 8px;
    }
    .edit {
      margin-right: 8px;
    }
    .title {
      flex: 1;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      font-size: 14px;
      position: relative;
      top: 2px;
      &.ant-input {
        margin-right: 20px;
      }
    }
    svg {
      vertical-align: middle;
    }
  }
  :deep(.ant-popover) {
    z-index: 9999 !important;
  }
  :deep(.ant-popconfirm) {
    z-index: 9999 !important;
  }
  .header {
    display: flex;
    padding: 20px 4px 0 4px;
    margin-left: 16px;
    .header-image {
      height: 35px;
      width: 35px;
      border-radius: 4px;
      margin-right: 10px;
    }
    .header-name {
      align-self: center;
      color: #1d2939;
      font-weight: 600;
      font-size: 16px;
    }
  }
  .left-footer{
    display:flex;
    margin-right: 20px;
    font-size: 12px;
    position: absolute;
    bottom: 4px;
    left: 50px;
    width: 100%;
  }
</style>