diff --git a/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/.gitignore b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/.gitignore new file mode 100755 index 00000000..0c2ad090 --- /dev/null +++ b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/.gitignore @@ -0,0 +1 @@ +.env diff --git a/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/README.md b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/README.md new file mode 100644 index 00000000..e69de29b diff --git a/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/__pycache__/main.cpython-312.pyc b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/__pycache__/main.cpython-312.pyc new file mode 100755 index 00000000..e637e6cb Binary files /dev/null and b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/__pycache__/main.cpython-312.pyc differ diff --git a/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/icon.png b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/icon.png new file mode 100755 index 00000000..fc523fb8 Binary files /dev/null and b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/icon.png differ diff --git a/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/manifest.json b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/manifest.json new file mode 100755 index 00000000..0e7bf8aa --- /dev/null +++ b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/manifest.json @@ -0,0 +1,12 @@ +{ + "manifest_version": 3, + "name": "YouTube AI Q&A", + "version": "1.0", + "description": "Ask questions about YouTube videos using AI", + "permissions": ["tabs"], + "action": { + "default_popup": "popup.html", + "default_icon": "icon.png" + }, + "host_permissions": [""] +} diff --git a/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/popup.html b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/popup.html new file mode 100755 index 00000000..33f2945e --- /dev/null +++ b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/popup.html @@ -0,0 +1,21 @@ + + + + + + YouTube AI Q&A + + + +
+

🎥 YouTube AI Q&A

