Selaa lähdekoodia

Merge remote-tracking branch 'origin/dev' into dev

cherishsince 1 vuosi sitten
vanhempi
commit
5c02e79105

+ 11 - 4
src/api/ai/chat/conversation/index.ts

@@ -13,6 +13,7 @@ export interface ChatConversationVO {
   maxTokens: number // 单条回复的最大 Token 数量
   maxContexts: number // 上下文的最大 Message 数量
   // 额外字段
+  modelName?: string // 模型名字
   roleAvatar?: string // 角色头像
   modelMaxTokens?: string // 模型的单条回复的最大 Token 数量
   modelMaxContexts?: string // 模型的上下文的最大 Message 数量
@@ -21,17 +22,23 @@ export interface ChatConversationVO {
 // AI 聊天会话 API
 export const ChatConversationApi = {
   // 获得【我的】聊天会话
-  getChatConversationMy: async (id: string) => {
+  getChatConversationMy: async (id: number) => {
     return await request.get({ url: `/ai/chat/conversation/get-my?id=${id}` })
   },
+
+  // 新增【我的】聊天会话
+  createChatConversationMy: async (data?: ChatConversationVO) => {
+    return await request.post({ url: `/ai/chat/conversation/create-my`, data })
+  },
+
   // 更新【我的】聊天会话
   updateChatConversationMy: async (data: ChatConversationVO) => {
     return await request.put({ url: `/ai/chat/conversation/update-my`, data })
   },
 
-  // 新增【我的】聊天会话
-  createChatConversationMy: async (data?: ChatConversationVO) => {
-    return await request.post({ url: `/ai/chat/conversation/create-my`, data })
+  // 删除【我的】聊天会话
+  deleteChatConversationMy: async (id: number) => {
+    return await request.delete({ url: `/ai/chat/conversation/delete-my?id=${id}` })
   },
 
   // 获得【我的】聊天会话列表

+ 10 - 12
src/api/ai/chat/message/index.ts

@@ -31,19 +31,16 @@ export const ChatMessageApi = {
     })
   },
 
-  // 发送 add 消息
-  add: async (data: ChatMessageSendVO) => {
-    return await request.post({ url: `/ai/chat/message/add`, data })
-  },
-
-  // 发送 send 消息
-  send: async (data: ChatMessageSendVO) => {
-    return await request.post({ url: `/ai/chat/message/send`, data })
-  },
-
   // 发送 send stream 消息
   // TODO axios 可以么? https://apifox.com/apiskills/how-to-create-axios-stream/
-  sendStream: async (id: string, ctrl, onMessage, onError, onClose) => {
+  sendStream: async (
+    conversationId: number,
+    content: string,
+    ctrl,
+    onMessage,
+    onError,
+    onClose
+  ) => {
     const token = getAccessToken()
     return fetchEventSource(`${config.base_url}/ai/chat/message/send-stream`, {
       method: 'post',
@@ -53,7 +50,8 @@ export const ChatMessageApi = {
       },
       openWhenHidden: true,
       body: JSON.stringify({
-        id: id
+        conversationId,
+        content
       }),
       onmessage: onMessage,
       onerror: onError,

+ 56 - 39
src/views/ai/chat/components/ChatConversationUpdateForm.vue

@@ -4,23 +4,46 @@
       ref="formRef"
       :model="formData"
       :rules="formRules"
-      label-width="100px"
+      label-width="130px"
       v-loading="formLoading"
     >
-      <el-form-item label="角色设定" prop="systemContext">
-        <el-input type="textarea" v-model="formData.systemContext" placeholder="请输入角色设定" />
+      <el-form-item label="角色设定" prop="systemMessage">
+        <el-input type="textarea" v-model="formData.systemMessage" placeholder="请输入角色设定" />
       </el-form-item>
       <el-form-item label="模型" prop="modelId">
-        <UploadImg v-model="formData.modelId" />
+        <el-select v-model="formData.modelId" placeholder="请选择模型">
+          <el-option
+            v-for="chatModel in chatModelList"
+            :key="chatModel.id"
+            :label="chatModel.name"
+            :value="chatModel.id"
+          />
+        </el-select>
       </el-form-item>
-      <el-form-item label="上下文数量" prop="category">
-        <el-input v-model="formData.category" placeholder="请输入角色类别" />
+      <el-form-item label="温度参数" prop="temperature">
+        <el-input-number
+          v-model="formData.temperature"
+          placeholder="请输入温度参数"
+          :min="0"
+          :max="2"
+          :precision="2"
+        />
       </el-form-item>
-      <el-form-item label="话题随机性" prop="description">
-        <el-input type="textarea" v-model="formData.description" placeholder="请输入角色描述" />
+      <el-form-item label="回复数 Token 数" prop="maxTokens">
+        <el-input-number
+          v-model="formData.maxTokens"
+          placeholder="请输入回复数 Token 数"
+          :min="0"
+          :max="4096"
+        />
       </el-form-item>
-      <el-form-item label="回复数" prop="systemMessage">
-        <el-input type="textarea" v-model="formData.systemMessage" placeholder="请输入角色设定" />
+      <el-form-item label="上下文数量" prop="maxContexts">
+        <el-input-number
+          v-model="formData.maxContexts"
+          placeholder="请输入上下文数量"
+          :min="0"
+          :max="20"
+        />
       </el-form-item>
     </el-form>
     <template #footer>
@@ -30,7 +53,6 @@
   </Dialog>
 </template>
 <script setup lang="ts">
-import { ChatRoleApi, ChatRoleVO } from '@/api/ai/model/chatRole'
 import { CommonStatusEnum } from '@/utils/constants'
 import { ChatModelApi, ChatModelVO } from '@/api/ai/model/chatModel'
 import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
@@ -45,25 +67,18 @@ const dialogVisible = ref(false) // 弹窗的是否展示
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const formData = ref({
   id: undefined,
-  systemContext: undefined,
-  modelId: undefined,
-  name: undefined,
-  avatar: undefined,
-  category: undefined,
-  sort: undefined,
-  description: undefined,
   systemMessage: undefined,
-  publicStatus: true,
-  status: CommonStatusEnum.ENABLE
+  modelId: undefined,
+  temperature: undefined,
+  maxTokens: undefined,
+  maxContexts: undefined
 })
 const formRules = reactive({
-  name: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
-  avatar: [{ required: true, message: '角色头像不能为空', trigger: 'blur' }],
-  category: [{ required: true, message: '角色类别不能为空', trigger: 'blur' }],
-  sort: [{ required: true, message: '角色排序不能为空', trigger: 'blur' }],
-  description: [{ required: true, message: '角色描述不能为空', trigger: 'blur' }],
-  systemMessage: [{ required: true, message: '角色设定不能为空', trigger: 'blur' }],
-  publicStatus: [{ required: true, message: '是否公开不能为空', trigger: 'blur' }]
+  modelId: [{ required: true, message: '模型不能为空', trigger: 'blur' }],
+  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
+  temperature: [{ required: true, message: '温度参数不能为空', trigger: 'blur' }],
+  maxTokens: [{ required: true, message: '回复数 Token 数不能为空', trigger: 'blur' }],
+  maxContexts: [{ required: true, message: '上下文数量不能为空', trigger: 'blur' }]
 })
 const formRef = ref() // 表单 Ref
 const chatModelList = ref([] as ChatModelVO[]) // 聊天模型列表
@@ -76,7 +91,13 @@ const open = async (id: number) => {
   if (id) {
     formLoading.value = true
     try {
-      formData.value = await ChatConversationApi.getChatConversationMy(id)
+      const data = await ChatConversationApi.getChatConversationMy(id)
+      formData.value = Object.keys(formData.value).reduce((obj, key) => {
+        if (data.hasOwnProperty(key)) {
+          obj[key] = data[key]
+        }
+        return obj
+      }, {})
     } finally {
       formLoading.value = false
     }
@@ -94,9 +115,9 @@ const submitForm = async () => {
   // 提交请求
   formLoading.value = true
   try {
-    const data = formData.value as unknown as ChatRoleVO
-    await ChatRoleApi.updateChatRole(data)
-    message.success(t('common.updateSuccess'))
+    const data = formData.value as unknown as ChatConversationVO
+    await ChatConversationApi.updateChatConversationMy(data)
+    message.success('对话配置已更新')
     dialogVisible.value = false
     // 发送操作成功的事件
     emit('success')
@@ -109,15 +130,11 @@ const submitForm = async () => {
 const resetForm = () => {
   formData.value = {
     id: undefined,
-    modelId: undefined,
-    name: undefined,
-    avatar: undefined,
-    category: undefined,
-    sort: undefined,
-    description: undefined,
     systemMessage: undefined,
-    publicStatus: true,
-    status: CommonStatusEnum.ENABLE
+    modelId: undefined,
+    temperature: undefined,
+    maxTokens: undefined,
+    maxContexts: undefined
   }
   formRef.value?.resetFields()
 }

+ 48 - 66
src/views/ai/chat/index.vue

@@ -22,7 +22,7 @@
         </el-input>
         <!-- 左中间:对话列表 -->
         <div class="conversation-list">
-          <!-- TODO @芋艿,置顶、聊天记录、一星期钱、30天前,前端对数据重新做一下分组,或者后端接口改一下 -->
+          <!-- TODO @fain:置顶、聊天记录、一星期钱、30天前,前端对数据重新做一下分组,或者后端接口改一下 -->
           <div>
             <el-text class="mx-1" size="small" tag="b">置顶</el-text>
           </div>
@@ -35,11 +35,12 @@
                 <img class="avatar" :src="conversation.roleAvatar" />
                 <span class="title">{{ conversation.title }}</span>
               </div>
+              <!-- TODO @fan:缺一个【置顶】按钮,效果改成 hover 上去展示 -->
               <div class="button-wrapper">
                 <el-icon title="编辑" @click="updateConversationTitle(conversation)">
                   <Icon icon="ep:edit" />
                 </el-icon>
-                <el-icon title="删除会话" @click="deleteConversationTitle(conversation)">
+                <el-icon title="删除会话" @click="deleteChatConversation(conversation)">
                   <Icon icon="ep:delete" />
                 </el-icon>
               </div>
@@ -68,17 +69,10 @@
         </div>
         <div>
           <!-- TODO @fan:样式改下;这里我已经改成点击后,弹出了 -->
-          <el-dropdown style="margin-right: 12px" @command="openChatConversationUpdateForm">
-            <el-button type="primary">
-              <span v-html="useModal?.name"></span>
-              <Icon icon="ep:setting" style="margin-left: 10px" />
-            </el-button>
-            <template #dropdown>
-              <el-dropdown-menu v-for="(item, index) in modalList" :key="index">
-                <el-dropdown-item :command="item">{{ item.name }}</el-dropdown-item>
-              </el-dropdown-menu>
-            </template>
-          </el-dropdown>
+          <el-button type="primary" @click="openChatConversationUpdateForm">
+            <span v-html="useConversation?.modelName"></span>
+            <Icon icon="ep:setting" style="margin-left: 10px" />
+          </el-button>
           <el-button>
             <Icon icon="ep:user" />
           </el-button>
@@ -96,6 +90,7 @@
         <div class="message-container" ref="messageContainer">
           <div class="chat-list" v-for="(item, index) in list" :key="index">
             <!--  靠左 message  -->
+            <!-- TODO 芋艿:类型判断 -->
             <div class="left-message message-item" v-if="item.type === 'system'">
               <div class="avatar">
                 <el-avatar
@@ -187,18 +182,16 @@
     </el-container>
   </el-container>
 
-  <ChatConversationUpdateForm ref="chatConversationUpdateFormRef" />
+  <ChatConversationUpdateForm
+    ref="chatConversationUpdateFormRef"
+    @success="getChatConversationList"
+  />
 </template>
 
 <script setup lang="ts">
 import { ChatMessageApi, ChatMessageSendVO, ChatMessageVO } from '@/api/ai/chat/message'
-import {
-  ChatConversationApi,
-  ChatConversationUpdateVO,
-  ChatConversationVO
-} from '@/api/ai/chat/conversation'
+import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
 import ChatConversationUpdateForm from './components/ChatConversationUpdateForm.vue'
-import { ChatModelApi, ChatModelVO } from '@/api/ai/model/chatModel'
 import { formatDate } from '@/utils/formatTime'
 import { useClipboard } from '@vueuse/core'
 // 转换 markdown
@@ -207,7 +200,6 @@ import { marked } from 'marked'
 import 'highlight.js/styles/vs2015.min.css'
 import hljs from 'highlight.js'
 
-const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
 // 自定义渲染器
@@ -229,8 +221,7 @@ const { copy } = useClipboard()
 const searchName = ref('') // 查询的内容
 const inputTimeout = ref<any>() // 处理输入中回车的定时器
 const conversationId = ref(0) // 选中的对话编号
-const conversationInProgress = ref<Boolean>() // 对话进行中
-conversationInProgress.value = false
+const conversationInProgress = ref(false) // 对话进行中
 const conversationInAbortController = ref<any>() // 对话进行中 abort 控制器(控制 stream 对话)
 
 const prompt = ref<string>() // prompt
@@ -243,9 +234,7 @@ const isComposing = ref(false) // 判断用户是否在输入
 /** chat message 列表 */
 // defineOptions({ name: 'chatMessageList' })
 const list = ref<ChatMessageVO[]>([]) // 列表的数据
-const useModal = ref<ChatModelVO>() // 使用的modal
 const useConversation = ref<ChatConversationVO>() // 使用的 Conversation
-const modalList = ref<ChatModelVO[]>([]) // 列表的数据
 
 /** 新建对话 */
 const createConversation = async () => {
@@ -282,13 +271,20 @@ const updateConversationTitle = async (conversation: ChatConversationVO) => {
 }
 
 /** 删除聊天会话 */
-const deleteConversationTitle = async (conversation: ChatConversationVO) => {
-  console.log(conversation)
-  // TODO 芋艿:待实现
+const deleteChatConversation = async (conversation: ChatConversationVO) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm(`是否确认删除会话 - ${conversation.title}?`)
+    // 发起删除
+    await ChatConversationApi.deleteChatConversationMy(conversation.id)
+    message.success('会话已删除')
+    // 刷新列表
+    await getChatConversationList()
+  } catch {}
 }
 
 const searchConversation = () => {
-  // TODO 芋艿:待实现
+  // TODO fan:待实现
 }
 
 /** send */
@@ -302,24 +298,28 @@ const onSend = async () => {
     return
   }
   const content = prompt.value?.trim()
-  if (content?.length < 2) {
+  if (content.length < 2) {
     ElMessage({
       message: '请输入内容!',
       type: 'error'
     })
     return
   }
+  // TODO 芋艿:这块交互要在优化;应该是先插入到 UI 界面,里面会有当前的消息,和正在思考中;之后发起请求;
   // 清空输入框
   prompt.value = ''
-  const requestParams = {
+  // const requestParams = {
+  //   conversationId: conversationId.value,
+  //   content: content
+  // } as unknown as ChatMessageSendVO
+  // // 添加 message
+  const userMessage = {
     conversationId: conversationId.value,
     content: content
-  } as unknown as ChatMessageSendVO
-  // 添加 message
-  const userMessage = (await ChatMessageApi.add(requestParams)) as unknown as ChatMessageVO
-  list.value.push(userMessage)
-  // 滚动到住下面
-  scrollToBottom()
+  }
+  // list.value.push(userMessage)
+  // // 滚动到住下面
+  // scrollToBottom()
   // stream
   await doSendStream(userMessage)
   //
@@ -335,13 +335,15 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
     let isFirstMessage = true
     let content = ''
     ChatMessageApi.sendStream(
-      userMessage.id,
+      userMessage.conversationId, // TODO 芋艿:这里可能要在优化;
+      userMessage.content,
       conversationInAbortController.value,
       (message) => {
         console.log('message', message)
-        const data = JSON.parse(message.data) as unknown as ChatMessageVO
+        const data = JSON.parse(message.data) // TODO 芋艿:类型处理;
+        // debugger
         // 如果没有内容结束链接
-        if (data.content === '') {
+        if (data.receive.content === '') {
           // 标记对话结束
           conversationInProgress.value = false
           // 结束 stream 对话
@@ -350,9 +352,12 @@ const doSendStream = async (userMessage: ChatMessageVO) => {
         // 首次返回需要添加一个 message 到页面,后面的都是更新
         if (isFirstMessage) {
           isFirstMessage = false
-          list.value.push(data)
+          // debugger
+          list.value.push(data.send)
+          list.value.push(data.receive)
         } else {
-          content = content + data.content
+          // debugger
+          content = content + data.receive.content
           const lastMessage = list.value[list.value.length - 1]
           lastMessage.content = marked(content) as unknown as string
           list.value[list.value - 1] = lastMessage
@@ -460,23 +465,10 @@ const stopStream = async () => {
 
 /** 修改聊天会话 */
 const chatConversationUpdateFormRef = ref()
-const openChatConversationUpdateForm = async (command) => {
-  // const update = {
-  //   id: conversationId.value,
-  //   modelId: command.id
-  // } as unknown as ChatConversationUpdateVO
-  // // 切换 modal
-  // useModal.value = command
-  // // 更新
-  // await ChatConversationApi.updateChatConversationMy(update)
+const openChatConversationUpdateForm = async () => {
   chatConversationUpdateFormRef.value.open(conversationId.value)
 }
 
-const getModalList = async () => {
-  // 获取模型  as unknown as ChatModelVO
-  modalList.value = (await ChatModelApi.getChatModelSimpleList(0)) as unknown as ChatModelVO[]
-}
-
 // 输入
 const onCompositionstart = () => {
   console.log('onCompositionstart。。。.')
@@ -516,14 +508,6 @@ const getConversation = async (conversationId: string) => {
   // 获取对话信息
   useConversation.value = await ChatConversationApi.getChatConversationMy(conversationId)
   console.log('useConversation.value', useConversation.value)
-  // 选中 modal
-  if (useConversation.value) {
-    modalList.value.forEach((item) => {
-      if (useConversation.value?.modelId === item.id) {
-        useModal.value = item
-      }
-    })
-  }
 }
 
 /** 获得聊天会话列表 */
@@ -545,8 +529,6 @@ const getChatConversationList = async () => {
 onMounted(async () => {
   // 获得聊天会话列表
   await getChatConversationList()
-  // 获取模型
-  getModalList()
   // 获取对话信息
   getConversation(conversationId.value)
   // 获取列表数据