From e0681805ee8a9dc032f879dadf37ba0f70e1620d Mon Sep 17 00:00:00 2001 From: "Estrada Irribarra, Rodrigo Andres" Date: Thu, 17 Oct 2024 11:22:17 -0300 Subject: [PATCH] refactor: Book Name and prompts tone --- .gitignore | 1 + docs/getting_started.md | 2 +- examples/example_usage.md | 2 +- storycraftr/agent/agents.py | 162 ++++++++++++++++++--------- storycraftr/agent/chapters.py | 57 +++++----- storycraftr/agent/iterate.py | 72 ++++++------ storycraftr/agent/outline.py | 96 +++++++++------- storycraftr/agent/retrieval.py | 6 +- storycraftr/agent/worldbuilding.py | 75 +++++++------ storycraftr/cli.py | 79 +++++++------ storycraftr/cmd/chapters.py | 48 ++++---- storycraftr/cmd/chat.py | 14 +-- storycraftr/cmd/iterate.py | 112 +++++++++--------- storycraftr/cmd/outline.py | 48 ++++---- storycraftr/cmd/publish.py | 14 +-- storycraftr/cmd/worldbuilding.py | 60 +++++----- storycraftr/prompts/chapters.py | 21 +--- storycraftr/prompts/iterate.py | 10 -- storycraftr/prompts/outline.py | 37 +++--- storycraftr/prompts/worldbuilding.py | 30 +---- storycraftr/utils/core.py | 10 +- storycraftr/utils/markdown.py | 36 +++--- storycraftr/utils/pdf.py | 8 +- tests/test_chapters.py | 2 +- 24 files changed, 522 insertions(+), 480 deletions(-) diff --git a/.gitignore b/.gitignore index 637c9fe..e893242 100644 --- a/.gitignore +++ b/.gitignore @@ -160,5 +160,6 @@ cython_debug/ #.idea/ La purga de los dioses +The Purge of the gods .DS_Store behavior.txt \ No newline at end of file diff --git a/docs/getting_started.md b/docs/getting_started.md index 17a1cb4..2799e95 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -179,7 +179,7 @@ To start chatting with your assistant, make sure your book project is initialize storycraftr chat ``` -Replace `"book_name"` with the actual name of your book. This will open an interactive session where you can type messages to your AI assistant. The responses will be formatted in Markdown, making it easy to read any formatted text, lists, or other structures returned by the assistant. +Replace `"book_path"` with the actual name of your book. This will open an interactive session where you can type messages to your AI assistant. The responses will be formatted in Markdown, making it easy to read any formatted text, lists, or other structures returned by the assistant. ### Example Chat Session diff --git a/examples/example_usage.md b/examples/example_usage.md index e739127..307d598 100644 --- a/examples/example_usage.md +++ b/examples/example_usage.md @@ -1,4 +1,4 @@ -storycraftr init "La purga de los dioses" --primary-language "es" --alternate-languages "en" --author "Rodrigo Estrada" --genre "science fiction" --behavior "behavior.txt" +storycraftr init "The Purge of the gods" --primary-language "en" --alternate-languages "es" --author "Rodrigo Estrada" --genre "science fiction" --behavior "behavior.txt" --reference-author="Brandon Sanderson" storycraftr outline general-outline "Summarize the overall plot of a dystopian science fiction where advanced technology, resembling magic, has led to the fall of humanity’s elite and the rise of a manipulative villain who seeks to destroy both the ruling class and the workers." diff --git a/storycraftr/agent/agents.py b/storycraftr/agent/agents.py index 4c0f035..a933386 100644 --- a/storycraftr/agent/agents.py +++ b/storycraftr/agent/agents.py @@ -5,6 +5,8 @@ from openai import OpenAI from rich.console import Console from rich.progress import Progress +from storycraftr.prompts.core import FORMAT_OUTPUT +from storycraftr.utils.core import load_book_config load_dotenv() @@ -13,12 +15,12 @@ # Function to load all Markdown files from the book's directory and subdirectories -def load_markdown_files(book_name): +def load_markdown_files(book_path): """Load all Markdown files from the book's directory and subdirectories.""" console.print( - f"[bold blue]Loading all Markdown files from '{book_name}'...[/bold blue]" + f"[bold blue]Loading all Markdown files from '{book_path}'...[/bold blue]" ) # Progress message - md_files = glob.glob(f"{book_name}/**/*.md", recursive=True) + md_files = glob.glob(f"{book_path}/**/*.md", recursive=True) console.print( f"[bold green]Loaded {len(md_files)} Markdown files.[/bold green]" ) # Success message @@ -26,8 +28,8 @@ def load_markdown_files(book_name): # Function to delete an existing assistant -def delete_assistant(book_name): - name = book_name.split("/")[-1] +def delete_assistant(book_path): + name = book_path.split("/")[-1] console.print( f"[bold blue]Checking if assistant '{name}' exists for deletion...[/bold blue]" ) # Progress message @@ -43,8 +45,8 @@ def delete_assistant(book_name): # Function to create or get an assistant with optional progress task -def create_or_get_assistant(book_name, progress: Progress = None, task=None): - name = book_name.split("/")[-1] +def create_or_get_assistant(book_path, progress: Progress = None, task=None): + name = book_path.split("/")[-1] # Progress message for searching an existing assistant if progress and task: @@ -72,23 +74,23 @@ def create_or_get_assistant(book_name, progress: Progress = None, task=None): # Step 1: Create a vector store for the book if progress and task: - progress.update(task, description=f"Creating vector store for '{book_name}'...") + progress.update(task, description=f"Creating vector store for '{book_path}'...") else: console.print( - f"[bold blue]Creating vector store for '{book_name}'...[/bold blue]" + f"[bold blue]Creating vector store for '{book_path}'...[/bold blue]" ) - vector_store = client.beta.vector_stores.create(name=f"{book_name} Docs") + vector_store = client.beta.vector_stores.create(name=f"{book_path} Docs") # Step 2: Upload Knowledge (Markdown files) if progress and task: - progress.update(task, description=f"Uploading knowledge from '{book_name}'...") + progress.update(task, description=f"Uploading knowledge from '{book_path}'...") else: console.print( - f"[bold blue]Uploading knowledge from '{book_name}'...[/bold blue]" + f"[bold blue]Uploading knowledge from '{book_path}'...[/bold blue]" ) - md_files = load_markdown_files(book_name) + md_files = load_markdown_files(book_path) file_streams = [open(file_path, "rb") for file_path in md_files] file_batch = client.beta.vector_stores.file_batches.upload_and_poll( @@ -150,7 +152,13 @@ def create_or_get_assistant(book_name, progress: Progress = None, task=None): def create_message( - thread_id, content, assistant, file_path=None, progress=None, task_id=None + book_path, + thread_id, + content, + assistant, + file_path=None, + progress=None, + task_id=None, ): """ Create a message in the thread and process it asynchronously. @@ -163,10 +171,12 @@ def create_message( progress (rich.progress.Progress, optional): Progress object for tracking. Defaults to None. task_id (int, optional): Task ID for the progress bar. Required if progress is passed. - Returns: - str: The text content of the last message returned by the assistant. + Raises: + OpenAIError: Custom exception if a problem occurs during the OpenAI request. """ + config = load_book_config(book_path) + # Flag to determine if we should print to the console should_print = progress is None @@ -174,20 +184,22 @@ def create_message( internal_progress = False if progress is None: progress = Progress() - task_id = progress.add_task("[cyan]Waiting for assistant response...", total=50) + task_id = progress.add_task( + "[cyan]Waiting for assistant response...", total=500 + ) internal_progress = True if should_print: console.print( f"[bold blue]Creating message in thread {thread_id}...[/bold blue]" - ) # Progress message + ) # Prepare the base prompt if file_path and os.path.exists(file_path): if should_print: console.print( f"[bold blue]Reading content from {file_path} for improvement...[/bold blue]" - ) # Progress message + ) with open(file_path, "r", encoding="utf-8") as f: file_content = f.read() # Append the file content to the prompt asking for improvement @@ -198,44 +210,92 @@ def create_message( if should_print: console.print( f"[bold blue]Using provided prompt to generate new content...[/bold blue]" - ) # Progress message + ) - # Prepare the message payload - message_payload = {"thread_id": thread_id, "role": "user", "content": content} + try: + # Send prompt to OpenAI API + client.beta.threads.messages.create( + thread_id=thread_id, + role="user", + content=f"content\n\n{FORMAT_OUTPUT.format(reference_author=config.reference_author)}", + ) - # Create the message in the thread - client.beta.threads.messages.create(**message_payload) + # Start the assistant run + run = client.beta.threads.runs.create( + thread_id=thread_id, assistant_id=assistant.id + ) + if should_print: + console.print("[bold blue]Sending prompt to OpenAI API...[/bold blue]") - # Start the assistant run - run = client.beta.threads.runs.create( - thread_id=thread_id, assistant_id=assistant.id - ) - if should_print: - console.print( - "[bold blue]Sending prompt to OpenAI API...[/bold blue]" - ) # Progress message + if internal_progress: + progress.start() - if internal_progress: - progress.start() + # Wait for the assistant response while updating the progress bar + while run.status == "queued" or run.status == "in_progress": + run = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id) + progress.update(task_id, advance=1) + time.sleep(0.5) - # Wait for the assistant response while updating the progress bar - while run.status == "queued" or run.status == "in_progress": - run = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id) - progress.update(task_id, advance=1) # Update progress bar - time.sleep(0.5) # Wait before checking the status again + if internal_progress: + progress.stop() - if internal_progress: - progress.stop() + if should_print: + console.print(f"[bold green]Generated content received.[/bold green]") - if should_print: - console.print( - f"[bold green]Generated content received.[/bold green]" - ) # Success message + # Retrieve the list of messages in the thread + messages = client.beta.threads.messages.list(thread_id=thread_id) - # Retrieve the list of messages in the thread and return the last message content - messages = client.beta.threads.messages.list(thread_id=thread_id) + response_text = messages.data[0].content[0].text.value - return messages.data[0].content[0].text.value + # Check if the response is the same as the original prompt (potential issue with credits) + if response_text.strip() == content.strip(): + console.print( + "[bold yellow]Warning: The response matches the original prompt. You might be out of credit.[/bold yellow]" + ) + raise OpenAIError( + "The response matches the original prompt. Check your account for credit availability." + ) + + return response_text + + except openai.error.Timeout as e: + console.print(f"[bold red]OpenAI API request timed out: {e}[/bold red]") + raise OpenAIError("OpenAI API request timed out. Please try again.") + except openai.error.APIError as e: + console.print(f"[bold red]OpenAI API returned an API Error: {e}[/bold red]") + raise OpenAIError(f"OpenAI API returned an API Error: {e}") + except openai.error.APIConnectionError as e: + console.print(f"[bold red]OpenAI API request failed to connect: {e}[/bold red]") + raise OpenAIError( + f"OpenAI API request failed to connect. Please check your network connection: {e}" + ) + except openai.error.InvalidRequestError as e: + console.print(f"[bold red]OpenAI API request was invalid: {e}[/bold red]") + raise OpenAIError( + f"OpenAI API request was invalid. Please check your request parameters: {e}" + ) + except openai.error.AuthenticationError as e: + console.print( + f"[bold red]OpenAI API request was not authorized: {e}[/bold red]" + ) + raise OpenAIError( + "OpenAI API request was not authorized. Please check your API key or credentials." + ) + except openai.error.PermissionError as e: + console.print(f"[bold red]OpenAI API request was not permitted: {e}[/bold red]") + raise OpenAIError( + "OpenAI API request was not permitted. Please check your permissions or access level." + ) + except openai.error.RateLimitError as e: + console.print( + f"[bold red]OpenAI API request exceeded rate limit: {e}[/bold red]" + ) + raise OpenAIError( + "OpenAI API request exceeded rate limit. Please wait and try again." + ) + except Exception as e: + console.print(f"[bold red]Unexpected error: {e}[/bold red]") + raise OpenAIError(f"Unexpected error: {e}") # Function to get a new thread @@ -244,9 +304,9 @@ def get_thread(): # Function to update the assistant's knowledge with new files -def update_agent_files(book_name, assistant): - delete_assistant(book_name) - create_or_get_assistant(book_name) +def update_agent_files(book_path, assistant): + delete_assistant(book_path) + create_or_get_assistant(book_path) console.print( f"[bold green]Files updated successfully in assistant '{assistant.name}'.[/bold green]" diff --git a/storycraftr/agent/chapters.py b/storycraftr/agent/chapters.py index 3f3b990..2bf4668 100644 --- a/storycraftr/agent/chapters.py +++ b/storycraftr/agent/chapters.py @@ -21,17 +21,17 @@ # Function to generate a new chapter based on a prompt -def generate_chapter(book_name, chapter_number, prompt): +def generate_chapter(book_path, chapter_number, prompt): """Generate a new chapter based on a prompt.""" console.print( f"[bold blue]Generating chapter {chapter_number}...[/bold blue]" ) # Progress message - assistant = create_or_get_assistant(book_name) + assistant = create_or_get_assistant(book_path) thread = get_thread() # Prepare the chapter file path chapter_file = f"chapter-{chapter_number}.md" - file_path = os.path.join(book_name, "chapters", chapter_file) + file_path = os.path.join(book_path, "chapters", chapter_file) # Check if the file exists and pass it as an attachment if os.path.exists(file_path): @@ -39,9 +39,10 @@ def generate_chapter(book_name, chapter_number, prompt): f"[yellow]Existing chapter found at {file_path}. Attaching for further refinement...[/yellow]" ) # Progress message content = CHAPTER_PROMPT_REFINE.format( - prompt=prompt, language=load_book_config(book_name).primary_language + prompt=prompt, language=load_book_config(book_path).primary_language ) chapter_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -52,15 +53,15 @@ def generate_chapter(book_name, chapter_number, prompt): "[yellow]No existing chapter found. Generating new content...[/yellow]" ) # Progress message content = CHAPTER_PROMPT_NEW.format( - prompt=prompt, language=load_book_config(book_name).primary_language + prompt=prompt, language=load_book_config(book_path).primary_language ) chapter_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save the updated chapter content to markdown save_to_markdown( - book_name, + book_path, "chapters/" + chapter_file, f"Chapter {chapter_number}", chapter_content, @@ -68,18 +69,18 @@ def generate_chapter(book_name, chapter_number, prompt): console.print( f"[bold green]✔ Chapter {chapter_number} generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return chapter_content -def generate_cover(book_name, prompt): +def generate_cover(book_path, prompt): """ Generate a professional book cover in markdown format using the book's metadata and a prompt for additional guidance. """ console.print("[bold blue]Generating book cover...[/bold blue]") # Progress message - config = load_book_config(book_name) - assistant = create_or_get_assistant(book_name) + config = load_book_config(book_path) + assistant = create_or_get_assistant(book_path) thread = get_thread() # Generate the cover content @@ -93,28 +94,29 @@ def generate_cover(book_name, prompt): ) cover_content = create_message( - thread_id=thread.id, content=prompt_content, assistant=assistant + book_path, thread_id=thread.id, content=prompt_content, assistant=assistant ) # Save the cover content to markdown - save_to_markdown(book_name, "chapters/cover.md", "Cover", cover_content) + save_to_markdown(book_path, "chapters/cover.md", "Cover", cover_content) console.print( "[bold green]✔ Cover generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return cover_content # Function to generate the back cover page -def generate_back_cover(book_name, prompt): +def generate_back_cover(book_path, prompt): """Generate the back cover page for the book.""" console.print("[bold blue]Generating back cover...[/bold blue]") # Progress message - config = load_book_config(book_name) - assistant = create_or_get_assistant(book_name) + config = load_book_config(book_path) + assistant = create_or_get_assistant(book_path) thread = get_thread() # Generate the back cover content back_cover_content = create_message( + book_path, thread_id=thread.id, content=BACK_COVER_PROMPT.format( title=config.book_name, @@ -130,24 +132,24 @@ def generate_back_cover(book_name, prompt): # Save to markdown save_to_markdown( - book_name, "chapters/back-cover.md", "Back Cover", back_cover_content + book_path, "chapters/back-cover.md", "Back Cover", back_cover_content ) console.print( "[bold green]✔ Back cover generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return back_cover_content # Function to generate the epilogue of the book -def generate_epilogue(book_name, prompt): +def generate_epilogue(book_path, prompt): """Generate the epilogue for the book.""" console.print("[bold blue]Generating epilogue...[/bold blue]") # Progress message - assistant = create_or_get_assistant(book_name) + assistant = create_or_get_assistant(book_path) thread = get_thread() # Prepare the epilogue file path - file_path = os.path.join(book_name, "chapters", "epilogue.md") + file_path = os.path.join(book_path, "chapters", "epilogue.md") # Check if the file exists and pass it as an attachment if os.path.exists(file_path): @@ -155,9 +157,10 @@ def generate_epilogue(book_name, prompt): f"[yellow]Existing epilogue found at {file_path}. Attaching for further refinement...[/yellow]" ) # Progress message content = EPILOGUE_PROMPT_REFINE.format( - prompt=prompt, language=load_book_config(book_name).primary_language + prompt=prompt, language=load_book_config(book_path).primary_language ) epilogue_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -168,16 +171,16 @@ def generate_epilogue(book_name, prompt): "[yellow]No existing epilogue found. Generating new content...[/yellow]" ) # Progress message content = EPILOGUE_PROMPT_NEW.format( - prompt=prompt, language=load_book_config(book_name).primary_language + prompt=prompt, language=load_book_config(book_path).primary_language ) epilogue_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save the updated epilogue content to markdown - save_to_markdown(book_name, "chapters/epilogue.md", "Epilogue", epilogue_content) + save_to_markdown(book_path, "chapters/epilogue.md", "Epilogue", epilogue_content) console.print( "[bold green]✔ Epilogue generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return epilogue_content diff --git a/storycraftr/agent/iterate.py b/storycraftr/agent/iterate.py index a276eae..327a9ca 100644 --- a/storycraftr/agent/iterate.py +++ b/storycraftr/agent/iterate.py @@ -20,7 +20,7 @@ console = Console() -def check_character_names_consistency(book_name, chapter_path, progress, task_id): +def check_character_names_consistency(book_path, chapter_path, progress, task_id): """ Checks for character name consistency in a chapter file. Uses an OpenAI assistant to perform the review. @@ -30,11 +30,12 @@ def check_character_names_consistency(book_name, chapter_path, progress, task_id prompt = CHECK_NAMES_PROMPT # Get or create the assistant and the thread - assistant = create_or_get_assistant(book_name, progress, task_id) + assistant = create_or_get_assistant(book_path, progress, task_id) thread = get_thread() # Create the message with the thread_id and assistant response = create_message( + book_path, thread_id=thread.id, content=prompt, assistant=assistant, @@ -49,20 +50,20 @@ def check_character_names_consistency(book_name, chapter_path, progress, task_id return response -def iterate_check_names(book_name): +def iterate_check_names(book_path): """ Iterates over all chapters in the directory and checks for name consistency. """ corrections = {} # Verificar si el directorio del libro existe - if not os.path.exists(book_name): + if not os.path.exists(book_path): console.print( - f"[bold red]El directorio del libro '{book_name}' no existe.[/bold red]" + f"[bold red]El directorio del libro '{book_path}' no existe.[/bold red]" ) return - chapters_dir = os.path.join(book_name, "chapters") + chapters_dir = os.path.join(book_path, "chapters") # Verificar si el directorio de capítulos existe if not os.path.exists(chapters_dir): @@ -97,7 +98,7 @@ def iterate_check_names(book_name): # Call the function to check name consistency in the chapter corrections[chapter_file] = check_character_names_consistency( - book_name, chapter_path, progress, task_openai + book_path, chapter_path, progress, task_openai ) # Save to markdown @@ -113,16 +114,16 @@ def iterate_check_names(book_name): # Advance the chapter processing task progress.update(task_chapters, advance=1) - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return corrections -def fix_name_in_chapters(book_name, original_name, new_name): +def fix_name_in_chapters(book_path, original_name, new_name): """ Function to update character names across all chapters in a book. """ - chapters_dir = os.path.join(book_name, "chapters") + chapters_dir = os.path.join(book_path, "chapters") if not os.path.exists(chapters_dir): raise FileNotFoundError( @@ -154,12 +155,13 @@ def fix_name_in_chapters(book_name, original_name, new_name): ) # Get the assistant and thread - assistant = create_or_get_assistant(book_name) + assistant = create_or_get_assistant(book_path) thread = get_thread() progress.reset(task_openai) # Send the message to perform the name change corrected_text = create_message( + book_path, thread_id=thread.id, content=prompt, assistant=assistant, @@ -170,7 +172,7 @@ def fix_name_in_chapters(book_name, original_name, new_name): # Save the corrected chapter save_to_markdown( - book_name, + book_path, os.path.join("chapters", chapter_file), "Character Name Update", corrected_text, @@ -180,15 +182,15 @@ def fix_name_in_chapters(book_name, original_name, new_name): # Advance the progress bar progress.update(task_chapters, advance=1) - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return -def refine_character_motivation(book_name, character_name, story_context): +def refine_character_motivation(book_path, character_name, story_context): """ Function to refine character motivations across all chapters in a book. """ - chapters_dir = os.path.join(book_name, "chapters") + chapters_dir = os.path.join(book_path, "chapters") if not os.path.exists(chapters_dir): raise FileNotFoundError( @@ -220,12 +222,13 @@ def refine_character_motivation(book_name, character_name, story_context): ) # Get the assistant and thread - assistant = create_or_get_assistant(book_name) + assistant = create_or_get_assistant(book_path) thread = get_thread() # Send the message to refine the character's motivations progress.reset(task_openai) refined_text = create_message( + book_path, thread_id=thread.id, content=prompt, assistant=assistant, @@ -236,7 +239,7 @@ def refine_character_motivation(book_name, character_name, story_context): # Save the refined chapter save_to_markdown( - book_name, + book_path, os.path.join("chapters", chapter_file), "Character Motivation Refinement", refined_text, @@ -247,15 +250,15 @@ def refine_character_motivation(book_name, character_name, story_context): # Advance the progress bar progress.update(task_chapters, advance=1) - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return -def strengthen_core_argument(book_name, argument): +def strengthen_core_argument(book_path, argument): """ Function to strengthen the core argument across all chapters in a book. """ - chapters_dir = os.path.join(book_name, "chapters") + chapters_dir = os.path.join(book_path, "chapters") if not os.path.exists(chapters_dir): raise FileNotFoundError( @@ -286,12 +289,13 @@ def strengthen_core_argument(book_name, argument): prompt = STRENGTHEN_ARGUMENT_PROMPT.format(argument=argument) # Get the assistant and thread - assistant = create_or_get_assistant(book_name) + assistant = create_or_get_assistant(book_path) thread = get_thread() progress.reset(task_openai) # Send the message to refine the argument in the chapter refined_text = create_message( + book_path, thread_id=thread.id, content=prompt, assistant=assistant, @@ -302,7 +306,7 @@ def strengthen_core_argument(book_name, argument): # Save the refined chapter save_to_markdown( - book_name, + book_path, os.path.join("chapters", chapter_file), "Core Argument Strengthening", refined_text, @@ -313,15 +317,15 @@ def strengthen_core_argument(book_name, argument): # Advance the progress bar progress.update(task_chapters, advance=1) - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return -def insert_new_chapter(book_name, position, prompt): +def insert_new_chapter(book_path, position, prompt): """ Function to insert a new chapter at the specified position, renaming chapters and adjusting content accordingly. """ - chapters_dir = os.path.join(book_name, "chapters") + chapters_dir = os.path.join(book_path, "chapters") if not os.path.exists(chapters_dir): raise FileNotFoundError( @@ -361,12 +365,13 @@ def insert_new_chapter(book_name, position, prompt): i -= 1 # Get or create the assistant and thread - assistant = create_or_get_assistant(book_name) + assistant = create_or_get_assistant(book_path) thread = get_thread() # Generate new chapter content using context prompt_text = INSERT_CHAPTER_PROMPT.format(prompt=prompt, position=position) new_chapter_text = create_message( + book_path, thread_id=thread.id, content=prompt_text, assistant=assistant, @@ -378,7 +383,7 @@ def insert_new_chapter(book_name, position, prompt): # Save the new chapter as the new chapter-{position}.md new_chapter_path = os.path.join(chapters_dir, f"chapter-{position}.md") save_to_markdown( - book_name, + book_path, os.path.join("chapters", f"chapter-{position}.md"), f"Chapter {position}", new_chapter_text, @@ -389,7 +394,7 @@ def insert_new_chapter(book_name, position, prompt): progress.update(task_chapters, advance=1) # Upload files to retrieval system - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) # Handle the chapters before and after the insertion point, if they exist prev_chapter = None if position == 1 else position - 1 @@ -411,7 +416,7 @@ def insert_new_chapter(book_name, position, prompt): if prev_chapter: progress.reset(task_openai) rewrite_chapters( - book_name, + book_path, prev_chapter_path, prev_chapter, position, @@ -423,7 +428,7 @@ def insert_new_chapter(book_name, position, prompt): if next_chapter: progress.reset(task_openai) rewrite_chapters( - book_name, + book_path, next_chapter_path, next_chapter, position, @@ -433,13 +438,13 @@ def insert_new_chapter(book_name, position, prompt): ) -def rewrite_chapters(book_name, path, num, position, prompt, progress, task_chapters): +def rewrite_chapters(book_path, path, num, position, prompt, progress, task_chapters): """ Function to rewrite the chapters before and after the inserted chapter to ensure consistency with the new chapter. Utilizes the retrieval system to access the full context of the book without loading chapter contents directly. """ # Get the assistant and thread - assistant = create_or_get_assistant(book_name) + assistant = create_or_get_assistant(book_path) thread = get_thread() # Rewrite chapters using retrieval context @@ -448,6 +453,7 @@ def rewrite_chapters(book_name, path, num, position, prompt, progress, task_chap ) updated_chapters = create_message( + book_path, thread_id=thread.id, content=rewrite_prompt, assistant=assistant, @@ -458,7 +464,7 @@ def rewrite_chapters(book_name, path, num, position, prompt, progress, task_chap # Save the updated chapters back to the markdown files save_to_markdown( - book_name, + book_path, os.path.join("chapters", f"chapter-{num}.md"), "Updated chapters with retrieval context", updated_chapters, diff --git a/storycraftr/agent/outline.py b/storycraftr/agent/outline.py index 6644a98..1bedf86 100644 --- a/storycraftr/agent/outline.py +++ b/storycraftr/agent/outline.py @@ -24,25 +24,28 @@ # Function to generate the general outline of the book -def generate_general_outline(book_name, prompt): +def generate_general_outline(book_path, prompt): """Generate the general outline of the book.""" console.print( "[bold blue]Generating general outline...[/bold blue]" ) # Progress message - language = load_book_config(book_name).primary_language - assistant = create_or_get_assistant(book_name) + language = load_book_config(book_path).primary_language + assistant = create_or_get_assistant(book_path) thread = get_thread() - + book_name = book_path.split("/")[-1] # File path for the general outline - file_path = os.path.join(book_name, "outline", "general_outline.md") + file_path = os.path.join(book_path, "outline", "general_outline.md") # Check if the file exists and pass it as an attachment if os.path.exists(file_path) and file_has_more_than_three_lines(file_path): console.print( f"[yellow]Existing general outline found at {file_path}. Attaching for further refinement...[/yellow]" ) # Progress message - content = GENERAL_OUTLINE_PROMPT_REFINE.format(prompt=prompt, language=language) + content = GENERAL_OUTLINE_PROMPT_REFINE.format( + prompt=prompt, language=language, book_name=book_name + ) general_outline_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -52,14 +55,16 @@ def generate_general_outline(book_name, prompt): console.print( "[yellow]No existing general outline found. Generating a new one...[/yellow]" ) # Progress message - content = GENERAL_OUTLINE_PROMPT_NEW.format(prompt=prompt, language=language) + content = GENERAL_OUTLINE_PROMPT_NEW.format( + prompt=prompt, language=language, book_name=book_name + ) general_outline_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save to markdown save_to_markdown( - book_name, + book_path, "outline/general_outline.md", "General Outline", general_outline_content, @@ -67,22 +72,22 @@ def generate_general_outline(book_name, prompt): console.print( "[bold green]✔ General outline generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return general_outline_content # Function to generate the character summary of the book -def generate_character_summary(book_name, prompt): +def generate_character_summary(book_path, prompt): """Generate the character summary for the book.""" console.print( "[bold blue]Generating character summary...[/bold blue]" ) # Progress message - language = load_book_config(book_name).primary_language - assistant = create_or_get_assistant(book_name) + language = load_book_config(book_path).primary_language + assistant = create_or_get_assistant(book_path) thread = get_thread() - + book_name = book_path.split("/")[-1] # File path for the character summary - file_path = os.path.join(book_name, "outline", "character_summary.md") + file_path = os.path.join(book_path, "outline", "character_summary.md") # Check if the file exists and pass it as an attachment if os.path.exists(file_path) and file_has_more_than_three_lines(file_path): @@ -90,9 +95,10 @@ def generate_character_summary(book_name, prompt): f"[yellow]Existing character summary found at {file_path}. Attaching for further refinement...[/yellow]" ) # Progress message content = CHARACTER_SUMMARY_PROMPT_REFINE.format( - prompt=prompt, language=language + prompt=prompt, language=language, book_name=book_name ) character_summary_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -102,14 +108,16 @@ def generate_character_summary(book_name, prompt): console.print( "[yellow]No existing character summary found. Generating a new one...[/yellow]" ) # Progress message - content = CHARACTER_SUMMARY_PROMPT_NEW.format(prompt=prompt, language=language) + content = CHARACTER_SUMMARY_PROMPT_NEW.format( + prompt=prompt, language=language, book_name=book_name + ) character_summary_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save to markdown save_to_markdown( - book_name, + book_path, "outline/character_summary.md", "Character Summary", character_summary_content, @@ -117,30 +125,33 @@ def generate_character_summary(book_name, prompt): console.print( "[bold green]✔ Character summary generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return character_summary_content # Function to generate the main plot points of the book -def generate_plot_points(book_name, prompt): +def generate_plot_points(book_path, prompt): """Generate the main plot points for the book.""" console.print( "[bold blue]Generating main plot points...[/bold blue]" ) # Progress message - language = load_book_config(book_name).primary_language - assistant = create_or_get_assistant(book_name) + language = load_book_config(book_path).primary_language + assistant = create_or_get_assistant(book_path) thread = get_thread() - + book_name = book_path.split("/")[-1] # File path for the plot points - file_path = os.path.join(book_name, "outline", "plot_points.md") + file_path = os.path.join(book_path, "outline", "plot_points.md") # Check if the file exists and pass it as an attachment if os.path.exists(file_path) and file_has_more_than_three_lines(file_path): console.print( f"[yellow]Existing plot points found at {file_path}. Attaching for further refinement...[/yellow]" ) # Progress message - content = PLOT_POINTS_PROMPT_REFINE.format(prompt=prompt, language=language) + content = PLOT_POINTS_PROMPT_REFINE.format( + prompt=prompt, language=language, book_name=book_name + ) plot_points_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -150,34 +161,36 @@ def generate_plot_points(book_name, prompt): console.print( "[yellow]No existing plot points found. Generating new ones...[/yellow]" ) # Progress message - content = PLOT_POINTS_PROMPT_NEW.format(prompt=prompt, language=language) + content = PLOT_POINTS_PROMPT_NEW.format( + prompt=prompt, language=language, book_name=book_name + ) plot_points_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save to markdown save_to_markdown( - book_name, "outline/plot_points.md", "Main Plot Points", plot_points_content + book_path, "outline/plot_points.md", "Main Plot Points", plot_points_content ) console.print( "[bold green]✔ Main plot points generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return plot_points_content # Function to generate the chapter-by-chapter synopsis of the book -def generate_chapter_synopsis(book_name, prompt): +def generate_chapter_synopsis(book_path, prompt): """Generate the chapter-by-chapter synopsis for the book.""" console.print( "[bold blue]Generating chapter-by-chapter synopsis...[/bold blue]" ) # Progress message - language = load_book_config(book_name).primary_language - assistant = create_or_get_assistant(book_name) + language = load_book_config(book_path).primary_language + assistant = create_or_get_assistant(book_path) thread = get_thread() - + book_name = book_path.split("/")[-1] # File path for the chapter synopsis - file_path = os.path.join(book_name, "outline", "chapter_synopsis.md") + file_path = os.path.join(book_path, "outline", "chapter_synopsis.md") # Check if the file exists and pass it as an attachment if os.path.exists(file_path) and file_has_more_than_three_lines(file_path): @@ -185,9 +198,10 @@ def generate_chapter_synopsis(book_name, prompt): f"[yellow]Existing chapter synopsis found at {file_path}. Attaching for further refinement...[/yellow]" ) # Progress message content = CHAPTER_SYNOPSIS_PROMPT_REFINE.format( - prompt=prompt, language=language + prompt=prompt, language=language, book_name=book_name ) chapter_synopsis_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -197,14 +211,16 @@ def generate_chapter_synopsis(book_name, prompt): console.print( "[yellow]No existing chapter synopsis found. Generating a new one...[/yellow]" ) # Progress message - content = CHAPTER_SYNOPSIS_PROMPT_NEW.format(prompt=prompt, language=language) + content = CHAPTER_SYNOPSIS_PROMPT_NEW.format( + prompt=prompt, language=language, book_name=book_name + ) chapter_synopsis_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save to markdown save_to_markdown( - book_name, + book_path, "outline/chapter_synopsis.md", "Chapter Synopsis", chapter_synopsis_content, @@ -212,5 +228,5 @@ def generate_chapter_synopsis(book_name, prompt): console.print( "[bold green]✔ Chapter-by-chapter synopsis generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return chapter_synopsis_content diff --git a/storycraftr/agent/retrieval.py b/storycraftr/agent/retrieval.py index 3929a7c..566a4a4 100644 --- a/storycraftr/agent/retrieval.py +++ b/storycraftr/agent/retrieval.py @@ -17,7 +17,7 @@ def summarize_content(assistant, original_prompt): # Enviar el mensaje a través del asistente y obtener la respuesta summary_response = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) if summary_response: @@ -40,7 +40,7 @@ def optimize_query_with_summary(assistant, summarized_prompt): # Enviar el mensaje a través del asistente optimized_response = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) if optimized_response: @@ -62,7 +62,7 @@ def final_query(assistant, optimized_prompt): console.print("[cyan]Executing the final query...[/cyan]") final_response = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) if final_response: diff --git a/storycraftr/agent/worldbuilding.py b/storycraftr/agent/worldbuilding.py index c5683cb..8c4028b 100644 --- a/storycraftr/agent/worldbuilding.py +++ b/storycraftr/agent/worldbuilding.py @@ -25,15 +25,15 @@ # Function to generate the geography of the world -def generate_geography(book_name, prompt): +def generate_geography(book_path, prompt): """Generate the geography details for the book.""" console.print("[bold blue]Generating geography...[/bold blue]") # Progress message - language = load_book_config(book_name).primary_language - assistant = create_or_get_assistant(book_name) + language = load_book_config(book_path).primary_language + assistant = create_or_get_assistant(book_path) thread = get_thread() # File path for the geography details - file_path = os.path.join(book_name, "worldbuilding", "geography.md") + file_path = os.path.join(book_path, "worldbuilding", "geography.md") # Check if the file exists and pass it as an attachment if os.path.exists(file_path) and file_has_more_than_three_lines(file_path): @@ -42,6 +42,7 @@ def generate_geography(book_name, prompt): ) # Progress message content = GEOGRAPHY_PROMPT_REFINE.format(prompt=prompt, language=language) geography_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -53,30 +54,30 @@ def generate_geography(book_name, prompt): ) # Progress message content = GEOGRAPHY_PROMPT_NEW.format(prompt=prompt, language=language) geography_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save to markdown save_to_markdown( - book_name, "worldbuilding/geography.md", "Geography", geography_content + book_path, "worldbuilding/geography.md", "Geography", geography_content ) console.print( "[bold green]✔ Geography generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return geography_content # Function to generate the history of the world -def generate_history(book_name, prompt): +def generate_history(book_path, prompt): """Generate the history details for the book.""" console.print("[bold blue]Generating history...[/bold blue]") # Progress message - language = load_book_config(book_name).primary_language - assistant = create_or_get_assistant(book_name) + language = load_book_config(book_path).primary_language + assistant = create_or_get_assistant(book_path) thread = get_thread() # File path for the history details - file_path = os.path.join(book_name, "worldbuilding", "history.md") + file_path = os.path.join(book_path, "worldbuilding", "history.md") # Check if the file exists and pass it as an attachment if os.path.exists(file_path) and file_has_more_than_three_lines(file_path): @@ -85,6 +86,7 @@ def generate_history(book_name, prompt): ) # Progress message content = HISTORY_PROMPT_REFINE.format(prompt=prompt, language=language) history_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -96,28 +98,28 @@ def generate_history(book_name, prompt): ) # Progress message content = HISTORY_PROMPT_NEW.format(prompt=prompt, language=language) history_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save to markdown - save_to_markdown(book_name, "worldbuilding/history.md", "History", history_content) + save_to_markdown(book_path, "worldbuilding/history.md", "History", history_content) console.print( "[bold green]✔ History generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return history_content # Function to generate the culture of the world -def generate_culture(book_name, prompt): +def generate_culture(book_path, prompt): """Generate the culture details for the book.""" console.print("[bold blue]Generating culture...[/bold blue]") # Progress message - language = load_book_config(book_name).primary_language - assistant = create_or_get_assistant(book_name) + language = load_book_config(book_path).primary_language + assistant = create_or_get_assistant(book_path) thread = get_thread() # File path for the culture details - file_path = os.path.join(book_name, "worldbuilding", "culture.md") + file_path = os.path.join(book_path, "worldbuilding", "culture.md") # Check if the file exists and pass it as an attachment if os.path.exists(file_path) and file_has_more_than_three_lines(file_path): @@ -126,6 +128,7 @@ def generate_culture(book_name, prompt): ) # Progress message content = CULTURE_PROMPT_REFINE.format(prompt=prompt, language=language) culture_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -137,30 +140,30 @@ def generate_culture(book_name, prompt): ) # Progress message content = CULTURE_PROMPT_NEW.format(prompt=prompt, language=language) culture_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save to markdown - save_to_markdown(book_name, "worldbuilding/culture.md", "Culture", culture_content) + save_to_markdown(book_path, "worldbuilding/culture.md", "Culture", culture_content) console.print( "[bold green]✔ Culture generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return culture_content # Function to generate the magic or science system of the world -def generate_magic_system(book_name, prompt): +def generate_magic_system(book_path, prompt): """Generate the magic/science system for the book.""" console.print( "[bold blue]Generating magic/science system...[/bold blue]" ) # Progress message - language = load_book_config(book_name).primary_language - assistant = create_or_get_assistant(book_name) + language = load_book_config(book_path).primary_language + assistant = create_or_get_assistant(book_path) thread = get_thread() # File path for the magic system - file_path = os.path.join(book_name, "worldbuilding", "magic_system.md") + file_path = os.path.join(book_path, "worldbuilding", "magic_system.md") # Check if the file exists and pass it as an attachment if os.path.exists(file_path) and file_has_more_than_three_lines(file_path): @@ -169,6 +172,7 @@ def generate_magic_system(book_name, prompt): ) # Progress message content = MAGIC_SYSTEM_PROMPT_REFINE.format(prompt=prompt, language=language) magic_system_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -180,12 +184,12 @@ def generate_magic_system(book_name, prompt): ) # Progress message content = MAGIC_SYSTEM_PROMPT_NEW.format(prompt=prompt, language=language) magic_system_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save to markdown save_to_markdown( - book_name, + book_path, "worldbuilding/magic_system.md", "Magic/Science System", magic_system_content, @@ -193,20 +197,20 @@ def generate_magic_system(book_name, prompt): console.print( "[bold green]✔ Magic/Science system generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return magic_system_content # Function to generate the technology of the world (if applicable) -def generate_technology(book_name, prompt): +def generate_technology(book_path, prompt): """Generate the technology details for the book.""" console.print("[bold blue]Generating technology...[/bold blue]") # Progress message - language = load_book_config(book_name).primary_language - assistant = create_or_get_assistant(book_name) + language = load_book_config(book_path).primary_language + assistant = create_or_get_assistant(book_path) thread = get_thread() # File path for the technology details - file_path = os.path.join(book_name, "worldbuilding", "technology.md") + file_path = os.path.join(book_path, "worldbuilding", "technology.md") # Check if the file exists and pass it as an attachment if os.path.exists(file_path) and file_has_more_than_three_lines(file_path): @@ -215,6 +219,7 @@ def generate_technology(book_name, prompt): ) # Progress message content = TECHNOLOGY_PROMPT_REFINE.format(prompt=prompt, language=language) technology_content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, @@ -226,15 +231,15 @@ def generate_technology(book_name, prompt): ) # Progress message content = TECHNOLOGY_PROMPT_NEW.format(prompt=prompt, language=language) technology_content = create_message( - thread_id=thread.id, content=content, assistant=assistant + book_path, thread_id=thread.id, content=content, assistant=assistant ) # Save to markdown save_to_markdown( - book_name, "worldbuilding/technology.md", "Technology", technology_content + book_path, "worldbuilding/technology.md", "Technology", technology_content ) console.print( "[bold green]✔ Technology generated successfully[/bold green]" ) # Success message - update_agent_files(book_name, assistant) + update_agent_files(book_path, assistant) return technology_content diff --git a/storycraftr/cli.py b/storycraftr/cli.py index ae52fa9..497b0dd 100644 --- a/storycraftr/cli.py +++ b/storycraftr/cli.py @@ -36,56 +36,59 @@ def load_openai_api_key(): load_openai_api_key() -def verify_book_path(book_name=None): +def verify_book_path(book_path=None): """Verify if the book path is valid and contains storycraftr.json.""" - if not book_name: - book_name = ( + if not book_path: + book_path = ( os.getcwd() - ) # Use the current directory if --book-name is not provided - storycraftr_file = os.path.join(book_name, "storycraftr.json") + ) # Use the current directory if --book-path is not provided + storycraftr_file = os.path.join(book_path, "storycraftr.json") if not os.path.exists(storycraftr_file): raise click.ClickException( - f"The file storycraftr.json was not found in the path: {book_name}" + f"The file storycraftr.json was not found in the path: {book_path}" ) - return book_name + return book_path -def is_initialized(book_name): +def is_initialized(book_path): """Check if the book structure is already initialized.""" - storycraftr_file = os.path.join(book_name, "storycraftr.json") + storycraftr_file = os.path.join(book_path, "storycraftr.json") return os.path.exists(storycraftr_file) # Function to show error if project is not initialized -def project_not_initialized_error(book_name): +def project_not_initialized_error(book_path): console.print( - f"[bold red]✖[/bold red] Project '[bold]{book_name}[/bold]' is not initialized. " - f"Run '[bold]storycraftr init {book_name}[/bold]' first.", + f"[bold red]✖[/bold red] Project '[bold]{book_path}[/bold]' is not initialized. " + f"Run '[bold]storycraftr init {book_path}[/bold]' first.", style="bold red", ) def init_structure( - book_name, + book_path, license, primary_language, alternate_languages, default_author, genre, behavior_content, + reference_author, ): # Show initialization start - console.print(f"[bold blue]Initializing book structure: {book_name}[/bold blue]") + console.print(f"[bold blue]Initializing book structure: {book_path}[/bold blue]") + + book_name = book_path.split("/")[-1] # Iterate over the list and create each file, showing progress for file in storycraftr.templates.folder.files_to_create: # Build the full file path - file_path = os.path.join(book_name, file["folder"], file["filename"]) + file_path = os.path.join(book_path, file["folder"], file["filename"]) # Ensure the directory exists - os.makedirs(os.path.join(book_name, file["folder"]), exist_ok=True) + os.makedirs(os.path.join(book_path, file["folder"]), exist_ok=True) # Write the content to the file with open(file_path, "w") as f: @@ -96,15 +99,17 @@ def init_structure( # Create the storycraftr.json file config_data = { + "book_path": book_path, "book_name": book_name, "primary_language": primary_language, "alternate_languages": alternate_languages, "default_author": default_author, "genre": genre, "license": license, + "reference_author": reference_author, } - config_file = os.path.join(book_name, "storycraftr.json") + config_file = os.path.join(book_path, "storycraftr.json") with open(config_file, "w") as f: json.dump(config_data, f, indent=4) @@ -113,8 +118,8 @@ def init_structure( f"[green]Configuration file created:[/green] {config_file}", style="green" ) - # Create 'behaviors' folder inside the root book_name directory - behaviors_dir = os.path.join(book_name, "behaviors") + # Create 'behaviors' folder inside the root book_path directory + behaviors_dir = os.path.join(book_path, "behaviors") os.makedirs(behaviors_dir, exist_ok=True) # Create the default.txt file inside the 'behaviors' folder with the behavior content @@ -129,12 +134,12 @@ def init_structure( # Confirm completion console.print( - f"[bold green]✔[/bold green] Project '[bold]{book_name}[/bold]' initialized successfully.", + f"[bold green]✔[/bold green] Project '[bold]{book_path}[/bold]' initialized successfully.", style="bold green", ) - # Ruta donde se creará la nueva carpeta de book_name/templates/ - new_template_dir = os.path.join(book_name, "templates") + # Ruta donde se creará la nueva carpeta de book_path/templates/ + new_template_dir = os.path.join(book_path, "templates") # Log: Creación de la carpeta console.log(f"Creando el directorio: {new_template_dir}", style="bold blue") @@ -146,18 +151,14 @@ def init_structure( new_template_path = os.path.join(new_template_dir, "template.tex") # Log: Escribiendo el archivo - console.log( - f"Escribiendo el archivo LaTeX en: {new_template_path}", style="bold green" - ) + console.log(f"Writing LaTex file: {new_template_path}", style="bold green") # Escribir el contenido del template en el archivo with open(new_template_path, "w") as f: f.write(TEMPLATE_TEX) # Log: Proceso completado - console.log( - f"Template copiado exitosamente a: {new_template_path}", style="bold magenta" - ) + console.log(f"LaTex template copied: {new_template_path}", style="bold magenta") @click.group() @@ -167,7 +168,7 @@ def cli(): @click.command() -@click.argument("book_name") +@click.argument("book_path") @click.option( "--license", default="CC BY-NC-SA", @@ -190,11 +191,22 @@ def cli(): @click.option( "--behavior", help="Behavior content, either as a string or a path to a file." ) +@click.option( + "--reference-author", + help="Behavior content, either as a string or a path to a file.", +) def init( - book_name, license, primary_language, alternate_languages, author, genre, behavior + book_path, + license, + primary_language, + alternate_languages, + author, + genre, + behavior, + reference_author="(None: use all your knwoledge to assume writing style )", ): """Initialize the book structure with relevant configuration and behavior content.""" - if not is_initialized(book_name): + if not is_initialized(book_path): alternate_languages_list = ( [lang.strip() for lang in alternate_languages.split(",")] if alternate_languages @@ -211,17 +223,18 @@ def init( ) init_structure( - book_name, + book_path, license, primary_language, alternate_languages_list, author, genre, behavior_content, + reference_author, ) else: console.print( - f"[bold yellow]⚠[/bold yellow] Project '[bold]{book_name}[/bold]' is already initialized.", + f"[bold yellow]⚠[/bold yellow] Project '[bold]{book_path}[/bold]' is already initialized.", style="yellow", ) diff --git a/storycraftr/cmd/chapters.py b/storycraftr/cmd/chapters.py index 50a94f1..ef46cd1 100644 --- a/storycraftr/cmd/chapters.py +++ b/storycraftr/cmd/chapters.py @@ -21,55 +21,55 @@ def chapters(): @chapters.command() @click.argument("chapter_number", type=int) @click.argument("prompt") -@click.option("--book-name", type=click.Path(), help="Path to the book directory") -def chapter(chapter_number, prompt, book_name=None): +@click.option("--book-path", type=click.Path(), help="Path to the book directory") +def chapter(chapter_number, prompt, book_path=None): """Generate a new chapter for the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_chapter(book_name, chapter_number, prompt) + generate_chapter(book_path, chapter_number, prompt) @chapters.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def cover(prompt, book_name=None): +def cover(prompt, book_path=None): """Generate the cover of the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_cover(book_name, prompt) + generate_cover(book_path, prompt) @chapters.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def back_cover(prompt, book_name=None): +def back_cover(prompt, book_path=None): """Generate the back cover of the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_back_cover(book_name, prompt) + generate_back_cover(book_path, prompt) @chapters.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def epilogue(prompt, book_name=None): +def epilogue(prompt, book_path=None): """Generate the epilogue of the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_epilogue(book_name, prompt) + generate_epilogue(book_path, prompt) diff --git a/storycraftr/cmd/chat.py b/storycraftr/cmd/chat.py index 795f14f..5e86445 100644 --- a/storycraftr/cmd/chat.py +++ b/storycraftr/cmd/chat.py @@ -8,19 +8,19 @@ @click.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") -def chat(book_name=None): +@click.option("--book-path", type=click.Path(), help="Path to the book directory") +def chat(book_path=None): """ Start a chat session with the assistant for the given book name. """ - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() console.print( - f"Starting chat for [bold]{book_name}[/bold]. Type [bold green]exit()[/bold green] to quit." + f"Starting chat for [bold]{book_path}[/bold]. Type [bold green]exit()[/bold green] to quit." ) # Create or get the assistant and thread - assistant = create_or_get_assistant(book_name) + assistant = create_or_get_assistant(book_path) thread = get_thread() while True: @@ -37,7 +37,7 @@ def chat(book_name=None): # Send message to assistant try: response = create_message( - thread_id=thread.id, content=user_input, assistant=assistant + book_path, thread_id=thread.id, content=user_input, assistant=assistant ) except Exception as e: console.print(f"[bold red]Error: {str(e)}[/bold red]") diff --git a/storycraftr/cmd/iterate.py b/storycraftr/cmd/iterate.py index 2a2da8a..1108179 100644 --- a/storycraftr/cmd/iterate.py +++ b/storycraftr/cmd/iterate.py @@ -21,26 +21,26 @@ def iterate(): @iterate.command() @click.option( - "--book-name", type=click.Path(), help="Path to the book directory", required=False + "--book-path", type=click.Path(), help="Path to the book directory", required=False ) @click.argument("prompt", default="Check character names for consistency.") -def check_names(prompt, book_name=None): +def check_names(prompt, book_path=None): """ Comando para revisar la consistencia de los nombres de personajes en los capítulos de un libro. - Los capítulos se encuentran en 'book_name/chapters'. + Los capítulos se encuentran en 'book_path/chapters'. """ - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None console.print( - f"[bold blue]Iniciando la revisión de consistencia de nombres en los capítulos del libro: {book_name}[/bold blue]" + f"[bold blue]Iniciando la revisión de consistencia de nombres en los capítulos del libro: {book_path}[/bold blue]" ) # Llamar a la función que revisa los nombres en los capítulos - iterate_check_names(book_name) + iterate_check_names(book_path) # Success log console.print(f"[green bold]Success![/green bold] Check Names!") @@ -48,27 +48,27 @@ def check_names(prompt, book_name=None): @iterate.command() @click.option( - "--book-name", type=click.Path(), help="Path to the book directory", required=False + "--book-path", type=click.Path(), help="Path to the book directory", required=False ) @click.argument("original_name") @click.argument("new_name") -def fix_name(original_name, new_name, book_name=None): +def fix_name(original_name, new_name, book_path=None): """ Comando para cambiar el nombre de un personaje en todos los capítulos del libro. Recibe como parámetros el nombre original y el nuevo nombre. """ - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None console.print( - f"[bold blue]Iniciando el cambio de nombre: '{original_name}' a '{new_name}' en el libro: {book_name}[/bold blue]" + f"[bold blue]Iniciando el cambio de nombre: '{original_name}' a '{new_name}' en el libro: {book_path}[/bold blue]" ) # Llamar a la función que realiza el cambio de nombres - fix_name_in_chapters(book_name, original_name, new_name) + fix_name_in_chapters(book_path, original_name, new_name) # Success log console.print( @@ -78,27 +78,27 @@ def fix_name(original_name, new_name, book_name=None): @iterate.command() @click.option( - "--book-name", type=click.Path(), help="Path to the book directory", required=False + "--book-path", type=click.Path(), help="Path to the book directory", required=False ) @click.argument("character_name") @click.argument("story_context") -def refine_motivation(character_name, story_context, book_name=None): +def refine_motivation(character_name, story_context, book_path=None): """ Command to refine the motivations of a character across all chapters of the book. It takes the character's name and the story context as parameters. """ - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None console.print( - f"[bold blue]Starting motivation refinement for '{character_name}' in the book: {book_name}[/bold blue]" + f"[bold blue]Starting motivation refinement for '{character_name}' in the book: {book_path}[/bold blue]" ) # Call the function to refine the character's motivations - refine_character_motivation(book_name, character_name, story_context) + refine_character_motivation(book_path, character_name, story_context) # Success log console.print( @@ -108,26 +108,26 @@ def refine_motivation(character_name, story_context, book_name=None): @iterate.command() @click.option( - "--book-name", type=click.Path(), help="Path to the book directory", required=False + "--book-path", type=click.Path(), help="Path to the book directory", required=False ) @click.argument("argument") -def strengthen_argument(argument, book_name=None): +def strengthen_argument(argument, book_path=None): """ Command to ensure the core argument of the story is strong and clear across all chapters. Takes the argument as a parameter. """ - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None console.print( - f"[bold blue]Starting to strengthen the core argument: '{argument}' in the book: {book_name}[/bold blue]" + f"[bold blue]Starting to strengthen the core argument: '{argument}' in the book: {book_path}[/bold blue]" ) # Call the function to strengthen the core argument in the chapters - strengthen_core_argument(book_name, argument) + strengthen_core_argument(book_path, argument) # Success log console.print( @@ -137,27 +137,27 @@ def strengthen_argument(argument, book_name=None): @iterate.command() @click.option( - "--book-name", type=click.Path(), help="Path to the book directory", required=False + "--book-path", type=click.Path(), help="Path to the book directory", required=False ) @click.argument("position", type=int) @click.argument("prompt") -def insert_chapter(position, prompt, book_name=None): +def insert_chapter(position, prompt, book_path=None): """ Command to insert a new chapter at the specified position, shifting existing chapters and renaming them accordingly. Adjusts the content of the chapters before and after the insertion, ensuring the new chapter fits contextually. """ - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None console.print( - f"[bold blue]Inserting a new chapter at position {position} in the book: {book_name}[/bold blue]" + f"[bold blue]Inserting a new chapter at position {position} in the book: {book_path}[/bold blue]" ) # Call the function to insert the new chapter and adjust the surrounding chapters - insert_new_chapter(book_name, position, prompt) + insert_new_chapter(book_path, position, prompt) # Success log console.print( @@ -166,15 +166,15 @@ def insert_chapter(position, prompt, book_name=None): @iterate.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") @click.argument("chapter_number", type=int) -def split_chapter(prompt, chapter_number, book_name): +def split_chapter(prompt, chapter_number, book_path): """Split a chapter and adjust the numbering of subsequent chapters.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None console.print( @@ -184,15 +184,15 @@ def split_chapter(prompt, chapter_number, book_name): @iterate.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") @click.argument("chapter_position", type=int) -def add_flashback(prompt, chapter_position, book_name): +def add_flashback(prompt, chapter_position, book_path): """Add a flashback scene between two chapters.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None console.print( @@ -204,14 +204,14 @@ def add_flashback(prompt, chapter_position, book_name): @iterate.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def update_plot_points(prompt, book_name): +def update_plot_points(prompt, book_path): """Refine key plot points across the story.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None console.print( @@ -221,14 +221,14 @@ def update_plot_points(prompt, book_name): @iterate.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def check_consistency(prompt, book_name): +def check_consistency(prompt, book_path): """Check for consistency across all chapters and elements of the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None # Placeholder for future retrieval-based consistency check diff --git a/storycraftr/cmd/outline.py b/storycraftr/cmd/outline.py index 63a53d8..b9ca21a 100644 --- a/storycraftr/cmd/outline.py +++ b/storycraftr/cmd/outline.py @@ -19,56 +19,56 @@ def outline(): @outline.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def general_outline(prompt, book_name=None): +def general_outline(prompt, book_path=None): """Generate the general outline of the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_general_outline(book_name, prompt) + generate_general_outline(book_path, prompt) @outline.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def character_summary(prompt, book_name=None): +def character_summary(prompt, book_path=None): """Generate the character summary of the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_character_summary(book_name, prompt) + generate_character_summary(book_path, prompt) @outline.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def plot_points(prompt, book_name=None): +def plot_points(prompt, book_path=None): """Generate the main plot points of the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_plot_points(book_name, prompt) + generate_plot_points(book_path, prompt) @outline.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def chapter_synopsis(prompt, book_name=None): +def chapter_synopsis(prompt, book_path=None): """Generate the chapter-by-chapter synopsis of the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_chapter_synopsis(book_name, prompt) + generate_chapter_synopsis(book_path, prompt) diff --git a/storycraftr/cmd/publish.py b/storycraftr/cmd/publish.py index f4de2e5..3c234ef 100644 --- a/storycraftr/cmd/publish.py +++ b/storycraftr/cmd/publish.py @@ -22,15 +22,15 @@ def publish(): default=None, help="Translate the book to this language before publishing", ) -@click.option("--book-name", type=click.Path(), help="Path to the book directory") -def pdf(primary_language, translate=None, book_name=None): +@click.option("--book-path", type=click.Path(), help="Path to the book directory") +def pdf(primary_language, translate=None, book_path=None): """Publish the book as a PDF.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): console.print( - f"[red bold]Error:[/red bold] Book configuration not found in {book_name}." + f"[red bold]Error:[/red bold] Book configuration not found in {book_path}." ) return None @@ -39,7 +39,7 @@ def pdf(primary_language, translate=None, book_name=None): f"Generating PDF for the book in [bold]{primary_language}[/bold] language..." ) - output_pdf_path = to_pdf(book_name, primary_language, translate) + output_pdf_path = to_pdf(book_path, primary_language, translate) # Success log console.print( diff --git a/storycraftr/cmd/worldbuilding.py b/storycraftr/cmd/worldbuilding.py index d125c2b..4e1157e 100644 --- a/storycraftr/cmd/worldbuilding.py +++ b/storycraftr/cmd/worldbuilding.py @@ -20,70 +20,70 @@ def worldbuilding(): @worldbuilding.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def geography(prompt, book_name=None): +def geography(prompt, book_path=None): """Generate geography details for the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_geography(book_name, prompt) + generate_geography(book_path, prompt) @worldbuilding.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def history(prompt, book_name=None): +def history(prompt, book_path=None): """Generate history details for the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_history(book_name, prompt) + generate_history(book_path, prompt) @worldbuilding.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def culture(prompt, book_name=None): +def culture(prompt, book_path=None): """Generate culture details for the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_culture(book_name, prompt) + generate_culture(book_path, prompt) @worldbuilding.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def magic_system(prompt, book_name=None): +def magic_system(prompt, book_path=None): """Generate magic or science system details for the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_magic_system(book_name, prompt) + generate_magic_system(book_path, prompt) @worldbuilding.command() -@click.option("--book-name", type=click.Path(), help="Path to the book directory") +@click.option("--book-path", type=click.Path(), help="Path to the book directory") @click.argument("prompt") -def technology(prompt, book_name=None): +def technology(prompt, book_path=None): """Generate technology details for the book.""" - if not book_name: - book_name = os.getcwd() + if not book_path: + book_path = os.getcwd() - if not load_book_config(book_name): + if not load_book_config(book_path): return None - generate_technology(book_name, prompt) + generate_technology(book_path, prompt) diff --git a/storycraftr/prompts/chapters.py b/storycraftr/prompts/chapters.py index 5709dbf..ebb5164 100644 --- a/storycraftr/prompts/chapters.py +++ b/storycraftr/prompts/chapters.py @@ -3,19 +3,13 @@ CHAPTER_PROMPT_NEW = """ Write a detailed and engaging chapter for the following book premise: {prompt}. Ensure the chapter contributes meaningfully to the plot, character development, and overall progression of the story. -Return only the markdown content of the chapter, ready for direct inclusion in the book. - -Do not include any additional explanations, notes, or comments. Write the content in {language}. """ CHAPTER_PROMPT_REFINE = """ -Refine and enhance the attached chapter file. +Refine and evolve the content based on this prompt: {prompt}. Improve the narrative flow, character development, and pacing, based on the following prompt: {prompt}. -Ensure that the refined chapter maintains consistency with the story's tone, themes, and existing character arcs. - -Return the refined markdown content, ready for direct inclusion in the book, without adding any extra explanations, notes, or comments. Write the content in {language}. """ @@ -23,8 +17,6 @@ COVER_PROMPT = """ Create a professional book cover in markdown format for the book titled '{title}'. Include only the title and author ('{author}'). -Replace any image markdown with the placeholder PUT_IMAGE_CODE_HERE so it can be replaced with the cover image later. -Do not include any explanations, notes, or additional comments in the output. Strictly return only the cover content in markdown format, ready for direct use in the book. Use the following as additional context: {prompt}. @@ -36,10 +28,6 @@ Generate a detailed and engaging synopsis for the back cover of the book titled '{title}', written by '{author}'. Include the genre ('{genre}') and any alternate languages ('{alternate_languages}') where the book is available. Also, include the license type '{license}', along with a professional description of the license. - -Return only the back cover in markdown format, with all the required elements for direct use in the book. -Do not include any additional explanations, notes, or comments. - Use the following as additional context: {prompt}. Write the content in {language}. """ @@ -48,18 +36,13 @@ EPILOGUE_PROMPT_NEW = """ Generate a complete and compelling epilogue for the book based on the following context: {prompt}. The epilogue should tie up loose ends and provide closure in a way that complements the main storyline. -Return only the markdown content of the epilogue, formatted and ready for direct inclusion in the book. - -Do not include any additional explanations, notes, or comments. Write the content in {language}. """ EPILOGUE_PROMPT_REFINE = """ -Refine and enhance the epilogue using the attached file as a reference. +Refine and evolve the content based on this prompt: {prompt}. Improve its narrative flow, tie up remaining plot points, and evolve the content based on this prompt: {prompt}. Ensure that the refined epilogue maintains the tone and themes of the story. - -Return the refined markdown content, ready for direct inclusion in the book, without adding any extra explanations, notes, or comments. Write the content in {language}. """ diff --git a/storycraftr/prompts/iterate.py b/storycraftr/prompts/iterate.py index 4806646..a776d25 100644 --- a/storycraftr/prompts/iterate.py +++ b/storycraftr/prompts/iterate.py @@ -4,31 +4,23 @@ You have access to the entire text of the book, which has been loaded for analysis. Your task is to correct any inconsistencies in character names throughout the entire text. Ensure that each character's name is used consistently and appropriately based on the context. - -Return the corrected text without adding any extra explanations, notes, or comments. -The output should be ready for use in the book directly, with all character name inconsist """ FIX_NAME_PROMPT = """ Update all instances of the character name '{original_name}' to '{new_name}' throughout the text, taking into account context. Ensure that any diminutives, nicknames, or variations of the name '{original_name}' are also replaced appropriately with contextually fitting forms of '{new_name}'. Preserve the tone, style, and flow of the text as much as possible. -Return the corrected text without adding any extra explanations, notes, or comments. -The output should be ready for use in the book directly, with the names fixed. """ REFINE_MOTIVATION_PROMPT = """ Refine the motivations of the character '{character_name}' throughout the story, ensuring that their actions, dialogue, and thoughts are aligned with a well-defined character arc. The character is involved in a story about '{story_context}'. Ensure the motivations are coherent with the plot and character development, maintaining the tone and style of the original text. -Return the refined text without adding any extra explanations, notes, or comments. -The output should be ready for use in the book directly, with refined character motivations. """ STRENGTHEN_ARGUMENT_PROMPT = """ Ensure that the core argument of the story, '{argument}', is clearly articulated throughout this chapter. Make sure the theme and message are consistent, reinforcing the central idea. -Return the revised markdown without adding any explanations, notes, or comments. """ INSERT_CHAPTER_PROMPT = """ @@ -37,12 +29,10 @@ For example, if a new chapter is inserted at position 3, the current chapter 3 will become chapter 4, chapter 4 will become chapter 5, and so on. Use the retrieval system to access the chapters before and after this position, ensuring that the new chapter fits seamlessly with the narrative, themes, and character arcs. Use this prompt for context: {prompt}. -Return the new chapter in markdown format, ready for inclusion in the book without adding any explanations, notes, or comments. """ REWRITE_SURROUNDING_CHAPTERS_PROMPT = """ Write the chapters {chapter}, ensuring that they fit seamlessly with the previous and next chapter. Utilize the retrieval system to gather context from the full book, making sure the tone, style, and character arcs remain consistent. Use this prompt for context: {prompt}. -Return the rewritten chapters in markdown format, without adding any explanations, notes, or comments. """ diff --git a/storycraftr/prompts/outline.py b/storycraftr/prompts/outline.py index 0859915..697e3c9 100644 --- a/storycraftr/prompts/outline.py +++ b/storycraftr/prompts/outline.py @@ -1,55 +1,44 @@ GENERAL_OUTLINE_PROMPT_NEW = """ -Create a general outline for a book based on this prompt: {prompt}. -Return only the general outline in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Create a general outline for the book {book_name} based on this prompt: {prompt}. Write the content in {language}. """ GENERAL_OUTLINE_PROMPT_REFINE = """ -Use the attached general outline file to refine and evolve the content based on this prompt: {prompt}. -Return the refined outline in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Refine and evolve the content based on this prompt: {prompt}. +Focus on improving the structure and flow of the outline, ensuring clarity and coherence throughout. Write the content in {language}. """ CHARACTER_SUMMARY_PROMPT_NEW = """ -Generate a detailed character summary for the book based on this prompt: {prompt}. -Return only the character summary in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Generate a detailed character summary for the book {book_name} based on this prompt: {prompt}. Write the content in {language}. """ CHARACTER_SUMMARY_PROMPT_REFINE = """ -Use the attached character summary file to refine and evolve the content based on this prompt: {prompt}. -Return the refined character summary in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Refine and evolve the content based on this prompt: {prompt}. +Enhance character depth, motivation, and consistency with the overall narrative. Write the content in {language}. """ PLOT_POINTS_PROMPT_NEW = """ -Generate the main plot points for the book based on this prompt: {prompt}. -Return only the plot points in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Generate the main plot points for the book {book_name} based on this prompt: {prompt}. Write the content in {language}. """ PLOT_POINTS_PROMPT_REFINE = """ -Use the attached plot points file to refine and evolve the content based on this prompt: {prompt}. -Return the refined plot points in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Refine and evolve the content based on this prompt: {prompt}. +Ensure logical progression between plot points and strengthen the links between key events. Write the content in {language}. """ CHAPTER_SYNOPSIS_PROMPT_NEW = """ -Generate a chapter-by-chapter synopsis for the book based on this prompt: {prompt}. -Return only the chapter-by-chapter synopsis in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Generate a chapter-by-chapter synopsis for the book {book_name} based on this prompt: {prompt}. +List all chapters and its synopsis. Write the content in {language}. """ CHAPTER_SYNOPSIS_PROMPT_REFINE = """ -Use the attached chapter-by-chapter synopsis file to refine and evolve the content based on this prompt: {prompt}. -Return the refined chapter-by-chapter synopsis in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Refine and evolve the content based on this prompt: {prompt}. +Focus on improving chapter summaries by clarifying pivotal moments and enhancing narrative tension. Write the content in {language}. """ diff --git a/storycraftr/prompts/worldbuilding.py b/storycraftr/prompts/worldbuilding.py index 11a36b3..cb76336 100644 --- a/storycraftr/prompts/worldbuilding.py +++ b/storycraftr/prompts/worldbuilding.py @@ -2,70 +2,50 @@ GEOGRAPHY_PROMPT_NEW = """ Generate detailed geography information for the book's world based on this prompt: {prompt}. -Return only the geography details in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. Write the content in {language}. """ GEOGRAPHY_PROMPT_REFINE = """ -Use the attached geography file to refine and evolve the content based on this prompt: {prompt}. -Return the refined geography details in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Refine and evolve the content based on this prompt: {prompt}. Write the content in {language}. """ HISTORY_PROMPT_NEW = """ Generate detailed history information for the book's world based on this prompt: {prompt}. -Return only the history details in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. Write the content in {language}. """ HISTORY_PROMPT_REFINE = """ -Use the attached history file to refine and evolve the content based on this prompt: {prompt}. -Return the refined history details in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Refine and evolve the content based on this prompt: {prompt}. Write the content in {language}. """ CULTURE_PROMPT_NEW = """ Generate detailed culture information for the book's world based on this prompt: {prompt}. -Return only the culture details in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. Write the content in {language}. """ CULTURE_PROMPT_REFINE = """ -Use the attached culture file to refine and evolve the content based on this prompt: {prompt}. -Return the refined culture details in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Refine and evolve the content based on this prompt: {prompt}. Write the content in {language}. """ MAGIC_SYSTEM_PROMPT_NEW = """ Generate a detailed magic or science system for the book's world based on this prompt: {prompt}. -Return only the magic/science system details in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. Write the content in {language}. """ MAGIC_SYSTEM_PROMPT_REFINE = """ -Use the attached magic/science system file to refine and evolve the content based on this prompt: {prompt}. -Return the refined magic/science system details in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Refine and evolve the content based on this prompt: {prompt}. Write the content in {language}. """ TECHNOLOGY_PROMPT_NEW = """ Generate detailed technology information for the book's world based on this prompt: {prompt}. -Return only the technology details in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. Write the content in {language}. """ TECHNOLOGY_PROMPT_REFINE = """ -Use the attached technology file to refine and evolve the content based on this prompt: {prompt}. -Return the refined technology details in markdown format, ready for direct inclusion in the book. -Do not include any additional explanations, notes, or comments. +Refine and evolve the content based on this prompt: {prompt}. Write the content in {language}. """ diff --git a/storycraftr/utils/core.py b/storycraftr/utils/core.py index 9d7f037..7c66500 100644 --- a/storycraftr/utils/core.py +++ b/storycraftr/utils/core.py @@ -8,33 +8,37 @@ # Define the structure for the book using NamedTuple class BookConfig(NamedTuple): + book_path: str book_name: str primary_language: str alternate_languages: list default_author: str genre: str license: str + reference_author: str # Function to load the JSON file and convert it into a BookConfig object -def load_book_config(book_name): +def load_book_config(book_path): try: with open( - os.path.join(book_name, "storycraftr.json"), "r", encoding="utf-8" + os.path.join(book_path, "storycraftr.json"), "r", encoding="utf-8" ) as file: data = json.load(file) # Create an instance of BookConfig with the values from the JSON book_config = BookConfig( + book_path=data["book_path"], book_name=data["book_name"], primary_language=data["primary_language"], alternate_languages=data["alternate_languages"], default_author=data["default_author"], genre=data["genre"], license=data["license"], + reference_author=data["reference_author"], ) except FileNotFoundError or NotADirectoryError: console.print( - f"[bold red]⚠[/bold red] Folder '[bold]{book_name}[/bold]' is not a storycraftr project.", + f"[bold red]⚠[/bold red] Folder '[bold]{book_path}[/bold]' is not a storycraftr project.", style="red", ) return None diff --git a/storycraftr/utils/markdown.py b/storycraftr/utils/markdown.py index 3e7495b..56e2355 100644 --- a/storycraftr/utils/markdown.py +++ b/storycraftr/utils/markdown.py @@ -9,12 +9,12 @@ # Function to save content to a markdown file with optional task for progress updates def save_to_markdown( - book_name, file_name, header, content, progress: Progress = None, task=None + book_path, file_name, header, content, progress: Progress = None, task=None ): """Save the generated content to the specified markdown file, creating a backup if the file exists. Optionally updates a progress task. """ - file_path = os.path.join(book_name, file_name) + file_path = os.path.join(book_path, file_name) backup_path = file_path + ".back" # If the file exists, create a backup @@ -49,9 +49,9 @@ def save_to_markdown( # Function to append content to an existing markdown file -def append_to_markdown(book_name, folder_name, file_name, content): +def append_to_markdown(book_path, folder_name, file_name, content): """Append content to an existing markdown file.""" - file_path = os.path.join(book_name, folder_name, file_name) + file_path = os.path.join(book_path, folder_name, file_name) if os.path.exists(file_path): with open(file_path, "a") as f: @@ -62,9 +62,9 @@ def append_to_markdown(book_name, folder_name, file_name, content): # Function to read content from a markdown file -def read_from_markdown(book_name, folder_name, file_name): +def read_from_markdown(book_path, folder_name, file_name): """Read content from the specified markdown file.""" - file_path = os.path.join(book_name, folder_name, file_name) + file_path = os.path.join(book_path, folder_name, file_name) if os.path.exists(file_path): with open(file_path, "r") as f: @@ -75,30 +75,21 @@ def read_from_markdown(book_name, folder_name, file_name): raise FileNotFoundError(f"File {file_path} does not exist.") -import os -import re -from storycraftr.agent.agents import create_or_get_assistant, get_thread, create_message -from rich.console import Console -from rich.progress import track - -console = Console() - - def consolidate_book_md( - book_name: str, primary_language: str, translate: str = None + book_path: str, primary_language: str, translate: str = None ) -> str: - chapters_dir = os.path.join(book_name, "chapters") + chapters_dir = os.path.join(book_path, "chapters") output_file_name = ( f"book-{primary_language}.md" if not translate else f"book-{translate}.md" ) - output_file_path = os.path.join(book_name, "book", output_file_name) + output_file_path = os.path.join(book_path, "book", output_file_name) # Ensure the "book" folder exists - book_dir = os.path.join(book_name, "book") + book_dir = os.path.join(book_path, "book") os.makedirs(book_dir, exist_ok=True) # Create or get the assistant and thread - assistant = create_or_get_assistant(book_name) + assistant = create_or_get_assistant(book_path) thread = get_thread() # Files to process in the specified order @@ -126,11 +117,11 @@ def consolidate_book_md( # Log start of consolidation and translation status if translate: console.print( - f"Consolidating chapters for [bold]{book_name}[/bold] and translating to [bold]{translate}[/bold]..." + f"Consolidating chapters for [bold]{book_path}[/bold] and translating to [bold]{translate}[/bold]..." ) else: console.print( - f"Consolidating chapters for [bold]{book_name}[/bold] without translation..." + f"Consolidating chapters for [bold]{book_path}[/bold] without translation..." ) # Create Progress object with two tasks @@ -166,6 +157,7 @@ def consolidate_book_md( if translate: progress.reset(task_translation) content = create_message( + book_path, thread_id=thread.id, content=content, assistant=assistant, diff --git a/storycraftr/utils/pdf.py b/storycraftr/utils/pdf.py index 55dc6a2..01beb8d 100644 --- a/storycraftr/utils/pdf.py +++ b/storycraftr/utils/pdf.py @@ -24,7 +24,7 @@ def check_tool_installed(tool_name: str) -> bool: return False -def to_pdf(book_name: str, primary_language: str, translate: str = None) -> str: +def to_pdf(book_path: str, primary_language: str, translate: str = None) -> str: # Check if pandoc is installed if not check_tool_installed("pandoc"): console.print( @@ -40,11 +40,11 @@ def to_pdf(book_name: str, primary_language: str, translate: str = None) -> str: raise SystemExit(1) # Log the start of the process - console.print(f"Starting PDF conversion for book: [bold]{book_name}[/bold]") + console.print(f"Starting PDF conversion for book: [bold]{book_path}[/bold]") # Get the path of the consolidated markdown console.print("Consolidating chapters into a single markdown file...") - consolidated_md_path = consolidate_book_md(book_name, primary_language, translate) + consolidated_md_path = consolidate_book_md(book_path, primary_language, translate) # Read the consolidated markdown content with open(consolidated_md_path, "r", encoding="utf-8") as f: @@ -59,7 +59,7 @@ def to_pdf(book_name: str, primary_language: str, translate: str = None) -> str: console.print(f"Markdown consolidated at [bold]{consolidated_md_path}[/bold]") # Path to the LaTeX template - template_path = os.path.join(book_name, "templates", "template.tex") + template_path = os.path.join(book_path, "templates", "template.tex") if not os.path.exists(template_path): console.print( diff --git a/tests/test_chapters.py b/tests/test_chapters.py index 4b9b43e..8bc8150 100644 --- a/tests/test_chapters.py +++ b/tests/test_chapters.py @@ -64,7 +64,7 @@ def test_generate_cover( # Mocks mock_load_book_config.return_value = MagicMock( primary_language="en", - book_name="My Book", + book_path="My Book", default_author="Author Name", genre="Science Fiction", alternate_languages=["es"],