CzRger недель назад: 3
Родитель
Сommit
ac950048c0

+ 23 - 3
src/api/modules/app/chat.ts

@@ -14,9 +14,29 @@ export const appTextToAudio = (params) =>
 // 查询应用的会话列表
 export const appConversationsSearch = (params) =>
   post(`/app/conversations/search`, params, {}, proxy)
-// x
-export const appConversations = (id, params) =>
-  get(`/app/conversations/${id}`, params, {}, proxy)
 // 对话重命名
 export const appConversationsRename = (params) =>
   post(`/app/conversations/rename`, params, {}, proxy)
+// 停止回答
+export const appConversationsStop = (id, params) =>
+  get(`/app/conversations/${id}/stop`, params, {}, proxy)
+// 停止回答
+export const appConversationsDel = (id, params) =>
+  del(`/app/conversations/${id}`, params, {}, proxy)
+// 查询会话消息分页
+export const appConversationsMessages = (conversationId, params) =>
+  get(`/app/conversations/${conversationId}/messages`, params, {}, proxy)
+// 查询问题建议
+export const appMessagesSuggestedQuestions = (messageId) =>
+  get(`/app/messages/suggested-questions/${messageId}`, {}, {}, proxy)
+// 清除应用的会话记录
+export const appDelConversations = (appId) =>
+  del(`/app/del-conversations/${appId}`, {}, {}, proxy)
+// 消息反馈(0-无,1-赞,2-踩)
+export const appConversationsMessageFeedback = (messageId, feedbackType) =>
+  get(
+    `/app/conversations/message-feedback/${messageId}/${feedbackType}`,
+    {},
+    {},
+    proxy,
+  )

+ 3 - 1
src/types/chat.ts

@@ -3,14 +3,16 @@ export type AnswerStruct = {
   loading?: boolean
   text?: string
   advise?: Array<string>
+  adviseLoading?: boolean
   time?: number
   tokens?: number
   prologue?: string
   prologueType?: string
   prologueQuestions?: Array<string>
-  feedback: 'good' | 'bad'
+  feedback?: 0 | 1 | 2 // 0-无,1-赞,2-踩
   finished?: boolean
   error?: boolean
   messageId?: string
   taskId?: string
+  ask?: string
 }

+ 62 - 13
src/views/chat/answer/index.vue

@@ -3,7 +3,7 @@
     <div class="h-11 w-11">
       <img class="h-full w-full" src="@/assets/images/chat/default-logo.png" />
     </div>
-    <div class="flex flex-1 flex-col overflow-hidden">
+    <div class="flex max-w-fit flex-1 flex-col overflow-hidden">
       <template v-if="item.prologue">
         <div
           class="answer-markdown w-fit overflow-hidden rounded-lg rounded-tl-none bg-[#EAF1FF] p-2.75 text-[#303133]"
@@ -49,7 +49,7 @@
       </template>
       <template v-else>
         <div
-          class="rounded-lg rounded-tl-none bg-[#EAF1FF] p-2.75 text-[#303133]"
+          class="w-fit rounded-lg rounded-tl-none bg-[#EAF1FF] p-2.75 text-[#303133]"
         >
           <template v-for="part in textCpt">
             <template v-if="part.type === 'think'">
@@ -66,17 +66,54 @@
         </div>
         <div
           class="mt-2 flex items-center gap-2.5 px-2.5 text-[0.63rem] text-[#909399]"
+          v-if="item.finished"
         >
           <div v-if="item.time">{{ formatTimeDuration(item.time) }}</div>
           <div v-if="item.tokens">{{ item.tokens }} Tokens</div>
           <div class="mx-auto" />
           <template v-if="!item.error">
