fix: parse JSON error during typing answer in chat

This commit is contained in:
ulleo
2025-08-14 14:38:39 +08:00
parent 1967ea179a
commit 2585526e3f
5 changed files with 211 additions and 220 deletions
+23 -23
View File
@@ -805,7 +805,7 @@ class LLMService:
try:
# return id
if in_chat:
yield orjson.dumps({'type': 'id', 'id': self.get_record().id}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'id', 'id': self.get_record().id}).decode() + '\n\n'
# return title
if self.change_title:
@@ -814,7 +814,7 @@ class LLMService:
rename_object=RenameChat(id=self.get_record().chat_id,
brief=self.chat_question.question.strip()[:20]))
if in_chat:
yield orjson.dumps({'type': 'brief', 'brief': brief}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'brief', 'brief': brief}).decode() + '\n\n'
# select datasource if datasource is none
if not self.ds:
@@ -823,11 +823,11 @@ class LLMService:
for chunk in ds_res:
SQLBotLogUtil.info(chunk)
if in_chat:
yield orjson.dumps(
yield 'data:' + orjson.dumps(
{'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'),
'type': 'datasource-result'}).decode() + '\n\n'
if in_chat:
yield orjson.dumps({'id': self.ds.id, 'datasource_name': self.ds.name,
yield 'data:' + orjson.dumps({'id': self.ds.id, 'datasource_name': self.ds.name,
'engine_type': self.ds.type_name or self.ds.type,
'type': 'datasource'}).decode() + '\n\n'
@@ -843,11 +843,11 @@ class LLMService:
for chunk in sql_res:
full_sql_text += chunk.get('content')
if in_chat:
yield orjson.dumps(
yield 'data:' + orjson.dumps(
{'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'),
'type': 'sql-result'}).decode() + '\n\n'
if in_chat:
yield orjson.dumps({'type': 'info', 'msg': 'sql generated'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'info', 'msg': 'sql generated'}).decode() + '\n\n'
# filter sql
SQLBotLogUtil.info(full_sql_text)
@@ -881,7 +881,7 @@ class LLMService:
SQLBotLogUtil.info(sql)
format_sql = sqlparse.format(sql, reindent=True)
if in_chat:
yield orjson.dumps({'content': format_sql, 'type': 'sql'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'content': format_sql, 'type': 'sql'}).decode() + '\n\n'
else:
yield f'```sql\n{format_sql}\n```\n\n'
@@ -889,7 +889,7 @@ class LLMService:
result = self.execute_sql(sql=sql)
self.save_sql_data(data_obj=result)
if in_chat:
yield orjson.dumps({'content': 'execute-success', 'type': 'sql-data'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'content': 'execute-success', 'type': 'sql-data'}).decode() + '\n\n'
# generate chart
chart_res = self.generate_chart()
@@ -897,18 +897,18 @@ class LLMService:
for chunk in chart_res:
full_chart_text += chunk.get('content')
if in_chat:
yield orjson.dumps(
yield 'data:' + orjson.dumps(
{'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'),
'type': 'chart-result'}).decode() + '\n\n'
if in_chat:
yield orjson.dumps({'type': 'info', 'msg': 'chart generated'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'info', 'msg': 'chart generated'}).decode() + '\n\n'
# filter chart
SQLBotLogUtil.info(full_chart_text)
chart = self.check_save_chart(res=full_chart_text)
SQLBotLogUtil.info(chart)
if in_chat:
yield orjson.dumps({'content': orjson.dumps(chart).decode(), 'type': 'chart'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'content': orjson.dumps(chart).decode(), 'type': 'chart'}).decode() + '\n\n'
else:
data = []
_fields = {}
@@ -940,7 +940,7 @@ class LLMService:
record = self.finish()
if in_chat:
yield orjson.dumps({'type': 'finish'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'finish'}).decode() + '\n\n'
else:
# todo generate picture
if chart['type'] != 'table':
@@ -956,7 +956,7 @@ class LLMService:
error_msg = orjson.dumps({'message': str(e), 'traceback': traceback.format_exc(limit=1)}).decode()
self.save_error(message=error_msg)
if in_chat:
yield orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n'
else:
yield f'> ❌ **ERROR**\n\n> \n\n> {error_msg}。'
@@ -990,37 +990,37 @@ class LLMService:
def run_analysis_or_predict_task(self, action_type: str):
try:
yield orjson.dumps({'type': 'id', 'id': self.get_record().id}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'id', 'id': self.get_record().id}).decode() + '\n\n'
if action_type == 'analysis':
# generate analysis
analysis_res = self.generate_analysis()
for chunk in analysis_res:
yield orjson.dumps(
yield 'data:' + orjson.dumps(
{'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'),
'type': 'analysis-result'}).decode() + '\n\n'
yield orjson.dumps({'type': 'info', 'msg': 'analysis generated'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'info', 'msg': 'analysis generated'}).decode() + '\n\n'
yield orjson.dumps({'type': 'analysis_finish'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'analysis_finish'}).decode() + '\n\n'
elif action_type == 'predict':
# generate predict
analysis_res = self.generate_predict()
full_text = ''
for chunk in analysis_res:
yield orjson.dumps(
yield 'data:' + orjson.dumps(
{'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'),
'type': 'predict-result'}).decode() + '\n\n'
full_text += chunk.get('content')
yield orjson.dumps({'type': 'info', 'msg': 'predict generated'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'info', 'msg': 'predict generated'}).decode() + '\n\n'
_data = self.check_save_predict_data(res=full_text)
if _data:
yield orjson.dumps({'type': 'predict-success'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'predict-success'}).decode() + '\n\n'
else:
yield orjson.dumps({'type': 'predict-failed'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'predict-failed'}).decode() + '\n\n'
yield orjson.dumps({'type': 'predict_finish'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'type': 'predict_finish'}).decode() + '\n\n'
self.finish()
except Exception as e:
@@ -1030,7 +1030,7 @@ class LLMService:
else:
error_msg = orjson.dumps({'message': str(e), 'traceback': traceback.format_exc(limit=1)}).decode()
self.save_error(message=error_msg)
yield orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n'
yield 'data:' + orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n'
finally:
# end
pass
@@ -98,11 +98,13 @@ const sendMessage = async () => {
const controller: AbortController = new AbortController()
const response = await chatApi.analysis(currentRecord.analysis_record_id, controller)
const reader = response.body.getReader()
const decoder = new TextDecoder()
const decoder = new TextDecoder('utf-8')
let analysis_answer = ''
let analysis_answer_thinking = ''
let tempResult = ''
while (true) {
if (stopFlag.value) {
controller.abort()
@@ -116,65 +118,61 @@ const sendMessage = async () => {
break
}
const chunk = decoder.decode(value)
let _list = [chunk]
const lines = chunk.trim().split('}\n\n{')
if (lines.length > 1) {
_list = []
for (let line of lines) {
if (!line.trim().startsWith('{')) {
line = '{' + line.trim()
}
if (!line.trim().endsWith('}')) {
line = line.trim() + '}'
}
_list.push(line)
}
let chunk = decoder.decode(value, { stream: true })
tempResult += chunk
const split = tempResult.match(/data:.*}\n\n/g)
if (split) {
chunk = split.join('')
tempResult = tempResult.replace(chunk, '')
} else {
continue
}
for (const str of _list) {
let data
try {
data = JSON.parse(str)
} catch (err) {
console.error('JSON string:', str)
throw err
}
if (chunk && chunk.startsWith('data:{')) {
if (split) {
for (const str of split) {
let data
try {
data = JSON.parse(str.replace('data:{', '{'))
} catch (err) {
console.error('JSON string:', str)
throw err
}
if (data.code && data.code !== 200) {
ElMessage({
message: data.msg,
type: 'error',
showClose: true,
})
_loading.value = false
return
}
if (data.code && data.code !== 200) {
ElMessage({
message: data.msg,
type: 'error',
showClose: true,
})
_loading.value = false
return
}
switch (data.type) {
case 'id':
currentRecord.id = data.id
_currentChat.value.records[index.value].id = data.id
break
case 'info':
console.info(data.msg)
break
case 'error':
currentRecord.error = data.content
emits('error')
break
case 'analysis-result':
analysis_answer += data.content
analysis_answer_thinking += data.reasoning_content
_currentChat.value.records[index.value].analysis = analysis_answer
_currentChat.value.records[index.value].analysis_thinking = analysis_answer_thinking
break
case 'analysis_finish':
emits('finish', currentRecord.id)
break
switch (data.type) {
case 'id':
currentRecord.id = data.id
_currentChat.value.records[index.value].id = data.id
break
case 'info':
console.info(data.msg)
break
case 'error':
currentRecord.error = data.content
emits('error')
break
case 'analysis-result':
analysis_answer += data.content
analysis_answer_thinking += data.reasoning_content
_currentChat.value.records[index.value].analysis = analysis_answer
_currentChat.value.records[index.value].analysis_thinking = analysis_answer_thinking
break
case 'analysis_finish':
emits('finish', currentRecord.id)
break
}
await nextTick()
}
}
await nextTick()
}
}
} catch (error) {
+73 -75
View File
@@ -104,11 +104,13 @@ const sendMessage = async () => {
}
const response = await questionApi.add(param, controller)
const reader = response.body.getReader()
const decoder = new TextDecoder()
const decoder = new TextDecoder('utf-8')
let sql_answer = ''
let chart_answer = ''
let tempResult = ''
while (true) {
if (stopFlag.value) {
controller.abort()
@@ -121,84 +123,80 @@ const sendMessage = async () => {
break
}
const chunk = decoder.decode(value)
let _list = [chunk]
const lines = chunk.trim().split('}\n\n{')
if (lines.length > 1) {
_list = []
for (let line of lines) {
if (!line.trim().startsWith('{')) {
line = '{' + line.trim()
}
if (!line.trim().endsWith('}')) {
line = line.trim() + '}'
}
_list.push(line)
}
let chunk = decoder.decode(value, { stream: true })
tempResult += chunk
const split = tempResult.match(/data:.*}\n\n/g)
if (split) {
chunk = split.join('')
tempResult = tempResult.replace(chunk, '')
} else {
continue
}
for (const str of _list) {
let data
try {
data = JSON.parse(str)
} catch (err) {
console.error('JSON string:', str)
throw err
}
if (chunk && chunk.startsWith('data:{')) {
if (split) {
for (const str of split) {
let data
try {
data = JSON.parse(str.replace('data:{', '{'))
} catch (err) {
console.error('JSON string:', str)
throw err
}
if (data.code && data.code !== 200) {
ElMessage({
message: data.msg,
type: 'error',
showClose: true,
})
_loading.value = false
return
}
if (data.code && data.code !== 200) {
ElMessage({
message: data.msg,
type: 'error',
showClose: true,
})
_loading.value = false
return
}
switch (data.type) {
case 'id':
currentRecord.id = data.id
_currentChat.value.records[index.value].id = data.id
break
case 'info':
console.info(data.msg)
break
case 'brief':
_currentChat.value.brief = data.brief
_chatList.value.forEach((c: Chat) => {
if (c.id === _currentChat.value.id) {
c.brief = _currentChat.value.brief
}
})
break
case 'error':
currentRecord.error = data.content
emits('error')
break
case 'sql-result':
sql_answer += data.reasoning_content
_currentChat.value.records[index.value].sql_answer = sql_answer
break
case 'sql':
_currentChat.value.records[index.value].sql = data.content
break
case 'sql-data':
getChatData(_currentChat.value.records[index.value].id)
break
case 'chart-result':
chart_answer += data.reasoning_content
_currentChat.value.records[index.value].chart_answer = chart_answer
break
case 'chart':
_currentChat.value.records[index.value].chart = data.content
break
case 'finish':
emits('finish', currentRecord.id)
break
switch (data.type) {
case 'id':
currentRecord.id = data.id
_currentChat.value.records[index.value].id = data.id
break
case 'info':
console.info(data.msg)
break
case 'brief':
_currentChat.value.brief = data.brief
_chatList.value.forEach((c: Chat) => {
if (c.id === _currentChat.value.id) {
c.brief = _currentChat.value.brief
}
})
break
case 'error':
currentRecord.error = data.content
emits('error')
break
case 'sql-result':
sql_answer += data.reasoning_content
_currentChat.value.records[index.value].sql_answer = sql_answer
break
case 'sql':
_currentChat.value.records[index.value].sql = data.content
break
case 'sql-data':
getChatData(_currentChat.value.records[index.value].id)
break
case 'chart-result':
chart_answer += data.reasoning_content
_currentChat.value.records[index.value].chart_answer = chart_answer
break
case 'chart':
_currentChat.value.records[index.value].chart = data.content
break
case 'finish':
emits('finish', currentRecord.id)
break
}
await nextTick()
}
}
await nextTick()
}
}
} catch (error) {
@@ -99,11 +99,13 @@ const sendMessage = async () => {
const controller: AbortController = new AbortController()
const response = await chatApi.predict(currentRecord.predict_record_id, controller)
const reader = response.body.getReader()
const decoder = new TextDecoder()
const decoder = new TextDecoder('utf-8')
let predict_answer = ''
let predict_content = ''
let tempResult = ''
while (true) {
if (stopFlag.value) {
controller.abort()
@@ -117,72 +119,68 @@ const sendMessage = async () => {
break
}
const chunk = decoder.decode(value)
let _list = [chunk]
const lines = chunk.trim().split('}\n\n{')
if (lines.length > 1) {
_list = []
for (let line of lines) {
if (!line.trim().startsWith('{')) {
line = '{' + line.trim()
}
if (!line.trim().endsWith('}')) {
line = line.trim() + '}'
}
_list.push(line)
}
let chunk = decoder.decode(value, { stream: true })
tempResult += chunk
const split = tempResult.match(/data:.*}\n\n/g)
if (split) {
chunk = split.join('')
tempResult = tempResult.replace(chunk, '')
} else {
continue
}
for (const str of _list) {
let data
try {
data = JSON.parse(str)
} catch (err) {
console.error('JSON string:', str)
throw err
}
if (chunk && chunk.startsWith('data:{')) {
if (split) {
for (const str of split) {
let data
try {
data = JSON.parse(str.replace('data:{', '{'))
} catch (err) {
console.error('JSON string:', str)
throw err
}
if (data.code && data.code !== 200) {
ElMessage({
message: data.msg,
type: 'error',
showClose: true,
})
return
}
if (data.code && data.code !== 200) {
ElMessage({
message: data.msg,
type: 'error',
showClose: true,
})
return
}
switch (data.type) {
case 'id':
currentRecord.id = data.id
_currentChat.value.records[index.value].id = data.id
break
case 'info':
console.info(data.msg)
break
case 'error':
currentRecord.error = data.content
emits('error')
break
case 'predict-result':
predict_answer += data.reasoning_content
predict_content += data.content
_currentChat.value.records[index.value].predict = predict_answer
_currentChat.value.records[index.value].predict_content = predict_content
break
case 'predict-failed':
emits('error')
break
case 'predict-success':
//currentChat.value.records[_index].predict_data = data.content
getChatPredictData(_currentChat.value.records[index.value].id)
emits('finish', currentRecord.id)
break
case 'predict_finish':
_loading.value = false
break
switch (data.type) {
case 'id':
currentRecord.id = data.id
_currentChat.value.records[index.value].id = data.id
break
case 'info':
console.info(data.msg)
break
case 'error':
currentRecord.error = data.content
emits('error')
break
case 'predict-result':
predict_answer += data.reasoning_content
predict_content += data.content
_currentChat.value.records[index.value].predict = predict_answer
_currentChat.value.records[index.value].predict_content = predict_content
break
case 'predict-failed':
emits('error')
break
case 'predict-success':
//currentChat.value.records[_index].predict_data = data.content
getChatPredictData(_currentChat.value.records[index.value].id)
emits('finish', currentRecord.id)
break
case 'predict_finish':
_loading.value = false
break
}
await nextTick()
}
}
await nextTick()
}
}
} catch (error) {
@@ -37,9 +37,6 @@ export class Pie extends BaseG2Chart {
{
position: 'outside',
text: (data: any) => {
console.log(
`${data[series[0].value]}: ${data[y[0].value]}${_data.isPercent ? '%' : ''}`
)
return `${data[series[0].value]}: ${data[y[0].value]}${_data.isPercent ? '%' : ''}`
},
transform: [{ type: 'exceedAdjust' }, { type: 'overlapHide' }],