Skip to content

Commit 167aacb

Browse files
committed
Merge remote-tracking branch 'origin/main'
# Conflicts: # frontend/components.d.ts
2 parents 8e85bd9 + 49513ae commit 167aacb

File tree

20 files changed

+1170
-299
lines changed

20 files changed

+1170
-299
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""006_add_chat
2+
3+
Revision ID: ff653d5df198
4+
Revises: 0a6f11be9be4
5+
Create Date: 2025-05-21 15:44:29.763091
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'ff653d5df198'
15+
down_revision = 'e6276ddab06e'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.create_table('chat',
23+
sa.Column('id', sa.Integer(), sa.Identity(always=True), nullable=False),
24+
sa.Column('create_time', sa.DateTime(timezone=True), nullable=True),
25+
sa.Column('create_by', sa.BigInteger(), nullable=True),
26+
sa.Column('brief', sqlmodel.sql.sqltypes.AutoString(length=64), nullable=True),
27+
sa.Column('chat_type', sqlmodel.sql.sqltypes.AutoString(length=20), nullable=False),
28+
sa.Column('datasource', sa.Integer(), nullable=False),
29+
sa.Column('engine_type', sqlmodel.sql.sqltypes.AutoString(length=64), nullable=False),
30+
sa.PrimaryKeyConstraint('id')
31+
)
32+
op.create_table('chat_record',
33+
sa.Column('id', sa.Integer(), sa.Identity(always=True), nullable=False),
34+
sa.Column('chat_id', sa.Integer(), nullable=True),
35+
sa.Column('create_time', sa.DateTime(timezone=True), nullable=True),
36+
sa.Column('create_by', sa.BigInteger(), nullable=True),
37+
sa.Column('datasource', sa.Integer(), nullable=False),
38+
sa.Column('engine_type', sqlmodel.sql.sqltypes.AutoString(length=64), nullable=False),
39+
sa.Column('question', sa.Text(), nullable=True),
40+
sa.Column('full_question', sa.Text(), nullable=True),
41+
sa.Column('answer', sa.Text(), nullable=True),
42+
sa.Column('run_time', sa.Float(), nullable=False),
43+
sa.PrimaryKeyConstraint('id')
44+
)
45+
# ### end Alembic commands ###
46+
47+
48+
def downgrade():
49+
# ### commands auto generated by Alembic - please adjust! ###
50+
op.drop_table('chat_record')
51+
op.drop_table('chat')
52+
# ### end Alembic commands ###

backend/apps/chat/api/chat.py

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
from fastapi.responses import StreamingResponse
33
from sqlmodel import select
44

5-
from apps.chat.curd.chat import list_chats, get_chat_with_records, create_chat, save_question, save_answer
6-
from apps.chat.models.chat_model import CreateChat, ChatRecord
5+
from apps.chat.curd.chat import list_chats, get_chat_with_records, create_chat, save_question, save_answer, rename_chat, \
6+
delete_chat
7+
from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, Chat
78
from apps.chat.schemas.chat_base_schema import LLMConfig
89
from apps.chat.schemas.chat_schema import ChatQuestion
910
from apps.chat.schemas.llm import AgentService
11+
from apps.datasource.crud.datasource import get_table_obj_by_ds
1012
from apps.datasource.models.datasource import CoreDatasource
1113
from apps.system.models.system_model import AiModelDetail
1214
from common.core.deps import SessionDep, CurrentUser
@@ -32,6 +34,28 @@ async def list_chat(session: SessionDep, current_user: CurrentUser, chart_id: in
3234
)
3335

3436

