diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..978792356 Binary files /dev/null and b/.DS_Store differ diff --git a/.cursor/rules/general-rule.mdc b/.cursor/rules/general-rule.mdc index 01ef0ac64..989407503 100644 --- a/.cursor/rules/general-rule.mdc +++ b/.cursor/rules/general-rule.mdc @@ -1,11 +1,15 @@ --- -description: -globs: +description: +globs: alwaysApply: true --- + ## Rules to Follow -- You must always commit your changes whenever you update code. +- You always prefer to use branch development. +- Before writing any code, you create a feature branch to hold those changes. +- After you are done, provide instructions in a "Merge.md" file that explains how to merge the changes back to main with both a Github PR route and a Github CLI route. +- You must always commit your changes whenever you update code. - You must always try and write code that is well documented. (self or commented is fine) - You must only work on a single feature at a time. -- You must explain your decisions thouroughly to the user. \ No newline at end of file +- You must explain your decisions thouroughly to the user. diff --git a/Merge.md b/Merge.md new file mode 100644 index 000000000..0126f4caf --- /dev/null +++ b/Merge.md @@ -0,0 +1,166 @@ +# Merge Instructions + +This repo follows a branch-based workflow. All work for this UI fix was done on `fix/chat-question-overwrite`. +Previous rollback context remains available on `rollback/ca7e9c0` (reset to commit `ca7e9c03403e3053ede6a2d14b00531facf95d98`). + +## Create PR on GitHub (UI Route) + +1. Push your branch (if not already pushed): + +```bash +git push -u origin fix/chat-question-overwrite +``` + +2. Open a Pull Request from `fix/chat-question-overwrite` into `main` on GitHub. +3. Add reviewers and wait for approvals and CI to pass. +4. Use β€œSquash and merge” or your preferred merge strategy. + +## GitHub CLI Route + +From the repo root: + +```bash +git push -u origin fix/chat-question-overwrite +gh pr create --fill --base main --head fix/chat-question-overwrite +gh pr view --web # optional, open in browser +gh pr merge --merge # or --squash / --rebase per your preference +``` + +## Post-merge cleanup + +```bash +git checkout main +git pull origin main +git branch -d fix/chat-question-overwrite +``` + +## Notes for Reviewers + +- Rollback branch `rollback/ca7e9c0` points to commit `ca7e9c0`. +- Effectively reverts subsequent changes after `ca7e9c0` by merging this branch. +- Validate that critical functionality at that commit still builds/tests cleanly. +- Any newer work has been preserved on its original feature branches. + +# Deployment Instructions + +This project consists of two separate deployments: + +1. Frontend (Next.js) +2. API (FastAPI) + +## API Deployment + +### Prerequisites + +1. Install Vercel CLI: + +```bash +npm install -g vercel +``` + +2. Login to Vercel: + +```bash +vercel login +``` + +### Deploy API + +1. Navigate to the API directory: + +```bash +cd api +``` + +2. Deploy to Vercel: + +```bash +vercel +``` + +3. After deployment, copy the API URL (you'll need it for the frontend) + +### Environment Variables for API + +Set these in your Vercel project dashboard: + +- `OPENAI_API_KEY`: Your OpenAI API key + +## Frontend Deployment + +### Prerequisites + +Same as API deployment (Vercel CLI and login) + +### Deploy Frontend + +1. Navigate to the frontend directory: + +```bash +cd frontend +``` + +2. Deploy to Vercel: + +```bash +vercel +``` + +### Environment Variables for Frontend + +Set these in your Vercel project dashboard: + +- `NEXT_PUBLIC_API_URL`: The URL of your deployed API (e.g., https://your-api.vercel.app) + +## Monitoring and Management + +### API Project + +1. Monitor API logs and performance in Vercel dashboard +2. Check Function execution logs +3. Monitor API rate limits and usage + +### Frontend Project + +1. Monitor build logs and deployment status +2. Check static asset delivery +3. Monitor page performance + +## Troubleshooting + +### API Issues + +1. Check API logs in Vercel dashboard +2. Verify environment variables are set +3. Test API endpoints directly + +### Frontend Issues + +1. Check build logs +2. Verify API URL is correctly set +3. Check browser console for errors +4. Verify API is accessible from frontend domain + +## Alternative: GitHub PR Route + +1. For API changes: + +```bash +cd api +gh pr create --title "Deploy API changes" --body "Deploy latest API changes to Vercel" +``` + +2. For Frontend changes: + +```bash +cd frontend +gh pr create --title "Deploy Frontend changes" --body "Deploy latest Frontend changes to Vercel" +``` + +3. After PRs are merged: + +```bash +gh pr merge +``` + +Vercel will automatically deploy changes when merged to main branch for each project. diff --git a/README.md b/README.md index 97bd3b1bd..4ed8db3a2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ height="auto"/>

- ##

πŸ‘‹ Welcome to the AI Engineer Challenge

