charles
2024-08-02 78e1cda41cf6ad133e84efc78da50d77f2e43eca
feat:完成新增会话的,会话记录的聊天模块
7个文件已修改
358 ■■■■■ 已修改文件
.idea/codeStyles/Project.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/interceptor.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/session.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dmx/model/components/addModel.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/session/sessionManager/components/addSession.vue 75 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/session/sessionManager/index.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sessionRecords/sessionRecordsManager/index.vue 236 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/codeStyles/Project.xml
@@ -3,7 +3,7 @@
    <HTMLCodeStyleSettings>
      <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
    </HTMLCodeStyleSettings>
    <JSCodeStyleSettings version="0">
    <JSCodeStyleSettings>
      <option name="FORCE_SEMICOLON_STYLE" value="true" />
      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
      <option name="FORCE_QUOTE_STYlE" value="true" />
@@ -11,7 +11,7 @@
      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
      <option name="SPACES_WITHIN_IMPORTS" value="true" />
    </JSCodeStyleSettings>
    <TypeScriptCodeStyleSettings version="0">
    <TypeScriptCodeStyleSettings>
      <option name="FORCE_SEMICOLON_STYLE" value="true" />
      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
      <option name="FORCE_QUOTE_STYlE" value="true" />
src/api/interceptor.ts
@@ -55,7 +55,7 @@
    const res = response.data;
    // if the custom code is not 20000, it is judged as an error.
    if (
    /*if (
      (res.retcode && res.retcode !== 0) ||
      (res.code && res.code !== 20000)
    ) {
@@ -88,7 +88,7 @@
      response.config.url === '/base/login'
    ) {
      setAuthorization(response.headers.authorization);
    }
    }*/
    return res;
  },
  (error) => {
src/api/session.ts
@@ -26,4 +26,8 @@
// 获取会话详情
export function getSessionDetailsApi(conversation_id:string) {
    return axios.get<ISessionListResult>('/api/conversation/get?modeltype=localragflow',{params:{conversation_id}});
}
// 获取智能助手列表
export function getDialogListApi() {
  return axios.get<ISessionListResult>('/api/dialog/list');
}
src/views/dmx/model/components/addModel.vue
@@ -1,4 +1,3 @@
<template>
  <a-button type="primary" @click="handleClick" style="margin-left: 10px">
    <template #icon>
@@ -138,7 +137,6 @@
        done(false)
      }else {
        console.log('请求数据');
      }
    })
};
@@ -176,12 +174,3 @@
})
</script>
<script lang="ts">
export default {
  name: 'add',
  methods: {
  }
};
</script>
src/views/session/sessionManager/components/addSession.vue
@@ -1,34 +1,75 @@
<script setup lang="ts">
    import { defineProps ,ref,defineEmits} from 'vue';
    import { defineProps,ref,defineEmits, onMounted } from 'vue';
    import { Message } from '@arco-design/web-vue';
    import { addSessionApi }from '@/api/session';
    import { addSessionApi ,getDialogListApi }from '@/api/session';
    const props=defineProps({
        modalObj:Object
    });
    const conversation_name=ref('');
    const conversation=ref({ dialog_id:'', conversation_desc:'' });
    const dialogList=ref([]);
    const emit = defineEmits(['addSession']);
    const handleOk=async ()=>{
        if(conversation_name.value){
            const {code}=await addSessionApi({conversation_name:conversation_name.value});
            if(code===200){
                Message.success('添加成功');
                emit('addSession')
            }
        }else{
            Message.warning('会话名称不能为空');
        }
    const queryDialogList=async ()=>{
      const { code, data } = await getDialogListApi();
      if(code===200){
        dialogList.value = data;
      }
    };
   const  rules = {
     dialog_id: [
       {
         required: true,
         message: '智能助手不能为空',
       },
     ],
     conversation_desc: [
       {
         required: true,
         message: '描述不能为空',
       }
     ]
   }
    onMounted(()=>{
      queryDialogList();
    });
    const formRef = ref();
    const handleOk=()=>{
      formRef.value.validate().then(async(res)=>{
        if(!res){
           const { code }=await addSessionApi({ ...conversation.value });
           if(code===200){
             Message.success('添加成功');
             emit('addSession');
             setTimeout(()=>{
               props.modalObj.add=false;
             },500);
           }else{
             Message.warning('添加失败');
           }
         }
      });
      return false;
    }
    const destroyData = ()=>{
      formRef.value.resetFields();
    }
</script>
<template>
    <div>
        <a-modal v-model:visible="modalObj.add" @ok="handleOk" @cancel="modalObj.add=false">
        <a-modal v-model:visible="modalObj.add" @before-ok="handleOk" @cancel="modalObj.add=false"  @before-close="destroyData">
            <template #title>
                新增会话
            </template>
            <a-form>
                <a-form-item label="会话名称:">
                    <a-input placeholder="请输入会话名称" v-model="conversation_name" style="width: 80%"></a-input>
            <a-form ref="formRef" :model="conversation" :rules="rules">
                <a-form-item label="助手关联:" field="dialog_id" @submit="handleSubmit">
                    <a-select style="width: 80%" v-model="conversation.dialog_id" placeholder="请选择关联助手">
                        <a-option v-for="dialog in dialogList" :key="dialog.id" :value="dialog.id">{{dialog.name}}</a-option>
                    </a-select>
                </a-form-item>
                <a-form-item label="描述:" field="conversation_desc">
                    <a-textarea placeholder="请输入描述" :max-length="100" show-word-limit   :auto-size="{minRows:4,maxRows:5}" v-model="conversation.conversation_desc" style="width: 80%"></a-textarea>
                </a-form-item>
            </a-form>
        </a-modal>
src/views/session/sessionManager/index.vue
@@ -25,20 +25,20 @@
    const streamStr=ref('');
    const modalObj=reactive({ add:false });
    //查询会话列表
  const querySessionList = async () => {
    const querySessionList = async () => {
        const { code, data } =await sessionListApi();
    if (code === 200) {
      sessionList.value = data;
            if(Array.isArray(data)&&data.length>0){
                activeSessionId.value=data[0].id;
                const res= await getSessionDetailsApi(data[0].id);
                if(res.code===200){
                    sessionDetailList.value=res.data.message;
                    refreshScroll();
        if (code === 200) {
          sessionList.value = data;
          if(Array.isArray(data)&&data.length>0){
                    activeSessionId.value=data[0].id;
                    const res= await getSessionDetailsApi(data[0].id);
                    if(res.code===200){
                        sessionDetailList.value=res.data.message;
                        refreshScroll();
                    }
                }
            }
        }else{
            Message.warning('查询失败');
          Message.warning('查询失败');
        }
    };
    //根据会话id删除会话
@@ -142,7 +142,7 @@
                        </a-button>
                    </template>
                    <a-scrollbar class="left-list" style="height: 60vh;overflow-y: auto;">
                        <div class="item" :class="{isLeftActive:activeSessionId===session.id}" v-for="session in sessionList" :key="session.id" @click="querySessionDetail(session)">
                        <div class="item" :class="{ isLeftActive:activeSessionId===session.id }" v-for="session in sessionList" :key="session.id" @click="querySessionDetail(session)">
                            <div class="item-left">
                                <IconQuestionCircleFill/> 
                                {{session.name}}
src/views/sessionRecords/sessionRecordsManager/index.vue
@@ -1,14 +1,65 @@
<script setup lang="ts">
    import { IconSearch,IconTiktokColor ,IconSend,IconClose} from '@arco-design/web-vue/es/icon';
    import { useAppStore} from '@/store';
    import {computed,ref,onMounted,reactive} from 'vue';
    import {sessionListApi}from '@/api/session';
    import { computed, ref, onMounted, reactive, nextTick } from 'vue';
    import { Message } from '@arco-design/web-vue';
    import moment from 'moment';
    import AddSession from '@/views/session/sessionManager/components/addSession.vue';
    import { sessionListApi, deleteSessionApi,getSessionDetailsApi,chatApi }from '@/api/session';
    const sessionDetailList=ref([]);//根据会话id出来的会话详情
    const sessionList=ref([]);//会话列表
    const modalObj=reactive({add:false});
    //查询会话列表
    const modalObj=reactive({ add:false });
    const currIndex = ref(0)
    const displayedText = ref('');// 正在显示的文字
    let timer: number|null = null;
    const streamStr=ref('');
    const inputMsg=ref('');
    const activeSessionId=ref('');
    const sendMessage= async (event)=>{
      event.preventDefault();
      if(!activeSessionId.value){
        Message.warning('请选择会话');
        return;
      }
      if(inputMsg.value){
        const {code,data} =await chatApi({conversation_id:activeSessionId.value,messages:inputMsg.value});
        const res= await getSessionDetailsApi(activeSessionId.value);
        if(res.code===200){
          sessionDetailList.value=res.data.message.map((item,index)=>{
            if(index===res.data.message.length-1){
              item.role='last';
              displayedText.value='';
              currIndex.value=0;
              streamStr.value=item.content;
              startDisplayStr();
            }
            return item;
          });
          refreshScroll();
        }
        inputMsg.value='';
      }else{
        Message.warning('消息不能为空');
      }
    };
    const querySessionDetail=async (session)=>{
      activeSessionId.value=session.id;
      const {code,data}= await getSessionDetailsApi(session.id);
      if(code===200){
        sessionDetailList.value=data.message;
        refreshScroll();//刷新滚动条位置
      }
    };
    const scrollbar = ref(null);
    const refreshScroll=()=>{
      nextTick(()=>{
        const container = document.getElementById('home');
        scrollbar.value.scrollTop(container.scrollHeight);
      });
    };
    // 查询会话列表
    const querySessionList=async ()=>{
        const {code,data} =await sessionListApi();
        if(code===200){
@@ -28,6 +79,22 @@
    const theme = computed(() => {
        return appStore.theme;
    });
    //文字动态输出
    const startDisplayStr = () => {
      if (timer) {
        clearTimeout(timer!);
      }
      const res = streamStr.value;
      // 将数组中的字符串拼接起来
      if (currIndex.value < res.length) {
        displayedText.value += res[currIndex.value];
        currIndex.value++;
        setTimeout(startDisplayStr, 100);
      } else {
        clearTimeout(timer!);
        timer = null
      }
    }
</script>
<template>
@@ -50,7 +117,7 @@
                </a-card>
                <a-card class="left">
                    <a-scrollbar class="left-list" style="height: calc(100vh - 160px);overflow-y: auto;overflow-x: hidden;">
                        <div class="item" v-for="session in sessionList">
                        <div class="item" v-for="session in sessionList" @click="querySessionDetail(session)" :class="{ isLeftActive:activeSessionId===session.id }">
                            <div class="text" :class="{light:theme==='dark'}">{{session.name}}</div>
                            <div class="time">{{moment(new Date(session.create_time)).format('YYYY-MM-DD HH:mm:ss')}}</div>
                        </div>
@@ -59,68 +126,102 @@
            </a-col>
            <a-col :span="15">
                <a-card class="center">
                    <div class="center-title">智能问答</div>
                    <div class="center-content">
                        我可以理解和学习人类的语言,具备多轮对话的能力,现在和我开始交流吧~
                    </div>
                    <div class="center-question">
                        <div class="center-question-left">试一试这样问我</div>
                        <div class="center-question-right">
                            <a-button type="primary">换一换</a-button>
                    <div v-if="sessionDetailList.length===0">
                        <div class="center-title">智能问答</div>
                        <div class="center-content">
                            我可以理解和学习人类的语言,具备多轮对话的能力,现在和我开始交流吧~
                        </div>
                        <div class="center-question">
                            <div class="center-question-left">试一试这样问我</div>
                            <div class="center-question-right">
                                <a-button type="primary">换一换</a-button>
                            </div>
                        </div>
                        <a-row  justify="space-around" class="center-list">
                            <a-col :span="7" class="item">
                                <div class="item-title">
                                    <IconTiktokColor></IconTiktokColor>抖音带货脚本
                                </div>
                                <div class="item-content" :class="{dark:theme==='dark'}">
                                    编写引人注目且具有说服力的、适用于产品的...
                                </div>
                            </a-col>
                            <a-col :span="7" class="item">
                                <div class="item-title">
                                    <IconTiktokColor></IconTiktokColor>抖音带货脚本
                                </div>
                                <div class="item-content" :class="{dark:theme==='dark'}">
                                    编写引人注目且具有说服力的、适用于产品的...
                                </div>
                            </a-col>
                            <a-col :span="7" class="item">
                                <div class="item-title">
                                    <IconTiktokColor></IconTiktokColor>抖音带货脚本
                                </div>
                                <div class="item-content" :class="{dark:theme==='dark'}">
                                    编写引人注目且具有说服力的、适用于产品的...
                                </div>
                            </a-col>
                            <a-col :span="7" class="item">
                                <div class="item-title">
                                    <IconTiktokColor></IconTiktokColor>抖音带货脚本
                                </div>
                                <div class="item-content" :class="{dark:theme==='dark'}">
                                    编写引人注目且具有说服力的、适用于产品的...
                                </div>
                            </a-col>
                            <a-col :span="7" class="item">
                                <div class="item-title">
                                    <IconTiktokColor></IconTiktokColor>抖音带货脚本
                                </div>
                                <div class="item-content" :class="{dark:theme==='dark'}">
                                    编写引人注目且具有说服力的、适用于产品的...
                                </div>
                            </a-col>
                            <a-col :span="7" class="item">
                                <div class="item-title">
                                    <IconTiktokColor></IconTiktokColor>抖音带货脚本
                                </div>
                                <div class="item-content" :class="{dark:theme==='dark'}">
                                    编写引人注目且具有说服力的、适用于产品的...
                                </div>
                            </a-col>
                        </a-row>
                    </div>
                    <a-row  justify="space-around" class="center-list">
                        <a-col :span="7" class="item">
                            <div class="item-title">
                                <IconTiktokColor></IconTiktokColor>抖音带货脚本
                            </div>
                            <div class="item-content" :class="{dark:theme==='dark'}">
                                编写引人注目且具有说服力的、适用于产品的...
                            </div>
                        </a-col>
                        <a-col :span="7" class="item">
                            <div class="item-title">
                                <IconTiktokColor></IconTiktokColor>抖音带货脚本
                            </div>
                            <div class="item-content" :class="{dark:theme==='dark'}">
                                编写引人注目且具有说服力的、适用于产品的...
                            </div>
                        </a-col>
                        <a-col :span="7" class="item">
                            <div class="item-title">
                                <IconTiktokColor></IconTiktokColor>抖音带货脚本
                            </div>
                            <div class="item-content" :class="{dark:theme==='dark'}">
                                编写引人注目且具有说服力的、适用于产品的...
                            </div>
                        </a-col>
                        <a-col :span="7" class="item">
                            <div class="item-title">
                                <IconTiktokColor></IconTiktokColor>抖音带货脚本
                            </div>
                            <div class="item-content" :class="{dark:theme==='dark'}">
                                编写引人注目且具有说服力的、适用于产品的...
                            </div>
                        </a-col>
                        <a-col :span="7" class="item">
                            <div class="item-title">
                                <IconTiktokColor></IconTiktokColor>抖音带货脚本
                            </div>
                            <div class="item-content" :class="{dark:theme==='dark'}">
                                编写引人注目且具有说服力的、适用于产品的...
                            </div>
                        </a-col>
                        <a-col :span="7" class="item">
                            <div class="item-title">
                                <IconTiktokColor></IconTiktokColor>抖音带货脚本
                            </div>
                            <div class="item-content" :class="{dark:theme==='dark'}">
                                编写引人注目且具有说服力的、适用于产品的...
                            </div>
                        </a-col>
                    </a-row>
                    <a-scrollbar ref="scrollbar" id="home" v-else class="chat-list" style="width:90%;overflow:auto;height: 60vh;margin: 0px auto">
                        <div class="chat-item" v-for="sessionDetail in sessionDetailList">
                            <a-comment
                              v-if="sessionDetail.role==='user'"
                              avatar="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp"
                            >
                                <template #content>
                                    <div :class="{light:theme==='light'}">{{sessionDetail.content}}</div>
                                </template>
                            </a-comment>
                            <a-comment
                              v-else-if="sessionDetail.role==='assistant'"
                              avatar="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/9eeb1800d9b78349b24682c3518ac4a3.png~tplv-uwbnlip3yd-webp.webp"
                            >
                                <template #content>
                                    <a-card class="chat-item-answer"  style="background-color: rgba(63, 64, 79, 1);">
                                        <div :class="{light:theme==='light'}">{{sessionDetail.content}}</div>
                                    </a-card>
                                </template>
                            </a-comment>
                            <a-comment
                              v-else-if="sessionDetail.role==='last'"
                              avatar="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/9eeb1800d9b78349b24682c3518ac4a3.png~tplv-uwbnlip3yd-webp.webp"
                            >
                                <template #content>
                                    <a-textarea readonly auto-size v-model="displayedText" class="chat-item-answer"  style="background-color: rgba(63, 64, 79, 1);">
                                    </a-textarea>
                                </template>
                            </a-comment>
                        </div>
                    </a-scrollbar>
                    <div class="center-bottom">
                        <a-textarea style="height: 180px" placeholder="输入您想了解的内容,Shift+Enter换行" :max-length="500" allow-clear show-word-limit>
                        <a-textarea v-model="inputMsg"   @keydown.shift.enter="sendMessage" style="height: 180px" placeholder="输入您想了解的内容,Shift+Enter发送" :max-length="500" allow-clear show-word-limit>
                        </a-textarea>
                    </div>
                </a-card>
@@ -181,6 +282,9 @@
</template>
<style scoped lang="scss">
    .isLeftActive{
        background-color:lightgrey;
    }
    .light{
        color: white !important;
    }
@@ -210,10 +314,12 @@
                    }
                    .text{
                        color: black;
                        padding-left: 10px;
                    }
                    .time{
                        color: gray;
                        font-size: 12px;
                        padding-left: 10px;
                    }
                }
            }
@@ -269,7 +375,7 @@
            .center-bottom{
                position: absolute;
                width: 90%;
                bottom: 70px;
                bottom: 20px;
                left:5%;
            }
        }