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

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

@@ -106,7 +106,9 @@ function parseChatJsonPayload(payload) {
106
     const json = JSON.parse(payload)
106
     const json = JSON.parse(payload)
107
     const sessionId = extractLlmSessionIdFromJson(json)
107
     const sessionId = extractLlmSessionIdFromJson(json)
108
     const delta = extractDeltaFromChatJson(json)
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
   } catch (e) {
112
   } catch (e) {
111
     return null
113
     return null
112
   }
114
   }
@@ -162,6 +164,12 @@ function consumeSseLines(state, lines, callbacks) {
162
         callbacks.onSessionId(parsed.sessionId)
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
     if (parsed.delta) {
173
     if (parsed.delta) {
166
       const before = state.fullText
174
       const before = state.fullText
167
       appendStreamDelta(state, parsed.delta)
175
       appendStreamDelta(state, parsed.delta)
@@ -192,6 +200,12 @@ function appendSseChunk(state, chunk, callbacks) {
192
           callbacks.onSessionId(parsed.sessionId)
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
       return
209
       return
196
     }
210
     }
197
   }
211
   }
@@ -250,14 +264,15 @@ function streamRequestHeaders() {
250
 }
264
 }
251
 
265
 
252
 function createStreamState(options) {
266
 function createStreamState(options) {
253
-  const { onDelta, onSessionId } = options
267
+  const { onDelta, onSessionId, onModel } = options
254
   return {
268
   return {
255
     startMs: Date.now(),
269
     startMs: Date.now(),
256
     buffer: '',
270
     buffer: '',
257
     fullText: '',
271
     fullText: '',
258
     sessionId: null,
272
     sessionId: null,
273
+    model: null,
259
     chunkReceived: false,
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
   return {
281
   return {
267
     content: state.fullText,
282
     content: state.fullText,
268
     sessionId: state.sessionId,
283
     sessionId: state.sessionId,
284
+    model: state.model,
269
     durationMs: Date.now() - state.startMs
285
     durationMs: Date.now() - state.startMs
270
   }
286
   }
271
 }
287
 }
@@ -362,8 +378,8 @@ function sendChatMessageStream(body, options) {
362
 
378
 
363
 /**
379
 /**
364
  * 聊天 POST /v1/chat/completions(默认 stream: true)
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
 export function sendChatMessage(data, options = {}) {
384
 export function sendChatMessage(data, options = {}) {
369
   const body = { stream: true, ...(data || {}) }
385
   const body = { stream: true, ...(data || {}) }

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

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

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

@@ -96,7 +96,9 @@ function parseChatJsonPayload(payload) {
96
     const json = JSON.parse(payload)
96
     const json = JSON.parse(payload)
97
     const sessionId = extractLlmSessionIdFromJson(json)
97
     const sessionId = extractLlmSessionIdFromJson(json)
98
     const delta = extractDeltaFromChatJson(json)
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
   } catch (e) {
102
   } catch (e) {
101
     return null
103
     return null
102
   }
104
   }
@@ -151,6 +153,12 @@ function consumeSseLines(state, lines, callbacks) {
151
         callbacks.onSessionId(parsed.sessionId)
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
     if (parsed.delta) {
162
     if (parsed.delta) {
155
       const before = state.fullText
163
       const before = state.fullText
156
       appendStreamDelta(state, parsed.delta)
164
       appendStreamDelta(state, parsed.delta)
@@ -181,6 +189,12 @@ function appendSseChunk(state, chunk, callbacks) {
181
           callbacks.onSessionId(parsed.sessionId)
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
       return
198
       return
185
     }
199
     }
186
   }
200
   }
@@ -220,9 +234,9 @@ function streamRequestHeaders() {
220
 /** 流式:fetch + ReadableStream 逐块解析(axios XHR 常会缓冲到结束才触发 progress) */
234
 /** 流式:fetch + ReadableStream 逐块解析(axios XHR 常会缓冲到结束才触发 progress) */
221
 async function sendChatMessageStream(data, options) {
235
 async function sendChatMessageStream(data, options) {
222
   const startMs = Date.now()
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
   const res = await fetch(chatCompletionsUrl(), {
241
   const res = await fetch(chatCompletionsUrl(), {
228
     method: "POST",
242
     method: "POST",
@@ -260,6 +274,7 @@ async function sendChatMessageStream(data, options) {
260
   return {
274
   return {
261
     content: state.fullText,
275
     content: state.fullText,
262
     sessionId: state.sessionId,
276
     sessionId: state.sessionId,
277
+    model: state.model,
263
     durationMs: Date.now() - startMs
278
     durationMs: Date.now() - startMs
264
   }
279
   }
265
 }
280
 }
@@ -267,8 +282,8 @@ async function sendChatMessageStream(data, options) {
267
 /**
282
 /**
268
  * 聊天 POST /v1/chat/completions(默认 stream: true)
283
  * 聊天 POST /v1/chat/completions(默认 stream: true)
269
  * - stream: false → JSON(走 request 默认响应)
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
 export function sendChatMessage(data, options = {}) {
288
 export function sendChatMessage(data, options = {}) {
274
   const body = { stream: true, ...(data || {}) }
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
 const LLM_SESSION_STORAGE_PREFIX = "ai_consult_llm_session_"
317
 const LLM_SESSION_STORAGE_PREFIX = "ai_consult_llm_session_"
318
 
318
 
319
 const MODEL_OPTION_DEFS = [
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
   { value: "yak-general", labelKey: "modelGeneral", shortKey: "modelGeneralShort", descKey: "modelGeneralDesc", icon: "el-icon-chat-dot-round" },
321
   { value: "yak-general", labelKey: "modelGeneral", shortKey: "modelGeneralShort", descKey: "modelGeneralDesc", icon: "el-icon-chat-dot-round" },
323
   { value: "yak-feeding", labelKey: "modelFeeding", shortKey: "modelFeedingShort", descKey: "modelFeedingDesc", icon: "el-icon-s-goods" },
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
   { value: "yak-growth", labelKey: "modelGrowth", shortKey: "modelGrowth", descKey: "modelGrowthDesc", icon: "el-icon-s-flag" },
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
       this.pendingPersist = {
1021
       this.pendingPersist = {
1022
         consultSessionId,
1022
         consultSessionId,
1023
         persistBody: { ...persistBody },
1023
         persistBody: { ...persistBody },
1024
         aiReplyContent,
1024
         aiReplyContent,
1025
         llmSessionId: llmSessionId != null ? llmSessionId : null,
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
     async flushPendingPersist() {
1030
     async flushPendingPersist() {
@@ -1036,14 +1037,15 @@ export default {
1036
           ...pending.persistBody,
1037
           ...pending.persistBody,
1037
           aiReplyContent: pending.aiReplyContent,
1038
           aiReplyContent: pending.aiReplyContent,
1038
           realSessionId: pending.llmSessionId != null ? pending.llmSessionId : null,
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
         this.pendingPersist = null
1043
         this.pendingPersist = null
1042
       } catch (err) {
1044
       } catch (err) {
1043
         // 离开页面时仍失败则保留队列,下次进入同页可再试(仅内存,刷新会丢)
1045
         // 离开页面时仍失败则保留队列,下次进入同页可再试(仅内存,刷新会丢)
1044
       }
1046
       }
1045
     },
1047
     },
1046
-    async persistConsultExchange(persistBody, aiReplyContent, costTime) {
1048
+    async persistConsultExchange(persistBody, aiReplyContent, costTime, aiCategory) {
1047
       const reply = (aiReplyContent || "").trim()
1049
       const reply = (aiReplyContent || "").trim()
1048
       if (!this.activeSessionId || !persistBody || !reply) {
1050
       if (!this.activeSessionId || !persistBody || !reply) {
1049
         return true
1051
         return true
@@ -1054,7 +1056,8 @@ export default {
1054
         ...persistBody,
1056
         ...persistBody,
1055
         aiReplyContent: reply,
1057
         aiReplyContent: reply,
1056
         realSessionId: llmSessionId || null,
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
       try {
1062
       try {
1060
         const res = await sendAiConsultMessage(consultSessionId, saveBody)
1063
         const res = await sendAiConsultMessage(consultSessionId, saveBody)
@@ -1069,7 +1072,7 @@ export default {
1069
         }
1072
         }
1070
         return true
1073
         return true
1071
       } catch (err) {
1074
       } catch (err) {
1072
-        this.queuePendingPersist(consultSessionId, persistBody, reply, llmSessionId, costTime)
1075
+        this.queuePendingPersist(consultSessionId, persistBody, reply, llmSessionId, costTime, aiCategory)
1073
         const msg = (err && err.message) || this.dtT("saveSessionFailed")
1076
         const msg = (err && err.message) || this.dtT("saveSessionFailed")
1074
         this.$modal.msgWarning(msg)
1077
         this.$modal.msgWarning(msg)
1075
         return false
1078
         return false
@@ -1231,6 +1234,7 @@ export default {
1231
       let aiReplyContent = ""
1234
       let aiReplyContent = ""
1232
       let aiMsg = null
1235
       let aiMsg = null
1233
       let costTime = null
1236
       let costTime = null
1237
+      let aiCategory = null
1234
       this.abortStreamRequest()
1238
       this.abortStreamRequest()
1235
       const abortController = new AbortController()
1239
       const abortController = new AbortController()
1236
       this.streamAbortController = abortController
1240
       this.streamAbortController = abortController
@@ -1241,6 +1245,9 @@ export default {
1241
           onSessionId: (llmSid) => {
1245
           onSessionId: (llmSid) => {
1242
             this.rememberLlmSessionId(session, llmSid)
1246
             this.rememberLlmSessionId(session, llmSid)
1243
           },
1247
           },
1248
+          onModel: (model) => {
1249
+            aiCategory = model
1250
+          },
1244
           onDelta: (fullText) => {
1251
           onDelta: (fullText) => {
1245
             aiReplyContent = fullText
1252
             aiReplyContent = fullText
1246
             if (!aiMsg) {
1253
             if (!aiMsg) {
@@ -1257,6 +1264,9 @@ export default {
1257
           this.rememberLlmSessionId(session, llmSid)
1264
           this.rememberLlmSessionId(session, llmSid)
1258
         }
1265
         }
1259
         costTime = streamResult.durationMs != null ? streamResult.durationMs : null
1266
         costTime = streamResult.durationMs != null ? streamResult.durationMs : null
1267
+        if (streamResult.model) {
1268
+          aiCategory = streamResult.model
1269
+        }
1260
         this.clearStreamRevealTimer()
1270
         this.clearStreamRevealTimer()
1261
         aiReplyContent = (streamResult.content || aiReplyContent || "").trim() || this.dtT("noContent")
1271
         aiReplyContent = (streamResult.content || aiReplyContent || "").trim() || this.dtT("noContent")
1262
         if (!aiMsg) {
1272
         if (!aiMsg) {
@@ -1287,7 +1297,12 @@ export default {
1287
         this.sending = false
1297
         this.sending = false
1288
         this.$nextTick(() => this.scrollChatBottom())
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
     extOf(fileName) {
1307
     extOf(fileName) {
1293
       if (!fileName || fileName.lastIndexOf(".") < 0) {
1308
       if (!fileName || fileName.lastIndexOf(".") < 0) {