-            <el-tooltip content="喜欢" placement="top">
-              <SvgIcon class="__hover" name="good" size="20" />
-            </el-tooltip>
-            <el-tooltip content="不喜欢" placement="top">
-              <SvgIcon class="__hover" name="good" size="20" rotate="180" />
-            </el-tooltip>
+            <template v-if="item.feedback == 0">
+              <el-tooltip content="喜欢" placement="top">
+                <SvgIcon
+                  class="__hover"
+                  name="good"
+                  size="20"
+                  @click="$emit('onFeedback', item, 1)"
+                />
+              </el-tooltip>
+              <el-tooltip content="不喜欢" placement="top">
+                <SvgIcon
+                  class="__hover"
+                  name="good"
+                  size="20"
+                  rotate="180"
+                  @click="$emit('onFeedback', item, 2)"
+                />
+              </el-tooltip>
+            </template>
+            <template v-else-if="item.feedback == 1">
+              <el-tooltip content="取消喜欢" placement="top">
+                <SvgIcon
+                  class="__hover"
+                  name="good"
+                  size="20"
+                  :active="true"
+                  @click="$emit('onFeedback', item, 0)"
+                />
+              </el-tooltip>
+            </template>
+            <template v-else-if="item.feedback == 2">
+              <el-tooltip content="取消不喜欢" placement="top">
+                <SvgIcon
+                  class="__hover"
+                  name="good"
+                  size="20"
+                  rotate="180"
+                  color="var(--czr-error-color)"
+                  @click="$emit('onFeedback', item, 0)"
+                />
+              </el-tooltip>
+            </template>
           </template>
           <el-tooltip content="复制" placement="top">
             <SvgIcon
@@ -91,13 +128,26 @@
             />
           </el-tooltip>
           <el-tooltip content="重新生成" placement="top">
-            <SvgIcon class="__hover" name="refresh" size="20" />
+            <SvgIcon
+              class="__hover"
+              name="refresh"
+              size="20"
+              @click="$emit('onReBuild', item)"
+            />
           </el-tooltip>
         </div>
-        <template v-if="item.advise?.length > 0">
+        <template v-if="item.adviseLoading">
+          <div class="mt-2 ml-2.5 text-sm text-[var(--czr-main-color)]">
+            问题建议生成中……
+          </div>
+        </template>
+        <template v-else-if="item.advise?.length > 0">
           <div class="mt-2 ml-2.5 flex flex-col gap-2">
             <template v-for="ad in item.advise">
-              <div class="__hover w-fit text-sm text-[var(--czr-main-color)]">
+              <div
+                class="__hover w-fit text-sm text-[var(--czr-main-color)]"
+                @click="$emit('setText', { text: ad, send: true })"
+              >
                 {{ ad }}
               </div>
             </template>
@@ -131,14 +181,13 @@ const md = new MarkdownIt({
     return `<pre class="hljs"><code>${md.utils.escapeHtml(str)}</code></pre>`
   },
 })
-const emit = defineEmits(['setText'])
+const emit = defineEmits(['setText', 'onReBuild', 'onFeedback'])
 const props = defineProps({
   item: {} as any,
   goodMap: {} as any,
   badMap: {} as any,
 })
 const state: any = reactive({})
