help debug this test please keep it simple:

```
import pytest
from chat.chat import chat_with_model, process_file_updates, detect_file_updates, calculate_diff, remove_full_content_from_response
from app_state import get_app_state
from efc.efc_cache import get_file_context
from unittest.mock import AsyncMock, MagicMock


@pytest.fixture
def mock_app_state():
    class MockAppState:
        def __init__(self):
            self.active_project = "/path/to/active_project"
            self.conversation_history = []
            self.file_histories = {}
            self.token_usage = {}

        def get_active_project_tree_string(self):
            return "Mocked project tree"

    return MockAppState()


@pytest.fixture
def mock_file_context():
    class MockFileContextTracker:
        def __init__(self):
            self.tracked_files = {}

        def generate_context_awareness_prompt(self, app_state) -> str:
            if not self.tracked_files:
                return "No files in context."
            context = ""
            for relative_path in self.tracked_files:
                content = self.get_file_content(relative_path)
                context += f"- **{relative_path}**:\n```\n{content}\n```\n"
            return context

        def get_file_content(self, relative_path: str) -> str:
            return self.tracked_files.get(relative_path, "")

        def add_or_update_file(self, relative_path: str, content: str, operation: str) -> None:
            print(f"MockFileContextTracker: Adding '{relative_path}' with content '{content}' via operation '{operation}'")
            self.tracked_files[relative_path] = content

    return MockFileContextTracker()


def test_detect_file_updates():
    assistant_message = "Fresh content for file1.txt: ```diff\n+ Added line\n- Removed line\n```"
    updates = detect_file_updates(assistant_message)
    assert updates == {"file1.txt": "+ Added line\n- Removed line"}


def test_calculate_diff():
    old_content = "Line1\nLine2\nLine3"
    new_content = "Line1\nLine2 modified\nLine3"
    diff = calculate_diff(old_content, new_content)
    expected_diff = """--- 
+++ 
@@ -1,3 +1,3 @@
 Line1
-Line2
+Line2 modified
 Line3"""
    assert diff == expected_diff


def test_remove_full_content_from_response():
    assistant_message = "Some message. Fresh content for file1.txt: ```diff\n+ Added line\n- Removed line\n``` More text."
    cleaned_message = remove_full_content_from_response(assistant_message, 'file1.txt')
    assert cleaned_message == "Some message. More text."


@pytest.mark.asyncio
async def test_initial_file_addition(mock_app_state, mock_file_context, monkeypatch):
    user_input = "Create file1.txt"

    # 1. Mock AI client
    ai_client_mock = AsyncMock()

    # 2. Mock the response returned by create_message
    create_message_response_mock = MagicMock(
        usage=MagicMock(
            prompt_tokens=10,
            completion_tokens=20,
            total_tokens=30,
            cache_creation_input_tokens=5,
            cache_read_input_tokens=5
        )
    )
    ai_client_mock.create_message.return_value = create_message_response_mock

    # 3. Mock the extract_response method on the AI client with a relative path
    ai_client_mock.extract_response = AsyncMock(return_value="Added file1.txt to context.")

    # 4. Apply monkeypatch to replace AIClientFactory.get_ai_client with our mock
    monkeypatch.setattr("chat.chat.AIClientFactory.get_ai_client", lambda x: ai_client_mock)

    # 5. Apply monkeypatch to replace get_app_state to return mock_app_state
    monkeypatch.setattr("chat.chat.get_app_state", lambda: mock_app_state)

    # 6. Apply monkeypatch to replace get_file_context to return mock_file_context
    monkeypatch.setattr("chat.chat.get_file_context", lambda: mock_file_context)

    # 7. Call the function under test
    assistant_response = await chat_with_model(user_input, MagicMock(), "gpt-4")

    # 8. Print internal states for debugging
    print("Conversation History:", mock_app_state.conversation_history)
    print("Tracked Files:", mock_file_context.tracked_files)

    # 9. Check that a notification message was appended
    expected_conversation_length = 3  # User input + Assistant response + Notification
    assert len(mock_app_state.conversation_history) == expected_conversation_length, \
        f"Expected {expected_conversation_length} messages in conversation_history, found {len(mock_app_state.conversation_history)}"

    assert mock_app_state.conversation_history[0]['role'] == 'user', \
        f"Expected first message role to be 'user', got {mock_app_state.conversation_history[0]['role']}"

    assert mock_app_state.conversation_history[0]['content'] == 'Create file1.txt', \
        f"Unexpected user message content: {mock_app_state.conversation_history[0]['content']}"

    assert mock_app_state.conversation_history[1]['role'] == 'assistant', \
        f"Expected second message role to be 'assistant', got {mock_app_state.conversation_history[1]['role']}"

    assert mock_app_state.conversation_history[1]['content'] == 'Added file1.txt to context.', \
        f"Unexpected assistant message content: {mock_app_state.conversation_history[1]['content']}"

    assert mock_app_state.conversation_history[2]['role'] == 'assistant', \
        f"Expected third message role to be 'assistant', got {mock_app_state.conversation_history[2]['role']}"

    assert mock_app_state.conversation_history[2]['content'] == "I've added `file1.txt` to the context.", \
        f"Unexpected notification message content: {mock_app_state.conversation_history[2]['content']}"

    # 10. Check that FileContextTracker was updated correctly
    assert "file1.txt" in mock_file_context.tracked_files, \
        f"Expected 'file1.txt' to be in tracked_files, got {list(mock_file_context.tracked_files.keys())}"

    assert mock_file_context.tracked_files["file1.txt"] is None, \
        f"Expected tracked_files['file1.txt'] to be None, got {mock_file_context.tracked_files.get('file1.txt')}"
    
```

