xsh_1997 2 天之前
父节点
当前提交
73a2d197d2

+ 21 - 5
ruoyi-ui-app/api/diseaseTreatment/aiOnlineConsult.js

@@ -106,7 +106,9 @@ function parseChatJsonPayload(payload) {
106 106
     const json = JSON.parse(payload)
107 107
     const sessionId = extractLlmSessionIdFromJson(json)
108 108
     const delta = extractDeltaFromChatJson(json)
109
-    return { sessionId, delta }
109
+    const model =
110
+      json.model != null && json.model !== '' ? String(json.model) : null
111
+    return { sessionId, delta, model }
110 112
   } catch (e) {
111 113
     return null
112 114
   }
@@ -162,6 +164,12 @@ function consumeSseLines(state, lines, callbacks) {
162 164
         callbacks.onSessionId(parsed.sessionId)
163 165
       }
164 166
     }
167
+    if (parsed.model) {
168
+      state.model = parsed.model
169
+      if (callbacks.onModel) {
170
+        callbacks.onModel(parsed.model)
171
+      }
172
+    }
165 173
     if (parsed.delta) {
166 174
       const before = state.fullText
167 175
       appendStreamDelta(state, parsed.delta)
@@ -192,6 +200,12 @@ function appendSseChunk(state, chunk, callbacks) {
192 200
           callbacks.onSessionId(parsed.sessionId)
193 201
         }
194 202
       }
203
+      if (parsed.model) {
204
+        state.model = parsed.model
205
+        if (callbacks.onModel) {
206
+          callbacks.onModel(parsed.model)
207
+        }
208
+      }
195 209
       return
196 210
     }
197 211
   }
@@ -250,14 +264,15 @@ function streamRequestHeaders() {
250 264
 }
251 265
 
252 266
 function createStreamState(options) {
253
-  const { onDelta, onSessionId } = options
267
+  const { onDelta, onSessionId, onModel } = options
254 268
   return {
255 269
     startMs: Date.now(),
256 270
     buffer: '',
257 271
     fullText: '',
258 272
     sessionId: null,
273
+    model: null,
259 274
     chunkReceived: false,
260
-    callbacks: { onDelta, onSessionId }
275
+    callbacks: { onDelta, onSessionId, onModel }
261 276
   }
262 277
 }
263 278
 
@@ -266,6 +281,7 @@ function finishStreamState(state) {
266 281
   return {
267 282
     content: state.fullText,
268 283
     sessionId: state.sessionId,
284
+    model: state.model,
269 285
     durationMs: Date.now() - state.startMs
270 286
   }
271 287
 }
