feat: add chat recommend questions

This commit is contained in:
ulleo
2025-07-08 18:13:28 +08:00
parent c8fc4762bd
commit 6b99a02734
11 changed files with 363 additions and 165 deletions
+37 -41
View File
@@ -1,13 +1,12 @@
import traceback
import orjson
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from apps.chat.curd.chat import list_chats, get_chat_with_records, create_chat, rename_chat, \
delete_chat
from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, ChatQuestion
from apps.chat.task.llm import LLMService, run_task
from apps.chat.task.llm import LLMService, run_task, run_analysis_or_predict_task, run_recommend_questions_task
from common.core.deps import SessionDep, CurrentUser
router = APIRouter(tags=["Data Q&A"], prefix="/chat")
@@ -62,6 +61,26 @@ async def start_chat(session: SessionDep, current_user: CurrentUser, create_chat
)
@router.get("/recommend_questions/{chat_record_id}")
async def recommend_questions(session: SessionDep, current_user: CurrentUser, chat_record_id: int):
try:
record = session.query(ChatRecord).get(chat_record_id)
if not record:
raise HTTPException(
status_code=400,
detail=f"Chat record with id {chat_record_id} not found"
)
request_question = ChatQuestion(chat_id=record.chat_id, question=record.question if record.question else '')
llm_service = LLMService(session, current_user, request_question)
llm_service.set_record(record)
return run_recommend_questions_task(llm_service)
except Exception as e:
traceback.print_exc()
return '[]'
@router.post("/question")
async def stream_sql(session: SessionDep, current_user: CurrentUser, request_question: ChatQuestion):
"""Stream SQL analysis results
@@ -88,61 +107,38 @@ async def stream_sql(session: SessionDep, current_user: CurrentUser, request_que
return StreamingResponse(run_task(llm_service, session), media_type="text/event-stream")
@router.post("/record/{chart_record_id}/{action_type}")
async def analysis_or_predict(session: SessionDep, current_user: CurrentUser, chart_record_id: int, action_type: str):
@router.post("/record/{chat_record_id}/{action_type}")
async def analysis_or_predict(session: SessionDep, current_user: CurrentUser, chat_record_id: int, action_type: str):
if action_type != 'analysis' and action_type != 'predict':
raise HTTPException(
status_code=404,
detail="Not Found"
)
record = session.query(ChatRecord).get(chart_record_id)
record = session.query(ChatRecord).get(chat_record_id)
if not record:
raise HTTPException(
status_code=400,
detail=f"Chat record with id {chart_record_id} not found"
detail=f"Chat record with id {chat_record_id} not found"
)
if not record.chart:
raise HTTPException(
status_code=500,
detail=f"Chat record with id {chart_record_id} has not generated chart, do not support to analyze it"
detail=f"Chat record with id {chat_record_id} has not generated chart, do not support to analyze it"
)
request_question = ChatQuestion(chat_id=record.chat_id, question='')
llm_service = LLMService(session, current_user, request_question)
llm_service.set_record(record)
try:
llm_service = LLMService(session, current_user, request_question)
llm_service.set_record(record)
except Exception as e:
traceback.print_exc()
raise HTTPException(
status_code=500,
detail=str(e)
)
def run_task():
try:
if action_type == 'analysis':
# generate analysis
analysis_res = llm_service.generate_analysis(session=session)
for chunk in analysis_res:
yield orjson.dumps({'content': chunk, 'type': 'analysis-result'}).decode() + '\n\n'
yield orjson.dumps({'type': 'info', 'msg': 'analysis generated'}).decode() + '\n\n'
yield orjson.dumps({'type': 'analysis_finish'}).decode() + '\n\n'
elif action_type == 'predict':
# generate predict
analysis_res = llm_service.generate_predict()
full_text = ''
for chunk in analysis_res:
yield orjson.dumps({'content': chunk, 'type': 'predict-result'}).decode() + '\n\n'
full_text += chunk
yield orjson.dumps({'type': 'info', 'msg': 'predict generated'}).decode() + '\n\n'
_data = llm_service.check_save_predict_data(res=full_text)
yield orjson.dumps({'type': 'predict', 'content': _data}).decode() + '\n\n'
yield orjson.dumps({'type': 'predict_finish'}).decode() + '\n\n'
except Exception as e:
traceback.print_exc()
# llm_service.save_error(session=session, message=str(e))
yield orjson.dumps({'content': str(e), 'type': 'error'}).decode() + '\n\n'
return StreamingResponse(run_task(), media_type="text/event-stream")
return StreamingResponse(run_analysis_or_predict_task(llm_service, action_type),
media_type="text/event-stream")
+41 -3
View File
@@ -1,12 +1,13 @@
import datetime
from typing import List
from sqlalchemy import and_
from sqlalchemy import and_, distinct
from sqlalchemy.orm import load_only
from apps.chat.models.chat_model import Chat, ChatRecord, CreateChat, ChatInfo, RenameChat, ChatQuestion
from apps.datasource.models.datasource import CoreDatasource
from common.core.deps import SessionDep, CurrentUser
from common.utils.utils import extract_nested_json
def list_chats(session: SessionDep, current_user: CurrentUser) -> List[Chat]:
@@ -130,8 +131,6 @@ def create_chat(session: SessionDep, current_user: CurrentUser, create_chat_obj:
_record.id = record.id
session.commit()
# todo suggest questions
chat_info.records.append(_record)
return chat_info
@@ -252,6 +251,36 @@ def save_full_select_datasource_message_and_answer(session: SessionDep, record_i
return result
def save_full_recommend_question_message_and_answer(session: SessionDep, record_id: int, answer: str,
full_message: str) -> ChatRecord:
if not record_id:
raise Exception("Record id cannot be None")
record = session.query(ChatRecord).filter(ChatRecord.id == record_id).first()
record.full_recommended_question_message = full_message
record.recommended_question_answer = answer
json_str = '[]'
if answer and answer != '':
try:
json_str = extract_nested_json(answer)
if not json_str:
json_str = '[]'
except Exception as e:
pass
record.recommended_question = json_str
result = ChatRecord(**record.model_dump())
session.add(record)
session.flush()
session.refresh(record)
session.commit()
return result
def save_sql(session: SessionDep, record_id: int, sql: str) -> ChatRecord:
if not record_id:
raise Exception("Record id cannot be None")
@@ -379,3 +408,12 @@ def finish_record(session: SessionDep, record_id: int) -> ChatRecord:
session.commit()
return result
def get_old_questions(session: SessionDep, datasource: int):
if not datasource:
return []
records = session.query(ChatRecord.question, ChatRecord.create_time).filter(ChatRecord.datasource == datasource,
ChatRecord.question != None).order_by(
ChatRecord.create_time.desc()).limit(20).all()
return records
+2 -2
View File
@@ -130,10 +130,10 @@ class AiModelQuestion(BaseModel):
def datasource_user_question(self, datasource_list: str = "[]"):
return get_datasource_template()['user'].format(question=self.question, data=datasource_list, lang=self.lang)
def datasource_guess_sys_question(self):
def guess_sys_question(self):
return get_guess_question_template()['system']
def datasource_guess_user_question(self, old_questions: str = "[]"):
def guess_user_question(self, old_questions: str = "[]"):
return get_guess_question_template()['user'].format(question=self.question, schema=self.db_schema,
old_questions=old_questions, lang=self.lang)
+83 -33
View File
@@ -17,13 +17,15 @@ from apps.ai_model.model_factory import LLMConfig, LLMFactory, get_default_confi
from apps.chat.curd.chat import save_question, save_full_sql_message, save_full_sql_message_and_answer, save_sql, \
save_error_message, save_sql_exec_data, save_full_chart_message, save_full_chart_message_and_answer, save_chart, \
finish_record, save_full_analysis_message_and_answer, save_full_predict_message_and_answer, save_predict_data, \
save_full_select_datasource_message_and_answer, list_records
save_full_select_datasource_message_and_answer, list_records, save_full_recommend_question_message_and_answer, \
get_old_questions
from apps.chat.models.chat_model import ChatQuestion, ChatRecord, Chat
from apps.datasource.crud.datasource import get_table_schema
from apps.datasource.models.datasource import CoreDatasource
from apps.db.db import exec_sql
from common.core.config import settings
from common.core.deps import SessionDep, CurrentUser
from common.utils.utils import extract_nested_json
warnings.filterwarnings("ignore")
@@ -59,7 +61,6 @@ class LLMService:
chat_question.engine = ds.type_name if ds.type != 'excel' else 'PostgreSQL'
history_records: List[ChatRecord] = list(
map(lambda x: ChatRecord(**x.model_dump()), filter(lambda r: True if r.first_chat != True else False,
list_records(session=self.session,
@@ -75,7 +76,6 @@ class LLMService:
self.chat_question = chat_question
self.config = get_default_config()
self.chat_question.ai_modal_id = self.config.model_id
# Create LLM instance through factory
llm_instance = LLMFactory.create_llm(self.config)
@@ -176,7 +176,7 @@ class LLMService:
fields.append(column_str)
return fields
def generate_analysis(self, session: SessionDep):
def generate_analysis(self):
fields = self.get_fields_from_chart()
self.chat_question.fields = orjson.dumps(fields).decode()
@@ -189,7 +189,7 @@ class LLMService:
if self.record.full_analysis_message and self.record.full_analysis_message.strip() != '':
history_msg = orjson.loads(self.record.full_analysis_message)
self.record = save_full_analysis_message_and_answer(session=session, record_id=self.record.id, answer='',
self.record = save_full_analysis_message_and_answer(session=self.session, record_id=self.record.id, answer='',
full_message=orjson.dumps(history_msg +
[{'type': msg.type,
'content': msg.content} for msg
@@ -210,7 +210,7 @@ class LLMService:
continue
analysis_msg.append(AIMessage(full_analysis_text))
self.record = save_full_analysis_message_and_answer(session=session, record_id=self.record.id,
self.record = save_full_analysis_message_and_answer(session=self.session, record_id=self.record.id,
answer=full_analysis_text,
full_message=orjson.dumps(history_msg +
[{'type': msg.type,
@@ -261,6 +261,47 @@ class LLMService:
in
predict_msg]).decode())
def generate_recommend_questions_task(self):
# get schema
if self.ds and not self.chat_question.db_schema:
self.chat_question.db_schema = get_table_schema(session=self.session, ds=self.ds)
guess_msg: List[Union[BaseMessage, dict[str, Any]]] = []
guess_msg.append(SystemMessage(content=self.chat_question.guess_sys_question()))
# todo old questions
old_questions = list(map(lambda q: q[0].strip(), get_old_questions(self.session, self.record.datasource)))
guess_msg.append(HumanMessage(content=self.chat_question.guess_user_question(orjson.dumps(old_questions).decode())))
self.record = save_full_recommend_question_message_and_answer(session=self.session, record_id=self.record.id,
answer='',
full_message=orjson.dumps([{'type': msg.type,
'content': msg.content}
for msg
in
guess_msg]).decode())
full_guess_text = ''
res = self.llm.stream(guess_msg)
for chunk in res:
print(chunk)
if isinstance(chunk, dict):
full_guess_text += chunk['content']
continue
if isinstance(chunk, AIMessageChunk):
full_guess_text += chunk.content
continue
guess_msg.append(AIMessage(full_guess_text))
self.record = save_full_recommend_question_message_and_answer(session=self.session, record_id=self.record.id,
answer=full_guess_text,
full_message=orjson.dumps([{'type': msg.type,
'content': msg.content}
for msg
in
guess_msg]).decode())
return self.record.recommended_question
def select_datasource(self):
datasource_msg: List[Union[BaseMessage, dict[str, Any]]] = []
datasource_msg.append(SystemMessage(self.chat_question.datasource_sys_question()))
@@ -486,33 +527,6 @@ class LLMService:
return exec_sql(self.ds, sql)
def extract_nested_json(text):
stack = []
start_index = -1
results = []
for i, char in enumerate(text):
if char in '{[':
if not stack: # 记录起始位置
start_index = i
stack.append(char)
elif char in '}]':
if stack and ((char == '}' and stack[-1] == '{') or (char == ']' and stack[-1] == '[')):
stack.pop()
if not stack: # 栈空时截取完整JSON
json_str = text[start_index:i + 1]
try:
orjson.loads(json_str) # 验证有效性
results.append(json_str)
except:
pass
else:
stack = [] # 括号不匹配则重置
if len(results) > 0 and results[0]:
return results[0]
return None
def execute_sql_with_db(db: SQLDatabase, sql: str) -> str:
"""Execute SQL query using SQLDatabase
@@ -647,6 +661,42 @@ def run_task(llm_service: LLMService, session: SessionDep, in_chat: bool = True)
yield f'> ❌ **ERROR**\n\n> \n\n> {str(e)}。'
def run_analysis_or_predict_task(llm_service: LLMService, action_type: str):
try:
if action_type == 'analysis':
# generate analysis
analysis_res = llm_service.generate_analysis()
for chunk in analysis_res:
yield orjson.dumps({'content': chunk, 'type': 'analysis-result'}).decode() + '\n\n'
yield orjson.dumps({'type': 'info', 'msg': 'analysis generated'}).decode() + '\n\n'
yield orjson.dumps({'type': 'analysis_finish'}).decode() + '\n\n'
elif action_type == 'predict':
# generate predict
analysis_res = llm_service.generate_predict()
full_text = ''
for chunk in analysis_res:
yield orjson.dumps({'content': chunk, 'type': 'predict-result'}).decode() + '\n\n'
full_text += chunk
yield orjson.dumps({'type': 'info', 'msg': 'predict generated'}).decode() + '\n\n'
_data = llm_service.check_save_predict_data(res=full_text)
yield orjson.dumps({'type': 'predict', 'content': _data}).decode() + '\n\n'
yield orjson.dumps({'type': 'predict_finish'}).decode() + '\n\n'
except Exception as e:
traceback.print_exc()
# llm_service.save_error(session=session, message=str(e))
yield orjson.dumps({'content': str(e), 'type': 'error'}).decode() + '\n\n'
def run_recommend_questions_task(llm_service: LLMService):
return llm_service.generate_recommend_questions_task()
def request_picture(chat_id: int, record_id: int, chart: dict, data: dict):
file_name = f'c_{chat_id}_r_{record_id}'
+29 -6
View File
@@ -1,12 +1,8 @@
import logging
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
import jwt
import orjson
from jwt.exceptions import InvalidTokenError
from common.core import security
@@ -46,4 +42,31 @@ def deepcopy_ignore_extra(src, dest):
src_value = getattr(src, attr)
dest_value = copy.deepcopy(src_value) # deep copy
setattr(dest, attr, dest_value)
return dest
return dest
def extract_nested_json(text):
stack = []
start_index = -1
results = []
for i, char in enumerate(text):
if char in '{[':
if not stack: # 记录起始位置
start_index = i
stack.append(char)
elif char in '}]':
if stack and ((char == '}' and stack[-1] == '{') or (char == ']' and stack[-1] == '[')):
stack.pop()
if not stack: # 栈空时截取完整JSON
json_str = text[start_index:i + 1]
try:
orjson.loads(json_str) # 验证有效性
results.append(json_str)
except:
pass
else:
stack = [] # 括号不匹配则重置
if len(results) > 0 and results[0]:
return results[0]
return None
+6 -2
View File
@@ -25,7 +25,7 @@ template:
### 请遵守以下输出要求:
- 请逐步推理后给出答案
- 推理过程中不需要输出JSON,仅在最终结果内输出符合要求的JSON
- 推理过程的步骤中不需要输出任何JSON,仅在最终结果内输出符合要求的JSON
步骤1: [思考内容]
步骤2: [思考内容]
……
@@ -75,7 +75,7 @@ template:
### 请遵守以下输出要求:
- 请逐步推理后给出答案
- 推理过程中不需要输出JSON,仅在最终结果内输出符合要求的JSON
- 推理过程的步骤中不需要输出任何JSON,仅在最终结果内输出符合要求的JSON
步骤1: [思考内容]
步骤2: [思考内容]
……
@@ -96,9 +96,13 @@ template:
### 说明:
您的任务是根据给定的表结构,用户问题以及以往用户提问,推测用户接下来可能提问的1-4个问题。
请遵循以下规则:
- 推测的问题需要与提供的表结构相关,生成的提问例子如:["查询所有用户数据","使用饼图展示各产品类型的占比","使用折线图展示销售额趋势",...]
- 推测问题如果涉及图形展示,支持的图形类型为:表格(table)、柱状图(column)、条形图(bar)、折线图(line)或饼图(pie)
- 推测的问题不能与当前用户问题重复
- 推测的问题必须与给出的表结构相关
- 若有以往用户提问列表,则根据以往用户提问列表,推测用户最频繁提问的问题,加入到你生成的推测问题中
- 忽略“重新生成”想关的问题
- 如果用户没有提问且没有以往用户提问,则仅根据提供的表结构推测问题
- 生成的推测问题使用JSON格式返回:
["推测问题1", "推测问题2", "推测问题3", "推测问题4"]
- 最多返回4个你推测出的结果
+62 -73
View File
@@ -23,6 +23,7 @@ export interface ChatMessage {
record?: ChatRecord
isTyping?: boolean
first_chat?: boolean
recommended_question?: string
}
export class ChatRecord {
@@ -43,6 +44,7 @@ export class ChatRecord {
error?: string
run_time: number = 0
first_chat: boolean = false
recommended_question?: string
constructor()
constructor(
@@ -62,7 +64,8 @@ export class ChatRecord {
finish: boolean,
error: string | undefined,
run_time: number,
first_chat: boolean
first_chat: boolean,
recommended_question: string | undefined
)
constructor(
id?: number,
@@ -81,7 +84,8 @@ export class ChatRecord {
finish?: boolean,
error?: string,
run_time?: number,
first_chat?: boolean
first_chat?: boolean,
recommended_question?: string
) {
this.id = id
this.chat_id = chat_id
@@ -100,6 +104,7 @@ export class ChatRecord {
this.error = error
this.run_time = run_time ?? 0
this.first_chat = !!first_chat
this.recommended_question = recommended_question
}
}
@@ -198,7 +203,7 @@ export class ChatInfo extends Chat {
}
}
function toChatRecord(data?: any): ChatRecord | undefined {
const toChatRecord = (data?: any): ChatRecord | undefined => {
if (!data) {
return undefined
}
@@ -219,11 +224,11 @@ function toChatRecord(data?: any): ChatRecord | undefined {
data.finish,
data.error,
data.run_time,
data.first_chat
data.first_chat,
data.recommended_question
)
}
function toChatRecordList(list: any = []): ChatRecord[] {
const toChatRecordList = (list: any = []): ChatRecord[] => {
const records: Array<ChatRecord> = []
for (let i = 0; i < list.length; i++) {
const record = toChatRecord(list[i])
@@ -234,72 +239,56 @@ function toChatRecordList(list: any = []): ChatRecord[] {
return records
}
function toChatInfo(data?: any): ChatInfo | undefined {
if (!data) {
return undefined
}
return new ChatInfo(
data.id,
data.create_time,
data.create_by,
data.brief,
data.chat_type,
data.datasource,
data.engine_type,
data.datasource_name,
data.datasource_exists,
toChatRecordList(data.records)
)
}
function toChatInfoList(list: any[] = []): ChatInfo[] {
const infos: Array<ChatInfo> = []
for (let i = 0; i < list.length; i++) {
const chatInfo = toChatInfo(list[i])
if (chatInfo) {
infos.push(chatInfo)
}
}
return infos
}
function list(): Promise<Array<ChatInfo>> {
return request.get('/chat/list')
}
function get(id: number): Promise<ChatInfo> {
return request.get(`/chat/get/${id}`)
}
function startChat(data: any): Promise<ChatInfo> {
return request.post('/chat/start', data)
}
function renameChat(chat_id: number | undefined, brief: string): Promise<string> {
return request.post('/chat/rename', { id: chat_id, brief: brief })
}
function deleteChat(id: number | undefined): Promise<string> {
return request.get(`/chat/delete/${id}`)
}
function analysis(record_id: number | undefined) {
return request.fetchStream(`/chat/record/${record_id}/analysis`, {})
}
function predict(record_id: number | undefined) {
return request.fetchStream(`/chat/record/${record_id}/predict`, {})
}
export const chatApi = {
toChatRecord,
toChatRecordList,
toChatInfo,
toChatInfoList,
list,
get,
startChat,
renameChat,
deleteChat,
analysis,
predict,
toChatInfo: (data?: any): ChatInfo | undefined => {
if (!data) {
return undefined
}
return new ChatInfo(
data.id,
data.create_time,
data.create_by,
data.brief,
data.chat_type,
data.datasource,
data.engine_type,
data.datasource_name,
data.datasource_exists,
toChatRecordList(data.records)
)
},
toChatInfoList: (list: any[] = []): ChatInfo[] => {
const infos: Array<ChatInfo> = []
for (let i = 0; i < list.length; i++) {
const chatInfo = chatApi.toChatInfo(list[i])
if (chatInfo) {
infos.push(chatInfo)
}
}
return infos
},
list: (): Promise<Array<ChatInfo>> => {
return request.get('/chat/list')
},
get: (id: number): Promise<ChatInfo> => {
return request.get(`/chat/get/${id}`)
},
startChat: (data: any): Promise<ChatInfo> => {
return request.post('/chat/start', data)
},
renameChat: (chat_id: number | undefined, brief: string): Promise<string> => {
return request.post('/chat/rename', { id: chat_id, brief: brief })
},
deleteChat: (id: number | undefined): Promise<string> => {
return request.get(`/chat/delete/${id}`)
},
analysis: (record_id: number | undefined) => {
return request.fetchStream(`/chat/record/${record_id}/analysis`, {})
},
predict: (record_id: number | undefined) => {
return request.fetchStream(`/chat/record/${record_id}/predict`, {})
},
recommendQuestions: (record_id: number | undefined) => {
return request.get(`/chat/recommend_questions/${record_id}`)
},
}
@@ -25,6 +25,9 @@ const show = computed(() => {
{{ currentChat.datasource_name }}
</div>
</div>
<template #footer>
<slot name="footer"></slot>
</template>
</ChatBlock>
</template>
+5 -1
View File
@@ -27,7 +27,11 @@ withDefaults(
<slot name="footer"></slot>
</template>
</ChatBlock>
<ChatRecordFirst v-else :current-chat="currentChat" :msg="msg" />
<ChatRecordFirst v-else :current-chat="currentChat" :msg="msg">
<template #footer>
<slot name="footer"></slot>
</template>
</ChatRecordFirst>
</div>
</template>
@@ -0,0 +1,62 @@
<script setup lang="ts">
import { computed } from 'vue'
import { endsWith, startsWith } from 'lodash-es'
const props = withDefaults(
defineProps<{
questions?: string
}>(),
{
questions: '[]',
}
)
const computedQuestions = computed<string>(() => {
if (
props.questions &&
props.questions.length > 0 &&
startsWith(props.questions.trim(), '[') &&
endsWith(props.questions.trim(), ']')
) {
return JSON.parse(props.questions)
}
return []
})
const emits = defineEmits(['clickQuestion'])
function clickQuestion(question: string): void {
emits('clickQuestion', question)
}
</script>
<template>
<div v-if="computedQuestions.length > 0" class="recommend-questions">
<div>推荐提问:</div>
<div v-for="(question, index) in computedQuestions" :key="index">
<span class="question" @click="clickQuestion(question)">
{{ question }}
</span>
</div>
</div>
</template>
<style scoped lang="less">
.recommend-questions {
padding: 8px;
font-size: 12px;
font-weight: 400;
display: flex;
flex-direction: column;
gap: 4px;
.question {
cursor: pointer;
background: lightgray;
border-radius: 4px;
padding: 4px;
line-height: 20px;
&:hover {
background: grey;
}
}
}
</style>
+33 -4
View File
@@ -91,7 +91,10 @@
</ChatAnswer>
</template>
<template v-if="message.role === 'assistant'" #footer>
<!--<div>Suggestion</div>-->
<RecommendQuestion
:questions="message.recommended_question"
@click-question="quickAsk"
/>
</template>
</ChatRow>
</template>
@@ -143,9 +146,10 @@ import ChatRow from './ChatRow.vue'
import ChatAnswer from './ChatAnswer.vue'
import MdComponent from './component/MdComponent.vue'
import PredictChartBlock from './component/PredictChartBlock.vue'
import RecommendQuestion from './RecommendQuestion.vue'
import ChatCreator from './ChatCreator.vue'
import { useI18n } from 'vue-i18n'
import { find } from 'lodash-es'
import { endsWith, find, startsWith } from 'lodash-es'
const { t } = useI18n()
@@ -192,11 +196,10 @@ const computedMessages = computed<Array<ChatMessage>>(() => {
record: record,
isTyping: i === currentChat.value.records.length - 1 && isTyping.value,
first_chat: record.first_chat,
recommended_question: record.recommended_question,
})
}
console.log(messages)
return messages
})
@@ -271,12 +274,36 @@ function onChatCreated(chat: ChatInfo) {
chatList.value.unshift(chat)
currentChatId.value = chat.id
currentChat.value = chat
if (chat.records.length === 1) {
getRecommendQuestions(chat.records[0].id)
}
}
function getRecommendQuestions(record_id?: number) {
chatApi.recommendQuestions(record_id).then((res) => {
if (res && res.length > 0 && startsWith(res.trim(), '[') && endsWith(res.trim(), ']')) {
if (currentChat.value?.records) {
for (let record of currentChat.value.records) {
if (record.id === record_id) {
record.recommended_question = res
}
}
}
}
})
}
onMounted(() => {
getChatList()
})
function quickAsk(question: string) {
inputMessage.value = question
nextTick(() => {
sendMessage()
})
}
const sendMessage = async () => {
if (!inputMessage.value.trim()) return
@@ -360,6 +387,7 @@ const sendMessage = async () => {
switch (data.type) {
case 'id':
currentRecord.id = data.id
currentChat.value.records[currentChat.value.records.length - 1].id = data.id
break
case 'info':
@@ -389,6 +417,7 @@ const sendMessage = async () => {
break
case 'finish':
isTyping.value = false
getRecommendQuestions(currentRecord.id)
break
}
await nextTick()