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, )