-
 const textCpt = computed(() => {
   const segments: any = []
   const rawContent = props.item.text

+ 1 - 0
src/views/chat/index.vue

@@ -28,6 +28,7 @@ const state: any = reactive({
 const ref_chat = ref()
 defineExpose({
   init: () => ref_chat.value?.init(),
+  history: (conveargsrsationId) => ref_chat.value?.history(conveargsrsationId),
 })
 </script>
 

+ 81 - 66
src/views/chat/normal.vue

@@ -14,6 +14,8 @@
           <answerCom
             :item="item"
             @setText="(val) => setText(val.text, val.send)"
+            @onReBuild="onReBuild"
+            @onFeedback="onFeedback"
           />
         </template>
       </template>
@@ -89,6 +91,12 @@ import { isValue } from '@/utils/czr-util'
 import { ElMessage } from 'element-plus'
 import { chatMessage } from '@/views/chat/chat'
 import { appModelConfigDetail } from '@/api/modules/app/make'
+import {
+  appConversationsMessageFeedback,
+  appConversationsMessages,
+  appConversationsStop,
+  appMessagesSuggestedQuestions,
+} from '@/api/modules/app/chat'
 
 const props = defineProps({
   ID: { default: '' },
@@ -116,11 +124,21 @@ const setText = (text: string, send = false) => {
     state.text = text
   }
 }
-const initChat = async () => {
+const onReset = () => {
   state.chats = []
+  state.text = ''
+  state.params.query = ''
+  state.params.modelConfig = {}
+  state.params.conversationId = ''
+  state.params.parentMessageId = ''
+  state.isWaiting = false
+  state.isStop = false
+}
+const initChat = async (isPrologue = true) => {
+  onReset()
   const { data }: any = await appModelConfigDetail(props.ID, props.test ? 0 : 1)
   state.params.modelConfig = data
-  if (state.params.modelConfig.openingStatement.trim()) {
+  if (isPrologue && state.params.modelConfig.openingStatement.trim()) {
     state.chats.push({
       type: 'answer',
       prologue: state.params.modelConfig.openingStatement,
@@ -128,64 +146,32 @@ const initChat = async () => {
       prologueQuestions: state.params.modelConfig.suggestedQuestions || [],
     })
   }
-  // state.chats = [
-  //   {
-  //     type: 'answer',
-  //     prologue: '您好,请不要问我问题!',
-  //     prologueNum: 'three',
-  //     prologueQuestions: ['马什么每', '马东什么', '什么冬梅'],
-  //   },
-  //   {
-  //     type: 'answer',
-  //     prologue: '您好,请不要问我问题!',
-  //     prologueNum: 'all',
-  //     prologueQuestions: [
-  //       '马什么每',
-  //       '马东什么',
-  //       '什么冬梅',
-  //       '马什么每',
-  //       '马东什么',
-  //       '什么冬梅',
-  //       '马什么每',
-  //       '马东什么',
-  //       '什么冬梅',
-  //       '马什么每',
-  //       '马东什么',
-  //       '什么冬梅',
-  //     ],
-  //   },
-  //   {
-  //     type: 'ask',
-  //     text: '现在几点了?',
-  //   },
-  //   {
-  //     type: 'answer',
-  //     text: '现在是5025年88月99日 77:55:44,星期九。',
-  //     advise: [],
-  //     time: 1000 * 2.7,
-  //     tokens: 22,
-  //   },
-  //   {
-  //     type: 'answer',
-  //     text: '',
-  //     advise: [],
-  //     loading: true,
-  //   },
-  //   {
-  //     type: 'ask',
-  //     text: '还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?还有别的问题建议吗?',
-  //   },
-  //   {
-  //     type: 'answer',
-  //     text: '还有一些问题建议',
-  //     advise: ['这个时间牛*不', '再问我一个时间吧'],
-  //     time: 1000 * 457,
-  //     tokens: 22222222,
-  //   },
-  // ]
-  initHistory()
 }
-const initHistory = () => {}
+const initHistory = async (conversationId) => {
+  await initChat(false)
+  appConversationsMessages(conversationId, { page: 1, size: 100 }).then(
+    ({ data }: any) => {
+      data.content.reverse().forEach((v, i) => {
+        state.params.conversationId = v.conversationId
+        state.params.parentMessageId = v.id
+        state.chats.push({
+          type: 'ask',
+          text: v.query,
+        })
+        state.chats.push({
+          type: 'answer',
+          ask: v.query,
+          text: v.answer,
+          loading: false,
+          error: false,
+          finished: true,
+          messageId: v.id,
+          feedback: v.feedback,
+        })
+      })
+    },
+  )
+}
 const initTextHandle = () => {
   const textarea = ref_text.value
   const textMax = 150
@@ -242,13 +228,14 @@ const onSend = (text = '', isSet = false) => {
       text: state.params.query + '',
     }
     state.chats.push(ask)
-    const answer = reactive({
+    const answer: AnswerStruct = reactive({
       type: 'answer',
+      ask: ask.text + '',
       text: '',
       loading: true,
-      error: false,
-      messageId: '',
-      taskId: '',
+      finished: false,
+      time: 0,
+      tokens: 0,
     })
     state.chats.push(answer)
     scrollToEnd()
@@ -262,7 +249,6 @@ const onSend = (text = '', isSet = false) => {
     }
     chatMessage(p, {
       onData: (text, data) => {
-        console.log(text, data)
         state.isWaiting = false
         state.params.conversationId = data.conversation_id
         answer.messageId = data.message_id
@@ -273,22 +259,42 @@ const onSend = (text = '', isSet = false) => {
       },
       onMessageEnd: (data) => {
         state.isStop = false
+        state.params.parentMessageId = data.message_id
+        answer.finished = true
+        if (data.metadata) {
+          answer.time = Number(data.metadata.usage.latency)
+          answer.tokens = Number(data.metadata.usage.prompt_tokens)
+          if (state.params.modelConfig.qsType.some((v) => v == 1)) {
+            answer.adviseLoading = true
+            appMessagesSuggestedQuestions(data.message_id).then(
+              ({ data }: any) => {
+                answer.advise = data
+                answer.adviseLoading = false
+                scrollToEnd()
+              },
+            )
+          }
+        }
       },
       onError: (text, data) => {
-        console.error(text, data)
         state.isWaiting = false
         answer.loading = false
         answer.text += text
         answer.error = true
         scrollToEnd()
         state.isStop = false
+        answer.finished = true
       },
     })
   } else {
     ElMessage.warning('请输入问题!')
   }
 }
-const onStop = () => {}
+const onStop = () => {
+  appConversationsStop(state.chats[state.chats.length - 1].taskId, {
+    appId: props.ID,
+  }).then(() => {})
+}
 const onAudio = (text) => {
   state.text += text
   nextTick(() => {
@@ -300,11 +306,20 @@ const onAudio = (text) => {
     })
   })
 }
+const onReBuild = (answer) => {
+  onSend(answer.ask, true)
+}
+const onFeedback = (answer, feedback) => {
+  appConversationsMessageFeedback(answer.messageId, feedback).then(() => {
+    answer.feedback = feedback
+  })
+}
 onMounted(() => {
   initTextHandle()
 })
 defineExpose({
   init: () => initChat(),
+  history: (conversationId) => initHistory(conversationId),
 })
 </script>
 

+ 6 - 1
src/views/manage/app/make/index.vue

@@ -598,7 +598,12 @@
               编排中有申请中的模型,预览暂不可用
             </div>
             <el-tooltip content="重新开始" :raw-content="true" placement="top">
-              <SvgIcon class="__hover ml-auto" name="refresh" :active="true" />
+              <SvgIcon
+                class="__hover ml-auto"
+                name="refresh"
+                :active="true"
+                @click="onRestart"
+              />
             </el-tooltip>
           </div>
           <div class="relative flex-1 overflow-hidden rounded-lg">

+ 71 - 21
src/views/manage/home/app/index.vue

@@ -37,13 +37,14 @@
               </el-tooltip>
             </div>
             <div class="flex gap-2 text-sm text-[#606266]">
-              <div>创建人名称</div>
-              <div>发布于:2025-02-03 44:55:66</div>
+              <div>{{ state.detail.creatorName }}</div>
+              <div>发布于:{{ state.detail.publishTime }}</div>
             </div>
           </div>
         </div>
         <div
           class="__hover mt-4 flex h-10.5 w-full items-center justify-center gap-2 rounded-lg bg-[#EAF1FF] text-[var(--czr-main-color)]"
+          @click="ref_chat?.init()"
         >
           <SvgIcon name="msg" :active="true" />开启新对话
         </div>
@@ -51,17 +52,22 @@
           记录
           <div
             class="__hover ml-auto flex items-center text-sm font-normal text-[#909399]"
+            @click="onClear"
           >
             <SvgIcon name="clean" class="mr-1" />清除记录
           </div>
         </div>
-        <div class="mt-4 mr-2 flex flex-1 flex-col gap-4 overflow-y-auto pr-2">
-          <template v-for="(item, index) in state.history">
+        <div
+          class="mt-4 mr-2 flex flex-1 flex-col gap-4 overflow-y-auto pr-2"
+          v-loading="state.history.loading"
+        >
+          <template v-for="(item, index) in state.history.data">
             <div
-              class="flex items-center gap-2 rounded-lg p-2"
+              class="__hover flex items-center gap-2 rounded-lg p-2"
               :style="{
                 backgroundColor: `rgba(${colors[index % colors.length]}, 0.05)`,
               }"
+              @click="onHistory(item)"
             >
               <div
                 class="flex h-5 min-w-8 items-center justify-center rounded-tl-sm rounded-br-sm px-1 text-sm text-[#ffffff] shadow"
@@ -74,7 +80,13 @@
               <div class="flex-1 text-base font-bold text-[#2E3238]" v-title>
                 {{ item.name }}
               </div>
-              <SvgIcon name="czr_del" class="__hover ml-auto" />
+              <el-tooltip content="删除" placement="top">
+                <SvgIcon
+                  name="czr_del"
+                  class="__hover ml-auto"
+                  @click.stop="onDel(item)"
+                />
+              </el-tooltip>
             </div>
           </template>
         </div>
@@ -95,19 +107,25 @@ import toBackCom from '@/views/manage/components/to-back.vue'
 import { useRoute, useRouter } from 'vue-router'
 import chat from '@/views/chat/index.vue'
 import {
-  appConversations,
+  appConversationsDel,
   appConversationsSearch,
-  appHistory,
+  appDelConversations,
 } from '@/api/modules/app/chat'
-import { appDetail } from '@/api/modules/app'
+import { appDel, appDetail } from '@/api/modules/app'
+import { ElMessage } from 'element-plus'
+import { useDialogStore } from '@/stores'
 
+const DialogStore = useDialogStore()
 const router = useRouter()
 const route = useRoute()
 const colors = ['46, 155, 62', '0, 159, 188', '119, 69, 222', '255, 162, 84']
 const state: any = reactive({
   ID: route.params.id,
   detail: {},
-  history: [],
+  history: {
+    loading: false,
+    data: [],
+  },
   chat: {
     isOnline: false,
     loading: false,
@@ -115,18 +133,52 @@ const state: any = reactive({
 })
 const ref_chat = ref()
 const initHistory = () => {
-  const arr = []
-  for (let i = 0; i < 20; i++) {
-    arr.push({
-      name: '水水水水水水水水水水水水水水水水水水水水水水水水水水水水水',
-    })
-  }
+  state.history.loading = true
   appConversationsSearch({
     appId: state.ID,
     page: 1,
     size: 100000,
-  }).then(({ data }: any) => {
-    state.history = data.records
+  })
+    .then(({ data }: any) => {
+      state.history.data = data.records
+    })
+    .finally(() => {
+      state.history.loading = false
+    })
+}
+const onDel = (row) => {
+  DialogStore.confirm({
+    title: '删除确认',
+    content: `请确认是否删除`,
+    onSubmit: () => {
+      appConversationsDel(row.id, { appId: state.ID })
+        .then(() => {
+          ElMessage.success('删除成功!')
+        })
+        .catch(() => {})
+        .finally(() => {
+          initHistory()
+        })
+    },
+  })
+}
+const onHistory = (row) => {
+  ref_chat.value?.history(row.id)
+}
+const onClear = () => {
+  DialogStore.confirm({
+    title: '删除确认',
+    content: `请确认是否清除记录?`,
+    onSubmit: () => {
+      appDelConversations(state.ID)
+        .then(() => {
+          ElMessage.success('清除记录成功!')
+        })
+        .catch(() => {})
+        .finally(() => {
+          initHistory()
+        })
+    },
   })
 }
 onMounted(() => {
@@ -138,7 +190,6 @@ const initDetail = () => {
     state.chat.loading = true
     appDetail(state.ID)
       .then(({ data }: any) => {
-        console.log(2222)
         state.detail = data
         document.title = state.detail.name
         state.chat.isOnline = state.detail.userInputMethod == 1
@@ -149,8 +200,7 @@ const initDetail = () => {
         initHistory()
       })
       .catch(() => {
-        console.log(3333)
-        // router.push({ name: '4806d051-e037-4d9d-99a0-78aa2f2f362b' })
+        router.push({ name: '4806d051-e037-4d9d-99a0-78aa2f2f362b' })
       })
   } else {
     router.push({ name: '4806d051-e037-4d9d-99a0-78aa2f2f362b' })