Spaces:
Running
Running
""" | |
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 |