From 6b99a02734299a73d91dcc33465e94e19dd199b8 Mon Sep 17 00:00:00 2001 From: ulleo Date: Tue, 8 Jul 2025 18:13:28 +0800 Subject: [PATCH] feat: add chat recommend questions --- backend/apps/chat/api/chat.py | 78 +++++----- backend/apps/chat/curd/chat.py | 44 +++++- backend/apps/chat/models/chat_model.py | 4 +- backend/apps/chat/task/llm.py | 116 ++++++++++----- backend/common/utils/utils.py | 35 ++++- backend/template.yaml | 8 +- frontend/src/api/chat.ts | 135 ++++++++---------- frontend/src/views/chat/ChatRecordFirst.vue | 3 + frontend/src/views/chat/ChatRow.vue | 6 +- frontend/src/views/chat/RecommendQuestion.vue | 62 ++++++++ frontend/src/views/chat/index.vue | 37 ++++- 11 files changed, 363 insertions(+), 165 deletions(-) create mode 100644 frontend/src/views/chat/RecommendQuestion.vue diff --git a/backend/apps/chat/api/chat.py b/backend/apps/chat/api/chat.py index 32f338a7..18a2f342 100644 --- a/backend/apps/chat/api/chat.py +++ b/backend/apps/chat/api/chat.py @@ -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") diff --git a/backend/apps/chat/curd/chat.py b/backend/apps/chat/curd/chat.py index bf204645..409a8529 100644 --- a/backend/apps/chat/curd/chat.py +++ b/backend/apps/chat/curd/chat.py @@ -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 diff --git a/backend/apps/chat/models/chat_model.py b/backend/apps/chat/models/chat_model.py index 66bb37a6..2de01ba4 100644 --- a/backend/apps/chat/models/chat_model.py +++ b/backend/apps/chat/models/chat_model.py @@ -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) diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index 5b4517da..78a84850 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -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}' diff --git a/backend/common/utils/utils.py b/backend/common/utils/utils.py index d168decd..e069b9de 100644 --- a/backend/common/utils/utils.py +++ b/backend/common/utils/utils.py @@ -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 \ No newline at end of file + 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 diff --git a/backend/template.yaml b/backend/template.yaml index f3d0b7ba..f78677fd 100644 --- a/backend/template.yaml +++ b/backend/template.yaml @@ -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个你推测出的结果 diff --git a/frontend/src/api/chat.ts b/frontend/src/api/chat.ts index 3b9c7e31..aee7c42f 100644 --- a/frontend/src/api/chat.ts +++ b/frontend/src/api/chat.ts @@ -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 = [] 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 = [] - for (let i = 0; i < list.length; i++) { - const chatInfo = toChatInfo(list[i]) - if (chatInfo) { - infos.push(chatInfo) - } - } - return infos -} - -function list(): Promise> { - return request.get('/chat/list') -} - -function get(id: number): Promise { - return request.get(`/chat/get/${id}`) -} - -function startChat(data: any): Promise { - return request.post('/chat/start', data) -} - -function renameChat(chat_id: number | undefined, brief: string): Promise { - return request.post('/chat/rename', { id: chat_id, brief: brief }) -} - -function deleteChat(id: number | undefined): Promise { - 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 = [] + for (let i = 0; i < list.length; i++) { + const chatInfo = chatApi.toChatInfo(list[i]) + if (chatInfo) { + infos.push(chatInfo) + } + } + return infos + }, + list: (): Promise> => { + return request.get('/chat/list') + }, + get: (id: number): Promise => { + return request.get(`/chat/get/${id}`) + }, + startChat: (data: any): Promise => { + return request.post('/chat/start', data) + }, + renameChat: (chat_id: number | undefined, brief: string): Promise => { + return request.post('/chat/rename', { id: chat_id, brief: brief }) + }, + deleteChat: (id: number | undefined): Promise => { + 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}`) + }, } diff --git a/frontend/src/views/chat/ChatRecordFirst.vue b/frontend/src/views/chat/ChatRecordFirst.vue index 8293a36d..84839537 100644 --- a/frontend/src/views/chat/ChatRecordFirst.vue +++ b/frontend/src/views/chat/ChatRecordFirst.vue @@ -25,6 +25,9 @@ const show = computed(() => { {{ currentChat.datasource_name }} + diff --git a/frontend/src/views/chat/ChatRow.vue b/frontend/src/views/chat/ChatRow.vue index cdfa8636..5ae2b87a 100644 --- a/frontend/src/views/chat/ChatRow.vue +++ b/frontend/src/views/chat/ChatRow.vue @@ -27,7 +27,11 @@ withDefaults( - + + + diff --git a/frontend/src/views/chat/RecommendQuestion.vue b/frontend/src/views/chat/RecommendQuestion.vue new file mode 100644 index 00000000..9f3345cf --- /dev/null +++ b/frontend/src/views/chat/RecommendQuestion.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/frontend/src/views/chat/index.vue b/frontend/src/views/chat/index.vue index 78669869..acbd890f 100644 --- a/frontend/src/views/chat/index.vue +++ b/frontend/src/views/chat/index.vue @@ -91,7 +91,10 @@ @@ -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>(() => { 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()