import uuid
from dash import html, dcc,dash_table
from datetime import datetime
from typing import Literal, Union
import requests
import feffery_antd_components as fac
import feffery_utils_components as fuc
import feffery_markdown_components as fmc
from dash.dependencies import Component
import json
from .conversation_cache import conversation_cache
from config.env import AppConfig
import pandas as pd
import dash
app = dash.Dash(__name__)
def get_translation_ct_type(key):
translation_dict = {
"datePicker": "单日筛选",
"dateRangePicker": "周期筛选",
"input": "输入框",
"select": "单项筛选",
"mselect": "多项筛选",
"radioGroup": "是否筛选"
}
return translation_dict.get(key, "未知") # 如果键不存在,返回 "未知"
def get_value(param):
# print("打印一下param")
# print(param)
# print(param['default_value'])
# if param["ct_type"] == "dateRangePicker":
# date_str = f"start_date: {param['default_value']['start_date']}, end_date: {param['default_value']['end_date']}"
# else:
# date_str = param['default_value']
# return date_str # 如果键不存在,返回 "未知"
if isinstance(param['default_value'], dict) and param["ct_type"] == "dateRangePicker":
date_str = f"start_date: {param['default_value']['start_date']}, end_date: {param['default_value']['end_date']}"
else:
# 处理其他情况,例如 default_value 是一个单独的日期字符串
date_str = param['default_value']
return date_str
def process_datas(datas):
# 初始化一个空字典用于存储树结构
tree = {}
# 遍历输入数据,根据 tab_cn_name 组织一级目录,fld_cn_name 作为二级目录
for data in datas:
tab_cn_name = data['tab_cn_name']
fld_cn_name = data['fld_cn_name']
fld_type = data['fld_type']
# 如果该 tab_cn_name 不在树中,则初始化一个新的一级目录
if tab_cn_name not in tree:
tree[tab_cn_name] = {
"title": tab_cn_name,
"key": tab_cn_name,
"enableFavorites": False,
"children": []
}
# 将 fld_cn_name 和 fld_type 作为子项添加到一级目录的 children 中
tree[tab_cn_name]["children"].append({
"title": fld_cn_name,
"key": f"{fld_cn_name}|{fld_type}"
})
# 将字典转换为列表形式
return list(tree.values())
def render_control_group(data_out_put,user_input_text, new_uuid):
"""生成新的控件组"""
# print(control_params)
# print(type(data_out_put))
control_params = data_out_put.get("filters")
data = data_out_put.get("data")
sql = data_out_put.get("sql")
datas = data_out_put.get("datas")
html_content = data_out_put.get("html_content")
print("画图代码")
print(html_content)
df = pd.DataFrame(data)
# 将DataFrame转换为HTML表格
# df_html = df.to_html(index=False, classes='table table-striped')
# 存储查询维度和查询指标参数
filter_divs = []
index_divs = []
form_values = {}
# 用于存储已存在的param["name"]
existing_names = set()
for param in control_params:
child_elements = []
# 如果 param["name"] 已经存在于 existing_names 中,继续加 '1' 直到找到唯一的名字
while param["name"] in existing_names:
param["name"] += "1"
# 将处理过的 name 加入集合
existing_names.add(param["name"])
if param["ct_type"] == "mselect":
child_elements.extend([
html.Span(f"{param['name']}:", style={'fontSize': '14px', "margin-right": "2px", "margin-left": "2px", 'margin-top': '5px'}), # 显示元素名称
fac.AntdSelect(
name=param["name"],
options=[
{'label': opt, 'value': opt} for opt in param.get("options", [])
],
mode="multiple" if param["ct_type"] == "mselect" else None,
popupMatchSelectWidth=True,
style={
'fontSize': '14px',
"min-width": "200px",
"white-space": "nowrap",
"height": 30
},
defaultValue=param["default_value"] if param["ct_type"] == "mselect" else None
)
])
form_values[param["name"]] = param["default_value"]
elif param["ct_type"] == "datePicker":
start_date = param["default_value"]
child_elements.extend([
html.Span(f"{param['name']}:", style={'fontSize': '14px', "margin-right": "2px", "margin-left": "2px", 'margin-top': '5px'}), # 显示元素名称
fac.AntdDatePicker(
name=param["name"],
style={'fontSize': '14px', "width": 120, "height": 30, "margin-right": "2px", "margin-left": "2px"}, # 设置日期选择器的宽度
value=start_date
)
])
form_values[param["name"]] = start_date
elif param["ct_type"] == "dateRangePicker":
start_date = param["default_value"].get("start_date")
end_date = param["default_value"].get("end_date")
child_elements.extend([
html.Span(f"{param['name']}:", style={'fontSize': '14px', "margin-right": "2px", "margin-left": "2px", 'margin-top': '5px'}), # 显示元素名称
fac.AntdDateRangePicker(
name=param["name"],
style={'fontSize': '14px', "width": 230, "height": 30, "margin-right": "2px", "margin-left": "2px"}, # 设置日期选择器的宽度
value=[
start_date,
end_date
]
)
])
form_values[param["name"]] = [start_date, end_date]
elif param["ct_type"] == "radioGroup":
child_elements.extend([
html.Span(f"{param['name']}:", style={'fontSize': '14px', "margin-right": "2px", "margin-left": "2px", 'margin-top': '5px'}), # 显示元素名称
fac.AntdRadioGroup(
name=param["name"],
style={'fontSize': '10px', "width": 110, "height": 30, 'margin-top': '5px'},
options=[
{
'label': f'{c}',
'value': c
}
for c in param.get("options", [])
],
direction='horizontal',
)
])
form_values[param["name"]] = param["default_value"]
elif param["ct_type"] == "input":
child_elements.extend([
html.Span(f"{param['name']}:", style={'fontSize': '14px', "margin-right": "2px", "margin-left": "2px", 'margin-top': '5px'}), # 显示元素名称
fac.AntdInput(
name=param["name"],
style={'fontSize': '14px', "width": 120, "height": 30},
value=param["default_value"], # 使用 param 中的默认值或提供一个默认值
allowClear=True,
)
])
form_values[param["name"]] = param["default_value"]
elif param["ct_type"] == "output" and param["cd_type"] == "输出结果":
child_elements.extend([
html.Span(f"{param['name']},", style={'fontSize': '14px', "margin-right": "2px", "margin-left": "2px", 'margin-top': '5px'}),
])
form_values[param["name"]] = param["default_value"]
else:
if param["name"] == "原始问题":
user_input_text = param["default_value"]
break
child_elements.extend([
html.Span(f"{param['name']}:", style={'fontSize': '14px', "margin-right": "2px", "margin-left": "2px", 'margin-top': '5px'}), # 显示元素名称
fac.AntdInput(
name=param["name"],
style={'fontSize': '14px', "width": 120, "height": 30},
value=param["default_value"], # 使用 param 中的默认值或提供一个默认值
placeholder='请补充其它条件',
)
])
form_values[param["name"]] = param["default_value"]
if param["cd_type"] in ('维度筛选', '时间筛选', '分组条件', '查询其它条件'):
filter_divs.append(html.Div(child_elements, className='filter-row', style={'display': 'flex', 'flexWrap': 'wrap', 'marginBottom': '2px'}))
else:
index_divs.append(html.Div(child_elements, className='filter-row', style={'display': 'flex', 'flexWrap': 'wrap', 'marginBottom': '2px'}))
existing_content = []
# print ("filter_divs")
# print(filter_divs)
# 使用 AntdRow 包含各个部分,减少列与列之间的宽度和垂直间距
existing_content.append(
fac.AntdRow(
[
# 原始问题部分
fac.AntdCol(
html.Div(
[
html.Span("原始问题:", style={'fontSize': '14px', 'fontWeight': 'bold', 'display': 'inline'}),
html.Span(user_input_text, style={'fontSize': '14px', 'display': 'inline', 'minWidth': '600px'}),
]
),
style={'marginBottom': '0px', 'marginTop': '0px'} # 减少垂直间距
)
]
)
)
# 如果查询条件存在,添加查询条件部分
if filter_divs:
existing_content.append(
fac.AntdRow(
[
fac.AntdCol(
html.Div(
[
html.Span("查询条件:", style={'fontSize': '14px', 'fontWeight': 'bold', 'display': 'block'}),
html.Div(filter_divs, className='filter-row', style={'display': 'flex', 'flexWrap': 'wrap'})
]
),
style={'marginBottom': '0px', 'marginTop': '0px'} # 减少垂直间距
)
]
)
)
if index_divs:
existing_content.append(
fac.AntdRow(
[
# fac.AntdCol(
# html.Div(
# [
# html.Span("输出结果:", style={'fontSize': '14px', 'fontWeight': 'bold', 'display': 'block'}),
# html.Div(index_divs, className='filter-row', style={'display': 'flex', 'flexWrap': 'wrap'})
# ]
# ),
# style={'marginBottom': '0px', 'marginTop': '0px'} # 减少垂直间距
# )
fac.AntdCol(
html.Div(
[
html.Span("输出结果:", style={'fontSize': '14px', 'fontWeight': 'bold', 'marginRight': '0px'}),
html.Div(index_divs, className='filter-row', style={'display': 'inline-flex', 'flexWrap': 'wrap'})
],
style={'display': 'flex', 'alignItems': 'center'}
),
style={'marginBottom': '0px', 'marginTop': '0px'} # 减少垂直间距
)
]
)
)
# 设定每列的基础宽度
base_column_width = 150 # 每列的最小宽度(可以根据需要调整)
# 最小列数为3,如果列数少于3,按3列计算宽度
min_columns = 4
# 确保宽度至少占3列
total_columns = max(len(df.columns), min_columns)
# 计算表格的总宽度
total_width = base_column_width * total_columns # 根据列数动态计算表格宽度
# 如果总宽度超出了最大宽度,可以设置一个上限
max_width = 1000 # 表格的最大宽度限制
if total_width > max_width:
total_width = max_width
# 定义固定内容高度,让两个内容区域保持一致
# if not html_content and df.shape[0] == 0:
# fixed_content_height = '100px'
# elif not html_content:
# fixed_content_height = f'{30 + df.shape[0] * 20}px' # 没有 html_content 但 df 有记录
# else:
# fixed_content_height = f'{30 + df.shape[0] * 20}px' # 没有 html_content 但 df 有记录
h1 =min(df.shape[0],5) * 35
fixed_content_height = f'{175 +h1}px'
father_content_height = f'{180 +h1}px'
print("h1为",h1)
print("father为",father_content_height)
# 提取数字部分并进行计算
# fixed_height_value = int(fixed_content_height.replace('px', '')) # 将 'px' 去掉并转为整数
# adjustment_value = max(0, 300 - df.shape[0] * 30) # 根据记录数减少的值
# father_content_height = f'{fixed_height_value + adjustment_value}px' # 计算父容器高度并重新添加 'px'
# 生成表格
if not df.empty:
existing_content.append(
fac.AntdTabs(
items=[
{
'key': 'data_result',
'label': '数据结果',
'children': fac.AntdTable(
id="data-table",
columns=[
{
'title': f'{i}',
'dataIndex': f'{i}',
'width': f'{base_column_width}px'
}
for i in df.columns
],
data=df.to_dict('records'),
style={
'width': '850px',
'margin': '0 auto',
'fontSize': '14px',
'height': fixed_content_height, # 统一设置高度
'overflowY': 'auto' # 允许内部滚动
},
pagination={
'pageSize': 5,
'showSizeChanger': True,
'pageSizeOptions': [5, 10, 20]
},
size="small",
),
},
{
'key': 'analysis_result',
'label': '分析结果',
'children': html.Iframe(
id='project-iframe',
srcDoc=html_content,
style={
'margin-right': '2px',
'width': '850px',
'height': fixed_content_height, # 统一设置高度
'border': 'none',
'overflowY': 'hidden', # 去掉子类的滚动条
'overflowX': 'auto' # 去掉子类的滚动条
}
),
},
],
style={'fontSize': '14px', 'height': father_content_height, 'overflowY': 'hidden'}, # 确保父容器可以滚动
size='small'
)
)
else:
existing_content.append(
html.Div(
[
html.Span("数据结果:", style={'fontSize': '14px', 'fontWeight': 'bold', 'display': 'block'}),
html.Span("查询结果为空,请重新提问,或者点击编辑按钮辅助提问^_^!", style={'fontSize': '14px', 'display': 'block'}),
]
)
)
return fac.AntdSpace(
[
fac.AntdForm(
children=existing_content,
id={"type": "control-group", "index": new_uuid},
layout="vertical",
enableBatchControl=True,
values=form_values # 设置表单的默认值
),
fac.Fragment(
[
# 控件组操作表格抽屉
fac.AntdDrawer(
fac.AntdRow(
[
fac.AntdCol(
html.Div(
[
fac.AntdTitle('可选数据', level=4, style={'textAlign': 'center'}),
fac.AntdSpace(
[
fac.AntdTree(
id={"type": "tree-favorites-demo", "index": new_uuid},
treeData=process_datas(datas),
# defaultExpandAll=True,
enableNodeFavorites=True,
style={
'textAlign': 'center',
'backgroundColor': 'rgb(240, 242, 245)'
}
),
],
direction='vertical',
)
]
),
style={
'backgroundColor': 'rgb(240, 242, 245)',
'height': 800, # 固定左侧列高度为800px
'width': 200,
'overflowY': 'auto' # 添加垂直滚动条
},
),
fac.AntdCol(
html.Div(
[
fac.AntdTitle('SQL代码及翻译', level=4, style={'textAlign': 'center', 'backgroundColor': 'rgb(240, 242, 245)'}),
html.Div(
fmc.FefferySyntaxHighlighter(
codeString=sql,
language='sql',
codeTheme='night-owl',
),
style={
'backgroundColor': 'rgb(240, 242, 245)',
'height': '35%', # 确保代码块高度不超过右侧区域的50%
'overflowY': 'scroll',
'overflowX': 'scroll',
# 'marginTop': '5px'
}
),
html.Div(
[
fac.AntdTitle('条件设置', level=4, style={'textAlign': 'center','marginTop': '2px'}),
fac.AntdTable(
id={"type": "edit-widgets-table", "index": new_uuid},
# style={
# 'fontSize': '200px'
# },
columns=[
{
"dataIndex": "字段名称",
"title": "字段名称",
"width": "16%",
},
{
"dataIndex": "控件类型",
"title": "控件类型",
"width": "14%",
},
{
"dataIndex": "条件类型",
"title": "条件类型",
"width": "12%",
},
# {
# "dataIndex": "控件类型",
# "title": "控件类型",
# "width": "14%",
# },
{
"dataIndex": "条件取值",
"title": "条件取值",
'align': "left",
"width": "35%",
'editable': True,
"renderOptions": {"renderType": "ellipsis"},
},
{
"dataIndex": "操作",
"title": "操作",
"width": "23%",
"renderOptions": {"renderType": "button"},
},
],
data=[
{
"字段名称": param["name"],
#维度筛选、时间筛选、分组条件、限制条件、排序条件
"条件类型": param["cd_type"],
# "控件类型": param["type"],
"控件类型": get_translation_ct_type(param["ct_type"]),
# "条件取值": form_values[param["name"]],
"条件取值": get_value(param),
# "控件可选项": (
# "、".join(param["options"])
# if param["type"] == "text"
# else None
# ),
"操作": [
{"content": "编辑", "type": "link"},
{"content": "上移", "type": "link"},
{"content": "下移", "type": "link"},
{
"content": "删除",
"type": "link",
"danger": True,
},
],
"param": param,
}
for param in control_params
],
pagination={
'pageSize': 5
},
bordered=True,
size="small",
),
fac.AntdSpace(
[
fac.AntdInput(
id={"type": "input-text1", "index": new_uuid},
# id="input-text1",
placeholder="Enter 发送,Shift + Enter 换行",
mode="text-area",
autoSize={"minRows": 3, "maxRows": 3},
style={
'height': 100,
"width": 860
},
),
fac.AntdCopyText(
id={"type": "copy-text-output", "index": new_uuid},
# id='copy-text-output',
beforeIcon='复制',
afterIcon= '成功',
text='无内容'
)
]
),
# fac.AntdButton(
# "发送",
# # id="send-input-text1",
# id={"type": "send-input-text1", "index": new_uuid},
# icon=fac.AntdIcon(icon="antd-export"),
# type="primary",
# size="large",
# loadingChildren="已发送",
# style={
# "position": "absolute",
# "right": 20,
# "bottom": 60,
# },
# ),
],
style={
'backgroundColor': 'rgba(240, 242, 245)',
'height': '65%',
"width": '100%'
}
)
],
style={
'height': '100%', # 确保右侧列高度与左侧列一致
'display': 'flex',
'flexDirection': 'column',
'backgroundColor': 'rgb(240, 242, 245)',
},
),
style={
'height': 800, # 固定右侧列高度为800px
'width': 900,
},
)
],
gutter=0,
),
id={"type": "edit-widgets-drawer", "index": new_uuid},
title="数据问答工作台",
placement="right",
width=1200,
),
# 指定控件编辑模态框
fac.AntdModal(
id={"type": "edit-widget-modal", "index": new_uuid},
title="条件编辑",
renderFooter=True,
),
dcc.Store(
id={"type": "control-group-store", "index": new_uuid},
data=control_params,
),
]
),
],
direction="vertical",
style={"width": "100%"}
)
def render(
conversation_id: str,
role: Literal["system", "user", "assistant"],
system_prompt: Union[str, Component] = None,
user_input_text: str = None,
message_uuid: str = None,
model_name: str = None,
sub_model_name: str = None,
temperature: float = 0.5,
max_tokens: int = 4000,
):
"""渲染不同角色的对话消息框"""
# 生成当前消息框唯一uuid
new_uuid = message_uuid or str(uuid.uuid4())
print("产生新的new_uuid"+new_uuid)
# 开场系统提示对话消息框
if role == "system":
# 更新当前对话缓存
conversation_cache.update(
{
conversation_id: [
{
"role": "system",
"content": system_prompt,
"message_id": new_uuid,
},
]
}
)
return fac.AntdRow(
fac.AntdCol(
fac.AntdFlex(
[
fac.AntdAvatar(
mode="icon",
icon="antd-robot",
style={"background": "#1677ff"},
),
html.Div(
[
system_prompt,
html.Div(
datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
className="chat-message-box-datetime-left",
),
],
style={"background": "#f2f2f2"},
className="chat-message-box",
),
],
vertical=True,
align="start",
gap=8,
style={"width": "100%"},
),
span=20,
style={"position": "relative"},
),
justify="start",
)
# 用户输入对话消息框
elif role == "user":
# 更新当前对话缓存
conversation_cache.update(
{
conversation_id: [
*conversation_cache.get(conversation_id),
{
"role": "user",
"content": user_input_text,
"message_id": new_uuid,
},
]
}
)
return fac.AntdRow(
fac.AntdCol(
fac.AntdFlex(
[
fac.AntdAvatar(
mode="icon",
icon="antd-user",
style={"background": "#1677ff"},
),
html.Div(
[
fac.AntdText(user_input_text, copyable=True),
html.Div(
datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
className="chat-message-box-datetime-right",
),
],
style={"background": "#e6f7ff"},
className="chat-message-box",
),
],
vertical=True,
align="end",
gap=8,
style={"width": "100%"},
),
span=20,
style={"position": "relative"},
),
justify="end",
)
# AI回复对话消息框
elif role == "assistant":
# 构建请求 URL
if model_name and sub_model_name:
url = "http://127.0.0.1:8088/data-chat?model_name={}&sub_model_name={}&temperature={}&max_tokens={}&conversation_id={}&message_id={}&question={}".format(
# AppConfig.app_port,
model_name,
sub_model_name,
temperature,
max_tokens,
conversation_id,
new_uuid,
user_input_text,
)
else:
url = "http://127.0.0.1:8088/data-chat?conversation_id={}&message_id={}&question={}".format(
# AppConfig.app_port,
conversation_id,
new_uuid,
user_input_text,
)
# 发送 HTTP 请求并获取响应
# print("准备发送response")
response = requests.get(url)
if response.status_code == 200:
# print(response)
data_out_put = response.json()
print("收到的服务端返回内容")
print(data_out_put)
filter_content = render_control_group(data_out_put,user_input_text,new_uuid)
else:
response_text = "请求失败,状态码: {}".format(response.status_code)
filter_content = response_text
return html.Div(
[
fac.AntdRow(
fac.AntdCol(
fac.AntdFlex(
[
fac.AntdAvatar(
mode="icon",
icon="antd-robot",
style={"background": "#1677ff"},
),
html.Div(
[
fuc.FefferyListenHover(
id={
"type": "chat-message-box-listen-hover",
"index": new_uuid,
},
targetSelector=".{}".format(
"chat-message-box-" + new_uuid
),
),
html.Div(
filter_content,
style={"background": "#f2f2f2"},
className="chat-message-box",
),
html.Div(
datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
className="chat-message-box-datetime-left",
),
fac.AntdSpace(
[
# fac.AntdButton(
# "重试",
# id={
# "type": "assistant-output-retry",
# "index": new_uuid,
# },
# icon=fac.AntdIcon(icon="antd-sync"),
# shape="round",
# size="small",
# ),
# fac.AntdButton(
# # "编辑",
# id={
# "type": "edit-widgets",
# "index": new_uuid,
# },
# icon=fac.AntdIcon(icon="antd-repair"),
# shape="round",
# size="small",
# ),
# fac.AntdButton(
# # "复制",
# id={
# "type": "copy-params",
# "index": new_uuid,
# },
# icon=fac.AntdIcon(icon="antd-copy"),
# shape="round",
# size="small",
# ),
# fac.AntdButton(
# # "模型",
# id={
# "type": "edit-widgets1",
# "index": new_uuid,
# },
# icon=fac.AntdIcon(icon="antd-copy"),
# shape="round",
# size="small",
# ),
fac.AntdButton(
# "编辑",
id={
"type": "edit-widgets",
"index": new_uuid,
},
icon=fac.AntdIcon(icon="fc-repair"),
shape="circle",
size="middle",
),
fac.AntdButton(
# "复制",
id={
"type": "copy-params",
"index": new_uuid,
},
icon=fac.AntdIcon(icon="fc-file"),
shape="circle",
size="middle",
),
# fac.AntdButton(
# # "模型",
# id={
# "type": "edit-widgets1",
# "index": new_uuid,
# },
# icon=fac.AntdIcon(icon="fc-radar-chart"),
# shape="circle",
# size="middle",
# ),
fac.AntdButton(
# "下载",
id='execute-download',
icon=fac.AntdIcon(icon="fc-download"),
shape="circle",
size="middle",
),
# 绑定的下载组件
dcc.Download(id='download-table')
],
id={
"type": "operation-button-group",
"index": new_uuid,
},
className="operation-button-group",
style={
"position": "absolute",
"top": -1,
"left": 100,
"opacity": 0,
"transform": "scale(1) translateY(0)",
},
),
],
),
],
vertical=True,
align="start",
gap=8,
style={"width": "100%"},
),
span=20,
style={"position": "relative"},
),
justify="start",
),
],
id={"type": "chat-message-box", "index": new_uuid},
className="chat-message-box-" + new_uuid,
)