```
___________________________________________________________ test_initial_file_addition ___________________________________________________________

mock_app_state = <test_conversation_history.mock_app_state.<locals>.MockAppState object at 0x10d62c440>
mock_file_context = <test_conversation_history.mock_file_context.<locals>.MockFileContextTracker object at 0x10d62c6e0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x10d62c620>

    @pytest.mark.asyncio
    async def test_initial_file_addition(mock_app_state, mock_file_context, monkeypatch):
        user_input = "Create file1.txt"
    
        # 1. Mock AI client
        ai_client_mock = AsyncMock()
    
        # 2. Mock the response returned by create_message
        create_message_response_mock = MagicMock(
            usage=MagicMock(
                prompt_tokens=10,
                completion_tokens=20,
                total_tokens=30,
                cache_creation_input_tokens=5,
                cache_read_input_tokens=5
            )
        )
        ai_client_mock.create_message.return_value = create_message_response_mock
    
        # 3. Mock the extract_response method on the AI client with a relative path
        ai_client_mock.extract_response = AsyncMock(return_value="Added file1.txt to context.")
    
        # 4. Apply monkeypatch to replace AIClientFactory.get_ai_client with our mock
        monkeypatch.setattr("chat.chat.AIClientFactory.get_ai_client", lambda x: ai_client_mock)
    
        # 5. Apply monkeypatch to replace get_app_state to return mock_app_state
        monkeypatch.setattr("chat.chat.get_app_state", lambda: mock_app_state)
    
        # 6. Apply monkeypatch to replace get_file_context to return mock_file_context
        monkeypatch.setattr("chat.chat.get_file_context", lambda: mock_file_context)
    
        # 7. Call the function under test
        assistant_response = await chat_with_model(user_input, MagicMock(), "gpt-4")
    
        # 8. Print internal states for debugging
        print("Conversation History:", mock_app_state.conversation_history)
        print("Tracked Files:", mock_file_context.tracked_files)
    
        # 9. Check that a notification message was appended
        expected_conversation_length = 3  # User input + Assistant response + Notification
>       assert len(mock_app_state.conversation_history) == expected_conversation_length, \
            f"Expected {expected_conversation_length} messages in conversation_history, found {len(mock_app_state.conversation_history)}"
E       AssertionError: Expected 3 messages in conversation_history, found 2
E       assert 2 == 3
E        +  where 2 = len([{'content': 'Create file1.txt', 'role': 'user'}, {'content': 'Added file1.txt to context.', 'role': 'assistant'}])
E        +    where [{'content': 'Create file1.txt', 'role': 'user'}, {'content': 'Added file1.txt to context.', 'role': 'assistant'}] = <test_conversation_history.mock_app_state.<locals>.MockAppState object at 0x10d62c440>.conversation_history

tests/test_chat/test_conversation_history.py:302: AssertionError
------------------------------------------------------------- Captured stdout setup --------------------------------------------------------------
DEBUG: Using selector: KqueueSelector
DEBUG: Using selector: KqueueSelector
--------------------------------------------------------------- Captured log setup ---------------------------------------------------------------
DEBUG    asyncio:selector_events.py:64 Using selector: KqueueSelector
DEBUG    asyncio:selector_events.py:64 Using selector: KqueueSelector
-------------------------------------------------------------- Captured stdout call --------------------------------------------------------------
INFO: Processing user input: Create file1.txt
INFO: Using model: gpt-4
INFO: Available tools: []
INFO: Using AI client: AsyncMock
INFO: context awareness prompt: No files in context.
INFO: History passed to AI client: []
INFO: Creating message with AI client
INFO: Message created successfully
INFO: EXTRACTED RESPONSE: Added file1.txt to context.
WARNING: No content provided for file1.txt.
INFO: Final assistant response: Added file1.txt to context.
Conversation History: [{'role': 'user', 'content': 'Create file1.txt'}, {'role': 'assistant', 'content': 'Added file1.txt to context.'}]
Tracked Files: {}
--------------------------------------------------------------- Captured log call ----------------------------------------------------------------
INFO     root:chat.py:257 Processing user input: Create file1.txt
INFO     root:chat.py:258 Using model: gpt-4
INFO     root:chat.py:259 Available tools: []
INFO     root:chat.py:269 Using AI client: AsyncMock
INFO     root:chat.py:275 context awareness prompt: No files in context.
INFO     root:chat.py:279 History passed to AI client: []
INFO     root:chat.py:282 Creating message with AI client
INFO     root:chat.py:289 Message created successfully
INFO     root:chat.py:319 EXTRACTED RESPONSE: Added file1.txt to context.
WARNING  root:chat.py:369 No content provided for file1.txt.
INFO     root:chat.py:335 Final assistant response: Added file1.txt to context.
------------------------------------------------------------ Captured stdout teardown ------------------------------------------------------------
DEBUG: Using selector: KqueueSelector
------------------------------------------------------------- Captured log teardown --------------------------------------------------------------
DEBUG    asyncio:selector_events.py:64 Using selector: KqueueSelector
============================================================ short test summary info =============================================================
FAILED tests/test_chat/test_conversation_history.py::test_initial_file_addition - AssertionError: Expected 3 messages in conversation_history, found 2
assert 2 == 3
 +  where 2 = len([{'content': 'Create file1.txt', 'role': 'user'}, {'content': 'Added file1.txt to context.', 'role': 'assistant'}])
 +    where [{'content': 'Create file1.txt', 'role': 'user'}, {'content': 'Added file1.txt to context.', 'role': 'assistant'}] = <test_conversation_history.mock_app_state.<locals>.MockAppState object at 0x10d62c440>.conversation_history
```

