first draft

This commit is contained in:
isaac hershenson
2024-06-26 19:15:13 -07:00
parent d023727fa0
commit 2f3b625d59
9 changed files with 184 additions and 48 deletions
+96
View File
@@ -0,0 +1,96 @@
import streamlit as st
from streamlit import runtime
from streamlit.runtime.scriptrunner import get_script_run_ctx
import asyncio
from streamlit_js_eval import streamlit_js_eval
import requests
async def main():
st.markdown("""
<style>
/* Centering title horizontally */
.centered-title {
text-align: center;
}
</style>""",unsafe_allow_html=True)
st.title("App Information")
st.write("This app is a quick example showing how you can use LangGraph Cloud in your development applications. \
Before playing with the app, I highly recommend reading through this info guide to gain a better understanding of how it works.")
st.header("Background")
st.write("This app was designed to show off some LangGraph Cloud features in a fun, interactive way. This app is designed to allow users to write a story \
with the help of a LangGraph agent. The app allows users to edit chapters they have written already, or continue the story by writing \
the next chapter as well. This means the user could have multiple versions of the same chapter number (for example chapter #3) and can select the \
one they like most to continue writing chpater #4. At the beginning the user provides the graph information on the summary of the story, the writing style \
they want, and any additional details important to the story. From that point on they just need to provide edit and continue instructions to steer the \
agent in the desired direction")
st.header("The Graph State")
st.write("One of the coolest features of LangGraph Cloud is the ability to have a persistent state across many runs of the graph. \
In this case we are able to retain information about the story as the user continues to write it. In our case, we keep an overall \
chapter state graph, which is just a dictionary containing the different chapter written so far. Each time you edit or continue the \
story a new chapter is added to the graph. Each chapter keeps information about its content, title, and the relationship it has with \
the other chapters in the story (i.e. what chapters are siblings, children, parents, or cousins to it). Below is an example of what the \
chapter graph would look like after a user has been using the Story Writing tool for a little bit:")
st.image('./img/Flowchart.jpg')
st.write("Let's dive into the graph to understand it a little better. First note that each color represents a different chapter number. In this instance \
we have two Chapter 1's, two Chapter 2's, three Chapter 3's, and a single Chapter 4. By following the node numbers we can reconstruct how this story \
was written. First, Node 1 was created when the user clicked on \"New Story\". Then Node 2 was created when the user pressed \"Continue\". The user \
then created Node 3 by editing the chapter that was contained in Node 1. You can follow the rest of the story creation on your own by tracking the \
increasing node numbers.")
st.write("When using the story app, you can navigate between previous chapters, next chapters, current chapters. It can be a little confusing to understand \
what chapters show up where, so let's take a look at an example where the user is currently viewing the chapter in Node 5. The following diagram \
highlights the relationships Node 5 has with other nodes, and the explanation below dives into how these relationships work and how they inform \
what previous, next, and current chapter options we have to choose from:")
st.image('./img/Flowchart-2.jpg')
st.write("In this diagram, we draw \
red arrows representing all of the other nodes the user could move to. \n \nThere is one \"Next Chapter\" option, Node 8, because Node 5 only has \
one child. If we were to press \"Continue\" again from Node 5 to create another child, there would then be two options for the \"Next Chapter\". \n \nThere\
are three current chapter options. The first is Node 5 itself (the chapter you are viewing is always an option to be the current chapter!) \
and then Nodes 6 and 7 are also options. Node 7 is a \"Sibling\" of Node 5 because it was created by editing from Node 5. If we were to further make \
an edit to Node 7, that new node would also be a siblig of Node 5. Any nodes that are direct \"edit descendants\" of a node are considered \"Siblings\" \
of that node. \n \nNode 6 is what we call a \"Cousin\" node because it originates from the same node as Node 5 (namely Node 4) but is not directly \
connected to it on our flow chart. Any nodes that originate from the same parent as a particular node are considered \"Cousin\" nodes. To summarize: \
the \"Current Chapter\" options consist of the current node itself, all of its \"Sibling\" nodes, and all of its \"Cousin\" nodes. \n\
Lastly, you can (unless you are at a node representing a Chapter 1 - in this case Node 1 and Node 3) go back to the previous chapter. Unlike the \
current or next chapter options, there is always only one previous chapter to go back to: your direct parent. For Node 5, its direct parent is \
Node 4, so that would show up as the option for the previous chapter. \n \nOne last important thing to note is that chapter options are displayed by their \
chapter title, which makes it slightly easier to know where you are going instead of relying on node numbers which don't have much significance to the user.")
st.header("Using the app")
st.subheader("While viewing a chapter")
st.write("There are a variety of different things to navigate through while using the app, let's walk through them by exploring this screenshot:")
st.image("./img/story_writing_screenshot.png")
st.write("At the top of the page you will see the title of the story, which is generated by the graph when the user starts the story. When you click on the \"Load Story\" \
button on the left hand navigation pane, stories are listed by their titles to make it easier for users to remember which story they are loading. \n \nBelow the \
story title, there are two dropdowns for selecting the previous or next chapter to navigate to. These only show up as dropdowns if there exist chapters \
to move to, otherwise text saying \"No next/previous chapters!\" will show up. \n \nBelow those dropdowns, you can see the current chapter title. Chapter \
titles are generated by the graph after the chapter has been written to try and allow the title to match the chapter content as much as possible. \n \nBelow \
the chapter title is the actual chapter content itself. The chapter content is inside a scrollable element to limit the amount of vertical space it takes up. \n \nBelow \
the chapter content is the chapter number. In this screenshot we are on Chapter 2 (remember from the node diagram that we can have multiple of a single chapter)")
st.subheader("While the graph is running")
st.image("./img/story_writing_graph_running.png")
st.header("Ideas for future work")
st.write("This ")
if __name__ == "__main__":
asyncio.run(main())
+2 -2
View File
@@ -133,11 +133,11 @@ def write_chapter(user_message, chapters_summary, state):
response = write_chain.invoke({'story_summary':chapters_summary,'action':user_message,'summary_request':state['summary_request'], \
'detail_request':state['detail_request'],'style_request':state['style_request'],'outline':outline})
chapter_content = response
chapter_title = summary_llm.invoke(f"Please come up with a title for the following chapter: {chapter_content}. The title should be 6 words or less.").content.replace("\"","").replace("\'","")
chapter_title = summary_llm.invoke(f"Please come up with a title for the following chapter: {chapter_content}. The title should be 6 words or less.").content.replace("\"","")
return chapter_content, chapter_title
def get_title(state,first_chapter):
return title_llm.invoke(f"Please come up with a short title, less than 6 words, for a story. The story has the following overall plot {state['summary']}, and here is the first chapter {first_chapter}").content.replace("\"","").replace("\'","")
return title_llm.invoke(f"Please come up with a short title, less than 6 words, for a story. The story has the following overall plot {state['summary']}, and here is the first chapter {first_chapter}").content.replace("\"","")
def write_first_chapter(state):
if state['summary']:
Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

