Newer
Older
dxCard-admin / src / views / super / airag / aiapp / AiAppList.vue
YFJ on 23 Sep 16 KB 项目推送
<!--知识库文档列表-->
<template>
  <div class="knowledge">
    <!--查询区域-->
    <div class="jeecg-basic-table-form-container">
      <a-form
        ref="formRef"
        @keyup.enter.native="searchQuery"
        :model="queryParam"
        :label-col="labelCol"
        :wrapper-col="wrapperCol"
        style="background-color: #f7f8fc"
      >
        <a-row :gutter="24">
          <a-col :xl="7" :lg="7" :md="8" :sm="24">
            <a-form-item name="name" label="应用名称">
              <JInput v-model:value="queryParam.name" placeholder="请输入应用名称" />
            </a-form-item>
          </a-col>
          <a-col :xl="7" :lg="7" :md="8" :sm="24">
            <a-form-item name="type" label="应用类型">
              <j-dict-select-tag v-model:value="queryParam.type" dict-code="ai_app_type" placeholder="请选择应用类型" />
            </a-form-item>
          </a-col>
          <a-col :xl="6" :lg="7" :md="8" :sm="24">
            <span style="float: left; overflow: hidden" class="table-page-search-submitButtons">
              <a-col :lg="6">
                <a-button type="primary" preIcon="ant-design:search-outlined" @click="searchQuery">查询</a-button>
                <a-button type="primary" preIcon="ant-design:reload-outlined" @click="searchReset" style="margin-left: 8px">重置</a-button>
              </a-col>
            </span>
          </a-col>
        </a-row>
      </a-form>
    </div>
    <a-row :span="24" class="knowledge-row">
      <a-col :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24">
        <a-card class="add-knowledge-card" @click="handleCreateApp">
          <div class="flex">
            <Icon icon="ant-design:plus-outlined" class="add-knowledge-card-icon" size="20"></Icon>
            <span class="add-knowledge-card-title">创建应用</span>
          </div>
        </a-card>
      </a-col>
      <a-col :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24" v-for="item in knowledgeAppDataList">
        <a-card class="knowledge-card pointer" @click="handleEditClick(item)">
          <div class="flex">
            <img class="header-img" :src="getImage(item.icon)" />
            <div class="header-text">
              <span class="header-text-top header-name ellipsis"> {{ item.name }} </span>
              <span class="header-text-top header-create ellipsis">
                <a-tag v-if="item.status === 'release'" color="green">已发布</a-tag>
                <a-tag v-if="item.status === 'disable'">已禁用</a-tag>
                <span>创建者:{{ item.createBy_dictText || item.createBy }}</span>
              </span>
            </div>
          </div>
          <div class="header-tag">
            <a-tag color="#EBF1FF" style="margin-right: 0" v-if="item.type === 'chatSimple'">
              <span style="color: #3370ff">简单配置</span>
            </a-tag>
            <a-tag color="#FDF6EC" style="margin-right: 0" v-if="item.type === 'chatFLow'">
              <span style="color: #e6a343">高级编排</span>
            </a-tag>
          </div>
          <div class="card-description">
            <span>{{ item.descr || '暂无描述' }}</span>
          </div>
          <div class="card-footer">
            <a-tooltip title="演示">
              <div class="card-footer-icon" @click.prevent.stop="handleViewClick(item.id)">
                <Icon class="operation" icon="ant-design:youtube-outlined" size="20" color="#1F2329"></Icon>
              </div>
            </a-tooltip>
            <template v-if="item.status !== 'release'">
              <a-divider type="vertical" style="float: left" />
              <a-tooltip title="删除">
                <div class="card-footer-icon" @click.prevent.stop="handleDeleteClick(item)">
                  <Icon icon="ant-design:delete-outlined" class="operation" size="18" color="#1F2329"></Icon>
                </div>
              </a-tooltip>
            </template>
            <a-divider type="vertical" style="float: left" />
            <a-tooltip title="发布">
              <a-dropdown class="card-footer-icon" placement="bottomRight" :trigger="['click']">
                <div @click.prevent.stop>
                  <Icon style="position: relative;top: 1px" icon="ant-design:send-outlined" size="16" color="#1F2329"></Icon>
                </div>
                <template #overlay>
                  <a-menu>
                    <template v-if="item.status === 'enable'">
                      <a-menu-item key="release" @click.prevent.stop="handleSendClick(item,'release')">
                        <Icon icon="lineicons:rocket-5" size="16"></Icon>
                        发布
                      </a-menu-item>
                      <a-menu-divider/>
                    </template>
                    <template v-else-if="item.status === 'release'">
                      <a-menu-item key="un-release" @click.prevent.stop="handleSendClick(item,'un-release')">
                        <Icon icon="tabler:rocket-off" size="16"></Icon>
                        取消发布
                      </a-menu-item>
                      <a-menu-divider/>
                    </template>
                    <a-menu-item key="web" @click.prevent.stop="handleSendClick(item,'web')">
                      <Icon icon="ant-design:dribbble-outlined" size="16"></Icon>
                      嵌入网站
                    </a-menu-item>
                    <a-menu-item v-if="isShowMenu" key="menu" @click.prevent.stop="handleSendClick(item,'menu')">
                      <Icon icon="ant-design:menu-outlined" size="16"></Icon> 配置菜单
                    </a-menu-item>
                  </a-menu>
                </template>
              </a-dropdown>
            </a-tooltip>
          </div>
        </a-card>
      </a-col>
    </a-row>
    <Pagination
      v-if="knowledgeAppDataList.length > 0"
      :current="pageNo"
      :page-size="pageSize"
      :page-size-options="pageSizeOptions"
      :total="total"
      :showQuickJumper="true"
      :showSizeChanger="true"
      @change="handlePageChange"
      class="list-footer"
      size="small"
      :show-total="() => `共${total}条` "
    />
    <!-- Ai新增弹窗  -->
    <AiAppModal @register="registerModal" @success="handleSuccess"></AiAppModal>
    <!-- Ai设置弹窗 -->
    <AiAppSettingModal @register="registerSettingModal" @success="reload"></AiAppSettingModal>
    <!-- 发布弹窗 -->
    <AiAppSendModal @register="registerAiAppSendModal"/>
  </div>