```
chat.py

import logging
import json
import os
import re
import difflib
from typing import List, Dict, Any, Tuple
from efc.efc_cache import get_file_context
from app_state import get_app_state
from chat.model_config import get_model_config
from tools.tools import ToolManager
from utils.token_usage import update_token_usage
from chat.factory import AIClientFactory
import asyncio

# Set logging level
logging.basicConfig(level=logging.INFO)


async def chat_with_model(user_input: str, tool_manager: ToolManager, model_name: str) -> str:
    logging.info(f"Processing user input: {user_input}")
    logging.info(f"Using model: {model_name}")
    logging.info(f"Available tools: {[tool['function']['name'] for tool in tool_manager.get_tool_descriptions()]}")

    app_state = get_app_state()
    # Add the new user input to the history
    app_state.conversation_history.append({"role": "user", "content": user_input})

    file_context = get_file_context()
    model_config = get_model_config(model_name)

    ai_client = AIClientFactory.get_ai_client(model_name)
    logging.info(f"Using AI client: {type(ai_client).__name__}")

    optimized_history = app_state.conversation_history[:-1]  # Exclude the latest user message

    # System message includes context awareness prompt
    file_context_summary = file_context.generate_context_awareness_prompt(app_state)
    logging.info(f"context awareness prompt: {file_context_summary}")
    system_message = f"{model_config['base_system_prompt']}\n\n{file_context_summary}"

    tools = tool_manager.get_tool_descriptions(format="claude" if model_name.lower() in ['sonnet', 'claude-3-sonnet-20240229'] else "default")
    logging.info(f"History passed to AI client: {optimized_history}")

    try:
        logging.info("Creating message with AI client")
        response = await ai_client.create_message(
            prompt=user_input,
            system_message=system_message,
            tools=tools,
            history=optimized_history
        )
        logging.info("Message created successfully")

        if hasattr(response, 'usage'):
            if model_name.lower() in ['sonnet', 'claude-3-sonnet-20240229']:
                update_token_usage(model_name, {
                    "prompt_tokens": response.usage.input_tokens,
                    "completion_tokens": response.usage.output_tokens,
                    "total_tokens": response.usage.input_tokens + response.usage.output_tokens,
                    "cache_creation_tokens": getattr(response.usage, 'cache_creation_input_tokens', 0),
                    "cache_read_tokens": getattr(response.usage, 'cache_read_input_tokens', 0)
                })
            else:
                # For Llama and GPT
                update_token_usage(model_name, {
                    "prompt_tokens": response.usage.prompt_tokens,
                    "completion_tokens": response.usage.completion_tokens,
                    "total_tokens": response.usage.total_tokens
                })
    except Exception as e:
        logging.error(f"API Error: {str(e)}")
        return f"I'm sorry, there was an error communicating with the AI. Please try again. Error: {str(e)}"

    # Extract assistant response and function calls
    if hasattr(ai_client, 'extract_response'):
        assistant_response = ai_client.extract_response(response)
        if asyncio.iscoroutine(assistant_response):
            assistant_response = await assistant_response
    else:
        assistant_response = response.content[0].text if response.content else ""

    logging.info(f"EXTRACTED RESPONSE: {assistant_response}")

    # Process file updates in assistant_response
    assistant_response = await process_file_updates(assistant_response, file_context, app_state)

    # Extract and execute function calls
    function_calls = ai_client.extract_function_calls(response)
    function_results = await execute_function_calls(function_calls, tool_manager, model_name)

    # Integrate function results into assistant response
    if not assistant_response and function_results:
        assistant_response = "I've performed the following actions based on your request:\n\n"
    assistant_response = integrate_function_results(assistant_response, function_results, model_config)

    # Append the finalized assistant response
    app_state.conversation_history.append({"role": "assistant", "content": assistant_response})
    logging.info(f"Final assistant response: {assistant_response}")
    return assistant_response


async def process_file_updates(assistant_response: str, file_context, app_state: Any) -> str:
    updated_files = detect_file_updates(assistant_response)

    for relative_path, new_content in updated_files.items():
        existing_content = file_context.get_file_content(relative_path)

        if new_content is not None:
            if existing_content is not None:
                # Existing file: calculate diff
                diff = calculate_diff(existing_content, new_content)
                if diff.strip():
                    # Format and append diff as an assistant message
                    diff_message = f"### Diff for {relative_path}\n```diff\n{diff}\n```"
                    app_state.conversation_history.append({"role": "assistant", "content": diff_message})
                else:
                    # No changes detected
                    notification_message = f"No changes detected in {relative_path}."
                    app_state.conversation_history.append({"role": "assistant", "content": notification_message})
            else:
                # First instance: insert simple notification
                notification_message = f"I've added `{relative_path}` to the context."
                app_state.conversation_history.append({"role": "assistant", "content": notification_message})

            # Update the FileContextTracker with new content
            file_context.add_or_update_file(relative_path, new_content, "updated via function call")

            # Remove the processed content from assistant_response
            assistant_response = remove_full_content_from_response(assistant_response, relative_path)
        else:
            # Handle cases where new_content is None
            logging.warning(f"No content provided for {relative_path}.")
            continue

    return assistant_response.strip()


def detect_file_updates(assistant_message: str) -> Dict[str, Optional[str]]:
    """
    Detects and extracts file updates from the assistant message.
    Returns a dictionary mapping relative_path to new_content or None for additions.
    """
    # Pattern for file updates: "Fresh content for {relative_path}: ```(diff|language)?\n...```"
    pattern = r"Fresh content for (?P<path>[^\n]+):\s*```(?:[^\n]*)\n(?P<content>[\s\S]*?)```"
    matches = re.finditer(pattern, assistant_message)
    updates = {}
    for match in matches:
        relative_path = match.group('path').strip()
        content = match.group('content').strip()
        updates[relative_path] = content

    # Pattern for additions: "Added {relative_path} to context."
    pattern_added = r"Added (?P<path>[^\n]+) to context\."
    matches_added = re.finditer(pattern_added, assistant_message)
    for match in matches_added:
        relative_path = match.group('path').strip()
        updates[relative_path] = None  # None content for additions

    return updates


def calculate_diff(old_content: str, new_content: str) -> str:
    old_lines = old_content.splitlines()
    new_lines = new_content.splitlines()
    diff = difflib.unified_diff(old_lines, new_lines, lineterm='')
    return '\n'.join(diff)


def remove_full_content_from_response(assistant_message: str, relative_path: str) -> str:
    """
    Removes the full file content from the assistant message, leaving only the relevant response.
    """
    # Remove the section containing the full file content
    pattern = rf"Fresh content for {re.escape(relative_path)}:.*?```.*?\n[\s\S]*?```"
    cleaned_message = re.sub(pattern, "", assistant_message, flags=re.MULTILINE)
    return cleaned_message.strip()


async def execute_function_calls(function_calls, tool_manager: ToolManager, model_name: str):
    results = []
    if asyncio.iscoroutine(function_calls):
        function_calls = await function_calls
    for function_call in function_calls:
        try:
            if isinstance(function_call, dict):
                # Llama format
                func_name = function_call.get('function')
                arguments = function_call.get('arguments', {})
            else:
                # GPT-4 format
                func_name = function_call.function.name
                arguments = json.loads(function_call.function.arguments)

            result = await tool_manager.execute_tool({"function": func_name, "arguments": arguments}, model_name)
            results.append((func_name, arguments, result))
        except Exception as e:
            logging.error(f"Error executing function: {str(e)}")
            results.append((func_name, arguments, f"Error executing function: {str(e)}"))
    return results


def integrate_function_results(assistant_response: str, function_results: List[Tuple[str, Dict, str]], model_config: Dict) -> str:
    if not assistant_response and function_results:
        assistant_response = "I've performed the following actions based on your request:\n\n"

    for func_name, args, result in function_results:
        if model_config.get('use_tool_calls', False):
            assistant_response += f"\n\nResult of {func_name}: {result}"
        else:
            # For models that embed function calls in the response
            placeholder = f"<function={func_name}>{json.dumps(args)}</function>"
            if placeholder in assistant_response:
                assistant_response = assistant_response.replace(placeholder, str(result))
            else:
                assistant_response += f"\n\nResult of {func_name}: {result}"

    return assistant_response

efc_cache.py

import time
from typing import Dict, Any, Optional
from datetime import datetime
import hashlib
import logging
import os

from app_state import get_app_state


class FileCache:
    def __init__(self):
        self._cache: Dict[str, Dict[str, Any]] = {}

    def get(self, relative_path: str) -> Optional[str]:
        if relative_path in self._cache:
            cache_entry = self._cache[relative_path]
            if time.time() - cache_entry['timestamp'] < 3000:  # 50 minutes expiration
                return cache_entry['content']
        return None

    def set(self, relative_path: str, content: str) -> None:
        self._cache[relative_path] = {
            'content': content,
            'timestamp': time.time()
        }

    def clear(self, relative_path: Optional[str] = None) -> None:
        if relative_path:
            self._cache.pop(relative_path, None)
        else:
            self._cache.clear()


class FileContextTracker:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(FileContextTracker, cls).__new__(cls)
            cls._instance.tracked_files = {}
            cls._instance.last_api_call_time = datetime.min
            cls._instance.file_cache = FileCache()
        return cls._instance

    def force_refresh(self):
        app_state = get_app_state()
        project_root = app_state.active_project
        for relative_path in list(self.tracked_files.keys()):
            try:
                full_path = os.path.join(project_root, relative_path)
                with open(full_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                content_hash = self._hash_content(content)

                cached_content = self.file_cache.get(relative_path)
                if cached_content and self._hash_content(cached_content) == content_hash:
                    self.tracked_files[relative_path]["last_checked"] = datetime.now()
                else:
                    self.add_or_update_file(relative_path, content, "refresh")
            except FileNotFoundError:
                del self.tracked_files[relative_path]
                self.file_cache.clear(relative_path)
            except Exception as e:
                logging.error(f"Error refreshing file {relative_path}: {str(e)}")

    def add_or_update_file(self, relative_path: str, content: str, operation: str) -> str:
        current_time = datetime.now()
        content_hash = self._hash_content(content)

        self.file_cache.set(relative_path, content)

        if relative_path in self.tracked_files:
            if self.tracked_files[relative_path]["content_hash"] == content_hash:
                self.tracked_files[relative_path]["last_checked"] = current_time
                return f"File {relative_path} unchanged, no update needed."

            self.tracked_files[relative_path].update({
                "last_operation": f"updated ({operation})",
                "last_updated": current_time,
                "content_hash": content_hash,
            })
            message = f"Updated {relative_path} in context due to content change."
        else:
            self.tracked_files[relative_path] = {
                "last_operation": operation,
                "last_updated": current_time,
                "content_hash": content_hash,
                "created_at": current_time
            }
            message = f"Added {relative_path} to context."

        return message

    def get_file_content(self, relative_path: str) -> Optional[str]:
        return self.file_cache.get(relative_path)

    def _get_context_for_llm(self) -> str:
        if not self.tracked_files:
            return "No files in context."
        context = ""
        for relative_path, info in self.tracked_files.items():
            content = self.get_file_content(relative_path) or "No content available."
            context += f"- **{relative_path}**:\n```\n{content}\n```\n"
        return context

    def generate_context_awareness_prompt(self, app_state) -> str:
        """Generates the prompt section for contextual awareness, including the active project structure."""
        project_tree = app_state.get_active_project_tree_string()

        context_prompt = f"""
## Contextual Awareness: IMPORTANT

ARC, before executing any function, **always check if the information you need is already available in your current context.** This will enhance your efficiency and demonstrate a deeper understanding of the project.

For example, if the file content or project structure you require has already been provided, use that information directly instead of making unnecessary function calls.

**Active Project Structure:**
{project_tree}

**Files in Context:**
{self._get_context_for_llm()}

When working with files, always use relative paths based on the active project structure shown above.
"""

        return context_prompt

    @staticmethod
    def _hash_content(content: str) -> str:
        return hashlib.md5(content.encode('utf-8')).hexdigest()


def get_file_context():
    return FileContextTracker()

app_state.py

import os

class AppState:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(AppState, cls).__new__(cls)
            cls._instance.conversation_history = []
            cls._instance.file_histories = {}
            cls._instance.file_contents = {}
            cls._instance.code_editor_memory = []
            cls._instance.code_editor_files = set()
            cls._instance.running_processes = {}
            cls._instance.automode = False
            cls._instance.edit_history = []
            cls._instance.raw_output_mode = False
            cls._instance.token_usage = {
                "main_model": {"input": 0, "output": 0, "cost": 0},
                "tool_usage": {"input": 0, "output": 0, "cost": 0}
            }
            cls._instance.active_project = None
            cls._instance.current_turn_total_tokens: int = 0
            cls._instance.active_project_tree = {}  # Attribute for storing the active project's tree structure

        return cls._instance

    def update_active_project_tree(self):
        """
        Update the tree structure of the active project.
        """
        if not self.active_project:
            self.active_project_tree = {}
            return

        self.active_project_tree = self._generate_tree_structure(self.active_project)

    def _generate_tree_structure(self, path):
        """
        Generate a tree structure of the given path.
        """
        tree = {}
        for item in os.listdir(path):
            item_path = os.path.join(path, item)
            if os.path.isdir(item_path):
                tree[item] = self._generate_tree_structure(item_path)
            else:
                tree[item] = "file"
        return tree

    def get_active_project_tree_string(self):
        """
        Get a string representation of the active project's tree structure.
        """
        if not self.active_project or not self.active_project_tree:
            return "No active project"

        def _tree_to_string(tree, indent=""):
            result = ""
            for i, (name, value) in enumerate(tree.items()):
                if i == len(tree) - 1:
                    result += f"{indent}└── {name}\n"
                    if isinstance(value, dict):
                        result += _tree_to_string(value, indent + "    ")
                else:
                    result += f"{indent}├── {name}\n"
                    if isinstance(value, dict):
                        result += _tree_to_string(value, indent + "│   ")
            return result

        project_name = os.path.basename(self.active_project)
        project_name_width = len("Active Project: ") + len(project_name)
        indent = " " * project_name_width
        return f"Active Project: {project_name}\n" + _tree_to_string(self.active_project_tree, indent=indent)


def get_app_state():
    return AppState()

```