diagram001 / app.py
allinaigc's picture
Update app.py
546ed9f verified
"""
1. 用Mermaid Mardown来画各种图,包括:脑图、流程图、客户历程图、关系图等。
1. 目前ChatGPT级别和百度文星一言等级别的大模型都可以比较好的输出Mermaid Markdown格式图。
1. 注意Mermaid代码中,不需要任何的’‘’, ````,或者"",这里只需要直接写Mermaid代码即可。
1. 完成了regenerate功能。
1. 完成了历史记忆功能,可以对图进行持续优化。但是不能保存中间结果。
1. 已知问题和解决方法:
1. 已解决。尝试了多个方法,目前无法把HTML展现的内容用图片保存,目前不行。直接保存MHTML文件。
1. HTML文件本身可以保存。
1. 用html2image库,可以把HTML文件转换为图片,但是无法在streamlit中展示(内容为空白)。
1. 可以想到的workaround:把HTML页面保存下来。
1. HTML的高度只能手工制定,无法自动调整。可能垂直的页面会被截断。已经解决,通过设定scrolling=True, 可以实现滚动条。高度不在重要。
1. 已解决。点击下载button后,页面会刷新。采用一个特殊的extension,https://github.com/PaleNeutron/streamlit-ext
"""
####TODO:
import json
import pprint
import streamlit as st
import chatgpt
import markdown
import md_mermaid
# from streamlit import components
import requests
import re
from openai import OpenAI
import streamlit_authenticator as stauth
import streamlit_ext as ste ##TODO: 为了点击download button后保持页面。
from datetime import datetime
from pytz import timezone
### streamlit app title
st.set_page_config(layout="wide", page_icon='llm_icon.png') ## 必须是第一行
st.title(f"大语言模型 - 体系图 | 框架图 | 逻辑图 | 流程图 - 辅助设计中心", anchor='Title')
st.subheader("AI Flowchart - Mindmap - Relation Diagram Design for Professionals")
st.info('如果输出图例时遇见任何问题(如:syntax error)或者不满意当前结果,请在左侧重新提交您的问题即可。一般建议至少尝试3-10次。')
## toast effect.
# msg = st.toast('程序正在启动中,请稍等...',)
# msg.toast('大语言模型成功加载!', icon = "🥞")
# st.toast('大语言模型成功加载!', icon = "🥞")
# st.divider()
# st.markdown('_说明:如果输出图例遇见有任何问题,请刷新页面再重新提交您的问题即可!_')
# st.snow() ##可以在页面上显示雪花效果。
# st.balloons() ##可以在页面上显示气球效果。
### system prompt设置
openai_client = OpenAI()
# user_input = ""
system_prompt = f"""你是一个Mermaid Markdown方面的设计专家。你需要根据'我的要求'用Mermaid Markdown来画出对应的图。你仅需要提供Mermaid Markdown格式的代码,你不允许输出任何说明、解释或者提示的内容。"""
# system_prompt = f"""你是一个Mermaid Markdown方面的设计专家。你需要根据'我的要求'用Mermaid Markdown来画出对应的图。你仅需要提供Mermaid Markdown格式的代码,你不允许输出任何说明、解释或者提示的内容。
# '我的要求'如下:{user_input}"""
### 用户输入框
# Initialize chat history
if "messages" not in st.session_state:
st.session_state.messages = [] ### original code here.
# st.session_state.messages = [{"role": "system", "content": "你是一个Mermaid Markdown专家"}]
# st.session_state.messages = [{"role": "system", "content": system_prompt}]
# st.session_state.messages = [{"role": "system", "content": '你是一个Mermaid Markdown方面的设计专家。'},{"role": "user", "content": ''}]
user_input = st.chat_input("说点什么吧...") ### original code here.
# if user_input:
# with st.chat_message("user"):
# st.markdown(user_input) ## 这里只需要输出用户的prompt,而不是total prompt。
# st.session_state.messages.append({"role": "user", "content": user_input})
print('st.session_state.messages:')
pprint.pprint(st.session_state.messages)
# print('user_input:', json.dumps(user_input))
print('user_input now:')
pprint.pprint(user_input)
# print('user_input now', user_input)
# st.session_state.messages.append({"role": "user", "content": user_input})
### 设定一个历史信息的列表,用于存储用户的输入。为了regenerate按钮的功能。
# hist_msg = []
### 配置前端有关函数
# user_input = None
def clear_all():
st.session_state.conversation = None
st.session_state.chat_history = None
st.session_state.messages = []
message_placeholder = st.empty()
st.session_state["my_question"] = None
return None
### 重新生成按钮, regenerate, rerun(streamlit中的rerun是把整个页面重新加载一次?), resubmit.
def regenerate():
html_file = ""
## 因为可能没有历史,第一次的时候,所以需要处理异常。
try:
print('st.session_state.messages inside REGENERATE function:')
pprint.pprint(st.session_state.messages)
st.session_state.messages = st.session_state.messages[:-1]
html_file = main(input=st.session_state.messages[0]['content']) ### original code here.
# html_file = main(input=st.session_state.messages[-1]['content']) ### original code here.
except Exception as e:
print('Error:', e)
pass
return html_file
### authentication with a local yaml file.
import yaml
from yaml.loader import SafeLoader
with open('./config.yaml') as file:
config = yaml.load(file, Loader=SafeLoader)
authenticator = stauth.Authenticate(
config['credentials'],
config['cookie']['name'],
config['cookie']['key'],
config['cookie']['expiry_days'],
config['preauthorized']
)
user, authentication_status, username = authenticator.login('main')
# user, authentication_status, username = authenticator.login('用户登录', 'main')
### streamlit sidebar
if authentication_status:
with st.sidebar:
st.markdown(
"""
<style>
[data-testid="stSidebar"][aria-expanded="true"]{
min-width: 450px;
max-width: 450px;
}
""",
unsafe_allow_html=True,
)
### siderbar的题目。
### siderbar的题目。
# st.header(f'**大语言模型专家系统工作设定区**')
# st.header(f'**系统控制面板** ')
st.header(f'**欢迎 **{username}** 来到人工智能的世界** ♠')
# st.header(f'**欢迎 **{username}** 使用本系统** ') ## 用户登录显示。
st.write(f'_Large Language Model System Environment_')
authenticator.logout('登出', 'sidebar')
# st.divider()
st.sidebar.button("清除记录,重启一轮新对话", on_click=clear_all, use_container_width=True, type='primary')
re_btn = st.sidebar.button("重新生成答案", use_container_width=True, type='secondary')
# if re_btn:
# regenerate()
# re_btn = st.sidebar.button("重新生成答案", on_click=regenerate, use_container_width=True, type='primary')
## 在sidebar上的三个分页显示,用st.tabs实现。
# tab_1, tab_2, tab_4 = st.tabs(['使用须知', '模型参数', '角色设定'])
tab_1, tab_2, tab_3, tab_4 = st.tabs(['基本介绍', '大模型参数', '提示词示例', '使用技巧'])
# with st.expander(label='**使用须知**', expanded=False):
with tab_1:
# st.markdown("#### 快速上手指南")
# with st.text(body="说明"):
# st.markdown("* 重启一轮新对话时,只需要刷新页面(按Ctrl/Command + R)即可。")
with st.text(body="说明"):
st.markdown("""* **使用大型语言模型设计体系图、框架图、逻辑图、流程图、关系图主要有以下几个步骤:**
1. **明确目的和要表达的内容**
在开始之前,你需要明确你想用图表来表达什么内容, 以及它的目的是什么。这将为接下来的步骤提供指导。
2. **收集并组织相关信息**
根据你的目的,收集所有相关的信息、数据和要点。将它们按合理的方式分类和组织, 为后续生成图表做好准备。
3. **结构化输入**
将组织好的内容转化为语言模型可以理解的结构化输入。比如使用简单描述、列表、树状结构等形式。
4. **生成参考版本**
将结构化输入提交给大型语言模型, 让它尝试生成供参考的流程图、脑图或关系图。
* 需要注意的是, 尽管大型语言模型可以生成很好的草图和初始版本, 但最终结果的质量仍然取决于你对目标和输入的描述质量。人工审查和调整是必不可少的环节。模型只是辅助工具, 无法完全替代人的判断和创造力。""")
## 大模型参数
# with st.expander(label='**大语言模型参数**', expanded=True):
with tab_2:
max_tokens = st.slider(label='Max_Token(生成结果时最大字数)', min_value=100, max_value=4096, value=2048, step=100)
temperature = st.slider(label='Temperature (温度)', min_value=0.0, max_value=1.0, value=0.8, step=0.1)
top_p = st.slider(label='Top_P (核采样)', min_value=0.0, max_value=1.0, value=0.6, step=0.1)
frequency_penalty = st.slider(label='Frequency Penalty (重复度惩罚因子)', min_value=-2.0, max_value=2.0, value=1.0, step=0.1)
presence_penalty = st.slider(label='Presence Penalty (控制主题的重复度)', min_value=-2.0, max_value=2.0, value=1.0, step=0.1)
## reset password widget
# try:
# if authenticator.reset_password(st.session_state["username"], 'Reset password'):
# st.success('Password modified successfully')
# except Exception as e:
# st.error(e)
# with st.header(body="欢迎"):
# st.markdown("# 欢迎使用大语言模型商业智能中心")
# with st.expander(label=("**重要的使用注意事项**"), expanded=True):
# with st.container():
##NOTE: 在SQL场景去不需要展示这些提示词。
with tab_3:
# st.write("#### Prompt提示词参考资料")
st.code(body="你输出一个复杂的电子商务流程示意图,必须包含客户投诉退款环节。", language='plaintext')
st.code(body="给我一个复杂的业务需求分析流程图。", language='plaintext')
st.code(body="完整的Hermes客户体验流程图。", language='plaintext')
st.code(body="完整的苹果公司线下实体店客户历程图。", language='plaintext')
st.code(body="绘制一个横向的详细的IT运维流程示例。", language='plaintext')
st.code(body="画一个汽车4S店的用户历程的复杂流程(至少要包含:客户邀约,客户试驾等)。", language='plaintext')
st.code(body="绘制一张保险项目的甘特图。", language='plaintext')
st.code(body="画一个用户历程的复杂流程。", language='plaintext')
st.code(body="绘制一张复杂的绩效管理脑图。", language='plaintext')
st.code(body="给我一个复杂的客户体验脑图示例。", language='plaintext')
st.code(body="你给我一个全面质量管理的头脑风暴脑图。", language='plaintext')
st.code(body="给我一个全面且复杂的精益管理流程图,图上需要每一步的说明。", language='plaintext')
st.code(body="你做一个青少儿教育培训机构的完整电话邀约流程。", language='plaintext')
st.code(body="在上面流程的基础上,继续细化”系统设计“部分的内容。", language='plaintext')
with tab_4:
st.markdown('''
1. 使用`下载图例`按钮,可以直接下载MHTML格式的文件,大部分浏览器支持直接打开。
1. 使用`下载代码`按钮,可以下载原始的Markdown代码,然后可以在类似 `https://mermaid.live/` 网站手动编辑。
1. 目前支持持续修改流程版本,但不建议过度修改。修改的中间过程内容不会被保存。
''')
elif authentication_status == False:
st.error('⛔ 用户名或密码错误!')
elif authentication_status == None:
st.warning('⬆ 请先登录!')
### 得到当前的时间
def get_current_time():
beijing_tz = timezone('Asia/Shanghai')
beijing_time = datetime.now(beijing_tz)
current_time = beijing_time.strftime('%H:%M:%S')
return current_time
## 显示Mermaid图的核心函数
def mermaid(code: str):
# st.write(code) ### 检查输入的Mermaid Markdown代码。
from streamlit import components
### 以下返回的是HTML文件。
html_file = components.v1.html(
f"""
<pre class="mermaid">
{code}
</pre>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
mermaid.initialize({{ startOnLoad: true }});
</script>
"""
,
height=400, ##NOTE: 这里可以有效改变HTML页面的高度。
scrolling=True, ##NOTE: 可以实现滚动条。高度不在重要。
)
## 以下是以字符串的形式返回HTML代码。
html_file = str(f"""
<pre class="mermaid">
{code}
</pre>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
mermaid.initialize({{ startOnLoad: true }});
</script>
""")
## 以下是以字符串的形式, 且只返回code部分,为了可以在https://mermaid.live/ 编辑。
mermaid_str = code
return html_file, mermaid_str
# return html_file
def markdown_chart(input:str):
# """answer general questions."""
# final_prompt = f"""你是一个Mermaid Markdown方面的设计专家。你需要根据'我的要求'用Mermaid Markdown来画出对应的图。你仅需要提供Mermaid Markdown格式的代码,你不允许输出任何说明、解释或者提示的内容。
# '我的要求'如下:{user_input}"""
# print('user_input now', user_input)
# st.session_state.messages.append(
# {"role": "user", "content": user_input})
# final_prompt = system_prompt + user_input
if input:
# st.session_state.messages.append({"role": "user", "content": input})
# with st.chat_message("user"):
# st.markdown(user_input) ## 这里只需要输出用户的prompt,而不是total prompt。
with st.chat_message("assistant", avatar="./llm_icon.png"):
message_placeholder = st.empty()
full_response = ""
# llm_response = chatgpt.chatgpt(user_prompt=final_prompt) ### original code here.
print('st.session_state.messages:')
pprint.pprint(st.session_state.messages)
# llm_response = chatgpt.chatgpt(user_prompt=st.session_state.messages) ### original code here.
chatgpt_response = openai_client.chat.completions.create(model="gpt-3.5-turbo-16k",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": json.dumps(st.session_state.messages, ensure_ascii=False)}, ##NOTE:注意这个形式。
],
# messages=[
# {"role": "system", "content": system_prompt},
# {"role": "user", "content": st.session_state.messages[0]['content']},
# ], ### original code here.
stream=False,
)
llm_response = chatgpt_response.choices[0].message.content
message_placeholder.markdown('结果如下:')
st.session_state.messages.append(
{"role": "assistant", "content": llm_response})
# llm_response = chatgpt.chatgpt(user_prompt=final_prompt) ### original code here.
###NOTE: 需要去除mermaid关键字,需要去除反引号。
try:
### 删除所有的非Mermaid格式的字符,反引号前后的文字。
# pattern = r"mermaid\n(.*?\n?)"
# match = re.search(pattern, llm_response, re.DOTALL)
# if match:
# md_input = match.group(1)
# md_input = str(re.split(r'^(.?)\nmermaid\n(.*?)\n*\n*(.*?)$', llm_response, flags=re.MULTILINE|re.DOTALL))
### 尝试各种正则式,为了只保留Mermaid Markdown格式的内容。
pattern = r'```mermaid([\s\S]*?)```'
match = re.search(pattern, llm_response)
if match:
md_input = match.group(1).strip()
# print(md_input)
if md_input:
md_input = md_input.replace('mermaid', '') ##! working!!!这里需要!
# md_input = md_input.replace('mermaid', ' ') ##! working!!!这里需要!
else:
md_input = llm_response.replace('mermaid', '') ##! working!!!这里需要!
md_input = md_input.replace("```", '') ###! working!注意这里需要去掉的是三个反引号(```),而不是一个。
except Exception as e:
print('Error:', e)
md_input = llm_response
# mermaid(md_input)
html_file = mermaid(md_input)
# ##保存HTML的方法
# html_file = mermaid(md_input)
# # print('type of html_file:', type(html_file))
# with open('output.html', 'w', encoding='utf-8') as f: ### 可以保存HTML文件。
# f.write(html_file)
# from html2image import Html2Image
# css_settings = '''
# .center {
# margin: auto;
# height: 500px;
# width: 500px;
# }
# body {
# background-color: lightgrey;
# height: 100%;
# display: grid;
# }
# '''
# css_settings = "body {background: grey;}"
###TODO:尝试将HTML文件转成图片,目前各种方法都不成功。
# hti = Html2Image()
# img_path = hti.screenshot(html_file='./output.html', save_as='html.png', size=(500, 200))
# img_path = hti.screenshot(html_file=html_file, save_as='html.png', css_str=css_settings, size=(500, 200))
# print('img path:', img_path)
# diagram_time = get_current_time()
# st.success(body=f'程序运行完成!当前时间:{diagram_time}。', icon='💯')
# return None
return html_file
### 测试prompt
# markdown_chart("画一个流程图,描述用户访问网站的流程。你只需要提供Mermaid Markdown格式的代码,不需要任何额外的说明或者解释")
## 给按键设置的CSS,据说可以把button放在一起更加紧密。参见: https://discuss.streamlit.io/t/st-button-in-one-line/25966/6
st.markdown("""
<style>
div[data-testid="column"] {
width: fit-content !important;
flex: unset;
}
div[data-testid="column"] * {
width: fit-content !important;
}
</style>
""", unsafe_allow_html=True)
## TODO:看看是否可以保持页面内容,在download button之后。
# user_input = st.chat_input("说点什么吧...")
def main(input):
# user_input = st.text_input(label='输入您的问题', placeholder='给我一个复杂的业务需求分析流程图。', label_visibility='visible')
if input:
# st.session_state.messages.append(user_input)
# with st.status('检索中...', expanded=False, state='running') as status:
with st.chat_message("user"):
st.markdown(input) ## 这里只需要输出用户的prompt,而不是total prompt。
st.session_state.messages.append({"role": "user", "content": input})
spinner = st.spinner('处理中...请耐心等待')
with spinner:
html_file, mermaid_code = markdown_chart(input=input)
diagram_time = get_current_time()
st.success(body=f'程序运行完成!当前时间:{diagram_time}。', icon='💯')
## 可以直接下载HTML文件。让可以展示所有的相关图片。
if html_file:
col1, col2, col3 = st.columns([1, 1, 8])
with col1:
ste.download_button(
label="下载图例",
data=html_file,
file_name='mydiagram.html',
# mime='text/markdown',
)
# st.download_button(
# label="下载上述图例",
# data=html_file,
# file_name='mydiagram.html',
# # mime='text/markdown',
# )
with col2:
ste.download_button(
label="下载代码",
data=mermaid_code,
file_name='mydiagram.txt',
# mime='text/markdown',
)
# st.download_button(
# label="下载上述图例",
# data=html_file,
# file_name='mydiagram.html',
# # mime='text/markdown',
# )
##NOTE:上面的download button的高度与一般的st.button不同。
# with col2:
# # st.button("重新生成答案")
# print('st.session_state.messages now', st.session_state.messages)
# st.button("重新生成答案", on_click=regenerate)
# if re_btn:
# regenerate()
return html_file
# user_input = st.text_input("说点什么吧...")
if __name__ == '__main__':
html_file = main(input=user_input)
##! working. 需要先在sidebar上设置re_btn,然后在这里调用regenerate函数。而不是在button里面直接用on-click来触发函数。
# if re_btn:
# regenerate()
try:
if re_btn:
regenerate()
except Exception as e:
print('Error:', e)
pass