</template>

<script lang="ts">
  import { ref, reactive, onMounted } from 'vue';
  import BasicModal from '@/components/Modal/src/BasicModal.vue';
  import { useModal, useModalInner } from '@/components/Modal';
  import { LoadingOutlined } from '@ant-design/icons-vue';
  import { Avatar, Modal, Pagination } from 'ant-design-vue';
  import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
  import defaultImg from './img/ailogo.png';
  import AiAppModal from './components/AiAppModal.vue';
  import AiAppSettingModal from './components/AiAppSettingModal.vue';
  import AiAppSendModal from './components/AiAppSendModal.vue';
  import Icon from '@/components/Icon';
  import { $electron } from "@/electron";
  import { appList, deleteApp, releaseApp } from './AiApp.api';
  import { useMessage } from '@/hooks/web/useMessage';
  import JInput from '@/components/Form/src/jeecg/components/JInput.vue';
  import JDictSelectTag from '@/components/Form/src/jeecg/components/JDictSelectTag.vue';
  import { useRouter } from "vue-router";

  export default {
    name: 'AiAppList',
    components: {
      JDictSelectTag,
      JInput,
      AiAppSendModal,
      Icon,
      Pagination,
      Avatar,
      LoadingOutlined,
      BasicModal,
      AiAppModal,
      AiAppSettingModal,
    },
    emits: ['success', 'register'],
    setup(props, { emit }) {
      /**
       * 创建应用的集合
       */
      const knowledgeAppDataList = ref<any>([]);
      //当前页数
      const pageNo = ref<number>(1);
      //每页条数
      const pageSize = ref<number>(10);
      //总条数
      const total = ref<number>(0);
      //可选择的页数
      const pageSizeOptions = ref<any>(['10', '20', '30']);
      //注册modal
      const [registerModal, { openModal }] = useModal();
      const [registerSettingModal, { openModal: openAppModal }] = useModal();
      const [registerAiAppSendModal, { openModal: openAiAppSendModal }] = useModal();
      const { createMessage, createConfirmSync } = useMessage();
      //查询参数
      const queryParam = reactive<any>({});
      //查询区域label宽度
      const labelCol = reactive({
        xs: 24,
        sm: 4,
        xl: 6,
        xxl: 6,
      });
      //查询区域组件宽度
      const wrapperCol = reactive({
        xs: 24,
        sm: 20,
      });
      //表单的ref
      const formRef = ref();

      reload();

      /**
       * 加载数据
       */
      function reload() {
        let params = {
          pageNo: pageNo.value,
          pageSize: pageSize.value,
          column: 'createTime',
          order: 'desc',
        };
        Object.assign(params, queryParam);
        appList(params).then((res) => {
          if (res.success) {
            knowledgeAppDataList.value = res.result.records;
            total.value = res.result.total;
          } else {
            knowledgeAppDataList.value = [];
            total.value = 0;
          }
        });
      }

      /**
       * 创建应用
       */
      function handleCreateApp() {
        openModal(true, {});
      }

      /**
       * 分页改变事件
       * @param page
       * @param current
       */
      function handlePageChange(page, current) {
        pageNo.value = page;
        pageSize.value = current;
        reload();
      }

      /**
       * 成功
       */
      function handleSuccess(id) {
        reload();
        //打开编辑弹窗
        openAppModal(true, {
          isUpdate: false,
          id: id,
        });
      }

      /**
       * 获取图片
       * @param url
       */
      function getImage(url) {
        return url ? getFileAccessHttpUrl(url) : defaultImg;
      }

      /**
       * 编辑
       * @param item
       */
      function handleEditClick(item) {
        console.log('item:::', item);
        openAppModal(true, {
          isUpdate: true,
          ...item,
        });
      }

      /**
       * 演示
       */
      function handleViewClick(id: string) {
        let url = '/ai/app/chat/' + id;

        // update-begin--author:sunjianlei---date:20250411---for:【QQYUN-9685】构建 electron 桌面应用
        if ($electron.isElectron()) {
          url = $electron.resolveRoutePath(url);
          window.open(url, '_blank', 'width=1200,height=800');
          return
        }
        // update-end----author:sunjianlei---date:20250411---for:【QQYUN-9685】构建 electron 桌面应用

        window.open(url, '_blank');
      }

      /**
       * 删除
       */
      function handleDeleteClick(item) {
        if(knowledgeAppDataList.value.length == 1 && pageNo.value > 1) {
          pageNo.value = pageNo.value - 1;
        }
        deleteApp({ id: item.id, name: item.name }, reload);
      }

      /**
       * 发布点击事件
       * @param item 数据
       * @param type 类别
       */
      function handleSendClick(item,type) {
        if (type === 'release' || type === 'un-release') {
          return onRelease(item);
        }

        openAiAppSendModal(true,{
          type: type,
          data: item
        })
      }

      async function onRelease(item) {
        const toRelease = item.status === 'enable';
        let flag = await createConfirmSync({
          title: toRelease ? '发布应用' : '取消发布应用',
          content: toRelease ? '确定要发布应用吗?发布后将不允许修改应用。' : '确定要取消发布应用吗?',
          okText: '确定',
          cancelText: '取消',
        });
        if (!flag) {
          return
        }
        doRelease(item, item.status === 'enable');
      }

      /**
       * 发布
       */
      async function doRelease(item, release: boolean) {
        let success: boolean = await releaseApp(item.id, release);
        if (success) {
          // 发布成功
          if (release) {
            item.status = 'release'
          } else {
            item.status = 'enable'
          }
        }
      }

      /**
       * 重置
       */
      function searchReset() {
        pageNo.value = 1;
        formRef.value.resetFields();
        queryParam.name = '';
        //刷新数据
        reload();
      }

      /**
       * 查询
       */
      function searchQuery(){
        pageNo.value = 1; 
        //刷新数据
        reload();
      }

      const router = useRouter();
      //是否显示菜单配置选项
      const isShowMenu = ref<boolean>(false);
      onMounted((()=>{
        let fullPath = router.currentRoute.value.fullPath;
        console.log(fullPath)
        if(fullPath === '/myapps/ai/app'){
          isShowMenu.value = false;
        } else {
          isShowMenu.value = true;
        }
      }))

      return {
        handleCreateApp,
        knowledgeAppDataList,
        pageNo,
        pageSize,
        total,
        pageSizeOptions,
        handlePageChange,
        cardBodyStyle: { textAlign: 'left', width: '100%' },
        registerModal,
        handleSuccess,
        getImage,
        handleEditClick,
        handleViewClick,
        handleDeleteClick,
        registerSettingModal,
        reload,
        queryParam,
        labelCol,
        wrapperCol,
        handleSendClick,
        registerAiAppSendModal,
        searchReset,
        formRef,
        isShowMenu,
        searchQuery,
      };
    },
  };