37+
@router.post("/rename")
38+
async def rename(session: SessionDep, chat: RenameChat):
39+
try:
40+
return rename_chat(session=session, rename_object=chat)
41+
except Exception as e:
42+
raise HTTPException(
43+
status_code=500,
44+
detail=str(e)
45+
)
46+
47+
48+
@router.get("/delete/{chart_id}")
49+
async def delete(session: SessionDep, chart_id: int):
50+
try:
51+
return delete_chat(session=session, chart_id=chart_id)
52+
except Exception as e:
53+
raise HTTPException(
54+
status_code=500,
55+
detail=str(e)
56+
)
57+
58+
3559
@router.post("/start")
3660
async def start_chat(session: SessionDep, current_user: CurrentUser, create_chat_obj: CreateChat):
3761
try:
@@ -57,26 +81,18 @@ async def stream_sql(session: SessionDep, current_user: CurrentUser, request_que
5781
"""
5882
question = request_question.question
5983

60-
# Get available AI model
61-
aimodel = session.exec(select(AiModelDetail).where(
62-
AiModelDetail.status == True,
63-
AiModelDetail.api_key.is_not(None)
64-
)).first()
65-
66-
# Get available datasource
67-
ds = session.exec(select(CoreDatasource).where(
68-
CoreDatasource.status == 'Success'
69-
)).first()
70-
71-
if not aimodel:
84+
chat = session.query(Chat).filter(Chat.id == request_question.chat_id).first()
85+
if not chat:
7286
raise HTTPException(
7387
status_code=400,
74-
detail="No available AI model configuration found"
88+
detail=f"Chat with id {request_question.chart_id} not found"
7589
)
7690

91+
# Get available datasource
92+
ds = session.query(CoreDatasource).filter(CoreDatasource.id == chat.datasource).first()
7793
if not ds:
7894
raise HTTPException(
79-
status_code=400,
95+
status_code=500,
8096
detail="No available datasource configuration found"
8197
)
8298

@@ -89,6 +105,17 @@ async def stream_sql(session: SessionDep, current_user: CurrentUser, request_que
89105
detail=str(e1)
90106
)
91107

108+
# Get available AI model
109+
aimodel = session.exec(select(AiModelDetail).where(
110+
AiModelDetail.status == True,
111+
AiModelDetail.api_key.is_not(None)
112+
)).first()
113+
if not aimodel:
114+
raise HTTPException(
115+
status_code=400,
116+
detail="No available AI model configuration found"
117+
)
118+
92119
# Use Tongyi Qianwen
93120
tongyi_config = LLMConfig(
94121
model_type="openai",
@@ -113,10 +140,39 @@ async def stream_sql(session: SessionDep, current_user: CurrentUser, request_que
113140
""" result = llm_service.generate_sql(question)
114141
return result """
115142

143+
# get schema
144+
schema_str = ""
145+
table_objs = get_table_obj_by_ds(session=session, ds=ds)
146+
db_name = table_objs[0].schema
147+
schema_str += f"【DB_ID】 {db_name}\n【Schema】\n"
148+
for obj in table_objs:
149+
schema_str += f"# Table: {db_name}.{obj.table.table_name}"
150+
table_comment = ''
151+
if obj.table.custom_comment:
152+
table_comment = obj.table.custom_comment.strip()
153+
if table_comment == '':
154+
schema_str += '\n[\n'
155+
else:
156+
schema_str += f", {table_comment}\n[\n"
157+
158+
field_list = []
159+
for field in obj.fields:
160+
field_comment = ''
161+
if field.custom_comment:
162+
field_comment = field.custom_comment.strip()
163+
if field_comment == '':
164+
field_list.append(f"({field.field_name}:{field.field_type})")
165+
else:
166+
field_list.append(f"({field.field_name}:{field.field_type}, {field_comment})")
167+
schema_str += ",\n".join(field_list)
168+
schema_str += '\n]\n'
169+
170+
print(schema_str)
171+
116172
async def event_generator():
117173
all_text = ''
118174
try:
119-
async for chunk in llm_service.async_generate(question):
175+
async for chunk in llm_service.async_generate(question, schema_str):
120176
data = json.loads(chunk.replace('data: ', ''))
121177

122178
if data['type'] in ['final', 'tool_result']:

backend/apps/chat/curd/chat.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from sqlalchemy import text, and_
66
from sqlmodel import select
77

8-
from apps.chat.models.chat_model import Chat, ChatRecord, CreateChat, ChatInfo
8+
from apps.chat.models.chat_model import Chat, ChatRecord, CreateChat, ChatInfo, RenameChat
99
from apps.chat.schemas.chat_schema import ChatQuestion
1010
from apps.datasource.models.datasource import CoreDatasource
1111
from common.core.deps import SessionDep, CurrentUser
@@ -17,6 +17,32 @@ def list_chats(session: SessionDep, current_user: CurrentUser) -> List[Chat]:
1717
return chart_list
1818

1919

20+
def rename_chat(session: SessionDep, rename_object: RenameChat) -> str:
21+
chat = session.query(Chat).filter(Chat.id == rename_object.id).first()
22+
if not chat:
23+
raise Exception(f"Chat with id {rename_object.id} not found")
24+
25+
chat.brief = rename_object.brief.strip()[:20]
26+
session.add(chat)
27+
session.flush()
28+
session.refresh(chat)
29+
30+
brief = chat.brief
31+
session.commit()
32+
return brief
33+
34+
35+
def delete_chat(session, chart_id) -> str:
36+
chat = session.query(Chat).filter(Chat.id == chart_id).first()
37+
if not chat:
38+
return f'Chat with id {chart_id} has been deleted'
39+
40+
session.delete(chat)
41+
session.commit()
42+
43+
return f'Chat with id {chart_id} has been deleted'
44+
45+
2046
def get_chat_with_records(session: SessionDep, chart_id: int, current_user: CurrentUser) -> ChatInfo:
2147
chat = session.query(Chat).filter(Chat.id == chart_id).first()
2248
if not chat:
@@ -33,7 +59,7 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
3359
chat_info.datasource_name = ds.name
3460

3561
record_list = session.query(ChatRecord).filter(
36-
and_(Chat.create_by == current_user.id, ChatRecord.id == chart_id)).order_by(ChatRecord.create_time).all()
62+
and_(Chat.create_by == current_user.id, ChatRecord.chat_id == chart_id)).order_by(ChatRecord.create_time).all()
3763

3864
chat_info.records = record_list
3965

@@ -98,10 +124,11 @@ def save_question(session: SessionDep, current_user: CurrentUser, question: Chat
98124

99125
return result
100126

127+
101128
def save_full_question(session: SessionDep, id: int, full_question: str) -> ChatRecord:
102129
if not id:
103130
raise Exception("Record id cannot be None")
104-
record = session.query(ChatRecord).filter(Chat.id == id).first()
131+
record = session.query(ChatRecord).filter(ChatRecord.id == id).first()
105132
record.full_question = full_question
106133

107134
result = ChatRecord(**record.model_dump())
@@ -114,11 +141,12 @@ def save_full_question(session: SessionDep, id: int, full_question: str) -> Chat
114141

115142
return result
116143

144+
117145
def save_answer(session: SessionDep, id: int, answer: str) -> ChatRecord:
118146
if not id:
119147
raise Exception("Record id cannot be None")
120148

121-
record = session.query(ChatRecord).filter(Chat.id == id).first()
149+
record = session.query(ChatRecord).filter(ChatRecord.id == id).first()
122150
record.answer = answer
123151

124152
result = ChatRecord(**record.model_dump())

backend/apps/chat/models/chat_model.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ class CreateChat(BaseModel):
3636
datasource: int = None
3737

3838

39+
class RenameChat(BaseModel):
40+
id: int = None
41+
brief: str = ''
42+
43+
3944
class ChatInfo(BaseModel):
4045
id: Optional[int] = None
4146
create_time: datetime = None

backend/apps/chat/schemas/llm.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,17 @@ def generate_sql(self, question: str) -> str:
101101
schema = self.db.get_table_info()
102102
return chain.invoke({"schema": schema, "question": question})
103103

104-
async def async_generate(self, question: str) -> AsyncGenerator[str, None]:
104+
async def async_generate(self, question: str, schema: str) -> AsyncGenerator[str, None]:
105105

106106
chain = self.prompt | self.agent_executor
107107
# schema = self.db.get_table_info()
108108

109-
schema_engine = SchemaEngine(engine=self.db._engine)
110-
mschema = schema_engine.mschema
111-
mschema_str = mschema.to_mschema()
109+
# schema_engine = SchemaEngine(engine=self.db._engine)
110+
# mschema = schema_engine.mschema
111+
# mschema_str = mschema.to_mschema()
112112

113-
async for chunk in chain.astream({"schema": mschema_str, "question": question}):
113+
# async for chunk in chain.astream({"schema": mschema_str, "question": question}):
114+
async for chunk in chain.astream({"schema": schema, "question": question}):
114115
if not isinstance(chunk, dict):
115116
continue
116117

backend/apps/datasource/crud/datasource.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,12 @@ def preview(session: SessionDep, id: int, data: TableObj):
211211
return exec_sql(ds, sql)
212212

213213

214-
def get_tableobj_by_ds(session: SessionDep, id: int) -> List[TableAndFields]:
215-
list: List = []
216-
tables = session.query(CoreTable).filter(CoreTable.ds_id == id).all()
214+
def get_table_obj_by_ds(session: SessionDep, ds: CoreDatasource) -> List[TableAndFields]:
215+
_list: List = []
216+
tables = session.query(CoreTable).filter(CoreTable.ds_id == ds.id).all()
217+
conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration))) if ds.type != "excel" else get_engine_config()
218+
schema = conf.dbSchema if conf.dbSchema is not None and conf.dbSchema != "" else conf.database
217219
for table in tables:
218220
fields = session.query(CoreField).filter(and_(CoreField.table_id == table.id, CoreField.checked == True)).all()
219-
list.append(TableAndFields(table=table, fields=fields))
220-
return list
221+
_list.append(TableAndFields(schema=schema, table=table, fields=fields))
222+
return _list

backend/apps/datasource/models/datasource.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,11 @@ def __init__(self, attr1, attr2, attr3):
111111

112112

113113
class TableAndFields:
114-
def __init__(self, table, fields):
114+
def __init__(self, schema, table, fields):
115+
self.schema = schema
115116
self.table = table
116117
self.fields = fields
117118

119+
schema: str
118120
table: CoreTable
119121
fields: List[CoreField]

backend/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ dependencies = [
3232
"pymssql (>=2.3.4,<3.0.0)",
3333
"pandas (>=2.2.3,<3.0.0)",
3434
"openpyxl (>=3.1.5,<4.0.0)",
35-
"psycopg2 (>=2.9.10,<3.0.0)",
35+
"psycopg2-binary (>=2.9.10,<3.0.0)",
3636
"oracledb (>=3.1.1,<4.0.0)"
3737
]
3838
[[tool.uv.index]]

frontend/src/api/chat.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ export const questionApi = {
1616
query: (id: number) => request.get(`/chat/question/${id}`)
1717
}
1818

19+
export interface ChatMessage {
20+
role: 'user' | 'assistant'
21+
create_time?: Date | string
22+
content?: string | number
23+
isTyping?: boolean
24+
isWelcome?: boolean
25+
}
26+
1927
export class ChatRecord {
2028
id?: number
2129
chat_id?: number
@@ -147,12 +155,22 @@ function startChat(data: any): Promise<ChatInfo> {
147155
return request.post('/chat/start', data)
148156
}
149157

158+
function renameChat(chat_id: number | undefined, brief: string): Promise<string> {
159+
return request.post('/chat/rename', {id: chat_id, brief: brief})
160+
}
161+
162+
function deleteChat(id: number | undefined): Promise<string> {
163+
return request.get(`/chat/delete/${id}`)
164+
}
165+
150166
export const chatApi = {
151167
toChatRecord,
152168
toChatRecordList,
153169
toChatInfo,
154170
toChatInfoList,
155171
list,
156172
get,
157-
startChat
173+
startChat,
174+
renameChat,
175+
deleteChat
158176
}

0 commit comments

Comments
 (0)