## πŸ€– Your First Vibe Coding LLM Application @@ -27,7 +26,6 @@ That's it! Head to the next step and start building your application! -
πŸ—οΈ Forking & Cloning This Repository @@ -43,81 +41,96 @@ Got everything in place? Let's move on! 1. Fork [this](https://github.com/AI-Maker-Space/The-AI-Engineer-Challenge) repo! - ![image](https://i.imgur.com/bhjySNh.png) + ![image](https://i.imgur.com/bhjySNh.png) 1. Clone your newly created repo. - ``` bash - # First, navigate to where you want the project folder to be created - cd PATH_TO_DESIRED_PARENT_DIRECTORY + ```bash + # First, navigate to where you want the project folder to be created + cd PATH_TO_DESIRED_PARENT_DIRECTORY + + # Then clone (this will create a new folder called The-AI-Engineer-Challenge) + git clone git@github.com:/The-AI-Engineer-Challenge.git + ``` + + > Note: This command uses SSH. If you haven't set up SSH with GitHub, the command will fail. In that case, use HTTPS by replacing `git@github.com:` with `https://github.com/` - you'll then be prompted for your GitHub username and personal access token. - # Then clone (this will create a new folder called The-AI-Engineer-Challenge) - git clone git@github.com:/The-AI-Engineer-Challenge.git - ``` +1. Verify your git setup: - > Note: This command uses SSH. If you haven't set up SSH with GitHub, the command will fail. In that case, use HTTPS by replacing `git@github.com:` with `https://github.com/` - you'll then be prompted for your GitHub username and personal access token. + ```bash + # Check that your remote is set up correctly + git remote -v -2. Verify your git setup: + # Check the status of your repository + git status - ```bash - # Check that your remote is set up correctly - git remote -v + # See which branch you're on + git branch + ``` - # Check the status of your repository - git status + - # See which branch you're on - git branch - ``` +1. Open the freshly cloned repository inside Cursor! - + ```bash + cd The-AI-Engineering-Challenge + cursor . + ``` -3. Open the freshly cloned repository inside Cursor! +1. Check out the existing backend code found in `/api/app.py` - ```bash - cd The-AI-Engineering-Challenge - cursor . - ``` +### Root Python Environment with `uv` -4. Check out the existing backend code found in `/api/app.py` +To keep things tidy, we use a single root-level virtual environment and install all Python deps at the repo root using `uv`. + +```bash +# At repo root +uv venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate +uv sync # installs deps from pyproject.toml + +# Run the FastAPI server from root +uv run python api/app.py +```
πŸ”₯Setting Up for Vibe Coding Success -While it is a bit counter-intuitive to set things up before jumping into vibe-coding - it's important to remember that there exists a gradient betweeen AI-Assisted Development and Vibe-Coding. We're only reaching *slightly* into AI-Assisted Development for this challenge, but it's worth it! +While it is a bit counter-intuitive to set things up before jumping into vibe-coding - it's important to remember that there exists a gradient betweeen AI-Assisted Development and Vibe-Coding. We're only reaching _slightly_ into AI-Assisted Development for this challenge, but it's worth it! 1. Check out the rules in `.cursor/rules/` and add theme-ing information like colour schemes in `frontend-rule.mdc`! You can be as expressive as you'd like in these rules! -2. We're going to index some docs to make our application more likely to succeed. To do this - we're going to start with `CTRL+SHIFT+P` (or `CMD+SHIFT+P` on Mac) and we're going to type "custom doc" into the search bar. +2. We're going to index some docs to make our application more likely to succeed. To do this - we're going to start with `CTRL+SHIFT+P` (or `CMD+SHIFT+P` on Mac) and we're going to type "custom doc" into the search bar. + + ![image](https://i.imgur.com/ILx3hZu.png) - ![image](https://i.imgur.com/ILx3hZu.png) 3. We're then going to copy and paste `https://nextjs.org/docs` into the prompt. - ![image](https://i.imgur.com/psBjpQd.png) + ![image](https://i.imgur.com/psBjpQd.png) 4. We're then going to use the default configs to add these docs to our available and indexed documents. - ![image](https://i.imgur.com/LULLeaF.png) + ![image](https://i.imgur.com/LULLeaF.png) 5. After that - you will do the same with Vercel's documentation. After which you should see: - ![image](https://i.imgur.com/hjyXhhC.png) + ![image](https://i.imgur.com/hjyXhhC.png)
😎 Vibe Coding a Front End for the FastAPI Backend -1. Use `Command-L` or `CTRL-L` to open the Cursor chat console. +1. Use `Command-L` or `CTRL-L` to open the Cursor chat console. 2. Set the chat settings to the following: - ![image](https://i.imgur.com/LSgRSgF.png) + ![image](https://i.imgur.com/LSgRSgF.png) 3. Ask Cursor to create a frontend for your application. Iterate as much as you like! -4. Run the frontend using the instructions Cursor provided. +4. Run the frontend using the instructions Cursor provided. > NOTE: If you run into any errors, copy and paste them back into the Cursor chat window - and ask Cursor to fix them! @@ -134,19 +147,19 @@ While it is a bit counter-intuitive to set things up before jumping into vibe-co 3. Run the command: - ```bash - npm install -g vercel - ``` + ```bash + npm install -g vercel + ``` 4. Run the command: - ```bash - vercel - ``` + ```bash + vercel + ``` 5. Follow the in-terminal instructions. (Below is an example of what you will see!) - ![image](https://i.imgur.com/D1iKGCq.png) + ![image](https://i.imgur.com/D1iKGCq.png) 6. Once the build is completed - head to the provided link and try out your app! @@ -156,13 +169,13 @@ While it is a bit counter-intuitive to set things up before jumping into vibe-co ### Vercel Link to Share -You'll want to make sure you share you *domains* hyperlink to ensure people can access your app! +You'll want to make sure you share you _domains_ hyperlink to ensure people can access your app! ![image](https://i.imgur.com/mpXIgIz.png) > NOTE: Test this is the public link by trying to open your newly deployed site in an Incognito browser tab! -### πŸŽ‰ Congratulations! +### πŸŽ‰ Congratulations! You just deployed your first LLM-powered application! πŸš€πŸš€πŸš€ Get on linkedin and post your results and experience! Make sure to tag us at @AIMakerspace! @@ -182,5 +195,5 @@ Looking forward to building with the community! πŸ™Œβœ¨Β Here's to many more cre Who else is diving into the world of AI? Let's connect! πŸŒπŸ’‘ -#FirstLLMApp +#FirstLLMApp ``` diff --git a/aimakerspace/__init__.py b/aimakerspace/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/aimakerspace/aimakerspace b/aimakerspace/aimakerspace new file mode 120000 index 000000000..422fea040 --- /dev/null +++ b/aimakerspace/aimakerspace @@ -0,0 +1 @@ +/Users/amanz/Documents/the-ai-engineer-challenge/aimakerspace \ No newline at end of file diff --git a/aimakerspace/openai_utils/__init__.py b/aimakerspace/openai_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/aimakerspace/openai_utils/chatmodel.py b/aimakerspace/openai_utils/chatmodel.py new file mode 100644 index 000000000..f55fbfe72 --- /dev/null +++ b/aimakerspace/openai_utils/chatmodel.py @@ -0,0 +1,66 @@ +import os +from typing import Any, AsyncIterator, Iterable, List, MutableMapping + +from dotenv import load_dotenv +from openai import AsyncOpenAI, OpenAI + +load_dotenv() + +ChatMessage = MutableMapping[str, Any] + + +class ChatOpenAI: + """Thin wrapper around the OpenAI chat completion APIs.""" + + def __init__(self, model_name: str = "gpt-4o-mini"): + self.model_name = model_name + self.openai_api_key = os.getenv("OPENAI_API_KEY") + if self.openai_api_key is None: + raise ValueError("OPENAI_API_KEY is not set") + + self._client = OpenAI() + self._async_client = AsyncOpenAI() + + def run( + self, + messages: Iterable[ChatMessage], + text_only: bool = True, + **kwargs: Any, + ) -> Any: + """Execute a chat completion request. + + ``messages`` must be an iterable of ``{"role": ..., "content": ...}`` + dictionaries. When ``text_only`` is ``True`` (the default) only the + completion text is returned; otherwise the full response object is + provided. + """ + + message_list = self._coerce_messages(messages) + response = self._client.chat.completions.create( + model=self.model_name, messages=message_list, **kwargs + ) + + if text_only: + return response.choices[0].message.content + + return response + + async def astream( + self, messages: Iterable[ChatMessage], **kwargs: Any + ) -> AsyncIterator[str]: + """Yield streaming completion chunks as they arrive from the API.""" + + message_list = self._coerce_messages(messages) + stream = await self._async_client.chat.completions.create( + model=self.model_name, messages=message_list, stream=True, **kwargs + ) + + async for chunk in stream: + content = chunk.choices[0].delta.content + if content is not None: + yield content + + def _coerce_messages(self, messages: Iterable[ChatMessage]) -> List[ChatMessage]: + if isinstance(messages, list): + return messages + return list(messages) diff --git a/aimakerspace/openai_utils/embedding.py b/aimakerspace/openai_utils/embedding.py new file mode 100644 index 000000000..e06016a89 --- /dev/null +++ b/aimakerspace/openai_utils/embedding.py @@ -0,0 +1,69 @@ +import asyncio +import os +from typing import Iterable, List + +from dotenv import load_dotenv +from openai import AsyncOpenAI, OpenAI + + +class EmbeddingModel: + """Helper for generating embeddings via the OpenAI API.""" + + def __init__(self, embeddings_model_name: str = "text-embedding-3-small"): + load_dotenv() + self.openai_api_key = os.getenv("OPENAI_API_KEY") + if self.openai_api_key is None: + raise ValueError( + "OPENAI_API_KEY environment variable is not set. " + "Please configure it with your OpenAI API key." + ) + + self.embeddings_model_name = embeddings_model_name + self.async_client = AsyncOpenAI() + self.client = OpenAI() + + async def async_get_embeddings(self, list_of_text: Iterable[str]) -> List[List[float]]: + """Return embeddings for ``list_of_text`` using the async client.""" + + embedding_response = await self.async_client.embeddings.create( + input=list(list_of_text), model=self.embeddings_model_name + ) + + return [item.embedding for item in embedding_response.data] + + async def async_get_embedding(self, text: str) -> List[float]: + """Return an embedding for a single text using the async client.""" + + embedding = await self.async_client.embeddings.create( + input=text, model=self.embeddings_model_name + ) + + return embedding.data[0].embedding + + def get_embeddings(self, list_of_text: Iterable[str]) -> List[List[float]]: + """Return embeddings for ``list_of_text`` using the sync client.""" + + embedding_response = self.client.embeddings.create( + input=list(list_of_text), model=self.embeddings_model_name + ) + + return [item.embedding for item in embedding_response.data] + + def get_embedding(self, text: str) -> List[float]: + """Return an embedding for a single text using the sync client.""" + + embedding = self.client.embeddings.create( + input=text, model=self.embeddings_model_name + ) + + return embedding.data[0].embedding + + +if __name__ == "__main__": + embedding_model = EmbeddingModel() + print(asyncio.run(embedding_model.async_get_embedding("Hello, world!"))) + print( + asyncio.run( + embedding_model.async_get_embeddings(["Hello, world!", "Goodbye, world!"]) + ) + ) diff --git a/aimakerspace/openai_utils/prompts.py b/aimakerspace/openai_utils/prompts.py new file mode 100644 index 000000000..b36f750c4 --- /dev/null +++ b/aimakerspace/openai_utils/prompts.py @@ -0,0 +1,60 @@ +import re +from typing import Any, Dict, List + + +class BasePrompt: + """Simple string template helper used to format prompt text.""" + + def __init__(self, prompt: str): + self.prompt = prompt + self._pattern = re.compile(r"\{([^}]+)\}") + + def format_prompt(self, **kwargs: Any) -> str: + """Return the prompt with ``kwargs`` substituted for placeholders.""" + + matches = self._pattern.findall(self.prompt) + replacements = {match: kwargs.get(match, "") for match in matches} + return self.prompt.format(**replacements) + + def get_input_variables(self) -> List[str]: + """Return the placeholder names used by this prompt.""" + + return self._pattern.findall(self.prompt) + + +class RolePrompt(BasePrompt): + """Prompt template that also captures an accompanying chat role.""" + + def __init__(self, prompt: str, role: str): + super().__init__(prompt) + self.role = role + + def create_message(self, apply_format: bool = True, **kwargs: Any) -> Dict[str, str]: + """Build an OpenAI chat message dictionary for this prompt.""" + + content = self.format_prompt(**kwargs) if apply_format else self.prompt + return {"role": self.role, "content": content} + + +class SystemRolePrompt(RolePrompt): + def __init__(self, prompt: str): + super().__init__(prompt, "system") + + +class UserRolePrompt(RolePrompt): + def __init__(self, prompt: str): + super().__init__(prompt, "user") + + +class AssistantRolePrompt(RolePrompt): + def __init__(self, prompt: str): + super().__init__(prompt, "assistant") + + +if __name__ == "__main__": + prompt = BasePrompt("Hello {name}, you are {age} years old") + print(prompt.format_prompt(name="John", age=30)) + + prompt = SystemRolePrompt("Hello {name}, you are {age} years old") + print(prompt.create_message(name="John", age=30)) + print(prompt.get_input_variables()) diff --git a/aimakerspace/text_utils.py b/aimakerspace/text_utils.py new file mode 100644 index 000000000..fa9b5d1e5 --- /dev/null +++ b/aimakerspace/text_utils.py @@ -0,0 +1,147 @@ +from pathlib import Path +from typing import Iterable, List + +import PyPDF2 + + +class TextFileLoader: + """Load plain-text documents from a single file or an entire directory.""" + + def __init__(self, path: str, encoding: str = "utf-8"): + self.path = Path(path) + self.encoding = encoding + self.documents: List[str] = [] + + def load(self) -> None: + """Populate ``self.documents`` from the configured path.""" + + self.documents = list(self._iter_documents()) + + def load_file(self) -> None: + """Load a single file specified by ``self.path``.""" + + self.documents = [self._read_text_file(self.path)] + + def load_directory(self) -> None: + """Load all text files contained within ``self.path``.""" + + self.documents = list(self._iter_directory(self.path)) + + def load_documents(self) -> List[str]: + """Convenience wrapper returning the loaded documents.""" + + self.load() + return self.documents + + def _iter_documents(self) -> Iterable[str]: + if self.path.is_dir(): + yield from self._iter_directory(self.path) + elif self.path.is_file() and self.path.suffix.lower() == ".txt": + yield self._read_text_file(self.path) + else: + raise ValueError( + "Provided path must be a directory or a .txt file: " f"{self.path}" + ) + + def _iter_directory(self, directory: Path) -> Iterable[str]: + for entry in sorted(directory.rglob("*.txt")): + if entry.is_file(): + yield self._read_text_file(entry) + + def _read_text_file(self, file_path: Path) -> str: + with file_path.open("r", encoding=self.encoding) as file_handle: + return file_handle.read() + + +class CharacterTextSplitter: + """Naively split long strings into overlapping character chunks.""" + + def __init__( + self, + chunk_size: int = 1000, + chunk_overlap: int = 200, + ): + if chunk_size <= chunk_overlap: + raise ValueError("Chunk size must be greater than chunk overlap") + + self.chunk_size = chunk_size + self.chunk_overlap = chunk_overlap + + def split(self, text: str) -> List[str]: + """Split ``text`` into chunks preserving the configured overlap.""" + + step = self.chunk_size - self.chunk_overlap + return [text[i : i + self.chunk_size] for i in range(0, len(text), step)] + + def split_texts(self, texts: List[str]) -> List[str]: + """Split multiple texts and flatten the resulting chunks.""" + + chunks: List[str] = [] + for text in texts: + chunks.extend(self.split(text)) + return chunks + + +class PDFLoader: + """Extract text from PDF files stored at a path.""" + + def __init__(self, path: str): + self.path = Path(path) + self.documents: List[str] = [] + + def load(self) -> None: + """Populate ``self.documents`` from the configured path.""" + + self.documents = list(self._iter_documents()) + + def load_file(self) -> None: + """Load a single PDF specified by ``self.path``.""" + + self.documents = [self._read_pdf(self.path)] + + def load_directory(self) -> None: + """Load all PDF files contained within ``self.path``.""" + + self.documents = list(self._iter_directory(self.path)) + + def load_documents(self) -> List[str]: + """Convenience wrapper returning the loaded documents.""" + + self.load() + return self.documents + + def _iter_documents(self) -> Iterable[str]: + if self.path.is_dir(): + yield from self._iter_directory(self.path) + elif self.path.is_file() and self.path.suffix.lower() == ".pdf": + yield self._read_pdf(self.path) + else: + raise ValueError( + "Provided path must be a directory or a .pdf file: " f"{self.path}" + ) + + def _iter_directory(self, directory: Path) -> Iterable[str]: + for entry in sorted(directory.rglob("*.pdf")): + if entry.is_file(): + yield self._read_pdf(entry) + + def _read_pdf(self, file_path: Path) -> str: + with file_path.open("rb") as file_handle: + pdf_reader = PyPDF2.PdfReader(file_handle) + extracted_pages = [page.extract_text() or "" for page in pdf_reader.pages] + return "\n".join(extracted_pages) + + +if __name__ == "__main__": + loader = TextFileLoader("data/KingLear.txt") + loader.load() + splitter = CharacterTextSplitter() + chunks = splitter.split_texts(loader.documents) + print(len(chunks)) + print(chunks[0]) + print("--------") + print(chunks[1]) + print("--------") + print(chunks[-2]) + print("--------") + print(chunks[-1]) diff --git a/aimakerspace/vectordatabase.py b/aimakerspace/vectordatabase.py new file mode 100644 index 000000000..1eb32c1e1 --- /dev/null +++ b/aimakerspace/vectordatabase.py @@ -0,0 +1,105 @@ +import asyncio +from typing import Callable, Dict, Iterable, List, Optional, Tuple, Union + +import numpy as np + +from aimakerspace.openai_utils.embedding import EmbeddingModel + + +def cosine_similarity(vector_a: np.ndarray, vector_b: np.ndarray) -> float: + """Return the cosine similarity between two vectors.""" + + norm_a = np.linalg.norm(vector_a) + norm_b = np.linalg.norm(vector_b) + if norm_a == 0 or norm_b == 0: + return 0.0 + + dot_product = np.dot(vector_a, vector_b) + return float(dot_product / (norm_a * norm_b)) + + +class VectorDatabase: + """Minimal in-memory vector store backed by numpy arrays.""" + + def __init__(self, embedding_model: Optional[EmbeddingModel] = None): + self.vectors: Dict[str, np.ndarray] = {} + self.embedding_model = embedding_model or EmbeddingModel() + + def insert(self, key: str, vector: Iterable[float]) -> None: + """Store ``vector`` so that it can be retrieved with ``key`` later on.""" + + self.vectors[key] = np.asarray(vector, dtype=float) + + def search( + self, + query_vector: Iterable[float], + k: int, + distance_measure: Callable[[np.ndarray, np.ndarray], float] = cosine_similarity, + ) -> List[Tuple[str, float]]: + """Return the ``k`` vectors most similar to ``query_vector``.""" + + if k <= 0: + raise ValueError("k must be a positive integer") + + query = np.asarray(query_vector, dtype=float) + scores = [ + (key, distance_measure(query, vector)) + for key, vector in self.vectors.items() + ] + scores.sort(key=lambda item: item[1], reverse=True) + return scores[:k] + + def search_by_text( + self, + query_text: str, + k: int, + distance_measure: Callable[[np.ndarray, np.ndarray], float] = cosine_similarity, + return_as_text: bool = False, + ) -> Union[List[Tuple[str, float]], List[str]]: + """Vector search using an embedding generated from ``query_text``.""" + + query_vector = self.embedding_model.get_embedding(query_text) + results = self.search(query_vector, k, distance_measure) + if return_as_text: + return [result[0] for result in results] + return results + + def retrieve_from_key(self, key: str) -> Optional[np.ndarray]: + """Return the stored vector for ``key`` if present.""" + + return self.vectors.get(key) + + async def abuild_from_list(self, list_of_text: List[str]) -> "VectorDatabase": + """Populate the vector store asynchronously from raw text snippets.""" + + embeddings = await self.embedding_model.async_get_embeddings(list_of_text) + for text, embedding in zip(list_of_text, embeddings): + self.insert(text, embedding) + return self + + +if __name__ == "__main__": + list_of_text = [ + "I like to eat broccoli and bananas.", + "I ate a banana and spinach smoothie for breakfast.", + "Chinchillas and kittens are cute.", + "My sister adopted a kitten yesterday.", + "Look at this cute hamster munching on a piece of broccoli.", + ] + + vector_db = VectorDatabase() + vector_db = asyncio.run(vector_db.abuild_from_list(list_of_text)) + k = 2 + + searched_vector = vector_db.search_by_text("I think fruit is awesome!", k=k) + print(f"Closest {k} vector(s):", searched_vector) + + retrieved_vector = vector_db.retrieve_from_key( + "I like to eat broccoli and bananas." + ) + print("Retrieved vector:", retrieved_vector) + + relevant_texts = vector_db.search_by_text( + "I think fruit is awesome!", k=k, return_as_text=True + ) + print(f"Closest {k} text(s):", relevant_texts) diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 000000000..e985853ed --- /dev/null +++ b/api/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/api/README.md b/api/README.md index 36fff229c..cc9a5d894 100644 --- a/api/README.md +++ b/api/README.md @@ -4,33 +4,38 @@ This is a FastAPI-based backend service that provides a streaming chat interface ## Prerequisites -- Python 3.8 or higher -- pip (Python package manager) +- Python 3.13 or higher (matches `pyproject.toml`) +- `uv` package manager (`pipx install uv` or `pip install uv`) - An OpenAI API key ## Setup -1. Create a virtual environment (recommended): +1. From the repository root (NOT this `api` directory), create and activate a root `.venv` with `uv`: + ```bash -python -m venv venv -source venv/bin/activate # On Windows, use: venv\Scripts\activate +cd .. # ensure you're at repo root +uv venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate ``` -2. Install the required dependencies: +2. Install dependencies defined in `pyproject.toml` at the root: + ```bash -pip install fastapi uvicorn openai pydantic +uv sync ``` ## Running the Server -1. Make sure you're in the `api` directory: +1. Ensure your root `.venv` is active and you're at the repository root: + ```bash -cd api +source .venv/bin/activate ``` -2. Start the server: +2. Start the server (from repo root) using `uv` to respect the root `.venv`: + ```bash -python app.py +uv run python api/app.py ``` The server will start on `http://localhost:8000` @@ -38,20 +43,24 @@ The server will start on `http://localhost:8000` ## API Endpoints ### Chat Endpoint + - **URL**: `/api/chat` - **Method**: POST - **Request Body**: + ```json { - "developer_message": "string", - "user_message": "string", - "model": "gpt-4.1-mini", // optional - "api_key": "your-openai-api-key" + "developer_message": "string", + "user_message": "string", + "model": "gpt-4.1-mini", // optional + "api_key": "your-openai-api-key" } ``` + - **Response**: Streaming text response ### Health Check + - **URL**: `/api/health` - **Method**: GET - **Response**: `{"status": "ok"}` @@ -59,6 +68,7 @@ The server will start on `http://localhost:8000` ## API Documentation Once the server is running, you can access the interactive API documentation at: + - Swagger UI: `http://localhost:8000/docs` - ReDoc: `http://localhost:8000/redoc` @@ -69,8 +79,9 @@ The API is configured to accept requests from any origin (`*`). This can be modi ## Error Handling The API includes basic error handling for: + - Invalid API keys - OpenAI API errors - General server errors -All errors will return a 500 status code with an error message. \ No newline at end of file +All errors will return a 500 status code with an error message. diff --git a/api/app.py b/api/app.py index 4fe8d0ba8..4f4f69581 100644 --- a/api/app.py +++ b/api/app.py @@ -1,5 +1,5 @@ # Import required FastAPI components for building the API -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, HTTPException, UploadFile, File, Request from fastapi.responses import StreamingResponse from fastapi.middleware.cors import CORSMiddleware # Import Pydantic for data validation and settings management @@ -7,7 +7,42 @@ # Import OpenAI client for interacting with OpenAI's API from openai import OpenAI import os -from typing import Optional +from typing import Optional, List, Set +import PyPDF2 +import io +import asyncio +import json +import logging +import time +import uuid + +# LangChain / LangGraph +from langchain_openai import ChatOpenAI, OpenAIEmbeddings +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain.docstore.document import Document +from langchain_community.vectorstores import Qdrant +from qdrant_client import QdrantClient +from langgraph.graph import END, StateGraph +from typing import TypedDict, Dict, Any +from dotenv import load_dotenv + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s %(name)s %(message)s", +) +logger = logging.getLogger("api") + +# Load environment variables from a .env file if present +load_dotenv() +logger.info("Environment variables loaded") + +qdrant_url = os.getenv("QDRANT_URL") +qdrant_api_key = os.getenv("QDRANT_API_KEY") + +client = QdrantClient(url=qdrant_url, api_key=qdrant_api_key) + +logger.info("QDRANT_URL: %s", os.getenv("QDRANT_URL")) +logger.info("QDRANT_API_KEY: %s", os.getenv("QDRANT_API_KEY")) # Initialize FastAPI application with a title app = FastAPI(title="OpenAI Chat API") @@ -17,35 +52,529 @@ app.add_middleware( CORSMiddleware, allow_origins=["*"], # Allows requests from any origin - allow_credentials=True, # Allows cookies to be included in requests + allow_credentials=False, # Allows cookies to be included in requests allow_methods=["*"], # Allows all HTTP methods (GET, POST, etc.) allow_headers=["*"], # Allows all headers in requests ) + +@app.middleware("http") +async def add_request_id_and_log(request: Request, call_next): + request_id = str(uuid.uuid4()) + start = time.time() + # Attach to state for downstream use + request.state.request_id = request_id + logger.info( + "request_start method=%s path=%s request_id=%s", + request.method, + request.url.path, + request_id, + ) + try: + response = await call_next(request) + duration_ms = int((time.time() - start) * 1000) + logger.info( + "request_end status=%s duration_ms=%s request_id=%s", + getattr(response, "status_code", "n/a"), + duration_ms, + request_id, + ) + return response + except Exception as e: + duration_ms = int((time.time() - start) * 1000) + logger.exception( + "request_error duration_ms=%s request_id=%s error=%s", + duration_ms, + request_id, + str(e), + ) + raise + +# App state for vector store and topics +app.state.topics_set = set() +app.state.qa_store = None # Qdrant-backed LangChain vector store for agent + # Define the data model for chat requests using Pydantic # This ensures incoming request data is properly validated +class ChatMessage(BaseModel): + role: str + content: str + + class ChatRequest(BaseModel): - developer_message: str # Message from the developer/system user_message: str # Message from the user model: Optional[str] = "gpt-4.1-mini" # Optional model selection with default api_key: str # OpenAI API key for authentication + history: Optional[List[ChatMessage]] = None # Prior conversation turns + + +class TopicQuestionRequest(BaseModel): + topic: str + api_key: str + num_choices: int = 4 + model: Optional[str] = "gpt-4.1-mini" + # Controls for diversification + seed: Optional[int] = None + diversity: float = 0.5 # 0..1 influences randomness and variation + num_contexts: int = 4 # how many retrieved chunks to feed generator + query_fanout: int = 5 # how many expanded queries to use + variation_id: Optional[int] = None # influences scenario style/phrasing + +def extract_text_from_pdf(pdf_file: bytes) -> List[str]: + """Extract text from PDF and split it into chunks.""" + pdf_reader = PyPDF2.PdfReader(io.BytesIO(pdf_file)) + chunks = [] + + for page in pdf_reader.pages: + text = page.extract_text() + # Split text into smaller chunks (simple approach - split by paragraphs) + paragraphs = text.split('\n\n') + chunks.extend([p.strip() for p in paragraphs if p.strip()]) + + return chunks + + +def _extract_topics_sync(chunk_text: str, client: OpenAI) -> List[str]: + """Blocking helper that calls OpenAI to extract topics for a single chunk. + + Returns a list of concise topic labels (strings). Falls back to an empty list + if the model output cannot be parsed as JSON. + """ + system_msg = ( + "You extract concise topic labels from provided text. " + "Return ONLY a JSON array of 1-5 short noun-phrase topics." + ) + user_msg = ( + "Text chunk:\n" + chunk_text[:6000] + ) + resp = client.chat.completions.create( + model="gpt-4.1-mini", + messages=[ + {"role": "system", "content": system_msg}, + {"role": "user", "content": user_msg}, + ], + temperature=0, + ) + content = resp.choices[0].message.content if resp.choices else "[]" + try: + parsed = json.loads(content) + if isinstance(parsed, list): + # Normalize topics: strip, lowercase, and remove empties + return [t.strip() for t in parsed if isinstance(t, str) and t.strip()] + except Exception: + pass + return [] + + +async def extract_topics_for_chunks(chunks: List[str], client: OpenAI, concurrency_limit: int = 5) -> List[str]: + """Extract topics for each chunk concurrently with a semaphore limit. + + Aggregates topics into a unique, sorted list. + """ + semaphore = asyncio.Semaphore(concurrency_limit) + + async def run_for_chunk(chunk: str) -> List[str]: + async with semaphore: + return await asyncio.to_thread(_extract_topics_sync, chunk, client) + + tasks = [run_for_chunk(chunk) for chunk in chunks] + all_results = await asyncio.gather(*tasks) + + unique_topics: Set[str] = set() + for topic_list in all_results: + for topic in topic_list: + normalized = topic.strip() + if normalized: + unique_topics.add(normalized) + + return sorted(unique_topics) + + +# ---------- LangGraph agent for topic-based MCQ generation ---------- + +class AgentState(TypedDict): + topic: str + queries: List[str] + retrieved: List[Dict[str, Any]] + question: Dict[str, Any] + seed: Optional[int] + diversity: float + num_contexts: int + query_fanout: int + variation_id: Optional[int] + + +def retrieve_node(state: AgentState) -> AgentState: + """Retrieve relevant documents using expanded queries with MMR for diversity. + + - If `queries` are provided in state, issue retrieval for each query and merge results. + - Otherwise, fall back to retrieving using the original `topic` only. + - Deduplicate by page content to reduce repetition. + """ + topic = state["topic"] + queries = state.get("queries") or [] + query_fanout = max(1, int(state.get("query_fanout") or 5)) + seed = state.get("seed") + if app.state.qa_store is None: + return {**state, "retrieved": []} + + diversity = float(state.get("diversity") or 0.5) + k = 6 if diversity < 0.5 else 8 + fetch_k = 24 if diversity < 0.5 else 36 + lambda_mult = 0.6 + 0.3 * min(1.0, max(0.0, diversity)) + retriever = app.state.qa_store.as_retriever( + search_type="mmr", + search_kwargs={"k": k, "fetch_k": fetch_k, "lambda_mult": lambda_mult}, + ) + + all_docs: List[Any] = [] + if queries: + # Use a seeded RNG to select a subset of queries for diversity + try: + import random + rng = random.Random(seed) + selected_queries = queries[:] + rng.shuffle(selected_queries) + selected_queries = selected_queries[: min(query_fanout, len(selected_queries))] + except Exception: + selected_queries = queries[: min(query_fanout, len(queries))] + + # Log the selected queries used for retrieval + try: + logger.info( + "research_selected_queries seed=%s selected=%s", + seed, + selected_queries, + ) + except Exception: + pass + + for q in selected_queries: + try: + q_docs = retriever.get_relevant_documents(q) + all_docs.extend(q_docs) + except Exception as e: + logger.warning("retrieve_query_failed query=%s error=%s", q[:120], str(e)) + else: + try: + all_docs = retriever.get_relevant_documents(topic) + except Exception as e: + logger.warning("retrieve_topic_failed topic=%s error=%s", topic[:120], str(e)) + + # Deduplicate by normalized page content + seen: Set[str] = set() + dedup_docs: List[Any] = [] + for d in all_docs: + content = (getattr(d, "page_content", None) or "").strip() + if not content: + continue + key = content.replace("\n", " ") + if key in seen: + continue + seen.add(key) + dedup_docs.append(d) + + # Sample contexts using seeded RNG for variation across runs + num_contexts = max(1, int(state.get("num_contexts") or 4)) + try: + import random + rng = random.Random(seed) + sampled_docs = dedup_docs[:] + rng.shuffle(sampled_docs) + sampled_docs = sampled_docs[: min(num_contexts, len(sampled_docs))] + except Exception: + sampled_docs = dedup_docs[: min(num_contexts, len(dedup_docs))] + + # Log sampled docs metadata and preview + try: + for i, d in enumerate(sampled_docs[:10]): + meta = getattr(d, "metadata", {}) or {} + logger.info( + "research_doc idx=%s meta=%s preview=%s", + i, + meta, + (d.page_content[:200] if getattr(d, "page_content", None) else ""), + ) + logger.info( + "topic_retrieve queries=%s docs_total=%s docs_used=%s topic=%s", + len(queries) or 0, + len(dedup_docs), + len(sampled_docs), + topic[:120], + ) + except Exception: + pass + + return {**state, "retrieved": [{"content": d.page_content, "meta": (getattr(d, "metadata", {}) or {})} for d in sampled_docs]} + + +class ChoiceModel(BaseModel): + label: str + text: str + + +class MCQModel(BaseModel): + question: str + choices: List[ChoiceModel] + correct: str + rationale: str + evidence: str + section: str +class QueryExpansionModel(BaseModel): + queries: List[str] + + +def expand_queries_node(model_name: str): + """Generate diverse, specific search queries from the topic. + + The goal is to improve retrieval diversity by proposing multiple concrete + phrases, synonyms, statute references, named entities, and subtopics. + """ + def node(state: AgentState) -> AgentState: + topic = state["topic"] + llm = ChatOpenAI(model=model_name, temperature=0.7) + structured_llm = llm.with_structured_output(QueryExpansionModel) + prompt = ( + "You expand a study topic into diverse short search queries for a vector database.\n" + "Return a JSON object with a 'queries' array of 5-8 items.\n" + "Queries should be specific and varied: include synonyms, key phrases,\n" + "canonical terminology, relevant statute/section identifiers if applicable,\n" + "named entities, and related subtopics.\n" + "Keep each query under 12 words. Avoid redundancy.\n\n" + f"Topic: {topic}" + ) + try: + result: QueryExpansionModel = structured_llm.invoke(prompt) + queries = [q.strip() for q in result.queries if isinstance(q, str) and q.strip()] + print("expand_queries_node queries",queries) + except Exception: + queries = [topic] + + # Seeded shuffle and trimming based on query_fanout + seed = state.get("seed") + query_fanout = max(1, int(state.get("query_fanout") or 5)) + try: + import random + rng = random.Random(seed) + rng.shuffle(queries) + queries = queries[: min(query_fanout * 2, len(queries))] # keep some extras for retrieval stage + except Exception: + queries = queries[: min(query_fanout * 2, len(queries))] + + # Log expanded queries for observability + try: + logger.info( + "research_expanded_queries topic=%s count=%s queries=%s", + topic[:120], + len(queries), + queries, + ) + except Exception: + pass + + return {**state, "queries": queries} + + return node + + + +def make_question_node(model_name: str): + def question_node(state: AgentState) -> AgentState: + # Temperature scales with diversity + diversity = float(state.get("diversity") or 0.5) + temperature = max(0.2, min(0.2 + diversity * 0.8, 0.9)) + # Encourage paraphrasing and reduce repetition with penalties + freq_penalty = 0.0 + 0.6 * diversity + pres_penalty = 0.0 + 0.4 * diversity + llm = ChatOpenAI( + model=model_name, + temperature=temperature, + frequency_penalty=round(freq_penalty, 2), + presence_penalty=round(pres_penalty, 2), + top_p=1.0, + ) + structured_llm = llm.with_structured_output(MCQModel) + context_items = state.get("retrieved", []) + context = "\n\n".join([d["content"] for d in context_items])[:6000] + variation_id = state.get("variation_id") + # Log generation parameters and a summary of contexts + try: + logger.info( + "question_gen diversity=%s temp=%s penalties=%s contexts=%s variation_id=%s", + diversity, + temperature, + {"frequency_penalty": round(freq_penalty, 2), "presence_penalty": round(pres_penalty, 2)}, + len(context_items), + variation_id, + ) + except Exception: + pass + instructions = ( + "You are a tutor creating a single multiple-choice question (MCQ) based strictly on the provided context.\n" + f"- Topic: {state['topic']}\n" + "- Avoid explicit or graphic content; use neutral, legal/educational framing.\n" + "- Create a short scenario that tests understanding of the topic.\n" + "- Provide exactly one question and 4 answer choices labeled A-D.\n" + "- Indicate the correct letter and provide a brief rationale.\n" + "- Also include an 'evidence' string quoting or closely paraphrasing the specific context segment supporting the correct answer.\n" + "- Strongly vary scenario details each time: change setting, actors, stakes, time period, and phrasing so successive runs are noticeably different.\n" + f"- Variation ID: {variation_id}. Use this to diversify scenario style and wording deterministically.\n" + "- Prefer concrete, domain-specific details (e.g., procurement, licensing, reporting, penalties) drawn from context.\n" + "- Use plausible distractors that are contextually grounded, not generic.\n" + "- Also include a 'section' string indicating the exact statute/section number (e.g., 'Sec. 22.041(b)') where the evidence comes from if present; else return an empty string.\n" + "- Output must conform exactly to the schema.\n" + "Context:\n" + context + ) + try: + result: MCQModel = structured_llm.invoke(instructions) + data = result.model_dump() + except Exception: + data = {"question": state["topic"], "choices": [], "correct": "A", "rationale": "", "evidence": "", "section": ""} + return {**state, "question": data} + + return question_node + + +def build_graph(model_name: str = "gpt-4.1-mini"): + graph = StateGraph(AgentState) + graph.add_node("expand_queries", expand_queries_node(model_name)) + graph.add_node("retrieve", retrieve_node) + graph.add_node("generate_question", make_question_node(model_name)) + graph.set_entry_point("expand_queries") + graph.add_edge("expand_queries", "retrieve") + graph.add_edge("retrieve", "generate_question") + graph.add_edge("generate_question", END) + return graph.compile() + +@app.post("/api/upload-pdf") +async def upload_pdf(request: Request, file: UploadFile = File(...), api_key: str = None): + if not api_key: + raise HTTPException(status_code=400, detail="API key is required") + + try: + # Read the PDF file + logger.info("upload_pdf_read_file request_id=%s filename=%s", request.state.request_id, file.filename) + contents = await file.read() + + # Extract text from PDF + text_chunks = extract_text_from_pdf(contents) + logger.info( + "upload_pdf_extracted_chunks request_id=%s chunks=%s", + request.state.request_id, + len(text_chunks), + ) + + if not text_chunks: + raise HTTPException(status_code=400, detail="No text could be extracted from the PDF") + + # Build LangChain vector store for agent (Qdrant) + os.environ["OPENAI_API_KEY"] = api_key + splitter = RecursiveCharacterTextSplitter(chunk_size=1200, chunk_overlap=150) + # Preserve filename and chunk index for observability + base_docs = [Document(page_content="\n\n".join(text_chunks), metadata={"filename": file.filename})] + docs = splitter.split_documents(base_docs) + for i, d in enumerate(docs): + d.metadata = {**(getattr(d, "metadata", {}) or {}), "chunk_index": i} + embeddings = OpenAIEmbeddings() + # Use remote Qdrant if QDRANT_URL is set; otherwise fall back to in-memory + if qdrant_url: + app.state.qa_store = Qdrant.from_documents( + documents=docs, + embedding=embeddings, + url=qdrant_url, + collection_name="pdf_chunks", + ) + else: + app.state.qa_store = Qdrant.from_documents( + documents=docs, + embedding=embeddings, + location=":memory:", + collection_name="pdf_chunks", + ) + + # Extract hierarchical topics from each chunk and aggregate (optional; disabled) + # client = OpenAI(api_key=api_key) + # topics_list = await extract_topics_for_chunks(text_chunks, client) + # app.state.topics_set = set(topics_list) + # logger.info( + # "upload_pdf_extracted_topics request_id=%s topics=%s", + # request.state.request_id, + # len(topics_list), + # ) + + return { + "message": "PDF processed successfully", + "chunk_count": len(docs), + } + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) from e # Define the main chat endpoint that handles POST requests @app.post("/api/chat") async def chat(request: ChatRequest): try: + # Ensure OpenAI API key is available for embedding queries if needed + if request.api_key: + os.environ["OPENAI_API_KEY"] = request.api_key + + # If we don't have an in-memory vector db and no qa_store set yet, but Qdrant is configured, + # try to lazily attach to the existing collection so chat works after reloads. + if app.state.qa_store is None: + if qdrant_url: + try: + embeddings = OpenAIEmbeddings() + app.state.qa_store = Qdrant( + client=client, + collection_name="pdf_chunks", + embeddings=embeddings, + ) + logger.info("chat_lazy_qdrant_attach url=%s collection=pdf_chunks", qdrant_url) + except Exception as e: + logger.warning("chat_lazy_qdrant_attach_failed error=%s", str(e)) + + # Proceed even if in-memory vectors are absent; we'll try Qdrant or use empty context + + # Get relevant chunks from Qdrant if available + relevant_chunks: List[str] = [] + if app.state.qa_store is not None: + retriever = app.state.qa_store.as_retriever(search_kwargs={"k": 6, "search_type": "mmr", "fetch_k": 20, "lambda_mult": 0.7}) + docs = retriever.get_relevant_documents(request.user_message) + relevant_chunks = [d.page_content for d in docs] + logger.info("chat_retrieved_chunks source=qdrant k=%s retrieved=%s", 6, len(relevant_chunks)) + logger.info("chat_retrieved_chunks relevant_chunks=%s", relevant_chunks) + # Create the system message with context + system_message = f"""You are a helpful AI assistant that answers questions based ONLY on the provided context. +If the question cannot be answered using the context, say that you cannot answer the question with the available information. +Do not make up or infer information that is not in the context. + +Context from the PDF: +{' '.join(relevant_chunks)}""" + # Initialize OpenAI client with the provided API key client = OpenAI(api_key=request.api_key) + # Build messages including optional history + messages: List[dict] = [ + {"role": "system", "content": system_message}, + ] + + # Append prior turns if provided (only roles user/assistant/system) + if request.history: + for m in request.history[-20:]: + role = m.role if m.role in {"user", "assistant", "system"} else "user" + content = m.content or "" + if content.strip(): + messages.append({"role": role, "content": content}) + + # Append the new user message + messages.append({"role": "user", "content": request.user_message}) + # Create an async generator function for streaming responses async def generate(): # Create a streaming chat completion request stream = client.chat.completions.create( model=request.model, - messages=[ - {"role": "developer", "content": request.developer_message}, - {"role": "user", "content": request.user_message} - ], + messages=messages, stream=True # Enable streaming response ) @@ -59,13 +588,63 @@ async def generate(): except Exception as e: # Handle any errors that occur during processing - raise HTTPException(status_code=500, detail=str(e)) + raise HTTPException(status_code=500, detail=str(e)) from e # Define a health check endpoint to verify API status @app.get("/api/health") async def health_check(): return {"status": "ok"} + +@app.post("/api/topic-question") +async def topic_question(req: TopicQuestionRequest): + try: + # Ensure OpenAI key is set for embeddings + if req.api_key: + os.environ["OPENAI_API_KEY"] = req.api_key + + # Lazily attach to an existing Qdrant collection if qa_store is not yet set + if app.state.qa_store is None: + if qdrant_url: + try: + embeddings = OpenAIEmbeddings() + app.state.qa_store = Qdrant( + client=client, + collection_name="pdf_chunks", + embeddings=embeddings, + ) + logger.info("topic_question_lazy_qdrant_attach url=%s collection=pdf_chunks", qdrant_url) + except Exception as e: + logger.warning("topic_question_lazy_qdrant_attach_failed error=%s", str(e)) + print("building graph") + # Proceed even if qa_store could not be attached; graph will operate with empty retrieval + graph = build_graph(req.model or "gpt-4.1-mini") + # Compute a variation id if not provided + import random + computed_variation = req.variation_id if req.variation_id is not None else (req.seed if req.seed is not None else random.randint(1, 10_000_000)) + result = graph.invoke({ + "topic": req.topic, + "queries": [], + "retrieved": [], + "seed": computed_variation, + "diversity": req.diversity, + "num_contexts": req.num_contexts, + "query_fanout": req.query_fanout, + "variation_id": computed_variation, + }) + # Do not generate if no context was retrieved + if not result.get("retrieved"): + raise HTTPException(status_code=400, detail="No relevant context found for the topic. Upload a PDF or configure QDRANT_URL.") + q = result.get("question", {}) + # Optionally truncate choices to the requested number + if isinstance(q.get("choices"), list) and req.num_choices > 0: + q["choices"] = q["choices"][: req.num_choices] + return q + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) from e + # Entry point for running the application directly if __name__ == "__main__": import uvicorn diff --git a/api/requirements.txt b/api/requirements.txt index f2d9a1cbc..c316c8d68 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -2,4 +2,12 @@ fastapi==0.115.12 uvicorn==0.34.2 openai==1.77.0 pydantic==2.11.4 -python-multipart==0.0.18 \ No newline at end of file +python-multipart==0.0.18 +PyPDF2==3.0.1 +numpy==1.26.4 +python-dotenv==1.0.1 +langchain==0.2.16 +langchain-openai==0.1.23 +langgraph==0.2.35 +langchain-community==0.2.16 +qdrant-client==1.12.1 diff --git a/api/vercel.json b/api/vercel.json index b5f952634..502c86cb7 100644 --- a/api/vercel.json +++ b/api/vercel.json @@ -1,9 +1,18 @@ { - "version": 2, - "builds": [ - { "src": "app.py", "use": "@vercel/python" } - ], - "routes": [ - { "src": "/(.*)", "dest": "app.py" } - ] - } \ No newline at end of file + "version": 2, + "builds": [ + { + "src": "app.py", + "use": "@vercel/python" + } + ], + "routes": [ + { + "src": "/(.*)", + "dest": "app.py" + } + ], + "env": { + "PYTHON_VERSION": "3.9" + } +} diff --git a/data/penalcode.pdf b/data/penalcode.pdf new file mode 100644 index 000000000..53622e81a Binary files /dev/null and b/data/penalcode.pdf differ diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json new file mode 100644 index 000000000..09f915d2e --- /dev/null +++ b/frontend/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": ["next/core-web-vitals"] +} + + diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 000000000..1403e903b --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + diff --git a/frontend/README.md b/frontend/README.md index 56347bab6..5db4871f5 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,3 +1,211 @@ -### Front End +# OpenAI Chat Frontend -Please populate this README with instructions on how to run the application! \ No newline at end of file +A beautiful, modern chat interface built with Next.js and TypeScript for interacting with the OpenAI Chat API backend. + +## Features + +- 🎨 **Modern UI**: Beautiful, responsive design with dark/light theme support +- πŸ” **Secure**: API key input with password-style field and client-side validation +- ⚑ **Real-time Streaming**: Live streaming responses from OpenAI's GPT models +- πŸ“± **Responsive**: Works perfectly on desktop, tablet, and mobile devices +- βš™οΈ **Configurable**: Adjustable system messages and model selection +- 🎯 **User-friendly**: Intuitive chat interface with typing indicators and message history + +## Prerequisites + +- Node.js 18.0 or higher +- npm or yarn package manager +- The FastAPI backend server running (see `../api/README.md`) +- An OpenAI API key + +## Installation + +1. **Navigate to the frontend directory:** + + ```bash + cd frontend + ``` + +2. **Install dependencies:** + + ```bash + npm install + ``` + +3. **Set up environment variables:** + + ```bash + cp .env.example .env.local + ``` + + Edit `.env.local` and update the API URL if needed: + + ``` + NEXT_PUBLIC_API_URL=http://localhost:8000 + ``` + +## Running the Application + +### Development Mode + +Start the development server: + +```bash +npm run dev +``` + +The application will be available at `http://localhost:3000` + +### Production Build + +1. **Build the application:** + + ```bash + npm run build + ``` + +2. **Start the production server:** + ```bash + npm start + ``` + +## Usage + +1. **Start the Backend**: Make sure the FastAPI backend is running on `http://localhost:8000` + +2. **Open the Frontend**: Navigate to `http://localhost:3000` in your browser + +3. **Enter API Key**: On first visit, you'll be prompted to enter your OpenAI API key + + - Get your API key from [OpenAI Platform](https://platform.openai.com/api-keys) + - The key is stored securely in your browser session only + +4. **Start Chatting**: + + - Type your message in the input field + - Press Enter to send (Shift+Enter for new lines) + - Watch as the AI responds in real-time with streaming + +5. **Configure Settings** (Optional): + - Click the settings icon to adjust the system message + - Select different GPT models (gpt-4.1-mini, gpt-4, gpt-3.5-turbo) + - Customize how the AI should behave + +## Deployment + +### Vercel Deployment + +This frontend is optimized for deployment on Vercel: + +1. **Connect your repository** to Vercel +2. **Set environment variables** in the Vercel dashboard: + - `NEXT_PUBLIC_API_URL`: Your deployed backend API URL +3. **Deploy**: Vercel will automatically build and deploy your application + +### Manual Deployment + +1. **Build the application:** + + ```bash + npm run build + ``` + +2. **Export static files** (optional): + + ```bash + npm run export + ``` + +3. **Deploy the `out/` or `.next/` directory** to your hosting provider + +## Project Structure + +``` +frontend/ +β”œβ”€β”€ components/ # React components +β”‚ β”œβ”€β”€ ApiKeySetup.tsx # API key input component +β”‚ β”œβ”€β”€ ChatInterface.tsx # Main chat interface +β”‚ └── MessageBubble.tsx # Individual message component +β”œβ”€β”€ pages/ # Next.js pages +β”‚ β”œβ”€β”€ _app.tsx # App wrapper +β”‚ β”œβ”€β”€ _document.tsx # HTML document structure +β”‚ └── index.tsx # Main page +β”œβ”€β”€ styles/ # Global styles +β”‚ └── globals.css # Tailwind CSS and custom styles +β”œβ”€β”€ types/ # TypeScript type definitions +β”‚ └── index.ts # Shared types +└── public/ # Static assets +``` + +## Configuration + +### Environment Variables + +- `NEXT_PUBLIC_API_URL`: The URL of your FastAPI backend (default: http://localhost:8000) + +### Styling + +The application uses Tailwind CSS with a custom design system: + +- Proper contrast ratios for accessibility +- Responsive breakpoints for mobile-first design +- Dark/light theme variables +- Custom animations and transitions + +## Troubleshooting + +### Common Issues + +1. **"Backend server is not accessible"** + + - Ensure the FastAPI backend is running on the correct port + - Check that CORS is properly configured in the backend + - Verify the `NEXT_PUBLIC_API_URL` environment variable + +2. **API Key Issues** + + - Ensure your OpenAI API key starts with "sk-" + - Check that you have sufficient credits in your OpenAI account + - Verify the key has the necessary permissions + +3. **Build Errors** + + - Run `npm install` to ensure all dependencies are installed + - Check for TypeScript errors with `npm run lint` + - Ensure Node.js version is 18.0 or higher + +4. **Streaming Not Working** + - Check browser console for JavaScript errors + - Verify the backend is returning streaming responses + - Test the backend API directly to isolate issues + +### Performance Tips + +- The application automatically handles message history and scrolling +- Streaming responses provide immediate feedback without waiting for complete responses +- API keys are stored securely in browser session storage +- Messages are optimized for mobile viewing with responsive design + +## Security Notes + +- API keys are stored only in browser session storage and never sent to our servers +- All communication with OpenAI happens through your own API key +- HTTPS is recommended for production deployments +- Consider implementing rate limiting for production use + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +## Support + +For issues and questions: + +1. Check the troubleshooting section above +2. Verify the backend API is working correctly +3. Check browser console for error messages +4. Ensure environment variables are set correctly diff --git a/frontend/components/ApiKeySetup.tsx b/frontend/components/ApiKeySetup.tsx new file mode 100644 index 000000000..6f93f9c0e --- /dev/null +++ b/frontend/components/ApiKeySetup.tsx @@ -0,0 +1,132 @@ +import { useState } from "react"; +import { Eye, EyeOff, Key, AlertCircle } from "lucide-react"; + +interface ApiKeySetupProps { + onApiKeySubmit: (apiKey: string) => void; +} + +export default function ApiKeySetup({ onApiKeySubmit }: ApiKeySetupProps) { + const [apiKey, setApiKey] = useState(""); + const [showApiKey, setShowApiKey] = useState(false); + const [error, setError] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); + + if (!apiKey.trim()) { + setError("Please enter your OpenAI API key"); + return; + } + + if (!apiKey.startsWith("sk-")) { + setError('Invalid API key format. OpenAI API keys start with "sk-"'); + return; + } + + setIsLoading(true); + + try { + // Test the API key by making a health check to the backend + const response = await fetch( + `${ + process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000" + }/api/health` + ); + + if (!response.ok) { + throw new Error( + "Backend server is not accessible. Please ensure the API server is running." + ); + } + + onApiKeySubmit(apiKey.trim()); + } catch (err) { + setError( + err instanceof Error ? err.message : "Failed to validate API key" + ); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+
+
+
+ +
+

Welcome!

+

+ Enter your OpenAI API key to get started with the chat assistant. +

+
+ +
+
+ +
+ setApiKey(e.target.value)} + placeholder="sk-..." + className="w-full px-3 py-2 pr-10 border border-input rounded-md bg-background focus:ring-2 focus:ring-ring focus:border-transparent outline-none transition-colors" + disabled={isLoading} + /> + +
+
+ + {error && ( +
+ + {error} +
+ )} + + +
+ +
+

+ Don't have an API key?{" "} + + Get one from OpenAI + +

+
+
+
+
+ ); +} + diff --git a/frontend/components/ChatInterface.tsx b/frontend/components/ChatInterface.tsx new file mode 100644 index 000000000..ba4b4f431 --- /dev/null +++ b/frontend/components/ChatInterface.tsx @@ -0,0 +1,2699 @@ +import { useState, useRef, useEffect } from "react"; +import { Send, Settings, MessageSquare, User, Bot } from "lucide-react"; +import MessageBubble from "./MessageBubble"; +import PDFUpload from "./PDFUpload"; +import TopicSelector from "./TopicSelector"; +import { Message, McqPayload } from "@/types"; + +const topics = [ + "AI-generated child pornography", + "Abandoning or endangering", + "Abuse of corpse offenses", + "Abuse of office definitions", + "Acceptance of honorarium", + "Access device tampering", + "Accomplice witness testimony", + "Adequate cause definition", + "Adjudicatory proceeding", + "Adjudicatory proceedings", + "Adult stem cells", + "Adulterated goods standards", + "Advertising by professionals", + "Affirmative defense", + "Affirmative defense criteria", + "Affirmative defense for minors", + "Affirmative defense in prosecution", + "Affirmative defense notifications", + "Affirmative defenses", + "Affirmative defenses for firearm access", + "Affirmative defenses for judicial and law enforcement officers", + "Affirmative defenses for possession offenses", + "Affirmative findings under Article 42.0197", + "Age Restrictions on Firearms", + "Age and criminal history limitations", + "Age-related offense rules", + "Agent conduct and corporate liability", + "Agent definitions", + "Aggravated Perjury", + "Aggravated Sexual Assault", + "Aggravated assault", + "Aggravated kidnapping", + "Aggravated perjury defenses", + "Aggravated promotion of prostitution", + "Aggravated robbery", + "Aggravated sexual assault", + "Aggregation of offenses", + "Aiding suicide offense", + "Airports", + "Alcohol concentration levels", + "Alcohol concentration measurement", + "Alcohol possession laws", + "Alcoholic beverage", + "Alcoholic beverage permits", + "Alternative fine provisions", + "Amendments", + "American vessel documentation", + "Amusement park definition", + "Animal abuse laws", + "Animal abuse penalties", + "Animal and livestock protection", + "Animal attack offenses", + "Animal cruelty offenses", + "Animal custody and care", + "Animal neglect and abandonment", + "Animal sexual contact", + "Appropriation of property", + "Arson", + "Arson offenses", + "Artificially generated media", + "Assault on public servants", + "Assaultive offenses", + "Assistance animal", + "Assisted reproduction", + "Assisted reproduction definitions", + "Attorney General assistance", + "Attorney General prosecution jurisdiction", + "Attorney general coordination", + "Attorney general jurisdiction", + "Automated teller machine", + "Automated teller machines", + "Bail jumping", + "Barratry offenses", + "Bestiality offense", + "Bias or prejudice offenses", + "Bias-motivated offenses", + "Bigamy statute", + "Bill Enactments", + "Bill Numbers", + "Boating while intoxicated", + "Boating with child passenger intoxication", + "Bodily injury", + "Bookmaking activities", + "Bribery offense", + "Burglary definitions", + "Burglary offense elements", + "Business entities", + "Capital felonies", + "Capital felony punishment", + "Capital felony punishments", + "Capital murder", + "Cardholder definition", + "Caregiver and coach influence", + "Catalytic converter", + "Cellular telephone components", + "Child Pornography", + "Child age criteria", + "Child and Disabled Individual Trafficking", + "Child custody determination", + "Child custody escape", + "Child custody interference", + "Child definition", + "Child endangerment offense", + "Child firearm discharge", + "Child labor laws", + "Child pornography laws", + "Child protection", + "Child protection laws", + "Child recruitment offenses", + "Child sexual conduct depictions", + "Child support obligations", + "Child trafficking", + "Child visual material offenses", + "Child-care facility employee offenses", + "Child-related obscene material penalties", + "Civil commitment facilities", + "Civil commitment facility", + "Civil remedies", + "Claims payment process", + "Class A Misdemeanor", + "Class A misdemeanor", + "Class A misdemeanor and felony penalties", + "Class A misdemeanor offense", + "Class A misdemeanor offenses", + "Class A misdemeanor penalties", + "Class A misdemeanor violations", + "Class A misdemeanors", + "Class B misdemeanor", + "Class B misdemeanor offense", + "Class B misdemeanor offenses", + "Class B misdemeanor penalties", + "Class C misdemeanor", + "Class C misdemeanor penalties", + "Classification of offenses", + "Classification of offenses and penalties", + "Classification of railroad-related offenses", + "Clergyman professional conduct", + "Cockfighting offense", + "Code of Criminal Procedure", + "Coercion", + "Coercion in gang membership", + "Coercion in trafficking", + "Combination in criminal activities", + "Commercial bribery definitions", + "Commercial social networking site", + "Communication common carrier", + "Communication common carrier regulations", + "Communication device regulations in correctional settings", + "Communication restrictions", + "Community standards for obscenity", + "Community supervision officers", + "Compelling Prostitution", + "Computer crimes definitions", + "Computer definitions", + "Computer network", + "Computer network offenses", + "Computer virus", + "Computer-generated child images", + "Concealed handgun laws", + "Concurrent Jurisdiction", + "Conditions of bond", + "Condominium and tenant defenses", + "Condominium handgun defenses", + "Consent", + "Consent and privacy", + "Consent as defense to assault", + "Consent defense", + "Consent for camping", + "Consent in assisted reproduction", + "Consent in media production", + "Consent requirements", + "Conspiracy to commit offenses", + "Consumer product definition", + "Consumer protection laws", + "Continuing course of conduct aggregation", + "Continuous Trafficking of Persons", + "Continuous family violence", + "Continuous promotion of prostitution", + "Continuous sexual abuse", + "Continuous smuggling of persons", + "Controlled substance", + "Controlled substance delivery", + "Controlled substance manufacturing", + "Controlled substance offenses", + "Controlled substance penalty enhancements", + "Controlled substances", + "Conviction criteria", + "Conviction definitions", + "Copper or brass material", + "Corporate and association punishments", + "Corporate criminal liability", + "Corporations and business entities", + "Correctional facilities", + "Correctional facility definitions", + "Correctional facility guards and peace officers", + "Correctional facility offenses", + "Correctional facility regulations", + "Correctional facility security", + "Counterfeit Mark Definition", + "Counterfeit gift cards", + "Counterfeit telecommunications devices", + "Court Order Violations", + "Court sentencing guidelines", + "Credit card abuse", + "Criminal Offense", + "Criminal Offenses and Penalties", + "Criminal Offenses and Prosecution", + "Criminal Penalties", + "Criminal Procedure", + "Criminal Responsibility", + "Criminal activity classification", + "Criminal charges", + "Criminal conspiracy penalties", + "Criminal convictions", + "Criminal instruments", + "Criminal investigation procedures", + "Criminal law terms", + "Criminal liability", + "Criminal mischief", + "Criminal misdemeanor offenses", + "Criminal negligence", + "Criminal negligence in firearm security", + "Criminal nonsupport", + "Criminal offense classification", + "Criminal offense definitions", + "Criminal offense prevention", + "Criminal offense punishment categories", + "Criminal offense reporting requirements", + "Criminal offenses", + "Criminal offenses and intent", + "Criminal offenses and penalties", + "Criminal penalties", + "Criminal penalties for data tampering", + "Criminal procedure", + "Criminal procedure amendments", + "Criminal prosecution classifications", + "Criminal responsibility", + "Criminal responsibility of business entities", + "Criminal solicitation", + "Criminal solicitation of a minor", + "Criminal street gang firearm offenses", + "Criminal street gangs", + "Criminally negligent homicide", + "Critical infrastructure facilities", + "Critical infrastructure facility", + "Critical infrastructure offenses", + "Cruelty to nonlivestock animals", + "Currency definitions", + "Custodial rights violations", + "Custody and detention", + "Cyber attack definition", + "Damaging critical infrastructure", + "Data definition and storage", + "Deadly conduct offense", + "Deadly force justification", + "Deadly weapon use", + "Deadly weapon use in felonies", + "Deadly weapons", + "Death penalty provisions", + "Debit card abuse", + "Deception in transactions", + "Deceptive business practices", + "Decryption and encrypted communications", + "Deep fake media definition", + "Defense to prosecution", + "Defenses for law enforcement and school administrators", + "Deferred Action for Childhood Arrivals", + "Deferred adjudication community supervision", + "Definition of emergency conditions", + "Definition of harmful material", + "Definition of property", + "Definition of service", + "Definition of sexual conduct", + "Definitions in bribery and corrupt influence", + "Definitions of animal-related terms", + "Definitions of business terms", + "Definitions of cockfighting terms", + "Definitions of deliver and publish", + "Definitions of emergency and utility", + "Definitions of fiduciary roles", + "Definitions of financial instruments", + "Definitions of intimate areas", + "Definitions of land types", + "Definitions of legal terms in correctional context", + "Definitions of marking devices", + "Definitions of sexual conduct", + "Definitions of sexual intercourse", + "Definitions of sexual offenses", + "Definitions of vulnerable individuals", + "Depiction of a child", + "Deportation offenses", + "Destruction of flag", + "Digital imprint", + "Disability classifications", + "Disability support animals", + "Disabled individual", + "Disabled individual abuse", + "Disciplinary use of force on minors", + "Disclosure of confidential information", + "Disorderly conduct offenses", + "Dissemination of personal information", + "Dog fighting offense", + "Dog-fighting equipment", + "Driver's license display requirements", + "Driving while intoxicated", + "Driving with child passenger intoxication", + "Drug dispensation and administration", + "Duress defense", + "Duty to report visual material", + "Economic benefit", + "Effect of Penal Code", + "Effective Dates", + "Effective consent", + "Effective consent criteria", + "Elderly age criteria", + "Elderly individual", + "Elderly individual definition", + "Elderly individual protection", + "Elderly protection", + "Election Code contributions", + "Election law", + "Electric generating plant", + "Electric utility", + "Electric utility substation", + "Electronic Transmission", + "Electronic communication", + "Electronic communication definition", + "Electronic communication definitions", + "Electronic communication offenses", + "Electronic communication threats", + "Electronic communications abuse", + "Electronic communications device misuse", + "Electronic communications service", + "Electronic data tampering", + "Electronic or mechanical tracking device", + "Elements of offense", + "Emergency call obstruction offense", + "Emergency medical services personnel", + "Emergency response offenses", + "Emergency service interference", + "Emergency services communication", + "Emergency services personnel", + "Emergency vehicle access", + "Employee Offense", + "Employee of a facility", + "Employment Harmful to Children", + "Employment harmful to children", + "Employment in child-care facilities", + "Encrypted private information", + "Encrypted signal decoding", + "Encryption service", + "Enhanced offenses", + "Enhanced penalties for elderly victims", + "Enticing a child", + "Escape definition", + "Escape implements", + "Escape offenses", + "Exceptions for licensed agents", + "Exclusions for travel discounts", + "Exemptions for accredited institutions", + "Exemptions for media dissemination", + "Expired card definition", + "Exploitation of emotional dependency", + "Exploitation of vulnerable individuals", + "Explosive and firearm classification", + "Exposure to bodily fluids", + "Extended power outage", + "Failure to appear", + "Failure to appear defenses", + "Failure to identify offense", + "Failure to notify and report deaths", + "Failure to provide death notice", + "Failure to report assault", + "Failure to report death of prisoner", + "Failure to report felony", + "False Statements", + "False advertising", + "False documents", + "False emergency reports", + "False entries in government documents", + "False identification as peace officer", + "False identification to peace officer", + "False or misleading statements", + "False report of missing child", + "False report to law enforcement", + "False reports", + "False reports to emergency services", + "False representation as lawyer", + "False statement offenses", + "Family violence", + "Family violence and trafficking shelter centers", + "Family violence offenses", + "Federal Communications Commission monitoring", + "Federal cigarette regulations", + "Federal firearm law enforcement", + "Federal law enforcement officer", + "Felony Classifications", + "Felony Conviction Firearm Prohibition", + "Felony Sentencing Guidelines", + "Felony and misdemeanor classifications", + "Felony and misdemeanor distinctions", + "Felony authorization in corporations", + "Felony barratry", + "Felony classification", + "Felony classification by benefit value", + "Felony classification by sale amount", + "Felony classification by value", + "Felony classifications", + "Felony classifications for trade secret theft", + "Felony convictions", + "Felony degree upgrades", + "Felony degrees and defenses", + "Felony fines", + "Felony fines and imprisonment", + "Felony firearm possession restrictions", + "Felony imprisonment terms", + "Felony offense classifications", + "Felony offenses", + "Felony offenses related to property damage", + "Felony offenses under Section 22.04", + "Felony reporting requirements", + "Felony sentencing guidelines", + "Fiduciary relationship breach", + "Financial abuse", + "Financial abuse of elderly", + "Financial exploitation", + "Financial instruments", + "Financing Statement", + "Financing barratry", + "Fines for business entities", + "Fire and explosion laws", + "Firearm", + "Firearm Sales Restrictions", + "Firearm accessibility to children", + "Firearm and stun gun definitions", + "Firearm definitions", + "Firearm discharge in municipalities", + "Firearm discharge regulations", + "Firearm possession after misdemeanor conviction", + "Firearm possession by armed forces", + "Firearm possession exceptions", + "Firearm possession offenses", + "Firearm regulation restrictions", + "Firearm smuggling", + "Firearm storage warning", + "Firearms registration", + "Firefighter definitions", + "Fireworks offenses and penalties", + "First Amendment speech protections", + "First degree felony punishment", + "First responder definition", + "First responder definitions", + "Fiscal agents", + "Flag definition and exceptions", + "Fleeing from law enforcement", + "Flying while intoxicated", + "Forced labor", + "Foreign country custody laws", + "Foreign government agents", + "Foreign government entities", + "Foreign terrorist organizations", + "Forgery definition", + "Forgery offenses", + "Fraud aggregation", + "Fraud and harm to victims", + "Fraudulent Filing", + "Fraudulent military records", + "Fraudulent transfer", + "Fugitive from justice penalties", + "Gambling Definitions", + "Gambling device restrictions", + "Gambling promotion offenses", + "Gang leadership activities", + "General Provisions", + "Gift card fraud", + "Gift card redemption information", + "Government Code Section 411.1884", + "Government Code reporting", + "Governmental function", + "Governmental functions", + "Governmental record definitions", + "Governmental records", + "Graffiti Offense", + "Gratuity regulations", + "Group home and care facility responsibilities", + "Group home regulations", + "Habitation and building", + "Handgun carry licenses", + "Handgun carry regulations", + "Handgun carrying laws", + "Handgun licensing law", + "Handgun possession defense", + "Handgun possession restrictions", + "Harassment in correctional facilities", + "Harassment of public servants", + "Harboring runaway child", + "Health and Safety Code", + "Health care and mental health providers", + "Health care claims", + "Health care fraud", + "Health care practitioners", + "Health care program offenses", + "Health care programs", + "Health care services provider roles", + "High managerial agent", + "High managerial agent liability", + "Hindering apprehension", + "Hindering secured creditors", + "Hoax bomb offenses", + "Homosexual conduct statute", + "Hospital personnel definitions", + "House Bills", + "Human Trafficking Offenses", + "Human fetal tissue legal definitions", + "Identifying information", + "Identity theft offense", + "Illegal divulgence of communications", + "Illegal employment solicitation methods", + "Illegal entry definitions", + "Illegal entry from foreign nation", + "Illegal reentry by certain aliens", + "Immediate life-threatening situation", + "Immigration detainer compliance", + "Immigration law enforcement", + "Immunity from prosecution", + "Impersonating public servant", + "Imprisonment for promoting child sexual material", + "Improper contact with victim", + "Improper influence", + "Improper relationships in schools", + "Improper sexual activity with committed person", + "Improvised explosive device", + "Incest laws", + "Inconsistent statements in perjury", + "Indecency with a child", + "Indecent Assault", + "Indecent assault", + "Indecent exposure offense", + "Injury or death during apprehension", + "Injury or death during flight", + "Injury to vulnerable individuals", + "Institution of higher education", + "Insufficient funds knowledge presumption", + "Insurance claim restitution", + "Insurance fraud offense", + "Insurance policy definitions", + "Intent to avoid payment", + "Intent to defraud", + "Intent to defraud or harm", + "Intent to harm or defraud", + "Interactive computer service", + "Interception of communications", + "Interference with emergency calls", + "Interference with government radio frequencies", + "Interference with guardian rights", + "Internet definition", + "Interstate firearm purchase", + "Intimate area photography", + "Intimate visual material", + "Intoxicated operation", + "Intoxication assault", + "Intoxication defense", + "Intoxication definitions", + "Intoxication manslaughter", + "Invasion of privacy", + "Invasive visual recording", + "Investigation and reporting of inmate deaths", + "Investigational stem cell treatment", + "Issuance of bad checks", + "Judicial proceedings", + "Jurisdiction recognition", + "Jury Unanimity", + "Jury unanimity", + "Jury unanimity in criminal cases", + "Jury unanimity in prosecution", + "Jury unanimity requirements", + "Justification for use of force", + "Justification of conduct", + "Juvenile detention facilities", + "Juvenile facility operations", + "Juvenile probation", + "Keeping a gambling place", + "Kidnapping definitions", + "Kidnapping intent", + "Kidnapping offense", + "Law enforcement", + "Law enforcement communication", + "Law enforcement duties", + "Law enforcement officer protection", + "Law enforcement orders", + "Law enforcement reporting", + "Law enforcement responsibilities", + "Law enforcement use of drones", + "Lawful arrest", + "Lease agreements", + "Leaving child in vehicle", + "Legal Amendments", + "Legal Definitions", + "Legal amendments", + "Legal amendments and effective dates", + "Legal amendments and statutes", + "Legal amendments to penal code", + "Legal amendments to video service device laws", + "Legal authority in property transactions", + "Legal authorization", + "Legal communication exceptions", + "Legal defenses", + "Legal defenses and penalties", + "Legal defenses and prosecution", + "Legal defenses for animal cruelty", + "Legal defenses for cockfighting", + "Legal defenses for gambling prosecution", + "Legal defenses for obscenity", + "Legal defenses for offenses", + "Legal defenses for online conduct", + "Legal defenses for pen register use", + "Legal defenses for riot", + "Legal defenses for unmanned aircraft operation", + "Legal defenses for weapon possession", + "Legal defenses in prosecution", + "Legal definitions", + "Legal definitions and amendments", + "Legal definitions and consent limitations", + "Legal definitions and offenses", + "Legal definitions in penal code", + "Legal definitions of child and firearm", + "Legal definitions of custody", + "Legal definitions of exploitation", + "Legal definitions of forgery", + "Legal definitions of nudity and toplessness", + "Legal definitions of vulnerable individuals", + "Legal document types", + "Legal documentation", + "Legal duties and offenses", + "Legal duties and omissions", + "Legal effective dates", + "Legal exceptions", + "Legal exceptions for gifts", + "Legal exceptions for organ donation", + "Legal exceptions for record destruction", + "Legal obligations", + "Legal offense criteria", + "Legal offenses", + "Legal offenses involving minors", + "Legal penalties", + "Legal penalties and amendments", + "Legal penalties and sentencing", + "Legal penalties for misuse of 9-1-1", + "Legal presumption of intent", + "Legal presumptions in defense", + "Legal provisions on animal treatment", + "Legal responsibility for care", + "Legal services exemptions", + "Legal standards for perjury", + "Legal statutes", + "Legislative Acts", + "Legislative Acts Amendments", + "Legislative Sessions", + "Legislative act history", + "Legislative acts", + "Legislative acts amendments", + "Legislative acts and effective dates", + "Legislative acts and statutes", + "Legislative amendments", + "Legislative amendments and effective dates", + "Legislative amendments to emergency call laws", + "Legislative amendments to insurance law", + "Legislative and executive gifts", + "License holder definition", + "License holder exceptions", + "License holder offenses", + "Life imprisonment criteria", + "Life imprisonment sentencing", + "Limitations on autonomous drone force", + "Limited liability company offenses", + "Local Government Code Chapter 179", + "Lottery promotion and sales", + "Mail theft definitions", + "Manslaughter", + "Manufacture and distribution of decoding devices", + "Marking of property boundaries", + "Marriage dissolution", + "Mass shooting", + "Massage therapy regulations", + "Material support or resources", + "Materiality in insurance statements", + "Materiality in perjury", + "Mechanical security devices", + "Medicaid program", + "Medical procedure exemptions", + "Mental health services provider definitions", + "Mental illness and chemical dependency commitments", + "Metal recycling regulations", + "Military installations", + "Minor definition", + "Minors", + "Misapplication of fiduciary property", + "Misdemeanor and felony classifications", + "Misdemeanor and felony distinctions", + "Misdemeanor and felony offenses", + "Misdemeanor and felony penalties", + "Misdemeanor categories", + "Misdemeanor classifications", + "Misdemeanor offenses", + "Misdemeanor penalties", + "Misdemeanor punishments", + "Mislabeling and adulteration", + "Misrepresenting child as family member", + "Mistake of law", + "Misuse of government property", + "Misuse of official information", + "Money laundering", + "Money laundering definitions", + "Motor vehicle", + "Motor vehicle insurance documentation", + "Motor vehicle theft", + "Multichannel video device offenses", + "Multichannel video services", + "Multiple offenses against minors", + "Murder during felony", + "Murder for remuneration", + "Murder in penal institution", + "Necessity defense", + "Neglect and omission of care", + "Notice requirements for bad checks", + "Nursing home loss offenses", + "Objectives of Code", + "Obscene devices", + "Obscenity definition", + "Obscenity definitions", + "Obscenity laws", + "Obstructing governmental operation", + "Obstructing passageway offense", + "Obstruction of public passage", + "Obstruction or retaliation offenses", + "Occupations Code", + "Occupations Code definitions", + "Occupations Code licensing", + "Ocean-going vessel gambling rules", + "Offense Definition", + "Offense against child", + "Offense and Penalties", + "Offense and misdemeanor", + "Offense by institution superintendent", + "Offense categorization", + "Offense causing death", + "Offense classification", + "Offense classifications", + "Offense classifications and fines", + "Offense criteria and penalties", + "Offense definitions", + "Offense elements and defenses", + "Offense grading and punishment", + "Offense intent and conduct", + "Offense location-based punishment", + "Offense locations", + "Offense prosecution", + "Offense punishment guidelines", + "Offense sentencing", + "Offense-related fines", + "Offenses against disabled individuals", + "Offenses against property", + "Offenses against public servants", + "Offenses against service animals", + "Offenses against the family", + "Offenses and penalties for fetal tissue transactions", + "Offenses by correctional employees", + "Offenses involving minors and harmful material", + "Offenses involving railroad property", + "Offenses related to communication interception", + "Offenses related to controlled substances", + "Offenses related to correctional facility contraband", + "Offenses related to providing wireless devices to inmates", + "Official duty retaliation", + "Official oppression by public servant", + "Official proceeding", + "Oil and gas equipment theft penalties", + "Online impersonation", + "Online promotion of prostitution", + "Online solicitation of minors", + "Open container in vehicle", + "Openly carried handgun", + "Operating aircraft while intoxicated", + "Operating amusement ride intoxication", + "Operating motor vehicle while intoxicated", + "Operating or assembling amusement ride while intoxicated", + "Operating watercraft while intoxicated", + "Operation of unmanned aircraft", + "Organized Criminal Activity", + "Organized crime definitions", + "Packers and Stockyards Act compliance", + "Parent-child special relationship", + "Parks and Wildlife Department employee", + "Parole officers weapon policies", + "Pattern of racketeering", + "Payment demand and refusal", + "Peace officer arrest restrictions", + "Peace officer definitions", + "Peace officer duties", + "Peace officer firearm exceptions", + "Peace officer identification", + "Peace officer murder", + "Peace officers weapon carry", + "Pecuniary Loss", + "Pecuniary Loss Classification", + "Pecuniary gain offenses", + "Pecuniary loss", + "Pecuniary loss aggregation", + "Pecuniary loss classifications", + "Pecuniary value", + "Pen register and trap and trace device use", + "Penal Code", + "Penal Code Amendments", + "Penal Code Provisions", + "Penal Code Section 30.06", + "Penal Code sections", + "Penal code", + "Penal code amendments", + "Penal code offenses", + "Penal code provisions", + "Penal code provisions on computer-generated child sexual conduct", + "Penal code sections", + "Penalties and classifications", + "Penalties based on pecuniary loss", + "Penalties for Felony Offenders", + "Penalties for child sexual exploitation", + "Penalties for distribution to minors", + "Penalties for firearm accessibility offenses", + "Penalties for fleeing", + "Penalties for intoxicated operation", + "Penalties for organ trade offenses", + "Penalties for use of metal or body armor", + "Performance and promotion", + "Perjury", + "Perjury and falsification definitions", + "Perjury offenses", + "Permitting or facilitating escape", + "Personal protection officer duties", + "Petroleum product definitions", + "Petroleum product theft penalties", + "Plea agreements in criminal cases", + "Police service animals", + "Political contributions", + "Port of entry offenses", + "Possession of alcoholic beverages in correctional facilities", + "Possession of obscene devices", + "Possession of tobacco products in jail", + "Preemption Clause", + "Premises and vehicle definitions", + "Presumption of interference with peace officer", + "Preventing execution of civil process", + "Price difference thresholds", + "Prior convictions impact", + "Privacy expectations in changing rooms", + "Privacy of involved persons", + "Privacy offense penalties", + "Privacy rights", + "Privileged information", + "Prize disclosure requirements", + "Proceeds of criminal activity", + "Production and promotion of sexual performances", + "Professional conduct exceptions", + "Professional licensing and certification", + "Prohibited Relationships", + "Prohibited camping", + "Prohibited cockfighting activities", + "Prohibited items in correctional facilities", + "Prohibited sexual conduct", + "Prohibited weapons", + "Prohibition of cellular phones in correctional and civil commitment facilities", + "Prohibition of sexually oriented performances", + "Prohibition on human organ trade", + "Prohibition on purchase and sale", + "Prohibition on purchase and sale of human fetal tissue", + "Promotion of obscene material", + "Promotion of prostitution", + "Proof of lawful immigration status", + "Property Code exceptions", + "Property Offenses", + "Property custody and retrieval", + "Property damage", + "Property damage thresholds", + "Property definition", + "Property deprivation", + "Property dispositions", + "Property misapplication penalties", + "Property owner notice", + "Property recovery rights", + "Property signage requirements", + "Property trespass prevention", + "Property value deduction", + "Property value thresholds", + "Prosecution and sentencing", + "Prosecution conditions", + "Prosecution defenses", + "Prosecution jurisdiction", + "Prosecution jurisdiction and authority", + "Prosecution limitations", + "Prosecution obstruction", + "Prosecution of insurance offenses", + "Prosecution under Alcoholic Beverage Code", + "Prosecution under multiple laws", + "Prosecution under multiple sections", + "Prostitution enterprise management", + "Prostitution involving minors", + "Prostitution offense", + "Prostitution offenses", + "Prostitution proceeds", + "Protected Mark Registration", + "Protected areas", + "Protected conduct", + "Protected locations", + "Protected property types", + "Protection of property", + "Protection of public servants", + "Protective order violation", + "Protective order violations", + "Protective orders", + "Providing false information to peace officer", + "Proximity to schools", + "Public Safety", + "Public duty", + "Public education reporting", + "Public exposure to minors", + "Public health regulations", + "Public intoxication offense", + "Public lewdness laws", + "Public media definition", + "Public officials", + "Public place camping regulations", + "Public place definition", + "Public school record tampering", + "Public servant benefits", + "Public servant discretion", + "Public servant gift regulations", + "Public servant misconduct", + "Public servant offenses", + "Public utility interference", + "Punishment enhancement criteria", + "Punishment grading", + "Punishment provisions", + "Qualified nonprofit organization", + "Racketeering definitions", + "Radio communication offenses", + "Radio communication privacy", + "Radio frequency licensing", + "Railroad Commission of Texas regulations", + "Railroad property definitions", + "Railroad signaling devices", + "Railway Labor Act", + "Ransomware definition", + "Ransomware software exceptions", + "Real property fraud", + "Real property interest", + "Rebuttable presumption in insurance claims", + "Reckless Damage or Destruction", + "Reckless driving offenses", + "Recklessness and danger presumption", + "Refusal of payment", + "Refusal to comply with return order", + "Regulations on research and disposal of fetal tissue", + "Removal orders", + "Rental property control", + "Renunciation defense", + "Repeal of legal sections", + "Repeat felony offender penalties", + "Repeat intoxication offenses", + "Repeat offender sentencing", + "Repeat offense convictions", + "Repeat offense penalties", + "Repeat offenses", + "Repeated offenses", + "Repeated telephone communications", + "Repeated violation of court orders", + "Reportable Conviction", + "Reporting and investigation procedures", + "Reporting death of institution resident", + "Residence address", + "Resisting arrest or search", + "Restitution for animal injury", + "Restraint of persons", + "Retail Value of Counterfeit Goods", + "Retail merchandise", + "Retail theft detector", + "Retail water utility service", + "Retired law enforcement officers", + "Retraction of false statements", + "Revenue Code of 1986", + "Riot definition", + "Riot offense classification", + "Riot offense elements", + "Robbery definitions", + "Robbery offense", + "Sale and lease of video service devices", + "Sale of harmful material to minors", + "Sales contest regulations", + "Sales contests", + "Satellite receiving antennas", + "Savings account deposits", + "School crossing zone offenses", + "School library", + "School-related events", + "Second degree felony punishment", + "Section 22.011 and 22.021 offenses", + "Section 25.072", + "Secured airport area", + "Security Officer Registration", + "Security agreements", + "Security interest", + "Security officer commission", + "Security officer firearm exceptions", + "Security officer roles", + "Self-defense law", + "Senate Bills", + "Sentencing guidelines", + "September 1, 2023", + "Serious bodily and mental injury", + "Serious bodily injury", + "Serious bodily injury to officials", + "Service agreements", + "Service definition", + "Sex offender registration", + "Sexual Assault Statute", + "Sexual Offenses", + "Sexual Performance by a Child", + "Sexual abuse offenses", + "Sexual abuse statutes", + "Sexual assault definitions", + "Sexual coercion", + "Sexual conduct", + "Sexual conduct definitions", + "Sexual conduct offenses", + "Sexual conduct solicitation", + "Sexual contact and intercourse", + "Sexual contact offenses", + "Sexual misconduct", + "Sexual misconduct by school employees", + "Sexual offense convictions", + "Sexual offenses", + "Sexual offenses in custody", + "Sexual offenses involving minors", + "Sexual performance by a child", + "Sexually explicit communication", + "Sexually explicit media distribution", + "Sexually explicit visual material", + "Sexually oriented commercial activity", + "Sexually oriented performance", + "Sexually violent predator commitment", + "Shelter definitions", + "Shielding or deactivation instrument", + "Signal encryption", + "Silent or abusive 9-1-1 calls", + "Simulating legal process", + "Smuggling of persons", + "Social networking sites", + "Solicitation of business", + "Solicitation of professional employment", + "Solicitation of prostitution", + "Spaceports", + "Specific felony offenses and punishments", + "Sports participant protections", + "Spousal offense", + "Stalking offense", + "State Bar disciplinary rules", + "State Jail Felony", + "State and federal law interaction", + "State jail felony", + "State jail felony criteria", + "State jail felony offense", + "State jail felony offenses", + "State jail felony punishment", + "State lottery regulations", + "State property offenses", + "Statute Amendments", + "Statute Text", + "Statutory Amendments", + "Statutory Effective Dates", + "Statutory Law", + "Statutory amendments", + "Statutory effective dates", + "Statutory history", + "Storage and release fees", + "Sudden passion definition", + "Superfund site designation", + "Taking weapon from law enforcement", + "Tampering with consumer product", + "Tampering with governmental records", + "Tampering with physical evidence", + "Tampering with railroad property", + "Tampering with witness", + "Tax Code cigarette stamps", + "Telecommunication access device", + "Telecommunication surveillance", + "Telecommunications access device", + "Telecommunications crimes", + "Telecommunications device", + "Telecommunications investigation", + "Telecommunications service", + "Telecommunications service provider", + "Telecommunications service provider offenses", + "Telephone number", + "Temporary insanity", + "Tenant handgun defenses", + "Territorial Jurisdiction", + "Terrorism definitions", + "Testimony corroboration", + "Texas Civil Commitment Office", + "Texas Department of Criminal Justice", + "Texas Department of Criminal Justice regulations", + "Texas Department of Criminal Justice sentencing", + "Texas Department of Insurance regulations", + "Texas Department of Public Safety", + "Texas Department of Transportation regulations", + "Texas Education Agency data integrity", + "Texas Juvenile Justice Department", + "Texas Legislature", + "Texas Parks and Wildlife Code", + "Texas Penal Code", + "Texas Penal Code Section 20A.02", + "Texas Penal Code Section 21.11", + "Texas Penal Code Sections 46.02 and 46.03", + "Texas Penal Code amendments", + "Texas Penal Code sections", + "Texas Private Security Board", + "Texas criminal justice sentencing", + "Texas criminal justice system", + "Texas criminal law", + "Texas criminal procedure articles", + "Texas criminal statutes", + "Texas gambling laws", + "Texas intoxication offense statutes", + "Texas law enforcement policies", + "Texas legislation", + "Texas legislative amendments", + "Texas penal code amendments", + "Theft Definition", + "Theft Offenses Consolidation", + "Theft and stealing", + "Theft and tampering with multichannel video services", + "Theft by check presumption", + "Theft conduct", + "Theft definition", + "Theft definitions", + "Theft of oil and gas equipment", + "Theft of petroleum products", + "Theft of service", + "Theft of trade secrets", + "Therapeutic use defense", + "Third degree felony punishment", + "Threatening communication", + "Threats and extortion", + "Threats and harassment", + "Threats and offensive contact", + "Tire deflation device", + "Tobacco and e-cigarette use", + "Touching of students", + "Trade secret definition", + "Trade secret theft offenses", + "Trademark Counterfeiting", + "Trafficking of persons", + "Transnational repression", + "Transportation and lodging exceptions", + "Transportation communications equipment", + "Trap and trace device", + "Trap-Neuter-Return Program", + "Trap-Neuter-Return program", + "Traumatic brain injury", + "Trespass by license holder", + "Trespass prevention", + "Trespassing penalties", + "Types of forged writings", + "Unauthorized acquisition of financial information", + "Unauthorized asset transfer", + "Unauthorized possession", + "Unauthorized practice of law", + "Unauthorized recording", + "Unauthorized tracking and monitoring", + "Unauthorized use of telecommunications service", + "Unauthorized use of vehicle", + "Unborn child conduct exceptions", + "Unborn child death exceptions", + "Unlawful Appropriation", + "Unlawful Weapon Transfer", + "Unlawful access to stored communications", + "Unlawful appropriation", + "Unlawful carrying of weapons", + "Unlawful custody", + "Unlawful deep fake media", + "Unlawful disclosure", + "Unlawful electronic transmission", + "Unlawful installation of tracking device", + "Unlawful possession of body armor", + "Unlawful possession of firearm", + "Unlawful restraint", + "Unlawful use of devices", + "Unlawful use of fireworks", + "Unlawful use of pen register", + "Unmanned teller machine", + "Unsolicited benefit donation", + "Use of collision report information", + "Use of deadly force", + "Use of deadly weapon", + "Use of firearms and drones", + "Use of force", + "Use of force against peace officer", + "Use of force and legal defense", + "Use of force criteria", + "Use of force in correctional facilities", + "Use of laser pointers", + "Use of less-lethal force weapons", + "Use of tire deflation device", + "Use of vehicle or watercraft in flight", + "Utilities Code definitions", + "Utility definitions", + "Utility types and classifications", + "Utility worker protections", + "Value determination", + "Value of insurance claims", + "Value of telecommunications service", + "Value thresholds for offenses", + "Value-based offense classification", + "Value-based offense grading", + "Vehicle definition", + "Vehicle possession transfer", + "Vehicle-related offenses", + "Veterinarian definition", + "Victim Age", + "Victim Protection and Charges", + "Victim age categories", + "Victim protection", + "Victim relationship", + "Victim-related charge limitations", + "Violation of court orders", + "Violation of protective orders", + "Violent crimes", + "Visual Material", + "Visual image transmission", + "Volunteer emergency services personnel", + "Voyeurism offense", + "Weapon offense classifications", + "Weapons definitions", + "Wholesale distributor of prescription drugs", + "Written communication requirements", + "Youth service centers", + "Zip gun definition", + "abduction agreements", + "abuse of official capacity", + "academic product definition", + "academic product preparation offense", + "access software provider", + "accountant-client confidentiality", + "acquisition of property interest", + "admission of unadjudicated offense", + "admitted offenses", + "adopted child definition", + "adoption regulations", + "advertising for child placement", + "affirmative defense", + "affirmative defense criteria", + "affirmative defense in prosecution", + "affirmative defense to gambling prosecution", + "affirmative defenses", + "affirmative defenses in sexual abuse cases", + "age determination methods", + "age of victim", + "age-related offense definitions", + "age-related prosecution limits", + "aggravating factors in sexual crimes", + "aggravating factors in smuggling", + "aggregate amount thresholds", + "aiding terrorism", + "altered gambling equipment", + "ammunition types", + "amusement devices", + "animal abandonment", + "animal abuse", + "animal control and code enforcement officers", + "animal control authority", + "animal cruelty offenses", + "animal husbandry practices", + "animal injury and killing defenses", + "animal neglect", + "animal transportation abuse", + "appeal procedures", + "armed forces and security personnel", + "armor-piercing ammunition", + "arrest and search", + "artificial intelligence application", + "artificial intelligence depictions", + "artificial intelligence generated images", + "artificially generated media", + "assault on public servants", + "attorney general assistance", + "attorney general jurisdiction", + "attorney general powers", + "attorney involvement", + "authorization for airport access", + "authorized vendor liability", + "benefit solicitation", + "benefit solicitation offenses", + "betting regulations", + "bigamy laws", + "bodily injury", + "bodily injury or death", + "bona fide contests", + "bookmaking", + "burden of proof", + "burglary of vehicles", + "burglary offenses", + "business entity offenses", + "camping prohibition regulations", + "capital felony", + "capital felony prosecution", + "card revocation notice", + "caregiver legal responsibilities", + "cargo theft", + "cargo theft offenses", + "carrying handgun while intoxicated", + "catalytic converter possession laws", + "catalytic converter theft", + "causation in criminal law", + "cemetery regulations", + "certificate of authority and title requirements", + "check issuance", + "chemical and explosive devices", + "chemical dependency", + "chemical dispensing device", + "child abandonment", + "child conduct law", + "child custody and guardianship", + "child custody offenses", + "child custody violations", + "child depiction laws", + "child exploitation", + "child exploitation offenses", + "child grooming offense", + "child labor laws", + "child mental illness", + "child placement exceptions", + "child pornography depiction", + "child pornography laws", + "child sexual abuse", + "child sexual abuse material", + "child sexual conduct offenses", + "child sexual exploitation", + "child sexual offense", + "child smuggling offense", + "child-like sex doll promotion", + "child-placing agency standards", + "cigarette restrictions", + "civil commitment facility offenses", + "civil commitment facility services", + "civil liability protection", + "civil rights violations", + "class C misdemeanor", + "classification of offenses", + "code reference conventions", + "coercion and force", + "coercion definitions", + "coercion of public servant", + "cohabitation and marriage", + "coin-operated machine offenses", + "commercial shipment regulations", + "communicating gambling information", + "communication carrier protections", + "communication common carrier", + "community corrections facility offenses", + "community supervision discharge", + "compelling prostitution", + "complaint and legal action procedures", + "complaint dismissal procedure", + "complicity", + "components of explosives", + "compulsion defense", + "computation of age", + "computer crime", + "computer crime defenses", + "computer fraud", + "computer property definition", + "computer security breach", + "computer security offenses", + "computer-generated child depictions", + "computer-generated child sexual conduct", + "computer-generated child sexual images", + "computer-related election offenses", + "concurrent and consecutive sentences", + "concurrent jurisdiction", + "concurrent sentences", + "condominium ownership rights", + "condominium property", + "conduct of another", + "confidential certification", + "confinement", + "conflict of law", + "consecutive sentences", + "consent for interception", + "consolidation of prosecutions", + "conspiracy liability", + "construction of penal code", + "contingent fee contracts", + "continuous sexual abuse", + "contraband definition", + "contraband in correctional facility", + "contraband introduction", + "contracts of indemnity", + "controlled substance exposure", + "controlled substance violations", + "conviction and penalty enhancement rules", + "conviction enhancement", + "conviction similarity", + "copper or brass theft", + "corporate liability", + "correctional and detention facilities", + "correctional facilities", + "correctional facility", + "correctional facility offenses", + "correctional facility regulations", + "correctional facility security", + "counterfeit credit and debit cards", + "counterfeit gift cards", + "counterfeit telecommunications device", + "counterfeiting offenses", + "court custody orders", + "court discretion on sentencing", + "court fines and penalties", + "court jurisdiction", + "court order violation", + "court orders and compliance", + "court orders for information gathering", + "court-ordered restitution", + "credit and property fraud", + "credit card data theft", + "credit card definitions", + "credit card fraud", + "credit card fraud offenses", + "credit card transaction laundering", + "credit transaction records", + "criminal activity exceptions", + "criminal attempt", + "criminal charges", + "criminal classification", + "criminal classifications", + "criminal combination criteria", + "criminal conspiracy", + "criminal episode definition", + "criminal homicide definitions", + "criminal intent", + "criminal law procedures", + "criminal mischief", + "criminal negligence", + "criminal negligence offenses", + "criminal objective", + "criminal offense categories", + "criminal offense classification", + "criminal offense classifications", + "criminal offense definitions", + "criminal offense knowledge", + "criminal offenses", + "criminal offenses and defenses", + "criminal penalties", + "criminal penalties for document fraud", + "criminal penalties for fraud", + "criminal penalties for gift card offenses", + "criminal penalties for offenses", + "criminal penalties for recruitment", + "criminal penalties for solicitation", + "criminal procedure amendments", + "criminal proceeds", + "criminal prosecution", + "criminal prosecution for bad checks", + "criminal prosecution limitations", + "criminal responsibility", + "criminal responsibility age", + "criminal responsibility defenses", + "criminal responsibility justification", + "criminal responsibility principles", + "criminal simulation", + "criminal street gang membership", + "criminal street gang offenses", + "criminal street gangs", + "criminal threats and violence", + "criminal trespass", + "criminal value thresholds", + "critical infrastructure", + "critical infrastructure damage", + "critical infrastructure facility", + "culpability requirement", + "culpable mental states", + "custody transfer offenses", + "cybercrime penalties", + "damage repair costs", + "dangerous wild animal", + "data theft", + "deadly force justification", + "deadly force limitations", + "deadly weapon in penal institution", + "deadly weapon use", + "debit card misuse", + "debit card violations", + "deceptive academic product marketing", + "decryption of privileged information", + "deduction of legal interest in stolen property", + "deep fake media", + "deep fake media consent", + "defendant capacity", + "defendant rights", + "defense in substance offenses", + "defense limitations", + "defense of third person", + "defense to firearm prosecution", + "defense to prosecution", + "defenses to academic misconduct", + "defenses to custody interference", + "defenses to prosecution", + "deferred adjudication", + "deferred adjudication community supervision", + "deferred adjudication impact", + "definition of correctional facility", + "definition of identifying information", + "definitions of livestock terms", + "definitions of writing", + "demand for payment notice", + "destruction of copper or brass components", + "detention facility", + "detention facility offenses", + "determining property or service value", + "device installation requirements", + "digital deception", + "digital gift card information", + "digital imprint fraud", + "disabled individual", + "disabled individual safety", + "disaster area offenses", + "discharge of firearm", + "disrupting meetings", + "driving while intoxicated", + "drug offenses", + "due diligence defense", + "education code", + "effective dates", + "elderly and disabled abandonment", + "elderly and disabled victims", + "elderly individual protection", + "elderly individual protections", + "elderly victim protection", + "electrical power facilities", + "electronic access interference", + "electronic communication privacy", + "emergency evacuation order", + "emergency evacuation orders", + "emergency infant care", + "emergency medical care", + "emergency report offenses", + "emergency services", + "emergency services personnel", + "emergency shelter during disaster", + "emergency shelter firearm rules", + "employment agency duties", + "employment and service fraud", + "employment restrictions for firearm possession", + "enforcement of federal firearm laws", + "enhanced penalties for vulnerable victims", + "enterprise control", + "entrapment", + "entry and notice definitions", + "equipment and communication wires", + "evidence admissibility", + "evidence tampering", + "exceptional sentences", + "exceptions for lawful conduct", + "exceptions to child sale prohibition", + "exceptions to offenses", + "exceptions to prosecution", + "explosive weapon definition", + "facility impairment or interruption", + "false alarm laws", + "false caller identification", + "false claims", + "false statement offenses", + "false statements", + "false statements in health care", + "family and dating relationships", + "family relationship defense", + "family violence", + "family violence exceptions", + "family violence provisions", + "fear and intimidation", + "felony and misdemeanor classifications", + "felony categorization", + "felony classification", + "felony classification by pecuniary gain", + "felony classification by property value", + "felony classification for voting offenses", + "felony classifications", + "felony convictions", + "felony degree enhancements", + "felony degrees", + "felony offense convictions", + "felony offenses", + "felony penalties", + "felony punishment", + "felony sentencing", + "felony theft thresholds", + "fiduciary offenses", + "financial abuse", + "financial abuse of elderly", + "financial information storage", + "financial institution liability", + "financing criminal activity", + "fire exit alarm deactivation", + "firearm confiscation program", + "firearm definitions", + "firearm discharge regulations", + "firearm possession", + "firearm possession defenses", + "firearm possession exemptions", + "firearm possession legal defenses", + "firearm possession offenses", + "firearm possession on property", + "firearm possession regulations", + "firearm possession restrictions", + "firearm prohibition signage", + "firearm regulations", + "firearm sale requirements", + "firearm transfer regulations", + "firearms and weapons offenses", + "fireworks definitions", + "first responder certification", + "first responder firearm exemptions", + "first responder handgun carry", + "foreign government agents", + "fraud and harm", + "fraud definitions", + "fraudulent benefit", + "fraudulent claims thresholds", + "fraudulent degree", + "fraudulent destruction of writing", + "fraudulent document execution", + "fraudulent health care claims", + "fraudulent lien claims", + "fraudulent lien or claim", + "fraudulent military record use", + "fraudulent use of cards", + "fraudulent use of identification", + "fraudulent use of identifying information", + "funeral service disruptions", + "funeral service regulations", + "gambling device", + "gambling offense classifications", + "gambling offenses", + "gambling paraphernalia", + "gambling place", + "gang-free zones", + "gang-free zones mapping", + "gas processing plant", + "gift card fraud", + "global positioning monitoring system tampering", + "government record fraud", + "government service contracts", + "governmental meeting locations", + "governmental record offenses", + "handgun carry defense", + "handgun carry restrictions", + "handgun display regulations", + "handgun license exceptions", + "handgun possession", + "handgun possession defenses", + "handgun possession during disaster", + "handgun possession restrictions", + "handgun visibility", + "handler control violations", + "health and safety code violations", + "health care fraud", + "health care fraud offenses", + "health care fraud penalties", + "health care program fraud", + "health care program participation", + "health care provider definitions", + "health care provider misconduct", + "high hazard dam", + "hindering official proceedings", + "hindering prosecution", + "historical reenactment regulations", + "hoax bomb definition", + "holster", + "hospital personnel", + "hotel firearm policies", + "hotel guest defenses", + "hotel guest handgun regulations", + "human corpse concealment", + "human smuggling offenses", + "human trafficking", + "human trafficking prevention services", + "identifiable child definition", + "identification number definition", + "ignition interlock device", + "illegal recruitment of athletes", + "illuminating aircraft with laser", + "imprisonment terms", + "improper educator-student relationship", + "in loco parentis definition", + "incapacity to consent", + "informant definition", + "injury and killing of police animals", + "insanity defense", + "institution of higher education", + "insufficient funds", + "insufficient funds check offense", + "insurance claim aggregation", + "insurance claims", + "insurance fraud", + "insurance fraud definitions", + "insurance fraud prosecution", + "intellectual property registration", + "intent to cause fear or disruption", + "intent to defraud", + "intent to defraud or harm", + "intent to deprive merchant", + "intent to facilitate crime", + "intentional conduct", + "interception device defense", + "interception device use", + "interception devices", + "interception of communications", + "intercollegiate athletics regulations", + "interference with child custody", + "interference with public duties", + "internet service provider liability", + "intimate visual material", + "intoxicated driving offenses", + "intoxication and consent", + "joinder of offenses", + "judicial and administrative corruption", + "judicial officers", + "jugging offense", + "jurisdiction based on death", + "jury instructions", + "jury unanimity", + "jury unanimity requirements", + "justifiable force", + "justification for deadly force", + "justification for force", + "juvenile adjudication as felony", + "juvenile court jurisdiction", + "juvenile offense exceptions", + "knife regulations", + "laser pointer offenses", + "law enforcement", + "law enforcement access", + "law enforcement agents", + "law enforcement authorization", + "law enforcement communications", + "law enforcement deception defenses", + "law enforcement defense", + "law enforcement duties", + "law enforcement insignia", + "law enforcement inspection", + "law enforcement interception", + "law enforcement officer definition", + "law enforcement officers", + "law enforcement orders", + "law enforcement pension", + "law enforcement procedures", + "law enforcement tactics", + "lawful arrest", + "lawful criminal investigation", + "lawful hunting and agriculture practices", + "lawful interception conditions", + "leasing personal property", + "legal advice restrictions", + "legal amendments", + "legal amendments and effective dates", + "legal amendments and statutes", + "legal amendments to gambling laws", + "legal consequences", + "legal defenses", + "legal defenses and exceptions", + "legal defenses for license holders", + "legal defenses in bigamy", + "legal defenses in prosecution", + "legal defenses in telecommunications offenses", + "legal definitions", + "legal definitions in election code", + "legal definitions in family violence context", + "legal definitions of assault", + "legal definitions of consent", + "legal definitions of entry", + "legal definitions of human corpse", + "legal definitions of laser pointer", + "legal definitions of offenses", + "legal duty to protect property", + "legal exceptions", + "legal exemptions", + "legal expenses recovery", + "legal interest deduction", + "legal justification for force", + "legal justification for non-deadly force", + "legal offenses", + "legal penalties", + "legal penalties for animal harm", + "legal penalties for deep fakes", + "legal penalties for visual material", + "legal presumption", + "legal presumptions in gift card crimes", + "legal presumptions of knowledge", + "legal presumptions of notice", + "legal procedures", + "legal process simulation", + "legal prosecution guidelines", + "legal prosecution standards", + "legal restitution", + "legal solicitation restrictions", + "legal standards for force", + "legal statutes", + "legal statutes and penalties", + "legislative amendments", + "legislative and executive misconduct", + "lesser included offense", + "license holder", + "license holder offenses", + "license plate removal and inventory", + "licensed child-placing agencies", + "licensed handgun carriers", + "licensing violations", + "livestock animal cruelty", + "local government employment", + "location-restricted knife offenses", + "lottery definitions", + "machine gun description", + "mail appropriation offense", + "mail appropriation offenses", + "managed care organization violations", + "manufactured home handgun rules", + "manufactured home regulations", + "maps as evidence", + "marriage eligibility", + "mental disease or defect", + "mental state proof requirements", + "metering device interference", + "methods of sending notice", + "military record misrepresentation", + "minimum imprisonment terms", + "minor definition", + "minor depiction laws", + "misdemeanor and felony classifications", + "misdemeanor and felony degrees", + "misdemeanor and felony grading", + "misdemeanor and felony offenses", + "misdemeanor and felony penalties", + "misdemeanor classification", + "misdemeanor classifications", + "misdemeanor classifications and penalties", + "misdemeanor offender penalties", + "misdemeanor offense", + "misdemeanor offenses", + "misdemeanor penalties", + "misdemeanor punishment", + "misleading information", + "misleading legal claims", + "misrepresentation in health care", + "misrepresentation of law enforcement property", + "mistake of fact defense", + "misuse of false records", + "misuse of government property", + "mitigation of punishment", + "monetary benefit valuation", + "mortgage loan appraisal fraud", + "mortgage loan offense investigation", + "motor vehicle", + "motor vehicle and watercraft", + "motor vehicle dismantling records", + "motor vehicle felony classification", + "motor vehicle offenses", + "motor vehicle salvage rules", + "multiple prosecutions", + "municipal engineer responsibilities", + "municipal ordinances", + "murder of minors", + "murder statute", + "natural gas facilities", + "negotiable instruments", + "negotiable instruments theft", + "new trial procedures", + "noise from space flight activities", + "non-consensual disclosure", + "non-consensual penetration", + "non-consensual sexual acts", + "nonconsensual interception", + "noncustodial parent offenses", + "notice demanding return", + "notice requirements", + "notice requirements for weapons", + "obscene material offenses", + "obstruction of interception", + "obstruction of investigation", + "offense against public servant", + "offense charges", + "offense classification", + "offense classification by retail value", + "offense classification by value", + "offense classifications", + "offense conduct", + "offense definitions", + "offense location and jurisdiction", + "offense location-based punishment enhancement", + "offense of soliciting confidential information", + "offense punishment enhancements", + "offense sentencing enhancements", + "offense severity", + "offense severity enhancements", + "offenses against elderly individuals", + "official duties exemptions", + "official duty retaliation", + "oil and gas facilities", + "online promotion offenses", + "open container offense", + "organized criminal activity", + "organized retail theft", + "parties to offenses", + "payment and return obligations", + "payment card data transfer", + "payment refusal", + "peace officer authority", + "peace officer citation procedures", + "peace officer device installation", + "peace officer exceptions", + "peace officer protection", + "peaceful assembly", + "pecuniary loss", + "pecuniary loss thresholds", + "penal code", + "penal code amendments", + "penal code amendments and effective dates", + "penal code penalties", + "penal code provisions", + "penalties and classifications", + "penalties for bias-motivated offenses", + "penalties for bigamy", + "penalties for card-related crimes", + "penalties for counterfeiting", + "penalties for drone violations", + "penalties for laser misuse", + "penalties for smoking", + "penalties for smuggling", + "penalty enhancement", + "penalty for retaliation", + "personal injury communication", + "personal property preservation", + "personnel and transportation assets", + "pesticide transaction documentation", + "picketing restrictions", + "plea agreement", + "plea agreements", + "police service animal offenses", + "political contributions exception", + "possession after conviction", + "possession and use offenses", + "possession of child pornography", + "possession of child-like sex dolls", + "possession of gambling devices", + "possession of lewd visual material", + "possession of stolen gift card information", + "possession offenses", + "postal service mail security", + "postsecondary degree", + "postsecondary educational institution", + "pregnant individuals", + "premises types", + "preparatory offense", + "preparatory offenses", + "presumption in criminal law", + "presumption of incapacity under age 15", + "prevention of escape", + "previous conviction impact", + "prior convictions", + "prior convictions impact", + "privacy protections for public servants", + "privacy violations", + "private investigator authorization", + "private place definition", + "proceeds accounting requirements", + "process server duties", + "prohibited firearm possession", + "prohibited locations", + "prohibited weapons", + "prohibition measures", + "prohibition on disclosure of non-public information", + "promotion of obscene visual material", + "property and service fraud", + "property code regulations", + "property damage", + "property damage offenses", + "property disposition rules", + "property entry regulations", + "property firearm prohibition notices", + "property interest defense", + "property price manipulation", + "property theft classifications", + "property types in theft", + "property use for crimes", + "property value assessment", + "property value classification", + "property value thresholds", + "prosecuting attorney authorization", + "prosecuting attorney obligations", + "prosecution conditions", + "prosecution cooperation", + "prosecution coordination", + "prosecution defense conditions", + "prosecution defenses", + "prosecution defenses excluded", + "prosecution for trafficking", + "prosecution guidelines", + "prosecution guidelines for laser incidents", + "prosecution jurisdiction", + "prosecution of computer crimes", + "prosecution provisions", + "prosecution under multiple laws", + "prostitution crimes", + "prostitution definitions", + "prostitution law", + "prostitution offense", + "prostitution offenses", + "prostitution promotion", + "protection of life or health", + "protection of persons", + "protection of third person's property", + "protective orders", + "provider management roles", + "proximity to protected locations", + "public highway regulations", + "public indecency laws", + "public nuisance noise", + "public place offenses", + "public places", + "public safety regulations", + "public security officers", + "public servant document filing", + "public servant information access", + "public servant misconduct", + "public servant offenses", + "public servant protection", + "public servant status", + "public servant theft", + "public utility tampering", + "punishment enhancement", + "punishment for repeat offenses", + "punishment stage", + "racketeering offenses", + "radio communication regulations", + "railroad switching yard access", + "railway labor act defense", + "reasonable medical care", + "rebuttable presumptions", + "reckless driving exhibition", + "reckless injury", + "recklessness", + "record alteration", + "recordkeeping requirements", + "refusal to release fraudulent lien", + "regulatory agency misconduct", + "regulatory commissions", + "rental agreement", + "rental agreement terms", + "renunciation defense", + "repeat felony convictions", + "repeat offender penalties", + "repeat offense convictions", + "repeat offense penalties", + "repeat offenses", + "reserve law enforcement officer", + "resisting arrest", + "restitution for damages", + "restitution for victims", + "restitution procedures", + "restricted premises", + "restricted-use pesticide definitions", + "restricted-use pesticide purchase regulations", + "retail theft detection prevention", + "retail theft instruments", + "retail theft prosecution", + "retaliation against public servants", + "retaliation murders", + "retreat requirement", + "rigging publicly exhibited contests", + "right to severance", + "robbery offenses", + "sale or purchase of child", + "satellite receiving antennas", + "school and postsecondary institution policies", + "school premises", + "school-related offenses", + "scientific research exemption", + "screening checkpoint procedures", + "secured property disposition", + "security interest defense", + "security officer authorization", + "security officer duties", + "security officer identification", + "security officer training", + "self-defense law", + "seller description requirements", + "sentencing for multiple offenses", + "sentencing hearing procedures", + "sentencing rules", + "serious bodily injury", + "service payment obligations", + "service theft classification", + "sex offender registration", + "sexual abuse offenses", + "sexual activity crimes", + "sexual assault of a child", + "sexual assault offenses", + "sexual assault victimization", + "sexual harassment", + "sexual offenses", + "sexual offenses in schools", + "sexually violent offense definition", + "sexually violent predator commitment", + "simulating document offenses", + "smoking prohibition", + "smoking regulations", + "soliciting benefits", + "speech and expression defense", + "sporting event reports", + "stash house operation", + "state and family code references", + "state funding restrictions", + "state government position fraud", + "state jail felony", + "state jail felony and misdemeanor classifications", + "state jail felony classifications", + "state jail felony criteria", + "state jail felony enhancement", + "state jail felony penalties", + "state jail felony reduction", + "state jail felony theft", + "state law enforcement agencies", + "state lottery participation", + "state of disaster declarations", + "state of disaster evacuation", + "statutory amendments", + "statutory amendments and effective dates", + "stealing or receiving stolen checks", + "steelmaking facility", + "stolen card possession", + "stolen property presumption", + "stolen vehicle reporting", + "substandard degree", + "super-intensive supervision program", + "tactical medical professional", + "tampering with electronic monitoring device", + "tampering with electronic voting machines", + "tampering with identification numbers", + "telecommunications access device fraud", + "telecommunications access device publication", + "telecommunications facilities", + "telecommunications fraud", + "telecommunications service theft", + "tenant firearm rights", + "tenant handgun rights", + "tenant rights", + "terrorism offenses", + "terroristic threat offense", + "testimonial immunity", + "theft aggregation", + "theft involving controlled substances", + "theft misdemeanor categories", + "theft of controlled substance", + "theft offense statutes", + "theft value classification", + "theft value thresholds", + "threatening behavior", + "threats", + "threats for benefit", + "tire deflation device", + "tracking and monitoring without consent", + "tracking device removal by health care provider", + "trafficked child", + "transmission facility", + "transportation communications equipment", + "treatment based on religious healing", + "trial severance", + "unactivated gift cards", + "unauthorized absence from correctional facility", + "unauthorized card use", + "unauthorized computer access", + "unauthorized enforcement of foreign law", + "unauthorized legal contact", + "unauthorized payments", + "unauthorized practice of law", + "unborn child injury exceptions", + "unlawful debt", + "unlawful debt collection", + "unlawful mail receptacle key conduct", + "unlawful telecommunications device", + "unmanned aircraft operation", + "unmanned aircraft regulations", + "unregulated custody transfer", + "use of deadly force", + "use of devices to protect property", + "use of force", + "use of force by educators", + "use of force by guardians", + "use of force justification", + "use of force to protect property", + "use of prior convictions", + "use of proceeds from racketeering", + "used goods business regulations", + "used motor vehicle purchase reporting", + "utility providers", + "valuation of stolen retail merchandise", + "value of documents in theft cases", + "value-based offense thresholds", + "vandalism of human remains", + "vehicle location disclosure", + "veterinarian negligence defense", + "violent crimes", + "virtual meeting disturbances", + "visual material definitions", + "visual material promotion", + "voluntary act or omission", + "voluntary renunciation", + "volunteer emergency personnel defense", + "voter influence offenses", + "vulnerable victims", + "water treatment facilities", + "watercraft definition", + "weapon carrying regulations", + "weapon possession restrictions", + "weapon theft from officer", + "weapon training", + "weapon-free school zone penalty", + "weapon-free zones", + "wildlife laws and regulations", + "wire communication interception", + "wiring material classifications", + "written notice requirements", + "youth and educational institutions", +]; + +// Robust unique ID generator to avoid collisions between rapid user/assistant messages +const generateId = (() => { + let counter = 0; + return () => { + counter += 1; + try { + if ( + typeof window !== "undefined" && + window.crypto && + "randomUUID" in window.crypto + ) { + return window.crypto.randomUUID(); + } + } catch (_) {} + return `${Date.now()}-${counter}-${Math.random() + .toString(36) + .slice(2, 10)}`; + }; +})(); + +interface ChatInterfaceProps { + apiKey: string; + onApiKeyReset: () => void; +} + +export default function ChatInterface({ + apiKey, + onApiKeyReset, +}: ChatInterfaceProps) { + const [messages, setMessages] = useState([]); + const [userMessage, setUserMessage] = useState(""); + const [model, setModel] = useState("gpt-4.1-mini"); + const [isLoading, setIsLoading] = useState(false); + const [showSettings, setShowSettings] = useState(false); + const [isPdfUploaded, setIsPdfUploaded] = useState(false); + const [uploadedFileName, setUploadedFileName] = useState( + undefined + ); + const [selectedTopic, setSelectedTopic] = useState(null); + const [isGeneratingMcq, setIsGeneratingMcq] = useState(false); + const messagesEndRef = useRef(null); + const textareaRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + useEffect(() => { + try { + const cached = localStorage.getItem("pdf_uploaded"); + const name = localStorage.getItem("pdf_file_name") || undefined; + if (cached === "1") { + setIsPdfUploaded(true); + setUploadedFileName(name); + } + } catch (_) {} + }, []); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!userMessage.trim() || isLoading || !isPdfUploaded) return; + + const newUserMessage: Message = { + id: generateId(), + role: "user", + content: userMessage, + timestamp: new Date(), + }; + + setMessages((prev) => [...prev, newUserMessage]); + setUserMessage(""); + setIsLoading(true); + + // Create assistant message with streaming content + const assistantMessage: Message = { + id: generateId(), + role: "assistant", + content: "", + timestamp: new Date(), + isStreaming: true, + }; + + setMessages((prev) => [...prev, assistantMessage]); + + try { + const response = await fetch( + `${ + process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000" + }/api/chat`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + user_message: userMessage, + model, + api_key: apiKey, + history: messages + .filter((m) => !m.isStreaming && !m.isError) + .map((m) => ({ role: m.role, content: m.content })), + }), + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + if (!response.body) { + throw new Error("No response body"); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + + if (done) break; + + const chunk = decoder.decode(value); + + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMessage.id + ? { ...msg, content: msg.content + chunk } + : msg + ) + ); + } + + // Mark streaming as complete + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMessage.id ? { ...msg, isStreaming: false } : msg + ) + ); + } catch (error) { + console.error("Chat error:", error); + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMessage.id + ? { + ...msg, + content: + "Sorry, there was an error processing your request. Please try again.", + isStreaming: false, + isError: true, + } + : msg + ) + ); + } finally { + setIsLoading(false); + } + }; + + const handleGenerateMcq = async () => { + if (!selectedTopic || !isPdfUploaded || isGeneratingMcq) return; + setIsGeneratingMcq(true); + + const assistantMessageId = generateId(); + const pendingAssistant: Message = { + id: assistantMessageId, + role: "assistant", + content: "", + timestamp: new Date(), + isStreaming: true, + }; + setMessages((prev) => [ + ...prev, + { + id: generateId(), + role: "user", + content: `Generate an MCQ for topic: ${selectedTopic}`, + timestamp: new Date(), + }, + pendingAssistant, + ]); + + try { + const response = await fetch( + `${ + process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000" + }/api/topic-question`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + topic: selectedTopic, + api_key: apiKey, + num_choices: 4, + model, + }), + } + ); + + if (!response.ok) { + const err = await response.json().catch(() => ({} as any)); + throw new Error(err.detail || `HTTP ${response.status}`); + } + + const data = await response.json(); + const mcq: McqPayload = { + question: data.question || selectedTopic || "", + choices: Array.isArray(data.choices) ? data.choices : [], + correct: data.correct || "", + rationale: data.rationale || "", + evidence: data.evidence || "", + section: data.section || "", + }; + + const formatted = [ + `Question: ${mcq.question}`, + ...mcq.choices.map((c) => `${c.label}) ${c.text}`), + ].join("\n"); + + setMessages((prev) => + prev.map((m) => + m.id === assistantMessageId + ? { ...m, content: formatted, mcq, isStreaming: false } + : m + ) + ); + } catch (error) { + console.error("MCQ generation error:", error); + setMessages((prev) => + prev.map((m) => + m.id === assistantMessageId + ? { + ...m, + content: + "Sorry, there was an error generating the MCQ. Please try again.", + isStreaming: false, + isError: true, + } + : m + ) + ); + } finally { + setIsGeneratingMcq(false); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSubmit(e as any); + } + }; + + const handleTextareaResize = () => { + if (textareaRef.current) { + textareaRef.current.style.height = "auto"; + textareaRef.current.style.height = + Math.min(textareaRef.current.scrollHeight, 120) + "px"; + } + }; + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

AI Assistant

+

{model}

+
+
+ +
+ + +
+
+ + {/* Settings Panel */} + {showSettings && ( +
+
+ + +
+
+ )} + + {/* Main Content: Chat left, Sidebar right */} +
+ {/* Chat Pane */} +
+ {/* Messages */} +
+ {messages.length === 0 ? ( +
+
+ +

+ Upload a PDF to start +

+

+ {isPdfUploaded + ? "PDF uploaded! Choose a topic and start asking questions." + : "Use the panel on the right to upload a PDF and pick a topic."} +

+
+
+ ) : ( + <> + {messages.map((message) => ( + + ))} +
+ + )} +
+ + {/* Input Form */} +
+
+
+