</script>

<style scoped lang="less">
  .knowledge {
    height: calc(100vh - 115px);
    background: #f7f8fc;
    padding: 24px;
    overflow-y: auto;
  }

  .add-knowledge-card {
    margin-bottom: 20px;
    background: #fcfcfd;
    border: 1px solid #f0f0f0;
    box-shadow: 0 2px 4px #e6e6e6;
    transition: all 0.3s ease;
    border-radius: 10px;
    display: inline-flex;
    justify-content: center;
    align-items: center;
    font-size: 16px;
    cursor: pointer;
    height: 152px;
    width: calc(100% - 20px);
    .add-knowledge-card-icon {
      padding: 8px;
      color: #1f2329;
      background-color: #f5f6f7;
      margin-right: 12px;
    }
    .add-knowledge-card-title {
      font-size: 16px;
      color:#1f2329;
      font-weight: 400;
      align-self: center;
    }
  }

  .knowledge-card {
    border-radius: 10px;
    margin-right: 20px;
    margin-bottom: 20px;
    height: 152px;
    background: #fcfcfd;
    border: 1px solid #f0f0f0;
    box-shadow: 0 2px 4px #e6e6e6;
    transition: all 0.3s ease;
    .header-img {
      width: 40px;
      height: 40px;
      border-radius: 0.5rem;
    }
    .header-text {
      margin-left: 5px;
      position: relative;
      font-size: 14px;
      display: grid;
      width: calc(100% - 100px);
      .header-name {
        font-weight: bold;
        color: #354052;
      }
      .header-create {
        font-size: 12px;
        color: #646a73;
      }
    }
    .header-tag {
      position: absolute;
      right: 4px;
      top: 6px;
    }
  }

  .add-knowledge-card,
  .knowledge-card {
    transition: box-shadow 0.3s ease;
  }

  .add-knowledge-card:hover,
  .knowledge-card:hover {
    box-shadow: 0 6px 12px #d0d3d8;
  }

  .knowledge-row {
    max-height: calc(100% - 100px);
    margin-top: 20px;
    overflow-y: auto;
  }

  .add-knowledge-doc {
    margin-top: 6px;
    color: #6f6f83;
    font-size: 13px;
    width: 100%;
    cursor: pointer;
    display: flex;
    span {
      margin-left: 4px;
      line-height: 28px;
    }
  }
  .add-knowledge-doc:hover {
    background: #c8ceda33;
  }
  .card-description {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    height: 4.5em;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.5;
    margin-top: 10px;
    text-align: left;
    font-size: 12px;
    color: #676f83;
  }
  .card-footer {
    position: absolute;
    bottom: 8px;
    left: 0;
    min-height: 30px;
    padding: 0 16px;
    width: 100%;
    align-items: center;
    display: flex;
  }

  .card-footer-icon {
    font-size: 14px;
    height: 24px;
    padding: 0 7px;
    border-radius: 4px;
    text-align: center;
    align-content: center;
    float: left;
    width: 36px;
  }

  .card-footer-icon:hover {
    color: #000000;
    background-color: #e9ecf2;
    border: none;
  }

  .operation {
    position: relative;
    top: 2px;
  }
  .list-footer {
    text-align: right;
    margin-top: 5px;
  }
  :deep(.ant-card .ant-card-body) {
    padding: 16px;
  }
  .ellipsis{
    overflow: hidden;
    text-wrap: nowrap;
    text-overflow: ellipsis;
  }
  .jeecg-basic-table-form-container {
    padding: 0;
    :deep(.ant-form) {
      background-color: transparent;
    }
    .table-page-search-submitButtons {
      display: block;
      margin-bottom: 24px;
      white-space: nowrap;
    }
  }
</style>
<style lang="less">
  .airag-knowledge-doc .scroll-container {
    padding: 0 !important;
  }
</style>