@@ -362,8 +378,8 @@ function sendChatMessageStream(body, options) {
362 378
 
363 379
 /**
364 380
  * 聊天 POST /v1/chat/completions(默认 stream: true)
365
- * options: onDelta, onSessionId, onReadyTask(task), signal(H5)
366
- * 流式 resolve:{ content, sessionId, durationMs },durationMs 为整段流式请求耗时(毫秒)
381
+ * options: onDelta, onSessionId, onModel, onReadyTask(task), signal(H5)
382
+ * 流式 resolve:{ content, sessionId, model, durationMs },durationMs 为整段流式请求耗时(毫秒)
367 383
  */
368 384
 export function sendChatMessage(data, options = {}) {
369 385
   const body = { stream: true, ...(data || {}) }

+ 22 - 7
ruoyi-ui-app/package-a/ai-assistant/index.vue

@@ -1110,13 +1110,14 @@ export default {
1110 1110
         if (norm.sendTime) row.sendTime = norm.sendTime
1111 1111
       }
1112 1112
     },
1113
-    queuePendingPersist(consultSessionId, persistBody, aiReplyContent, llmSessionId, costTime) {
1113
+    queuePendingPersist(consultSessionId, persistBody, aiReplyContent, llmSessionId, costTime, aiCategory) {
1114 1114
       this.pendingPersist = {
1115 1115
         consultSessionId,
1116 1116
         persistBody: { ...persistBody },
1117 1117
         aiReplyContent,
1118 1118
         llmSessionId: llmSessionId != null ? llmSessionId : null,
1119
-        costTime: costTime != null ? costTime : null
1119
+        costTime: costTime != null ? costTime : null,
1120
+        aiCategory: aiCategory != null && aiCategory !== '' ? aiCategory : null
1120 1121
       }
1121 1122
     },
1122 1123
     flushPendingPersist() {
@@ -1128,14 +1129,15 @@ export default {
1128 1129
         ...pending.persistBody,
1129 1130
         aiReplyContent: pending.aiReplyContent,
1130 1131
         realSessionId: pending.llmSessionId != null ? pending.llmSessionId : null,
1131
-        costTime: pending.costTime != null ? pending.costTime : undefined
1132
+        costTime: pending.costTime != null ? pending.costTime : undefined,
1133
+        aiCategory: pending.aiCategory != null ? pending.aiCategory : undefined
1132 1134
       })
1133 1135
         .then(() => {
1134 1136
           this.pendingPersist = null
1135 1137
         })
1136 1138
         .catch(() => {})
1137 1139
     },
1138
-    persistConsultExchange(persistBody, aiReplyContent, costTime) {
1140
+    persistConsultExchange(persistBody, aiReplyContent, costTime, aiCategory) {
1139 1141
       const reply = (aiReplyContent || '').trim()
1140 1142
       const consultSessionId = this.consultSessionIdForApi()
1141 1143
       if (!consultSessionId || !persistBody || !reply) {
@@ -1146,7 +1148,8 @@ export default {
1146 1148
         ...persistBody,
1147 1149
         aiReplyContent: reply,
1148 1150
         realSessionId: llmSessionId || null,
1149
-        costTime: costTime != null ? costTime : undefined
1151
+        costTime: costTime != null ? costTime : undefined,
1152
+        aiCategory: aiCategory != null && aiCategory !== '' ? aiCategory : undefined
1150 1153
       })
1151 1154
         .then((res) => {
1152 1155
           this.mergePersistResult(res.data, reply)
@@ -1160,7 +1163,7 @@ export default {
1160 1163
           return true
1161 1164
         })
1162 1165
         .catch((err) => {
1163
-          this.queuePendingPersist(consultSessionId, persistBody, reply, llmSessionId, costTime)
1166
+          this.queuePendingPersist(consultSessionId, persistBody, reply, llmSessionId, costTime, aiCategory)
1164 1167
           const msg = (err && err.message) || this.$t('aiOnlineConsult.saveSessionFailed')
1165 1168
           uni.showToast({ title: msg, icon: 'none' })
1166 1169
           return false
@@ -1246,6 +1249,7 @@ export default {
1246 1249
       let aiReplyContent = ''
1247 1250
       let aiMsg = null
1248 1251
       let costTime = null
1252
+      let aiCategory = null
1249 1253
       this.abortStreamRequest()
1250 1254
       this.freezeFeedScrollTopForStream()
1251 1255
       this.sending = true
@@ -1258,6 +1262,9 @@ export default {
1258 1262
           onSessionId: (llmSid) => {
1259 1263
             this.rememberLlmSessionId(llmSid)
1260 1264
           },
1265
+          onModel: (model) => {
1266
+            aiCategory = model
1267
+          },
1261 1268
           onDelta: (fullText) => {
1262 1269
             aiReplyContent = fullText
1263 1270
             if (!aiMsg) {
@@ -1275,6 +1282,9 @@ export default {
1275 1282
           this.rememberLlmSessionId(llmSid)
1276 1283
         }
1277 1284
         costTime = streamResult.durationMs != null ? streamResult.durationMs : null
1285
+        if (streamResult.model) {
1286
+          aiCategory = streamResult.model
1287
+        }
1278 1288
         this.clearStreamRevealTimer()
1279 1289
         aiReplyContent = (streamResult.content || aiReplyContent || '').trim() || this.$t('aiOnlineConsult.noContent')
1280 1290
         if (!aiMsg) {
@@ -1308,7 +1318,12 @@ export default {
1308 1318
         }
1309 1319
         this.$nextTick(() => this.scheduleScrollToBottom())
1310 1320
       }
1311
-      await this.persistConsultExchange(persistBody, (aiReplyContent || '').trim(), costTime)
1321
+      await this.persistConsultExchange(
1322
+        persistBody,
1323
+        (aiReplyContent || '').trim(),
1324
+        costTime,
1325
+        aiCategory
1326
+      )
1312 1327
     },
1313 1328
     validateMediaFile(filePath, fileName, kind) {
1314 1329
       const rule = MEDIA_RULES[kind]

+ 21 - 6
ruoyi-ui/src/api/diseaseTreatment/aiOnlineConsult.js

@@ -96,7 +96,9 @@ function parseChatJsonPayload(payload) {
96 96
     const json = JSON.parse(payload)
97 97
     const sessionId = extractLlmSessionIdFromJson(json)
98 98
     const delta = extractDeltaFromChatJson(json)
99
-    return { sessionId, delta }
99
+    const model =
100
+      json.model != null && json.model !== "" ? String(json.model) : null
101
+    return { sessionId, delta, model }
100 102
   } catch (e) {
101 103
     return null
102 104
   }
@@ -151,6 +153,12 @@ function consumeSseLines(state, lines, callbacks) {
151 153
         callbacks.onSessionId(parsed.sessionId)
152 154
       }
153 155
     }
156
+    if (parsed.model) {
157
+      state.model = parsed.model
158
+      if (callbacks.onModel) {
159
+        callbacks.onModel(parsed.model)
160
+      }
161
+    }
154 162
     if (parsed.delta) {
155 163
       const before = state.fullText
156 164
       appendStreamDelta(state, parsed.delta)
@@ -181,6 +189,12 @@ function appendSseChunk(state, chunk, callbacks) {
181 189
           callbacks.onSessionId(parsed.sessionId)
182 190
         }
183 191
       }
192
+      if (parsed.model) {
193
+        state.model = parsed.model
194
+        if (callbacks.onModel) {
195
+          callbacks.onModel(parsed.model)
196
+        }
197
+      }
184 198
       return
185 199
     }
186 200
   }
@@ -220,9 +234,9 @@ function streamRequestHeaders() {
220 234
 /** 流式:fetch + ReadableStream 逐块解析(axios XHR 常会缓冲到结束才触发 progress) */
221 235
 async function sendChatMessageStream(data, options) {
222 236
   const startMs = Date.now()
223
-  const { onDelta, onSessionId, signal } = options
224
-  const state = { buffer: "", fullText: "", sessionId: null }
225
-  const callbacks = { onDelta, onSessionId }
237
+  const { onDelta, onSessionId, onModel, signal } = options
238
+  const state = { buffer: "", fullText: "", sessionId: null, model: null }
239
+  const callbacks = { onDelta, onSessionId, onModel }
226 240
 
227 241
   const res = await fetch(chatCompletionsUrl(), {
228 242
     method: "POST",
@@ -260,6 +274,7 @@ async function sendChatMessageStream(data, options) {
260 274
   return {
261 275
     content: state.fullText,
262 276
     sessionId: state.sessionId,
277
+    model: state.model,
263 278
     durationMs: Date.now() - startMs
264 279
   }
265 280
 }
@@ -267,8 +282,8 @@ async function sendChatMessageStream(data, options) {
267 282
 /**
268 283
  * 聊天 POST /v1/chat/completions(默认 stream: true)
269 284
  * - stream: false → JSON(走 request 默认响应)
270
- * - 其余情况 → SSE,options 可传 onDelta / onSessionId / signal
271
- * - 流式 resolve:{ content, sessionId, durationMs },durationMs 为整段流式请求耗时(毫秒)
285
+ * - 其余情况 → SSE,options 可传 onDelta / onSessionId / onModel / signal
286
+ * - 流式 resolve:{ content, sessionId, model, durationMs },durationMs 为整段流式请求耗时(毫秒)
272 287
  */
273 288
 export function sendChatMessage(data, options = {}) {
274 289
   const body = { stream: true, ...(data || {}) }

+ 24 - 9
ruoyi-ui/src/views/diseaseTreatment/onlineConsult/ai/index.vue

@@ -317,10 +317,10 @@ const DEFAULT_SESSION_TITLE = "新会话"
317 317
 const LLM_SESSION_STORAGE_PREFIX = "ai_consult_llm_session_"
318 318
 
319 319
 const MODEL_OPTION_DEFS = [
320
-  { value: "auto", labelKey: "modelAuto", shortKey: "modelAutoShort", descKey: "modelAutoDesc", icon: "el-icon-cpu" },
321
-  { value: "yak-disease", labelKey: "modelDisease", shortKey: "modelDiseaseShort", descKey: "modelDiseaseDesc", icon: "el-icon-s-flag" },
320
+  // { value: "auto", labelKey: "modelAuto", shortKey: "modelAutoShort", descKey: "modelAutoDesc", icon: "el-icon-cpu" },
322 321
   { value: "yak-general", labelKey: "modelGeneral", shortKey: "modelGeneralShort", descKey: "modelGeneralDesc", icon: "el-icon-chat-dot-round" },
323 322
   { value: "yak-feeding", labelKey: "modelFeeding", shortKey: "modelFeedingShort", descKey: "modelFeedingDesc", icon: "el-icon-s-goods" },
323
+  { value: "yak-disease", labelKey: "modelDisease", shortKey: "modelDiseaseShort", descKey: "modelDiseaseDesc", icon: "el-icon-s-flag" },
324 324
   { value: "yak-growth", labelKey: "modelGrowth", shortKey: "modelGrowth", descKey: "modelGrowthDesc", icon: "el-icon-s-flag" },
325 325
 ]
326 326
 
@@ -1017,13 +1017,14 @@ export default {
1017 1017
         }
1018 1018
       }
1019 1019
     },
1020
-    queuePendingPersist(consultSessionId, persistBody, aiReplyContent, llmSessionId, costTime) {
1020
+    queuePendingPersist(consultSessionId, persistBody, aiReplyContent, llmSessionId, costTime, aiCategory) {
1021 1021
       this.pendingPersist = {
1022 1022
         consultSessionId,
1023 1023
         persistBody: { ...persistBody },
1024 1024
         aiReplyContent,
1025 1025
         llmSessionId: llmSessionId != null ? llmSessionId : null,
1026
-        costTime: costTime != null ? costTime : null
1026
+        costTime: costTime != null ? costTime : null,
1027
+        aiCategory: aiCategory != null && aiCategory !== "" ? aiCategory : null
1027 1028
       }
1028 1029
     },
1029 1030
     async flushPendingPersist() {
@@ -1036,14 +1037,15 @@ export default {
1036 1037
           ...pending.persistBody,
1037 1038
           aiReplyContent: pending.aiReplyContent,
1038 1039
           realSessionId: pending.llmSessionId != null ? pending.llmSessionId : null,
1039
-          costTime: pending.costTime != null ? pending.costTime : undefined
1040
+          costTime: pending.costTime != null ? pending.costTime : undefined,
1041
+          aiCategory: pending.aiCategory != null ? pending.aiCategory : undefined
1040 1042
         })
1041 1043
         this.pendingPersist = null
1042 1044
       } catch (err) {
1043 1045
         // 离开页面时仍失败则保留队列,下次进入同页可再试(仅内存,刷新会丢)
1044 1046
       }
1045 1047
     },
1046
-    async persistConsultExchange(persistBody, aiReplyContent, costTime) {
1048
+    async persistConsultExchange(persistBody, aiReplyContent, costTime, aiCategory) {
1047 1049
       const reply = (aiReplyContent || "").trim()
1048 1050
       if (!this.activeSessionId || !persistBody || !reply) {
1049 1051
         return true
@@ -1054,7 +1056,8 @@ export default {
1054 1056
         ...persistBody,
1055 1057
         aiReplyContent: reply,
1056 1058
         realSessionId: llmSessionId || null,
1057
-        costTime: costTime != null ? costTime : undefined
1059
+        costTime: costTime != null ? costTime : undefined,
1060
+        aiCategory: aiCategory != null && aiCategory !== "" ? aiCategory : undefined
1058 1061
       }
1059 1062
       try {
1060 1063
         const res = await sendAiConsultMessage(consultSessionId, saveBody)
@@ -1069,7 +1072,7 @@ export default {
1069 1072
         }
1070 1073
         return true
1071 1074
       } catch (err) {
1072
-        this.queuePendingPersist(consultSessionId, persistBody, reply, llmSessionId, costTime)
1075
+        this.queuePendingPersist(consultSessionId, persistBody, reply, llmSessionId, costTime, aiCategory)
1073 1076
         const msg = (err && err.message) || this.dtT("saveSessionFailed")
1074 1077
         this.$modal.msgWarning(msg)
1075 1078
         return false
@@ -1231,6 +1234,7 @@ export default {
1231 1234
       let aiReplyContent = ""
1232 1235
       let aiMsg = null
1233 1236
       let costTime = null
1237
+      let aiCategory = null
1234 1238
       this.abortStreamRequest()
1235 1239
       const abortController = new AbortController()
1236 1240
       this.streamAbortController = abortController
@@ -1241,6 +1245,9 @@ export default {
1241 1245
           onSessionId: (llmSid) => {
1242 1246
             this.rememberLlmSessionId(session, llmSid)
1243 1247
           },
1248
+          onModel: (model) => {
1249
+            aiCategory = model
1250
+          },
1244 1251
           onDelta: (fullText) => {
1245 1252
             aiReplyContent = fullText
1246 1253
             if (!aiMsg) {
@@ -1257,6 +1264,9 @@ export default {
1257 1264
           this.rememberLlmSessionId(session, llmSid)
1258 1265
         }
1259 1266
         costTime = streamResult.durationMs != null ? streamResult.durationMs : null
1267
+        if (streamResult.model) {
1268
+          aiCategory = streamResult.model
1269
+        }
1260 1270
         this.clearStreamRevealTimer()
1261 1271
         aiReplyContent = (streamResult.content || aiReplyContent || "").trim() || this.dtT("noContent")
1262 1272
         if (!aiMsg) {
@@ -1287,7 +1297,12 @@ export default {
1287 1297
         this.sending = false
1288 1298
         this.$nextTick(() => this.scrollChatBottom())
1289 1299
       }
1290
-      await this.persistConsultExchange(persistBody, (aiReplyContent || "").trim(), costTime)
1300
+      await this.persistConsultExchange(
1301
+        persistBody,
1302
+        (aiReplyContent || "").trim(),
1303
+        costTime,
1304
+        aiCategory
1305
+      )
1291 1306
     },
1292 1307
     extOf(fileName) {
1293 1308
       if (!fileName || fileName.lastIndexOf(".") < 0) {