-4
View File
@@ -1,10 +1,6 @@
{
"python_version": "3.11",
"dependencies": [
"langchain_anthropic",
"langgraph",
"langchain_openai",
"langchain_core",
"."
],
"graphs": {
@@ -6,6 +6,7 @@ import asyncio
from langsmith import Client
from streamlit_extras.stylable_container import stylable_container
import requests
st.set_page_config(layout="wide")
feedback_client = Client(api_url="https://beta.api.smith.langchain.com")
@@ -72,7 +73,7 @@ async def run_graph_with_input(client,thread,assistant,input,metadata={}):
# Need to add streaming capability
data = []
async for chunk in client.runs.stream(
thread['thread_id'], assistant['assistant_id'], input=input, config={'configurable':metadata}, stream_mode="updates",
thread['thread_id'], assistant['assistant_id'], input=input, config={'configurable':metadata}, stream_mode="updates", multitask_strategy="rollback",
):
if chunk.data and 'run_id' not in chunk.data:
for t in ['rewrite','continue','first']:
@@ -93,7 +94,9 @@ llm_to_title = {
async def generate_answer(placeholder, placeholder_title, input, client, thread, assistant, metadata = {}):
current_llm = "starting"
placeholder_title.write(llm_to_title[current_llm])
placeholder_title.markdown(f"<h4 style='text-align: center; color: rgb(206,234,253);'> \
{llm_to_title[current_llm]} \
</h4>",unsafe_allow_html=True)
current_ind = 0
ans = ""
async for chunk in client.runs.stream(
@@ -103,7 +106,9 @@ async def generate_answer(placeholder, placeholder_title, input, client, thread,
if chunk.data and 'run_id' not in chunk.data:
if isinstance(chunk.data,dict):
current_llm = chunk.data[list(chunk.data.keys())[0]]['metadata']['name']
placeholder_title.write(llm_to_title[current_llm])
placeholder_title.markdown(f"<h4 style='text-align: center; color: rgb(206,234,253);'> \
{llm_to_title[current_llm]} \
</h4>",unsafe_allow_html=True)
elif current_llm == "write_llm" and chunk.data[0]['content']:
ans += chunk.data[0]['content'][current_ind:]
placeholder.info(ans)
@@ -161,6 +166,7 @@ async def update_session_variables():
async def reset_session_variables(ip_address):
st.session_state.chapter_graph = {"-1":{'content':"Click Start Story to begin writing!", 'title':"Pre-start Chapter"}}
st.session_state.currently_selected_chapter = "-1"
st.session_state.chapter_number = 0
st.session_state.current_node_id = '1'
st.session_state.story_title = ""
st.session_state.current_chapter_options = ["-1"]
@@ -170,10 +176,18 @@ async def stream(*args):
await asyncio.gather(call_async_function_safely(generate_answer,*args))
async def main():
st.markdown("""
<style>
/* Centering title horizontally */
.centered-title {
text-align: center;
}
</style>
""", unsafe_allow_html=True)
if "story_title" not in st.session_state or st.session_state.story_title == "":
st.title("Story Writing with Langgraph")
st.markdown("<h1 class='centered-title'>Story Writing with Langgraph</h1>", unsafe_allow_html=True)
else:
st.title(st.session_state.story_title)
st.markdown(f"<h1 class='centered-title'>{st.session_state.story_title}</h1>", unsafe_allow_html=True)
if "page_loaded" not in st.session_state:
st.session_state.page_loaded = False
@@ -227,13 +241,12 @@ async def main():
await stream(st.session_state.box,st.session_state.box_title,{'summary':summary_text,'details':detail_text,'style':style_text},st.session_state.client,st.session_state.thread,
st.session_state.assistant,{"node_id":st.session_state.current_node_id})
st.session_state.box_title.write("Saving chapter and returning to story view")
await asyncio.sleep(5)
st.session_state.story_started = True
await update_session_variables()
st.session_state.show_start_input = False
st.session_state.writing = False
st.session_state.chapter_number = 1
st.rerun()
elif st.session_state.show_edit_input:
edit_chapter_text = st.sidebar.text_area("Edit Instructions")
@@ -249,8 +262,7 @@ async def main():
await stream(st.session_state.box,st.session_state.box_title,{'rewrite_instructions':edit_chapter_text},st.session_state.client,st.session_state.thread,
st.session_state.assistant,{"node_id":st.session_state.current_node_id})
st.session_state.box_title.write("Saving chapter and returning to story view")
await asyncio.sleep(5)
await update_session_variables()
st.session_state.show_edit_input = False
st.session_state.writing = False
@@ -269,11 +281,11 @@ async def main():
await stream(st.session_state.box,st.session_state.box_title,{'continue_instructions':next_chapter_text},st.session_state.client,st.session_state.thread,
st.session_state.assistant,{"node_id":st.session_state.current_node_id})
st.session_state.box_title.write("Saving chapter and returning to story view")
await asyncio.sleep(5)
await update_session_variables()
st.session_state.show_continue_input = False
st.session_state.writing = False
st.session_state.chapter_number += 1
st.rerun()
elif st.session_state.show_load_story:
col1, col2 = st.sidebar.columns([1, 1])
@@ -318,6 +330,15 @@ async def main():
st.sidebar.write(" ")
st.markdown("""
<style>
div[data-testid="stHorizontalBlock"] > * {
max-height: 300px;
overflow-y: auto;
}
</style>
""", unsafe_allow_html=True)
col1, _, col3 = st.columns([1, 2, 1])
if st.session_state.writing == False:
@@ -328,7 +349,9 @@ async def main():
label_visibility="collapsed",key=f"previous_chapter_{st.session_state.num_selected}")
else:
st.session_state.selected_previous_chapter = None
st.write("No previous chapters!")
st.markdown(f"<p style='text-align: center;'> \
No previous chapters! \
</p>",unsafe_allow_html=True)
if st.session_state.selected_previous_chapter is not None:
@@ -337,6 +360,7 @@ async def main():
await call_async_function_safely(update_current_state,st.session_state.client,st.session_state.thread,{'chapter_id_viewing':new_chapter_selected})
await call_async_function_safely(update_session_variables)
st.session_state.chapter_number -= 1
st.rerun()
with col3:
options = transform_titles_into_options([st.session_state.chapter_graph[chapter_id]['title'] for chapter_id in st.session_state.next_chapter_options])
@@ -345,7 +369,9 @@ async def main():
label_visibility="collapsed",key=f"next_chapter_{st.session_state.num_selected}")
else:
st.session_state.selected_next_chapter = None
st.write("No next chapters!")
st.markdown(f"<p style='text-align: center;'> \
No next chapters! \
</p>",unsafe_allow_html=True)
if st.session_state.selected_next_chapter is not None:
@@ -354,36 +380,48 @@ async def main():
await call_async_function_safely(update_current_state,st.session_state.client,st.session_state.thread,{'chapter_id_viewing':new_chapter_selected})
await call_async_function_safely(update_session_variables)
st.session_state.chapter_number += 1
st.rerun()
_, col_middle_title, _ = st.columns([1, 6, 1])
if "box_title" not in st.session_state:
if "box_title" not in st.session_state or st.session_state.writing == False:
st.session_state.box_title = col_middle_title.empty()
elif st.session_state.writing == True:
with col_middle_title:
st.write("Waiting for user input...")
st.markdown(f"<h4 style='text-align: center; color: rgb(206,234,253);'> \
Waiting for user input... \
</h4>",unsafe_allow_html=True)
_, col_middle, _ = st.columns([1, 6, 1])
st.session_state.chapter_title = st.markdown(f"<h2 style='text-align: center; color: white;'> \
{st.session_state.chapter_graph[st.session_state.currently_selected_chapter]['title']} \
</h2>",unsafe_allow_html=True)
_, col_middle, col_scroll = st.columns([1, 6, 1])
if "box" not in st.session_state:
st.session_state.box = col_middle.empty()
st.rerun()
else:
st.session_state.box.info(st.session_state.chapter_graph[st.session_state.currently_selected_chapter]['content'])
if st.session_state.writing == False:
st.session_state.chapter_title = st.markdown(f"<h2 style='text-align: center; color: white;'> \
{st.session_state.chapter_graph[st.session_state.currently_selected_chapter]['title']} \
</h2>",unsafe_allow_html=True)
st.session_state.chapter_content = st.text_area(" ", value=st.session_state.chapter_graph[st.session_state.currently_selected_chapter]['content'], height=450)
with col_scroll:
st.write("🔺 \nScroll \n🔻")
st.markdown(f"<h5 style='text-align: center;'> \
Chapter {st.session_state.chapter_number} \
</h5>",unsafe_allow_html=True)
_, col2, _ = st.columns([1, 2, 1])
if st.session_state.writing == False:
with col2:
options = transform_titles_into_options([st.session_state.chapter_graph[chapter_id]['title'] for chapter_id in st.session_state.current_chapter_options])
options = transform_titles_into_options([st.session_state.chapter_graph[chapter_id]['title'] for chapter_id in st.session_state.current_chapter_options if chapter_id != "-1"])
if len(options) > 0:
st.session_state.selected_current_chapter = st.selectbox("",options,index=None,placeholder="Select current chapter", \
label_visibility="collapsed",key=f"current_chapter_{st.session_state.num_selected}")
else:
st.session_state.selected_current_chapter = None
st.write("No alternate current chapters!")
st.markdown(f"<p style='text-align: center;'> \
No alternate current chapters! \
</p>",unsafe_allow_html=True)
if st.session_state.selected_current_chapter is not None:
st.session_state.num_selected += 1
@@ -404,6 +442,7 @@ async def main():
background-color: red;
color: white;
border-radius: 20px;
margin-left: 80px;
}
""",
):
@@ -424,6 +463,7 @@ async def main():
background-color: green;
color: white;
border-radius: 20px;
margin-left: 80px;
}
""",
):
+23 -19
View File
@@ -1,32 +1,36 @@
import streamlit as st
from streamlit import components
import streamlit as st
st.set_page_config(layout="wide")
def main():
# Custom CSS to potentially hide tooltips on selectbox
custom_css = """
<style>
/* Example CSS to hide tooltips on selectbox */
option {
pointer-events: none; /* Disable mouse events */
cursor: default; /* Remove cursor change */
/* Add other CSS properties to hide tooltips */
}
</style>
"""
st.title("Multiple Columns Example")
# Injecting custom CSS
st.markdown(custom_css, unsafe_allow_html=True)
# First set of columns
col1, col2 = st.columns([1, 3])
# Example usage of selectbox
with col1:
st.header("Column 1")
st.write("Content for column 1")
_, col2, _ = st.columns([3, 1, 3])
with col2:
option = st.selectbox("Select an option", ["Option 1 more text", \
"Option 2 also more text", "Option 3 lots of text"])
st.header("Column 2")
st.write("Content for column 2")
# Second set of columns
col3, col4, col5 = st.columns([1, 1, 1])
with col3:
st.header("Column 3")
st.write("Content for column 3")
with col4:
st.header("Column 4")
st.write("Content for column 4")
with col5:
st.header("Column 5")
st.write("Content for column 5")
if __name__ == "__main__":
main()
main()