+ + +
+ Loading... +
+
+
+ + + diff --git a/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/popup.js b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/popup.js new file mode 100755 index 00000000..9776bd0a --- /dev/null +++ b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/popup.js @@ -0,0 +1,33 @@ +document.getElementById("askBtn").addEventListener("click", async () => { + const question = document.getElementById("question").value.trim(); + const answerBox = document.getElementById("answer"); + const loadingEl = document.getElementById("loading"); + + answerBox.textContent = ""; + loadingEl.style.display = "block"; + + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + const urlParams = new URLSearchParams(new URL(tab.url).search); + const videoId = urlParams.get("v"); + + if (!videoId) { + loadingEl.style.display = "none"; + answerBox.textContent = "Not a valid YouTube video."; + return; + } + + try { + const response = await fetch("http://localhost:8000/ask", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ video_id: videoId, question }) + }); + + const data = await response.json(); + answerBox.textContent = response.ok ? data.answer : "Error: " + data.detail; + } catch (err) { + answerBox.textContent = "Failed to connect to backend."; + } + + loadingEl.style.display = "none"; +}); diff --git a/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/styles.css b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/styles.css new file mode 100755 index 00000000..4d6c0d65 --- /dev/null +++ b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/extension/styles.css @@ -0,0 +1,81 @@ +:root { + --bg: #f9f9f9; + --text: #1f1f1f; + --card: #ffffff; + --border: #ddd; + --primary: #007bff; +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #121212; + --text: #f0f0f0; + --card: #1e1e1e; + --border: #444; + --primary: #4e9eff; + } +} + +body { + margin: 0; + background-color: var(--bg); + color: var(--text); + font-family: 'Segoe UI', sans-serif; + width: 320px; +} + +.container { + padding: 16px; +} + +.title { + font-size: 18px; + margin-bottom: 10px; +} + +input { + width: 100%; + padding: 10px; + margin-bottom: 8px; + border-radius: 5px; + border: 1px solid var(--border); + background-color: var(--card); + color: var(--text); +} + +button { + width: 100%; + padding: 10px; + background-color: var(--primary); + color: white; + font-weight: bold; + border: none; + border-radius: 5px; + cursor: pointer; +} + +button:hover { + background-color: #0056b3; +} + +#loading { + text-align: center; + display: none; + margin-top: 10px; +} + +#loading img { + width: 24px; + height: 24px; +} + +#answer { + margin-top: 12px; + background-color: var(--card); + border: 1px solid var(--border); + border-radius: 6px; + padding: 10px; + font-size: 14px; + white-space: pre-wrap; + min-height: 40px; +} diff --git a/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/langchain.ipynb b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/langchain.ipynb new file mode 100755 index 00000000..ac22f599 --- /dev/null +++ b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/langchain.ipynb @@ -0,0 +1,324 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "eab60294", + "metadata": {}, + "outputs": [], + "source": [ + "import os \n", + "from dotenv import load_dotenv\n", + "load_dotenv()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f8442cf", + "metadata": {}, + "outputs": [], + "source": [ + "from youtube_transcript_api import YouTubeTranscriptApi, TranscriptsDisabled\n", + "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", + "from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings\n", + "from langchain_community.vectorstores import FAISS\n", + "from langchain_core.prompts import PromptTemplate\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "9afa72eb", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22d3fe68", + "metadata": {}, + "outputs": [], + "source": [ + "video_id=\"XBuv4HHTRjI\"\n", + "try:\n", + " transcriptlist=YouTubeTranscriptApi.get_transcript(video_id,languages=[\"en\"])\n", + " transcript=\" \".join(chunk[\"text\"] for chunk in transcriptlist)\n", + " print(transcript)\n", + "except TranscriptsDisabled:\n", + " print(\"No Captions\") " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "396db28d", + "metadata": {}, + "outputs": [], + "source": [ + "transcriptlist" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98de0884", + "metadata": {}, + "outputs": [], + "source": [ + "splitter= RecursiveCharacterTextSplitter(chunk_size=1000,chunk_overlap=200)\n", + "chunks=splitter.create_documents([transcript])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f296457d", + "metadata": {}, + "outputs": [], + "source": [ + "len(chunks)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b0be8ea", + "metadata": {}, + "outputs": [], + "source": [ + "embeddings = GoogleGenerativeAIEmbeddings(model=\"models/embedding-001\")\n", + "vector_store=FAISS.from_documents(chunks,embeddings)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09ee957d", + "metadata": {}, + "outputs": [], + "source": [ + "vector_store.index_to_docstore_id" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a09d5be7", + "metadata": {}, + "outputs": [], + "source": [ + "retriever=vector_store.as_retriever(search_type=\"similarity\",search_kwargs={\"k\":4})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b3b37e4", + "metadata": {}, + "outputs": [], + "source": [ + "retriever" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7191bb00", + "metadata": {}, + "outputs": [], + "source": [ + "retriever.invoke(\"Tell me about DeepMind\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82f0d192", + "metadata": {}, + "outputs": [], + "source": [ + "llm=ChatGoogleGenerativeAI(model=\"gemini-2.0-flash\",temperature=0.2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89fae467", + "metadata": {}, + "outputs": [], + "source": [ + "prompt=PromptTemplate(\n", + " template=\"\"\"You are a helpful assistant.\n", + " Answer only from the provided transcript context\n", + " if the context is insufficient ,jusy say you dont know\n", + " \n", + " {context}\n", + " Question:{question}\n", + " \"\"\",\n", + " input_variables=['context','question']\n", + " )\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84cf2a04", + "metadata": {}, + "outputs": [], + "source": [ + "question = \"is the topic of nuclear fusion discussed in this video? if yes then what was discussed\"\n", + "retrieved_docs = retriever.invoke(question)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49bd1e9c", + "metadata": {}, + "outputs": [], + "source": [ + "retrieved_docs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab3ae2fe", + "metadata": {}, + "outputs": [], + "source": [ + "context_text = \"\\n\\n\".join(doc.page_content for doc in retrieved_docs)\n", + "context_text" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96def819", + "metadata": {}, + "outputs": [], + "source": [ + "final_prompt = prompt.invoke({\"context\": context_text, \"question\": question})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3dfe33aa", + "metadata": {}, + "outputs": [], + "source": [ + "final_prompt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1cfde7a0", + "metadata": {}, + "outputs": [], + "source": [ + "answer=llm.invoke(final_prompt)\n", + "print(answer.content)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5630a2ea", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.runnables import RunnableParallel,RunnablePassthrough,RunnableLambda\n", + "from langchain_core.output_parsers import StrOutputParser\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a901005", + "metadata": {}, + "outputs": [], + "source": [ + "def format_docs(retrieved_docs):\n", + " context_text=\"\\n\\n\".join(doc.page_content for doc in retrieved_docs)\n", + " return context_text" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ecfad850", + "metadata": {}, + "outputs": [], + "source": [ + "parallel_chain=RunnableParallel({\n", + " 'context': retriever|RunnableLambda(format_docs),\n", + " 'question':RunnablePassthrough()\n", + "})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1707940", + "metadata": {}, + "outputs": [], + "source": [ + "parallel_chain.invoke('Who is Demis')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41a42539", + "metadata": {}, + "outputs": [], + "source": [ + "parser=StrOutputParser()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "190c5379", + "metadata": {}, + "outputs": [], + "source": [ + "main_chain =parallel_chain|prompt|llm|parser" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc3efabf", + "metadata": {}, + "outputs": [], + "source": [ + "main_chain.invoke('summarize this video')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/main.py b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/main.py new file mode 100755 index 00000000..38fa76a8 --- /dev/null +++ b/Domains/AI-ML/MiniProjects/Yt_chatbot_extension/main.py @@ -0,0 +1,62 @@ +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +from youtube_transcript_api import YouTubeTranscriptApi, TranscriptsDisabled +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings +from langchain_community.vectorstores import FAISS +from langchain_core.prompts import PromptTemplate +from langchain_core.runnables import RunnableParallel, RunnablePassthrough, RunnableLambda +from langchain_core.output_parsers import StrOutputParser +import os +from dotenv import load_dotenv + +load_dotenv() +app = FastAPI() + +# LangChain setup +llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0.2) +prompt = PromptTemplate( + template="""You are a helpful assistant. Answer only from the provided transcript context. + If the context is insufficient, just say you don't know. + + {context} + Question: {question} + """, + input_variables=["context", "question"] +) + +class Query(BaseModel): + video_id: str + question: str + +@app.post("/ask") +def ask_question(query: Query): + try: + transcriptlist = YouTubeTranscriptApi.get_transcript(query.video_id, languages=["en","hi"]) + transcript = " ".join(chunk["text"] for chunk in transcriptlist) + except TranscriptsDisabled: + raise HTTPException(status_code=404, detail="No captions available.") + + splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) + chunks = splitter.create_documents([transcript]) + + embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001") + vector_store = FAISS.from_documents(chunks, embeddings) + retriever = vector_store.as_retriever(search_type="similarity", search_kwargs={"k": 4}) + + def format_docs(retrieved_docs): + return "\n\n".join(doc.page_content for doc in retrieved_docs) + + parallel_chain = RunnableParallel({ + 'context': retriever | RunnableLambda(format_docs), + 'question': RunnablePassthrough() + }) + + main_chain = parallel_chain | prompt | llm | StrOutputParser() + + try: + result = main_chain.invoke(query.question) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + return {"answer": result}