mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
feat: improve regenerate SQL chat feature
This commit is contained in:
@@ -24,7 +24,7 @@ config = context.config
|
||||
|
||||
# from apps.system.models.user import SQLModel # noqa
|
||||
# from apps.settings.models.setting_models import SQLModel
|
||||
# from apps.chat.models.chat_model import SQLModel
|
||||
from apps.chat.models.chat_model import SQLModel
|
||||
from apps.terminology.models.terminology_model import SQLModel
|
||||
#from apps.custom_prompt.models.custom_prompt_model import SQLModel
|
||||
from apps.data_training.models.data_training_model import SQLModel
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
"""054_update_chat_record_dll
|
||||
|
||||
Revision ID: 24e961f6326b
|
||||
Revises: 5755c0b95839
|
||||
Create Date: 2025-12-04 15:51:42.900778
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel.sql.sqltypes
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '24e961f6326b'
|
||||
down_revision = '5755c0b95839'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('chat_record', sa.Column('regenerate_record_id', sa.BigInteger(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('chat_record', 'regenerate_record_id')
|
||||
# ### end Alembic commands ###
|
||||
@@ -12,9 +12,10 @@ from sqlalchemy import and_, select
|
||||
from apps.chat.curd.chat import list_chats, get_chat_with_records, create_chat, rename_chat, \
|
||||
delete_chat, get_chat_chart_data, get_chat_predict_data, get_chat_with_records_with_data, get_chat_record_by_id, \
|
||||
format_json_data, format_json_list_data, get_chart_config, list_recent_questions
|
||||
from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, ChatQuestion, AxisObj
|
||||
from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, ChatQuestion, AxisObj, QuickCommand
|
||||
from apps.chat.task.llm import LLMService
|
||||
from common.core.deps import CurrentAssistant, SessionDep, CurrentUser, Trans
|
||||
from common.utils.command_utils import parse_quick_command
|
||||
from common.utils.data_format import DataFormat
|
||||
|
||||
router = APIRouter(tags=["Data Q&A"], prefix="/chat")
|
||||
@@ -141,20 +142,99 @@ async def recommend_questions(session: SessionDep, current_user: CurrentUser, da
|
||||
return list_recent_questions(session=session, current_user=current_user, datasource_id=datasource_id)
|
||||
|
||||
|
||||
def find_base_question(record_id: int, session: SessionDep):
|
||||
stmt = select(ChatRecord.question, ChatRecord.regenerate_record_id).where(
|
||||
and_(ChatRecord.id == record_id))
|
||||
_record = session.execute(stmt).fetchone()
|
||||
if not _record:
|
||||
raise Exception(f'Cannot find base chat record')
|
||||
rec_question, rec_regenerate_record_id = _record
|
||||
if rec_regenerate_record_id:
|
||||
return find_base_question(rec_regenerate_record_id, session)
|
||||
else:
|
||||
return rec_question
|
||||
|
||||
|
||||
@router.post("/question")
|
||||
async def question_answer(session: SessionDep, current_user: CurrentUser, request_question: ChatQuestion,
|
||||
current_assistant: CurrentAssistant):
|
||||
try:
|
||||
command, text_before_command, record_id, warning_info = parse_quick_command(request_question.question)
|
||||
if command:
|
||||
# todo 暂不支持分析和预测,需要改造前端
|
||||
if command == QuickCommand.ANALYSIS or command == QuickCommand.PREDICT_DATA:
|
||||
raise Exception(f'Command: {command.value} temporary not supported')
|
||||
|
||||
if record_id is not None:
|
||||
# 排除analysis和predict
|
||||
stmt = select(ChatRecord.id, ChatRecord.chat_id, ChatRecord.analysis_record_id,
|
||||
ChatRecord.predict_record_id, ChatRecord.regenerate_record_id,
|
||||
ChatRecord.first_chat).where(
|
||||
and_(ChatRecord.id == record_id))
|
||||
_record = session.execute(stmt).fetchone()
|
||||
if not _record:
|
||||
raise Exception(f'Record id: {record_id} does not exist')
|
||||
|
||||
rec_id, rec_chat_id, rec_analysis_record_id, rec_predict_record_id, rec_regenerate_record_id, rec_first_chat = _record
|
||||
|
||||
if rec_chat_id != request_question.chat_id:
|
||||
raise Exception(f'Record id: {record_id} does not belong to this chat')
|
||||
if rec_first_chat:
|
||||
raise Exception(f'Record id: {record_id} does not support this operation')
|
||||
|
||||
if command == QuickCommand.REGENERATE:
|
||||
if rec_analysis_record_id:
|
||||
raise Exception('Analysis record does not support this operation')
|
||||
if rec_predict_record_id:
|
||||
raise Exception('Predict data record does not support this operation')
|
||||
|
||||
else: # get last record id
|
||||
stmt = select(ChatRecord.id, ChatRecord.chat_id, ChatRecord.regenerate_record_id).where(
|
||||
and_(ChatRecord.chat_id == request_question.chat_id,
|
||||
ChatRecord.first_chat == False,
|
||||
ChatRecord.analysis_record_id.is_(None),
|
||||
ChatRecord.predict_record_id.is_(None))).order_by(
|
||||
ChatRecord.create_time.desc()).limit(1)
|
||||
_record = session.execute(stmt).fetchone()
|
||||
|
||||
if not _record:
|
||||
raise Exception(f'You have not ask any question')
|
||||
|
||||
rec_id, rec_chat_id, rec_regenerate_record_id = _record
|
||||
|
||||
# 没有指定的,就查询上一个
|
||||
if not rec_regenerate_record_id:
|
||||
rec_regenerate_record_id = rec_id
|
||||
|
||||
# 针对已经是重新生成的提问,需要找到原来的提问是什么
|
||||
base_question_text = find_base_question(rec_regenerate_record_id, session)
|
||||
text_before_command = text_before_command + ("\n" if text_before_command else "") + base_question_text
|
||||
|
||||
if command == QuickCommand.REGENERATE:
|
||||
request_question.question = text_before_command
|
||||
request_question.regenerate_record_id = rec_id
|
||||
return await stream_sql(session, current_user, request_question, current_assistant)
|
||||
|
||||
elif command == QuickCommand.ANALYSIS:
|
||||
return await analysis_or_predict(session, current_user, rec_id, 'analysis', current_assistant)
|
||||
|
||||
elif command == QuickCommand.PREDICT_DATA:
|
||||
return await analysis_or_predict(session, current_user, rec_id, 'predict', current_assistant)
|
||||
else:
|
||||
raise Exception(f'Unknown command: {command.value}')
|
||||
else:
|
||||
return await stream_sql(session, current_user, request_question, current_assistant)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
|
||||
def _err(_e: Exception):
|
||||
yield 'data:' + orjson.dumps({'content': str(_e), 'type': 'error'}).decode() + '\n\n'
|
||||
|
||||
return StreamingResponse(_err(e), media_type="text/event-stream")
|
||||
|
||||
|
||||
async def stream_sql(session: SessionDep, current_user: CurrentUser, request_question: ChatQuestion,
|
||||
current_assistant: CurrentAssistant):
|
||||
"""Stream SQL analysis results
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
current_user: CurrentUser
|
||||
request_question: User question model
|
||||
|
||||
Returns:
|
||||
Streaming response with analysis results
|
||||
"""
|
||||
|
||||
try:
|
||||
llm_service = await LLMService.create(session, current_user, request_question, current_assistant,
|
||||
embedding=True)
|
||||
@@ -172,6 +252,12 @@ async def stream_sql(session: SessionDep, current_user: CurrentUser, request_que
|
||||
|
||||
|
||||
@router.post("/record/{chat_record_id}/{action_type}")
|
||||
async def analysis_or_predict_question(session: SessionDep, current_user: CurrentUser, chat_record_id: int,
|
||||
action_type: str,
|
||||
current_assistant: CurrentAssistant):
|
||||
return await analysis_or_predict(session, current_user, chat_record_id, action_type, current_assistant)
|
||||
|
||||
|
||||
async def analysis_or_predict(session: SessionDep, current_user: CurrentUser, chat_record_id: int, action_type: str,
|
||||
current_assistant: CurrentAssistant):
|
||||
try:
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import datetime
|
||||
from typing import List
|
||||
from sqlalchemy import desc, func
|
||||
|
||||
import orjson
|
||||
import sqlparse
|
||||
from sqlalchemy import and_, select, update
|
||||
from sqlalchemy import desc, func
|
||||
from sqlalchemy.orm import aliased
|
||||
|
||||
from apps.chat.models.chat_model import Chat, ChatRecord, CreateChat, ChatInfo, RenameChat, ChatQuestion, ChatLog, \
|
||||
TypeEnum, OperationEnum, ChatRecordResult
|
||||
from apps.datasource.crud.recommended_problem import get_datasource_recommended, get_datasource_recommended_chart
|
||||
from apps.datasource.models.datasource import CoreDatasource, DsRecommendedProblem
|
||||
from apps.datasource.crud.recommended_problem import get_datasource_recommended_chart
|
||||
from apps.datasource.models.datasource import CoreDatasource
|
||||
from apps.system.crud.assistant import AssistantOutDsFactory
|
||||
from common.core.deps import CurrentAssistant, SessionDep, CurrentUser, Trans
|
||||
from common.utils.utils import extract_nested_json
|
||||
@@ -28,11 +28,13 @@ def get_chat_record_by_id(session: SessionDep, record_id: int):
|
||||
engine_type=r.engine_type, ai_modal_id=r.ai_modal_id, create_by=r.create_by)
|
||||
return record
|
||||
|
||||
|
||||
def get_chat(session: SessionDep, chat_id: int) -> Chat:
|
||||
statement = select(Chat).where(Chat.id == chat_id)
|
||||
chat = session.exec(statement).scalars().first()
|
||||
return chat
|
||||
|
||||
|
||||
def list_chats(session: SessionDep, current_user: CurrentUser) -> List[Chat]:
|
||||
oid = current_user.oid if current_user.oid is not None else 1
|
||||
chart_list = session.query(Chat).filter(and_(Chat.create_by == current_user.id, Chat.oid == oid)).order_by(
|
||||
@@ -57,6 +59,7 @@ def list_recent_questions(session: SessionDep, current_user: CurrentUser, dataso
|
||||
)
|
||||
return [record[0] for record in chat_records] if chat_records else []
|
||||
|
||||
|
||||
def rename_chat(session: SessionDep, rename_object: RenameChat) -> str:
|
||||
chat = session.get(Chat, rename_object.id)
|
||||
if not chat:
|
||||
@@ -191,7 +194,8 @@ dynamic_ds_types = [1, 3]
|
||||
|
||||
|
||||
def get_chat_with_records(session: SessionDep, chart_id: int, current_user: CurrentUser,
|
||||
current_assistant: CurrentAssistant, with_data: bool = False,trans: Trans = None) -> ChatInfo:
|
||||
current_assistant: CurrentAssistant, with_data: bool = False,
|
||||
trans: Trans = None) -> ChatInfo:
|
||||
chat = session.get(Chat, chart_id)
|
||||
if not chat:
|
||||
raise Exception(f"Chat with id {chart_id} not found")
|
||||
@@ -200,7 +204,7 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
|
||||
|
||||
if current_assistant and current_assistant.type in dynamic_ds_types:
|
||||
out_ds_instance = AssistantOutDsFactory.get_instance(current_assistant)
|
||||
ds = out_ds_instance.get_ds(chat.datasource,trans)
|
||||
ds = out_ds_instance.get_ds(chat.datasource, trans)
|
||||
else:
|
||||
ds = session.get(CoreDatasource, chat.datasource) if chat.datasource else None
|
||||
|
||||
@@ -221,6 +225,7 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
|
||||
ChatRecord.question, ChatRecord.sql_answer, ChatRecord.sql,
|
||||
ChatRecord.chart_answer, ChatRecord.chart, ChatRecord.analysis, ChatRecord.predict,
|
||||
ChatRecord.datasource_select_answer, ChatRecord.analysis_record_id, ChatRecord.predict_record_id,
|
||||
ChatRecord.regenerate_record_id,
|
||||
ChatRecord.recommended_question, ChatRecord.first_chat,
|
||||
ChatRecord.finish, ChatRecord.error,
|
||||
sql_alias_log.reasoning_content.label('sql_reasoning_content'),
|
||||
@@ -247,6 +252,7 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
|
||||
ChatRecord.question, ChatRecord.sql_answer, ChatRecord.sql,
|
||||
ChatRecord.chart_answer, ChatRecord.chart, ChatRecord.analysis, ChatRecord.predict,
|
||||
ChatRecord.datasource_select_answer, ChatRecord.analysis_record_id, ChatRecord.predict_record_id,
|
||||
ChatRecord.regenerate_record_id,
|
||||
ChatRecord.recommended_question, ChatRecord.first_chat,
|
||||
ChatRecord.finish, ChatRecord.error, ChatRecord.data, ChatRecord.predict_data).where(
|
||||
and_(ChatRecord.create_by == current_user.id, ChatRecord.chat_id == chart_id)).order_by(
|
||||
@@ -264,6 +270,7 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
|
||||
analysis=row.analysis, predict=row.predict,
|
||||
datasource_select_answer=row.datasource_select_answer,
|
||||
analysis_record_id=row.analysis_record_id, predict_record_id=row.predict_record_id,
|
||||
regenerate_record_id=row.regenerate_record_id,
|
||||
recommended_question=row.recommended_question, first_chat=row.first_chat,
|
||||
finish=row.finish, error=row.error,
|
||||
sql_reasoning_content=row.sql_reasoning_content,
|
||||
@@ -280,6 +287,7 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
|
||||
analysis=row.analysis, predict=row.predict,
|
||||
datasource_select_answer=row.datasource_select_answer,
|
||||
analysis_record_id=row.analysis_record_id, predict_record_id=row.predict_record_id,
|
||||
regenerate_record_id=row.regenerate_record_id,
|
||||
recommended_question=row.recommended_question, first_chat=row.first_chat,
|
||||
finish=row.finish, error=row.error, data=row.data, predict_data=row.predict_data))
|
||||
|
||||
@@ -347,8 +355,9 @@ def format_record(record: ChatRecordResult):
|
||||
|
||||
return _dict
|
||||
|
||||
|
||||
def get_chat_brief_generate(session: SessionDep, chat_id: int):
|
||||
chat = get_chat(session=session,chat_id=chat_id)
|
||||
chat = get_chat(session=session, chat_id=chat_id)
|
||||
if chat is not None and chat.brief_generate is not None:
|
||||
return chat.brief_generate
|
||||
else:
|
||||
@@ -468,6 +477,7 @@ def save_question(session: SessionDep, current_user: CurrentUser, question: Chat
|
||||
record.datasource = chat.datasource
|
||||
record.engine_type = chat.engine_type
|
||||
record.ai_modal_id = question.ai_modal_id
|
||||
record.regenerate_record_id = question.regenerate_record_id
|
||||
|
||||
result = ChatRecord(**record.model_dump())
|
||||
|
||||
|
||||
@@ -48,6 +48,12 @@ class ChatFinishStep(Enum):
|
||||
GENERATE_CHART = 3
|
||||
|
||||
|
||||
class QuickCommand(Enum):
|
||||
REGENERATE = '/regenerate'
|
||||
ANALYSIS = '/analysis'
|
||||
PREDICT_DATA = '/predict'
|
||||
|
||||
|
||||
# TODO choose table / check connection / generate description
|
||||
|
||||
class ChatLog(SQLModel, table=True):
|
||||
@@ -78,7 +84,7 @@ class Chat(SQLModel, table=True):
|
||||
datasource: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
engine_type: str = Field(max_length=64)
|
||||
origin: Optional[int] = Field(
|
||||
sa_column=Column(Integer, nullable=False, default=0)) # 0: default, 1: mcp, 2: assistant
|
||||
sa_column=Column(Integer, nullable=False, default=0)) # 0: default, 1: mcp, 2: assistant
|
||||
brief_generate: bool = Field(default=False)
|
||||
|
||||
|
||||
@@ -110,6 +116,7 @@ class ChatRecord(SQLModel, table=True):
|
||||
error: str = Field(sa_column=Column(Text, nullable=True))
|
||||
analysis_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
predict_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
regenerate_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
|
||||
|
||||
class ChatRecordResult(BaseModel):
|
||||
@@ -134,6 +141,7 @@ class ChatRecordResult(BaseModel):
|
||||
error: Optional[str] = None
|
||||
analysis_record_id: Optional[int] = None
|
||||
predict_record_id: Optional[int] = None
|
||||
regenerate_record_id: Optional[int] = None
|
||||
sql_reasoning_content: Optional[str] = None
|
||||
chart_reasoning_content: Optional[str] = None
|
||||
analysis_reasoning_content: Optional[str] = None
|
||||
@@ -184,6 +192,7 @@ class AiModelQuestion(BaseModel):
|
||||
data_training: str = ""
|
||||
custom_prompt: str = ""
|
||||
error_msg: str = ""
|
||||
regenerate_record_id: Optional[int] = None
|
||||
|
||||
def sql_sys_question(self, db_type: Union[str, DB], enable_query_limit: bool = True):
|
||||
_sql_template = get_sql_example_template(db_type)
|
||||
@@ -213,7 +222,10 @@ class AiModelQuestion(BaseModel):
|
||||
example_answer_3=_example_answer_3)
|
||||
|
||||
def sql_user_question(self, current_time: str, change_title: bool):
|
||||
return get_sql_template()['user'].format(engine=self.engine, schema=self.db_schema, question=self.question,
|
||||
_question = self.question
|
||||
if self.regenerate_record_id:
|
||||
_question = get_sql_template()['regenerate_hint'] + self.question
|
||||
return get_sql_template()['user'].format(engine=self.engine, schema=self.db_schema, question=_question,
|
||||
rule=self.rule, current_time=current_time, error_msg=self.error_msg,
|
||||
change_title=change_title)
|
||||
|
||||
|
||||
@@ -167,6 +167,11 @@ class LLMService:
|
||||
def init_messages(self):
|
||||
last_sql_messages: List[dict[str, Any]] = self.generate_sql_logs[-1].messages if len(
|
||||
self.generate_sql_logs) > 0 else []
|
||||
if self.chat_question.regenerate_record_id:
|
||||
# filter record before regenerate_record_id
|
||||
_temp_log = next(
|
||||
filter(lambda obj: obj.pid == self.chat_question.regenerate_record_id, self.generate_sql_logs), None)
|
||||
last_sql_messages: List[dict[str, Any]] = _temp_log.messages if _temp_log else []
|
||||
|
||||
# todo maybe can configure
|
||||
count_limit = 0 - base_message_count_limit
|
||||
@@ -947,6 +952,11 @@ class LLMService:
|
||||
# return id
|
||||
if in_chat:
|
||||
yield 'data:' + orjson.dumps({'type': 'id', 'id': self.get_record().id}).decode() + '\n\n'
|
||||
if self.get_record().regenerate_record_id:
|
||||
yield 'data:' + orjson.dumps({'type': 'regenerate_record_id',
|
||||
'regenerate_record_id': self.get_record().regenerate_record_id}).decode() + '\n\n'
|
||||
yield 'data:' + orjson.dumps(
|
||||
{'type': 'question', 'question': self.get_record().question}).decode() + '\n\n'
|
||||
if not stream:
|
||||
json_result['record_id'] = self.get_record().id
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from apps.chat.models.chat_model import QuickCommand
|
||||
|
||||
|
||||
def parse_quick_command(input_str: str) -> Tuple[Optional[QuickCommand], str, Optional[int], Optional[str]]:
|
||||
"""
|
||||
解析字符串中的快速命令
|
||||
|
||||
Args:
|
||||
input_str: 输入字符串
|
||||
|
||||
Returns:
|
||||
Tuple[Optional[QuickCommand], str, Optional[int], Optional[str]]:
|
||||
(命令枚举, 去除命令的字符串, 数字参数, 警告信息)
|
||||
如果解析成功: (命令, 文本, 数字, None)
|
||||
如果解析失败: (None, 原字符串, None, 警告信息)
|
||||
"""
|
||||
|
||||
# 获取所有命令值
|
||||
command_values = [cmd.value for cmd in QuickCommand]
|
||||
|
||||
# 1. 检查字符串中是否包含任何命令
|
||||
found_commands = []
|
||||
for cmd_value in command_values:
|
||||
# 使用正则表达式查找独立的命令(前后是单词边界或空格)
|
||||
pattern = r'(?<!\S)' + re.escape(cmd_value) + r'(?!\S)'
|
||||
if re.search(pattern, input_str):
|
||||
found_commands.append(cmd_value)
|
||||
|
||||
# 如果没有找到任何命令,直接返回原字符串
|
||||
if not found_commands:
|
||||
return None, input_str, None, None
|
||||
|
||||
# 2. 检查是否包含多个命令
|
||||
if len(found_commands) > 1:
|
||||
return None, input_str, None, f"错误: 字符串中包含多个命令: {', '.join(found_commands)}"
|
||||
|
||||
# 此时只有一个命令
|
||||
command_str = found_commands[0]
|
||||
|
||||
# 3. 构建完整匹配模式,匹配命令及其后的可选数字
|
||||
# 模式: 命令 + 可选的空格 + 可选的数字
|
||||
full_pattern = r'(?<!\S)(' + re.escape(command_str) + r')(?:\s+(\d+))?(?!\S)'
|
||||
match = re.search(full_pattern, input_str)
|
||||
|
||||
if not match:
|
||||
return None, input_str, None, f"错误: 命令格式不正确"
|
||||
|
||||
command_part = match.group(1)
|
||||
number_part = match.group(2)
|
||||
|
||||
# 4. 检查命令是否在字符串末尾
|
||||
# 计算命令的结束位置
|
||||
command_end_pos = match.end()
|
||||
|
||||
# 如果命令不在字符串的末尾(忽略尾部空格)
|
||||
if command_end_pos < len(input_str.rstrip()):
|
||||
# 检查命令后面是否有非空格字符
|
||||
remaining_text = input_str[command_end_pos:].strip()
|
||||
if remaining_text:
|
||||
return None, input_str, None, f"错误: 命令不在字符串末尾,命令后还有内容: '{remaining_text}'"
|
||||
|
||||
# 5. 检查命令前面是否有内容,但中间没有空格
|
||||
# 找到命令前面的部分
|
||||
before_command = input_str[:match.start()]
|
||||
|
||||
# 如果命令前面有内容,检查是否有空格分隔
|
||||
if before_command and not before_command.endswith(' '):
|
||||
# 检查命令是否在字符串开头
|
||||
if match.start() > 0:
|
||||
return None, input_str, None, f"错误: 命令与前面的文本没有用空格分隔"
|
||||
|
||||
# 6. 获取命令枚举
|
||||
command = None
|
||||
for cmd in QuickCommand:
|
||||
if cmd.value == command_part:
|
||||
command = cmd
|
||||
break
|
||||
|
||||
if not command:
|
||||
return None, input_str, None, f"错误: 未识别的命令: {command_part}"
|
||||
|
||||
# 7. 提取去除命令和数字后的文本
|
||||
# 获取命令前的文本
|
||||
text_before_command = input_str[:match.start()].rstrip()
|
||||
|
||||
# 8. 处理数字参数
|
||||
record_id = None
|
||||
if number_part:
|
||||
try:
|
||||
record_id = int(number_part)
|
||||
except ValueError:
|
||||
return None, input_str, None, f"错误: 数字参数格式不正确: {number_part}"
|
||||
|
||||
return command, text_before_command, record_id, None
|
||||
|
||||
|
||||
@@ -6,16 +6,20 @@ template:
|
||||
|
||||
{data_training}
|
||||
sql:
|
||||
regenerate_hint: |
|
||||
你之前生成的回答不符合预期或者系统出现了其他问题,请再次检查提示词内要求的规则和提供的信息,重新回答:
|
||||
|
||||
process_check: |
|
||||
<SQL-Generation-Process>
|
||||
<step>1. 分析用户问题,确定查询需求</step>
|
||||
<step>2. 根据表结构生成基础SQL</step>
|
||||
<step>3. <strong>强制检查:应用数据量限制规则</strong></step>
|
||||
<step>4. 应用其他规则(引号、别名等)</step>
|
||||
<step>5. <strong>强制检查:检查语法是否正确?</strong></step>
|
||||
<step>6. 确定图表类型</step>
|
||||
<step>7. 确定对话标题</step>
|
||||
<step>8. 返回JSON结果</step>
|
||||
<step>3. <strong>强制检查:验证SQL中使用的表名和字段名是否在<m-schema>中定义</strong></step>
|
||||
<step>4. <strong>强制检查:应用数据量限制规则(默认限制或用户指定数量)</strong></step>
|
||||
<step>5. 应用其他规则(引号、别名、格式化等)</step>
|
||||
<step>6. <strong>强制检查:验证SQL语法是否符合<db-engine>规范</strong></step>
|
||||
<step>7. 确定图表类型(根据规则选择table/column/bar/line/pie)</step>
|
||||
<step>8. 确定对话标题</step>
|
||||
<step>9. 返回JSON结果</step>
|
||||
</SQL-Generation-Process>
|
||||
query_limit: |
|
||||
<rule priority="critical" id="data-limit-policy">
|
||||
|
||||
@@ -51,6 +51,7 @@ export class ChatRecord {
|
||||
recommended_question?: string
|
||||
analysis_record_id?: number
|
||||
predict_record_id?: number
|
||||
regenerate_record_id?: number
|
||||
|
||||
constructor()
|
||||
constructor(
|
||||
@@ -75,7 +76,8 @@ export class ChatRecord {
|
||||
first_chat: boolean,
|
||||
recommended_question: string | undefined,
|
||||
analysis_record_id: number | undefined,
|
||||
predict_record_id: number | undefined
|
||||
predict_record_id: number | undefined,
|
||||
regenerate_record_id: number | undefined
|
||||
)
|
||||
constructor(
|
||||
id?: number,
|
||||
@@ -99,7 +101,8 @@ export class ChatRecord {
|
||||
first_chat?: boolean,
|
||||
recommended_question?: string,
|
||||
analysis_record_id?: number,
|
||||
predict_record_id?: number
|
||||
predict_record_id?: number,
|
||||
regenerate_record_id?: number
|
||||
) {
|
||||
this.id = id
|
||||
this.chat_id = chat_id
|
||||
@@ -123,6 +126,7 @@ export class ChatRecord {
|
||||
this.recommended_question = recommended_question
|
||||
this.analysis_record_id = analysis_record_id
|
||||
this.predict_record_id = predict_record_id
|
||||
this.regenerate_record_id = regenerate_record_id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +256,8 @@ const toChatRecord = (data?: any): ChatRecord | undefined => {
|
||||
data.first_chat,
|
||||
data.recommended_question,
|
||||
data.analysis_record_id,
|
||||
data.predict_record_id
|
||||
data.predict_record_id,
|
||||
data.regenerate_record_id
|
||||
)
|
||||
}
|
||||
const toChatRecordList = (list: any = []): ChatRecord[] => {
|
||||
|
||||
@@ -235,6 +235,7 @@
|
||||
"continue_to_ask": "Continue asking:",
|
||||
"data_analysis": "Data Analysis",
|
||||
"data_predict": "Data Prediction",
|
||||
"data_regenerated": "Regenerate",
|
||||
"chat_search": "Search",
|
||||
"thinking": "Thinking",
|
||||
"thinking_step": "Thought Process",
|
||||
|
||||
@@ -235,6 +235,7 @@
|
||||
"continue_to_ask": "계속 질문하기:",
|
||||
"data_analysis": "데이터 분석",
|
||||
"data_predict": "데이터 예측",
|
||||
"data_regenerated": "재생성",
|
||||
"chat_search": "검색",
|
||||
"thinking": "생각 중",
|
||||
"thinking_step": "사고 과정",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"continue_to_ask": "继续提问:",
|
||||
"data_analysis": "数据分析",
|
||||
"data_predict": "数据预测",
|
||||
"data_regenerated": "重新生成",
|
||||
"chat_search": "搜索",
|
||||
"thinking": "思考中",
|
||||
"thinking_step": "思考过程",
|
||||
|
||||
@@ -163,6 +163,15 @@ const sendMessage = async () => {
|
||||
currentRecord.id = data.id
|
||||
_currentChat.value.records[index.value].id = data.id
|
||||
break
|
||||
case 'regenerate_record_id':
|
||||
currentRecord.regenerate_record_id = data.regenerate_record_id
|
||||
_currentChat.value.records[index.value].regenerate_record_id =
|
||||
data.regenerate_record_id
|
||||
break
|
||||
case 'question':
|
||||
currentRecord.question = data.question
|
||||
_currentChat.value.records[index.value].question = data.question
|
||||
break
|
||||
case 'info':
|
||||
console.info(data.msg)
|
||||
break
|
||||
|
||||
@@ -3,9 +3,11 @@ import type { ChatMessage } from '@/api/chat.ts'
|
||||
import icon_copy_outlined from '@/assets/embedded/icon_copy_outlined.svg'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
message?: ChatMessage
|
||||
allMessages?: ChatMessage[]
|
||||
}>()
|
||||
const { t } = useI18n()
|
||||
const { copy } = useClipboard({ legacy: true })
|
||||
@@ -16,6 +18,13 @@ function clickAnalysis() {
|
||||
function clickPredict() {
|
||||
console.info('predict_record_id: ' + props.message?.record?.predict_record_id)
|
||||
}
|
||||
function clickRegenerated() {
|
||||
console.info('regenerate_record_id: ' + props.message?.record?.regenerate_record_id)
|
||||
}
|
||||
|
||||
const isRegenerated = computed(() => {
|
||||
return !!props.message?.record?.regenerate_record_id
|
||||
})
|
||||
|
||||
const copyCode = () => {
|
||||
const str = props.message?.content || ''
|
||||
@@ -37,6 +46,9 @@ const copyCode = () => {
|
||||
<span v-else-if="message?.record?.predict_record_id" class="prefix-title" @click="clickPredict">
|
||||
{{ t('qa.data_predict') }}
|
||||
</span>
|
||||
<span v-else-if="isRegenerated" class="prefix-title" @click="clickRegenerated">
|
||||
{{ t('qa.data_regenerated') }}
|
||||
</span>
|
||||
<span style="width: 100%">{{ message?.content }}</span>
|
||||
<div style="position: absolute; right: 12px; bottom: -24px">
|
||||
<el-tooltip :offset="12" effect="dark" :content="t('datasource.copy')" placement="top">
|
||||
|
||||
@@ -211,7 +211,11 @@
|
||||
<!-- @stop="onChatStop"-->
|
||||
<!-- @loading-over="loadingOver"-->
|
||||
<!-- />-->
|
||||
<UserChat v-if="message.role === 'user'" :message="message" />
|
||||
<UserChat
|
||||
v-if="message.role === 'user'"
|
||||
:message="message"
|
||||
:all-messages="computedMessages"
|
||||
/>
|
||||
<template v-if="message.role === 'assistant' && !message.first_chat">
|
||||
<ChartAnswer
|
||||
v-if="
|
||||
@@ -397,7 +401,7 @@
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 1, maxRows: 8.583 }"
|
||||
:placeholder="t('qa.question_placeholder')"
|
||||
@keydown.enter.exact.prevent="($event: any) => sendMessage($event)"
|
||||
@keydown.enter.exact.prevent="($event: any) => sendMessage(undefined, $event)"
|
||||
@keydown.ctrl.enter.exact.prevent="handleCtrlEnter"
|
||||
/>
|
||||
|
||||
@@ -764,7 +768,10 @@ const assistantPrepareSend = async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
const sendMessage = async ($event: any = {}) => {
|
||||
const sendMessage = async (
|
||||
regenerate_record_id: number | undefined = undefined,
|
||||
$event: any = {}
|
||||
) => {
|
||||
if ($event?.isComposing) {
|
||||
return
|
||||
}
|
||||
@@ -783,6 +790,7 @@ const sendMessage = async ($event: any = {}) => {
|
||||
currentRecord.create_time = new Date()
|
||||
currentRecord.chat_id = currentChatId.value
|
||||
currentRecord.question = inputMessage.value
|
||||
currentRecord.regenerate_record_id = regenerate_record_id
|
||||
currentRecord.sql_answer = ''
|
||||
currentRecord.sql = ''
|
||||
currentRecord.chart_answer = ''
|
||||
@@ -829,9 +837,21 @@ function onAnalysisAnswerError() {
|
||||
}
|
||||
|
||||
function askAgain(message: ChatMessage) {
|
||||
inputMessage.value = message.record?.question ?? ''
|
||||
if (message.record?.question?.trim() === '') {
|
||||
return
|
||||
}
|
||||
// regenerate
|
||||
inputMessage.value = '/regenerate'
|
||||
let regenerate_record_id = message.record?.id
|
||||
if (message.record?.id == undefined && message.record?.regenerate_record_id) {
|
||||
//只有当前对话内,上一次执行失败的重试会进这里
|
||||
regenerate_record_id = message.record?.regenerate_record_id
|
||||
}
|
||||
if (regenerate_record_id) {
|
||||
inputMessage.value = inputMessage.value + ' ' + regenerate_record_id
|
||||
}
|
||||
nextTick(() => {
|
||||
sendMessage()
|
||||
sendMessage(regenerate_record_id)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user