mirror of
https://github.com/langchain-ai/story-writing.git
synced 2026-07-01 20:04:02 -04:00
second draft
This commit is contained in:
+10
-10
@@ -12,23 +12,23 @@ async def main():
|
||||
|
||||
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.write("This app is a quick example showing how you can use LangGraph Cloud in your applications. \
|
||||
Before using 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 \
|
||||
the next chapter. This means the user can have multiple versions of the same chapter number and can select the \
|
||||
one they like most to continue the story from. 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 they just need to provide edit and continue instructions to steer the \
|
||||
agent in the desired direction. \n \nNote: This app is a prototype and not ready for deployment. There might be bugs/poor results from the agent.")
|
||||
|
||||
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 \
|
||||
In this case we are able to retain information about the story as the graph continues to write it. In our case, we keep an overall \
|
||||
chapter state graph, which is just a dictionary containing the different chapters 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:")
|
||||
@@ -41,7 +41,7 @@ async def main():
|
||||
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 \
|
||||
st.write("When using the story app, you can navigate between previous chapters, next chapters, current chapters. It can be a little hard 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:")
|
||||
@@ -53,8 +53,8 @@ async def main():
|
||||
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\" \
|
||||
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 make \
|
||||
further edits 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\
|
||||
|
||||
@@ -7,6 +7,7 @@ from langchain_core.prompts import ChatPromptTemplate
|
||||
from copy import deepcopy
|
||||
|
||||
title_llm = ChatOpenAI(model="gpt-4o",metadata={"name":"title_llm"})
|
||||
chapter_title_llm = ChatOpenAI(model="gpt-4o",metadata={"name":"chapter_title_llm"})
|
||||
summary_llm = ChatOpenAI(model="gpt-4o",metadata={"name":"summary_llm"})
|
||||
brainstorm_llm = ChatOpenAI(model="gpt-4o",temperature=1,metadata={"name":"brainstorm_llm"})
|
||||
plan_llm = ChatAnthropic(model="claude-3-haiku-20240307",metadata={"name":"plan_llm"})
|
||||
@@ -71,58 +72,23 @@ brainstorm_prompt = ChatPromptTemplate.from_messages(brainstorm_messages)
|
||||
outline_prompt = ChatPromptTemplate.from_messages(outline_messages)
|
||||
write_prompt = ChatPromptTemplate.from_messages(write_messages)
|
||||
|
||||
# Turn these into nodes with llm as router to run tests on each of the chains
|
||||
# Add few shot prompting to improve the performance (get results from Langsmith)
|
||||
summary_chain = summary_prompt | summary_llm | StrOutputParser()
|
||||
brainstorm_chain = brainstorm_prompt | brainstorm_llm | StrOutputParser()
|
||||
outline_chain = outline_prompt | plan_llm | StrOutputParser()
|
||||
write_chain = write_prompt | write_llm | StrOutputParser()
|
||||
|
||||
class Chapter(TypedDict):
|
||||
content: str
|
||||
title: str
|
||||
children: list
|
||||
siblings: list
|
||||
cousins: list
|
||||
parent: int
|
||||
|
||||
def update_chapter_graph(old_chapter_graph, new_chapter_graph):
|
||||
if isinstance(new_chapter_graph,dict):
|
||||
old_chapter_graph.update(new_chapter_graph)
|
||||
return old_chapter_graph
|
||||
|
||||
class State(TypedDict):
|
||||
# User's initial inputs (could be updated later)
|
||||
summary: str
|
||||
details: str
|
||||
style: str
|
||||
summary_request: str = ""
|
||||
detail_request: str = ""
|
||||
style_request: str = ""
|
||||
chapter_graph: Annotated[dict[str, Chapter], update_chapter_graph]
|
||||
chapter_id_viewing: str
|
||||
current_chapter_id: str
|
||||
rewrite_instructions: str
|
||||
continue_instructions: str
|
||||
story_title: str = ""
|
||||
|
||||
edit_prompt = """Here is the current state of the new chapter:
|
||||
|
||||
<Draft>
|
||||
{draft}
|
||||
</Draft>
|
||||
|
||||
Here are some edits I want to make to that chapter:
|
||||
|
||||
<EditInstructions>
|
||||
{edit}
|
||||
</EditInstructions>"""
|
||||
|
||||
continue_prompt = """Here is what I want in the next chapter:
|
||||
|
||||
<Instructions>
|
||||
{instructions}
|
||||
</Instructions>"""
|
||||
def summarize_current_story(state,chapter_id):
|
||||
if chapter_id == '-1':
|
||||
return ""
|
||||
current_chapter_id = chapter_id
|
||||
chapters_currently_selected_text = [state['chapter_graph'][current_chapter_id]['content']]
|
||||
while state['chapter_graph'][current_chapter_id]['parent'] != '-1':
|
||||
chapters_currently_selected_text.append(state['chapter_graph'][current_chapter_id]['content'])
|
||||
current_chapter_id = state['chapter_graph'][current_chapter_id]['parent']
|
||||
chapters_str = "\n\n".join(
|
||||
[f"Chapter {i}\n\n{chapters_currently_selected_text[i]}" for i in range(len(chapters_currently_selected_text))]
|
||||
).strip()
|
||||
return summary_chain.invoke({"chapters_str": chapters_str})
|
||||
|
||||
def write_chapter(user_message, chapters_summary, state):
|
||||
brainstorm_ideas = brainstorm_chain.invoke({'story_summary':chapters_summary,'action':user_message,'summary_request':state['summary_request'], \
|
||||
@@ -133,7 +99,7 @@ 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("\"","")
|
||||
chapter_title = chapter_title_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):
|
||||
@@ -157,18 +123,17 @@ def write_first_chapter(state):
|
||||
state['story_title'] = get_title(state,chapter_content)
|
||||
return state
|
||||
|
||||
def summarize_current_story(state,chapter_id):
|
||||
if chapter_id == '-1':
|
||||
return ""
|
||||
current_chapter_id = chapter_id
|
||||
chapters_currently_selected_text = [state['chapter_graph'][current_chapter_id]['content']]
|
||||
while state['chapter_graph'][current_chapter_id]['parent'] != '-1':
|
||||
chapters_currently_selected_text.append(state['chapter_graph'][current_chapter_id]['content'])
|
||||
current_chapter_id = state['chapter_graph'][current_chapter_id]['parent']
|
||||
chapters_str = "\n\n".join(
|
||||
[f"Chapter {i}\n\n{chapters_currently_selected_text[i]}" for i in range(len(chapters_currently_selected_text))]
|
||||
).strip()
|
||||
return summary_chain.invoke({"chapters_str": chapters_str})
|
||||
edit_prompt = """Here is the current state of the new chapter:
|
||||
|
||||
<Draft>
|
||||
{draft}
|
||||
</Draft>
|
||||
|
||||
Here are some edits I want to make to that chapter:
|
||||
|
||||
<EditInstructions>
|
||||
{edit}
|
||||
</EditInstructions>"""
|
||||
|
||||
def edit_chapter(state):
|
||||
chapters_summary = summarize_current_story(state,state['chapter_graph'][state["chapter_id_viewing"]]['parent'])
|
||||
@@ -182,11 +147,11 @@ def edit_chapter(state):
|
||||
|
||||
#create new chapter
|
||||
state['chapter_graph'][str(int(state["current_chapter_id"])+1)] = Chapter(content=chapter_content,title=chapter_title,children=[], \
|
||||
siblings=deepcopy(state["chapter_graph"][state["chapter_id_viewing"]]['siblings']+[state["chapter_id_viewing"]]), \
|
||||
cousins=deepcopy(state["chapter_graph"][state["chapter_id_viewing"]]['children']), \
|
||||
parent=deepcopy(state["chapter_graph"][state["chapter_id_viewing"]]['parent']))
|
||||
siblings=deepcopy(state["chapter_graph"][state["chapter_id_viewing"]]['siblings']+[state["chapter_id_viewing"]]), \
|
||||
cousins=deepcopy(state["chapter_graph"][state["chapter_id_viewing"]]['cousins']), \
|
||||
parent=deepcopy(state["chapter_graph"][state["chapter_id_viewing"]]['parent']))
|
||||
#update siblings
|
||||
for sibling in state["chapter_graph"][state["chapter_id_viewing"]]['siblings']:
|
||||
for sibling in state["chapter_graph"][str(int(state["chapter_id_viewing"]+1))]['siblings']:
|
||||
state["chapter_graph"][sibling]['siblings'].append(str(int(state["current_chapter_id"])+1))
|
||||
|
||||
state["chapter_graph"][state["chapter_id_viewing"]]['siblings'].append(str(int(state["current_chapter_id"])+1))
|
||||
@@ -194,6 +159,11 @@ def edit_chapter(state):
|
||||
state["chapter_id_viewing"] = deepcopy(state["current_chapter_id"])
|
||||
return state
|
||||
|
||||
continue_prompt = """Here is what I want in the next chapter:
|
||||
|
||||
<Instructions>
|
||||
{instructions}
|
||||
</Instructions>"""
|
||||
|
||||
def continue_chapter(state):
|
||||
chapters_summary = summarize_current_story(state,state["chapter_id_viewing"])
|
||||
@@ -216,6 +186,52 @@ def continue_chapter(state):
|
||||
state["chapter_id_viewing"] = deepcopy(state["current_chapter_id"])
|
||||
return state
|
||||
|
||||
# State Definitions
|
||||
class Chapter(TypedDict):
|
||||
content: str
|
||||
"""Content of the chapter"""
|
||||
title: str
|
||||
"""Title of the chapter"""
|
||||
children: list
|
||||
"""Direct descendants of the chapter - chapters you get by clicking Continue"""
|
||||
siblings: list
|
||||
"""Direct edit descendants of the chapter - chapters you get by clicking Edit"""
|
||||
cousins: list
|
||||
"""Chapters that originated from the same parent but are not directly related"""
|
||||
parent: int
|
||||
"""For chapter number n, it is the node of chapter number n-1 that is directly related"""
|
||||
|
||||
def update_chapter_graph(old_chapter_graph, new_chapter_graph):
|
||||
# Always add nodes to the overall graph
|
||||
if isinstance(new_chapter_graph,dict):
|
||||
old_chapter_graph.update(new_chapter_graph)
|
||||
return old_chapter_graph
|
||||
|
||||
class State(TypedDict):
|
||||
summary: str
|
||||
"""Summary user passes in at beginning of story"""
|
||||
details: str
|
||||
"""Details provided by user at beginning of story"""
|
||||
style: str
|
||||
"""Desired writing style from the user"""
|
||||
summary_request: str = ""
|
||||
"""Prompt engineered summary instructions for LLM"""
|
||||
detail_request: str = ""
|
||||
"""Prompt engineered detial instructions for LLM"""
|
||||
style_request: str = ""
|
||||
"""Prompt engineered writing style instructions for LLM"""
|
||||
chapter_graph: Annotated[dict[str, Chapter], update_chapter_graph]
|
||||
"""Graph containing all of our chapter"""
|
||||
chapter_id_viewing: str
|
||||
"""What node in the graph the user is currently viewing"""
|
||||
current_chapter_id: str
|
||||
"""What is the current highest node (i.e. how many chapters have been written so far)"""
|
||||
rewrite_instructions: str
|
||||
"""Instructions to edit a chapter from the user"""
|
||||
continue_instructions: str
|
||||
"""Instructions to write the next chapter from the suer"""
|
||||
story_title: str = ""
|
||||
"""Overall title of the story"""
|
||||
|
||||
def router(state):
|
||||
if len(state.get('chapter_graph', [])) == 0:
|
||||
@@ -225,7 +241,6 @@ def router(state):
|
||||
else:
|
||||
return "continue"
|
||||
|
||||
|
||||
graph = StateGraph(State)
|
||||
graph.set_conditional_entry_point(router)
|
||||
graph.add_node("first", write_first_chapter)
|
||||
|
||||
+1
-1
@@ -7,4 +7,4 @@
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
}
|
||||
+79
-91
@@ -8,28 +8,10 @@ from streamlit_extras.stylable_container import stylable_container
|
||||
import requests
|
||||
st.set_page_config(layout="wide")
|
||||
|
||||
# Langsmith feedback client
|
||||
feedback_client = Client(api_url="https://beta.api.smith.langchain.com")
|
||||
|
||||
def get_public_ip():
|
||||
try:
|
||||
# Use an external API to fetch public IP address
|
||||
response = requests.get('https://api64.ipify.org?format=json')
|
||||
data = response.json()
|
||||
ip_address = data['ip']
|
||||
return ip_address
|
||||
except requests.RequestException as e:
|
||||
print(f"Error fetching IP address: {e}")
|
||||
return None
|
||||
|
||||
async def start_agent(ip_address):
|
||||
client = get_client(url="https://ht-monthly-attachment-15-c1f10ef48e7c5c198bda57ca-g3ps4aazkq-uc.a.run.app")
|
||||
assistants = await client.assistants.search()
|
||||
assistants = [a for a in assistants if not a['config']]
|
||||
thread = await client.threads.create(metadata={"user":ip_address})
|
||||
assistant = assistants[0]
|
||||
return [client,thread,assistant]
|
||||
|
||||
|
||||
# Find run id for giving feedback
|
||||
async def get_run_id_corresponding_to_node(client, thread, node_id):
|
||||
'''Get the run id corresponding to the chapter written'''
|
||||
runs = await client.runs.list(thread_id=thread['thread_id'])
|
||||
@@ -39,25 +21,22 @@ async def get_run_id_corresponding_to_node(client, thread, node_id):
|
||||
return r['run_id']
|
||||
return None
|
||||
|
||||
async def get_new_thread(client,ip_address):
|
||||
thread = await client.threads.create(metadata={"user":ip_address})
|
||||
return thread
|
||||
# Create the agent
|
||||
async def start_agent(session_id):
|
||||
client = get_client(url="https://ht-monthly-attachment-15-c1f10ef48e7c5c198bda57ca-g3ps4aazkq-uc.a.run.app")
|
||||
assistants = await client.assistants.search()
|
||||
assistants = [a for a in assistants if not a['config']]
|
||||
thread = await client.threads.create(metadata={"user":session_id})
|
||||
assistant = assistants[0]
|
||||
return [client,thread,assistant]
|
||||
|
||||
# Find a story that we had previously written
|
||||
async def get_thread_state(client,thread_id):
|
||||
return await client.threads.get_state(thread_id)
|
||||
|
||||
async def get_user_threads(client,ip_address):
|
||||
try:
|
||||
# Try to run the async function using the existing event loop
|
||||
threads = await client.threads.search(metadata={"user":ip_address})
|
||||
except RuntimeError as e:
|
||||
if "Event loop is closed" in str(e):
|
||||
# If the event loop is closed, create a new one and run the async function
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
threads = await client.threads.search(metadata={"user":ip_address})
|
||||
else:
|
||||
raise e
|
||||
# Find stories user has written
|
||||
async def get_user_threads(client,session_id):
|
||||
threads = await client.threads.search(metadata={"user":session_id})
|
||||
|
||||
untitled_count = 1
|
||||
for t in threads:
|
||||
@@ -69,58 +48,55 @@ async def get_user_threads(client,ip_address):
|
||||
untitled_count += 1
|
||||
return threads
|
||||
|
||||
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", multitask_strategy="rollback",
|
||||
):
|
||||
if chunk.data and 'run_id' not in chunk.data:
|
||||
for t in ['rewrite','continue','first']:
|
||||
try:
|
||||
data = chunk.data[t]['chapters']
|
||||
except:
|
||||
pass
|
||||
return data
|
||||
|
||||
llm_to_title = {
|
||||
"starting":"Waiting for user input ...",
|
||||
"brainstorm_llm": "Brainstorming ideas for chapter...",
|
||||
"plan_llm": "Planning outline for chapter...",
|
||||
"summary_llm": "Summarizing story so far...",
|
||||
"write_llm": "Writing the chapter...",
|
||||
"title_llm": "Generating a title for the story..."
|
||||
"title_llm": "Generating a title for the story...",
|
||||
"chapter_title_llm": "Generating title for chapter..."
|
||||
}
|
||||
|
||||
# Streaming chapter writing
|
||||
async def generate_answer(placeholder, placeholder_title, input, client, thread, assistant, metadata = {}):
|
||||
current_llm = "starting"
|
||||
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(
|
||||
thread['thread_id'], assistant['assistant_id'], input=input, config={"configurable":metadata}, \
|
||||
stream_mode="messages",
|
||||
):
|
||||
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.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)
|
||||
current_ind += len(chunk.data[0]['content'][current_ind:])
|
||||
current_llm = "starting"
|
||||
chapter_written = False
|
||||
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(
|
||||
thread['thread_id'], assistant['assistant_id'], input=input, config={"configurable":metadata}, \
|
||||
stream_mode="messages", multitask_strategy="rollback"
|
||||
):
|
||||
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.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)
|
||||
current_ind += len(chunk.data[0]['content'][current_ind:])
|
||||
|
||||
# Update variables after chapter has been written
|
||||
async def get_current_state(client,thread):
|
||||
current_state = await client.threads.get_state(thread_id=thread['thread_id'])
|
||||
return current_state
|
||||
|
||||
# When user selects a different chapter to view
|
||||
async def update_current_state(client,thread,values):
|
||||
updated_state = await client.threads.update_state(thread_id=thread['thread_id'],values=values)
|
||||
await client.threads.update_state(thread_id=thread['thread_id'],values=values)
|
||||
|
||||
# Create new thread for new story
|
||||
async def get_new_thread(client,session_id):
|
||||
thread = await client.threads.create(metadata={"user":session_id})
|
||||
return thread
|
||||
|
||||
# Make sure event loop never closes
|
||||
async def call_async_function_safely(func,*args):
|
||||
try:
|
||||
# Try to run the async function using the existing event loop
|
||||
@@ -135,6 +111,7 @@ async def call_async_function_safely(func,*args):
|
||||
raise e
|
||||
return result
|
||||
|
||||
# Helper function for chapter options
|
||||
def transform_titles_into_options(titles):
|
||||
name_counts = {}
|
||||
for name in set(titles):
|
||||
@@ -152,6 +129,7 @@ def transform_titles_into_options(titles):
|
||||
|
||||
return transformed_titles[::-1]
|
||||
|
||||
# Update variables after writing
|
||||
async def update_session_variables():
|
||||
current_state = await call_async_function_safely(get_current_state,st.session_state.client,st.session_state.thread)
|
||||
st.session_state.chapter_graph = current_state['values']['chapter_graph']
|
||||
@@ -162,8 +140,9 @@ async def update_session_variables():
|
||||
st.session_state.current_chapter_options = st.session_state.chapter_graph[st.session_state.currently_selected_chapter]['siblings'] \
|
||||
+ st.session_state.chapter_graph[st.session_state.currently_selected_chapter]['cousins'] + [st.session_state.currently_selected_chapter]
|
||||
st.session_state.previous_chapter_options = [x for x in [st.session_state.chapter_graph[st.session_state.currently_selected_chapter]['parent']] if x!= '-1']
|
||||
|
||||
async def reset_session_variables(ip_address):
|
||||
|
||||
# Reset variables on new story
|
||||
async def reset_session_variables():
|
||||
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
|
||||
@@ -185,7 +164,7 @@ async def main():
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
if "story_title" not in st.session_state or st.session_state.story_title == "":
|
||||
st.markdown("<h1 class='centered-title'>Story Writing with Langgraph</h1>", unsafe_allow_html=True)
|
||||
st.markdown("<h1 class='centered-title'>Story Writing with LangGraph</h1>", unsafe_allow_html=True)
|
||||
else:
|
||||
st.markdown(f"<h1 class='centered-title'>{st.session_state.story_title}</h1>", unsafe_allow_html=True)
|
||||
|
||||
@@ -195,11 +174,13 @@ async def main():
|
||||
st.session_state.story_title = ""
|
||||
st.session_state.num_selected = 0
|
||||
st.session_state.writing = False
|
||||
st.session_state.ip_address = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
|
||||
|
||||
if "session_id" not in st.session_state:
|
||||
st.session_state.session_id = ''.join(random.choices(string.ascii_letters + string.digits, k=10))
|
||||
|
||||
if st.session_state.page_loaded == False:
|
||||
st.session_state.client,st.session_state.thread,st.session_state.assistant = await call_async_function_safely(start_agent,st.session_state.ip_address)
|
||||
await reset_session_variables(st.session_state.ip_address)
|
||||
st.session_state.client,st.session_state.thread,st.session_state.assistant = await call_async_function_safely(start_agent,st.session_state.session_id)
|
||||
await reset_session_variables()
|
||||
st.session_state.page_loaded = True
|
||||
|
||||
if "story_started" not in st.session_state:
|
||||
@@ -217,7 +198,7 @@ async def main():
|
||||
if "show_load_story" not in st.session_state:
|
||||
st.session_state.show_load_story = False
|
||||
|
||||
|
||||
# Starting/New story
|
||||
if st.session_state.show_start_input:
|
||||
summary_text = st.sidebar.text_area("Summary")
|
||||
detail_text = st.sidebar.text_area("Details")
|
||||
@@ -233,21 +214,19 @@ async def main():
|
||||
st.session_state.writing = True
|
||||
# If we start a new story, reset all of our session variables
|
||||
if st.session_state.story_started:
|
||||
st.session_state.thread = await call_async_function_safely(get_new_thread,st.session_state.client,st.session_state.ip_address)
|
||||
await reset_session_variables(st.session_state.ip_address)
|
||||
|
||||
st.session_state.thread = await call_async_function_safely(get_new_thread,st.session_state.client,st.session_state.session_id)
|
||||
await reset_session_variables()
|
||||
|
||||
|
||||
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.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()
|
||||
# Editing story
|
||||
elif st.session_state.show_edit_input:
|
||||
edit_chapter_text = st.sidebar.text_area("Edit Instructions")
|
||||
col1, col2 = st.sidebar.columns([1, 1])
|
||||
@@ -258,15 +237,14 @@ async def main():
|
||||
st.rerun()
|
||||
with col2:
|
||||
if st.button("Submit",key="edit-submit"):
|
||||
|
||||
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})
|
||||
|
||||
|
||||
await update_session_variables()
|
||||
st.session_state.show_edit_input = False
|
||||
st.session_state.writing = False
|
||||
st.rerun()
|
||||
# Continuing story
|
||||
elif st.session_state.show_continue_input:
|
||||
next_chapter_text = st.sidebar.text_area("Next Chapter Instructions")
|
||||
col1, col2 = st.sidebar.columns([1, 1])
|
||||
@@ -277,8 +255,6 @@ async def main():
|
||||
st.rerun()
|
||||
with col2:
|
||||
if st.button("Submit",key="continue-submit"):
|
||||
|
||||
|
||||
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})
|
||||
|
||||
@@ -287,10 +263,11 @@ async def main():
|
||||
st.session_state.writing = False
|
||||
st.session_state.chapter_number += 1
|
||||
st.rerun()
|
||||
# Loading story
|
||||
elif st.session_state.show_load_story:
|
||||
col1, col2 = st.sidebar.columns([1, 1])
|
||||
threads = await call_async_function_safely(get_user_threads,st.session_state.client,st.session_state.ip_address)
|
||||
threads_without_current = [t for t in threads if t['thread_id'] != st.session_state.thread['thread_id']]
|
||||
threads = await call_async_function_safely(get_user_threads,st.session_state.client,st.session_state.session_id)
|
||||
threads_without_current = [t for t in threads if t['thread_id'] != st.session_state.thread['thread_id'] and 'Untitled' not in t['story_title']]
|
||||
options = [t['story_title'] for t in threads_without_current]
|
||||
|
||||
if len(options) > 0:
|
||||
@@ -304,15 +281,20 @@ async def main():
|
||||
st.session_state.thread = threads_without_current[options.index(selected_story)]
|
||||
await update_session_variables()
|
||||
st.session_state.show_load_story = False
|
||||
st.session_state.chapter_number = 1
|
||||
cur_chapter = st.session_state.currently_selected_chapter
|
||||
while st.session_state.chapter_graph[cur_chapter]['parent'] != '-1':
|
||||
st.session_state.chapter_number += 1
|
||||
cur_chapter = st.session_state.chapter_graph[cur_chapter]['parent']
|
||||
st.rerun()
|
||||
with col1:
|
||||
if st.button("Back",key="load-story-back"):
|
||||
st.session_state.show_load_story = False
|
||||
st.rerun()
|
||||
# Default Navigation pane
|
||||
else:
|
||||
st.sidebar.header("Navigation")
|
||||
if st.sidebar.button("New Story" if st.session_state.story_started else "Start Story"):
|
||||
st.session_state.story_title = ""
|
||||
st.session_state.writing = True
|
||||
st.session_state.show_start_input = True
|
||||
st.rerun()
|
||||
@@ -334,7 +316,7 @@ async def main():
|
||||
<style>
|
||||
div[data-testid="stHorizontalBlock"] > * {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
@@ -342,6 +324,7 @@ async def main():
|
||||
col1, _, col3 = st.columns([1, 2, 1])
|
||||
|
||||
if st.session_state.writing == False:
|
||||
# Previous chapter options
|
||||
with col1:
|
||||
options = transform_titles_into_options([st.session_state.chapter_graph[chapter_id]['title'] for chapter_id in st.session_state.previous_chapter_options])
|
||||
if len(options) > 0:
|
||||
@@ -362,6 +345,7 @@ async def main():
|
||||
await call_async_function_safely(update_session_variables)
|
||||
st.session_state.chapter_number -= 1
|
||||
st.rerun()
|
||||
# Next chapter options
|
||||
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])
|
||||
if len(options) > 0:
|
||||
@@ -412,6 +396,7 @@ async def main():
|
||||
|
||||
_, col2, _ = st.columns([1, 2, 1])
|
||||
if st.session_state.writing == False:
|
||||
# Current chapter options
|
||||
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 if chapter_id != "-1"])
|
||||
if len(options) > 0:
|
||||
@@ -432,8 +417,10 @@ async def main():
|
||||
await call_async_function_safely(update_session_variables)
|
||||
st.rerun()
|
||||
|
||||
# Feedback options
|
||||
if st.session_state.current_node_id != '1':
|
||||
_, col2a,col2b, _ = st.columns([1, 1,1, 1])
|
||||
# Bad writing
|
||||
with col2a:
|
||||
with stylable_container(
|
||||
key="red_button",
|
||||
@@ -455,6 +442,7 @@ async def main():
|
||||
score=0.0,
|
||||
comment="comment",
|
||||
)
|
||||
# Good writing
|
||||
with col2b:
|
||||
with stylable_container(
|
||||
key="green_button",
|
||||
|
||||
Reference in New Issue
Block a user