浏览代码

init: book mindmap

Young Han 2 月之前
父节点
当前提交
414c093c38
共有 28 个文件被更改,包括 21490 次插入0 次删除
  1. 23 0
      end-to-end-use-cases/long_context/book-character-mindmap/.gitignore
  2. 20351 0
      end-to-end-use-cases/long_context/book-character-mindmap/package-lock.json
  3. 49 0
      end-to-end-use-cases/long_context/book-character-mindmap/package.json
  4. 二进制
      end-to-end-use-cases/long_context/book-character-mindmap/public/favicon.ico
  5. 43 0
      end-to-end-use-cases/long_context/book-character-mindmap/public/index.html
  6. 二进制
      end-to-end-use-cases/long_context/book-character-mindmap/public/logo192.png
  7. 二进制
      end-to-end-use-cases/long_context/book-character-mindmap/public/logo512.png
  8. 25 0
      end-to-end-use-cases/long_context/book-character-mindmap/public/manifest.json
  9. 3 0
      end-to-end-use-cases/long_context/book-character-mindmap/public/robots.txt
  10. 38 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/App.css
  11. 17 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/App.js
  12. 8 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/App.test.js
  13. 26 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/approuter.jsx
  14. 3 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/index.css
  15. 13 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/index.js
  16. 1 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/logo.svg
  17. 19 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/pages/Layout.jsx
  18. 72 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/pages/bookPage/components/AISearch.jsx
  19. 132 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/pages/bookPage/components/CharacterGraph.jsx
  20. 29 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/pages/bookPage/components/ErrorBoundary.jsx
  21. 364 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/pages/bookPage/index.jsx
  22. 78 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/pages/homePage/components/Features.jsx
  23. 53 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/pages/homePage/components/Hero.jsx
  24. 89 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/pages/homePage/components/HowItWorks.jsx
  25. 13 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/pages/homePage/index.jsx
  26. 13 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/reportWebVitals.js
  27. 5 0
      end-to-end-use-cases/long_context/book-character-mindmap/src/setupTests.js
  28. 23 0
      end-to-end-use-cases/long_context/book-character-mindmap/tailwind.config.js

+ 23 - 0
end-to-end-use-cases/long_context/book-character-mindmap/.gitignore

@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*

文件差异内容过多而无法显示
+ 20351 - 0
end-to-end-use-cases/long_context/book-character-mindmap/package-lock.json


+ 49 - 0
end-to-end-use-cases/long_context/book-character-mindmap/package.json

@@ -0,0 +1,49 @@
+{
+  "name": "bookmind",
+  "version": "0.1.0",
+  "private": true,
+  "dependencies": {
+    "@testing-library/jest-dom": "^5.17.0",
+    "@testing-library/react": "^13.4.0",
+    "@testing-library/user-event": "^13.5.0",
+    "axios": "^1.7.7",
+    "fs": "^0.0.1-security",
+    "lottie-react": "^2.4.0",
+    "lucide-react": "^0.460.0",
+    "react": "^18.3.1",
+    "react-dom": "^18.3.1",
+    "react-force-graph": "^1.44.7",
+    "react-force-graph-2d": "^1.25.8",
+    "react-icons": "^4.10.1",
+    "react-router-dom": "^7.0.1",
+    "react-scripts": "^5.0.1",
+    "web-vitals": "^2.1.4"
+  },
+  "scripts": {
+    "start": "react-scripts start",
+    "build": "react-scripts build",
+    "test": "react-scripts test",
+    "eject": "react-scripts eject"
+  },
+  "eslintConfig": {
+    "extends": [
+      "react-app",
+      "react-app/jest"
+    ]
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  },
+  "devDependencies": {
+    "tailwindcss": "^3.4.15"
+  }
+}

二进制
end-to-end-use-cases/long_context/book-character-mindmap/public/favicon.ico


+ 43 - 0
end-to-end-use-cases/long_context/book-character-mindmap/public/index.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta
+      name="description"
+      content="Web site created using create-react-app"
+    />
+    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+    <!--
+      manifest.json provides metadata used when your web app is installed on a
+      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
+    -->
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <!--
+      Notice the use of %PUBLIC_URL% in the tags above.
+      It will be replaced with the URL of the `public` folder during the build.
+      Only files inside the `public` folder can be referenced from the HTML.
+
+      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+      work correctly both with client-side routing and a non-root public URL.
+      Learn how to configure a non-root public URL by running `npm run build`.
+    -->
+    <title>React App</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+    <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+  </body>
+</html>

二进制
end-to-end-use-cases/long_context/book-character-mindmap/public/logo192.png


二进制
end-to-end-use-cases/long_context/book-character-mindmap/public/logo512.png


+ 25 - 0
end-to-end-use-cases/long_context/book-character-mindmap/public/manifest.json

@@ -0,0 +1,25 @@
+{
+  "short_name": "React App",
+  "name": "Create React App Sample",
+  "icons": [
+    {
+      "src": "favicon.ico",
+      "sizes": "64x64 32x32 24x24 16x16",
+      "type": "image/x-icon"
+    },
+    {
+      "src": "logo192.png",
+      "type": "image/png",
+      "sizes": "192x192"
+    },
+    {
+      "src": "logo512.png",
+      "type": "image/png",
+      "sizes": "512x512"
+    }
+  ],
+  "start_url": ".",
+  "display": "standalone",
+  "theme_color": "#000000",
+  "background_color": "#ffffff"
+}

+ 3 - 0
end-to-end-use-cases/long_context/book-character-mindmap/public/robots.txt

@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:

+ 38 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/App.css

@@ -0,0 +1,38 @@
+.App {
+  text-align: center;
+}
+
+.App-logo {
+  height: 40vmin;
+  pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  .App-logo {
+    animation: App-logo-spin infinite 20s linear;
+  }
+}
+
+.App-header {
+  background-color: #282c34;
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  font-size: calc(10px + 2vmin);
+  color: white;
+}
+
+.App-link {
+  color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}

+ 17 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/App.js

@@ -0,0 +1,17 @@
+import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
+import Home from "./homePage/index";
+import SearchPage from "./bookPage/components/SearchPage";
+
+function App() {
+  return (
+    <Router>
+      <Routes>
+        {/* Define routes for Home and SearchPage */}
+        <Route path="/" element={<Home />} />
+        <Route path="/search" element={<SearchPage />} />
+      </Routes>
+    </Router>
+  );
+}
+
+export default App;

+ 8 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/App.test.js

@@ -0,0 +1,8 @@
+import { render, screen } from '@testing-library/react';
+import App from './App';
+
+test('renders learn react link', () => {
+  render(<App />);
+  const linkElement = screen.getByText(/learn react/i);
+  expect(linkElement).toBeInTheDocument();
+});

+ 26 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/approuter.jsx

@@ -0,0 +1,26 @@
+/* eslint-disable no-extra-semi */
+/* eslint-disable react/prop-types */
+/* eslint-disable no-unused-vars */
+import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
+
+import Home from "./pages/homePage";
+import BookPage from "./pages/bookPage";
+
+const AppRouter = function () {
+  return (
+    <>
+      <Routes>
+        <Route exact path="/" element={<Home />} />
+        <Route exact path="/search" element={<BookPage />} />
+      </Routes>
+    </>
+  );
+};
+
+const App = () => (
+  <Router>
+    <AppRouter />
+  </Router>
+);
+
+export default App;

+ 3 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/index.css

@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;

+ 13 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/index.js

@@ -0,0 +1,13 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import "./index.css";
+import AppRouter from "./approuter";
+import reportWebVitals from "./reportWebVitals";
+
+const root = ReactDOM.createRoot(document.getElementById("root"));
+root.render(<AppRouter />);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();

文件差异内容过多而无法显示
+ 1 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/logo.svg


+ 19 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/pages/Layout.jsx

@@ -0,0 +1,19 @@
+import "../index.css";
+
+export const metadata = {
+  title: "BookMind - Unravel Stories, One Map at a Time",
+  description:
+    "Explore character relationships and storylines with AI-powered visualizations.",
+};
+
+export default function RootLayout({ children }) {
+  return (
+    <html lang="en">
+      <head>
+        <title>{metadata.title}</title>
+        <meta name="description" content={metadata.description} />
+      </head>
+      <body>{children}</body>
+    </html>
+  );
+}

+ 72 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/pages/bookPage/components/AISearch.jsx

@@ -0,0 +1,72 @@
+import { useState } from "react";
+import { FaSearch, FaSpinner } from "react-icons/fa";
+import axios from "axios";
+import Together from "together-ai";
+
+export default function AISearch({ bookTitle, onQueryResponse }) {
+  const [query, setQuery] = useState("");
+  const [response, setResponse] = useState("");
+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState(null);
+
+  const handleSearch = async () => {
+    if (!query.trim()) return;
+
+    setIsLoading(true);
+    setError(null);
+
+    try {
+      const response = await axios.post("http://localhost:5001/query", {
+        query: `About ${bookTitle}: ${query}`,
+      });
+
+      setResponse(response.data.response);
+      onQueryResponse?.(response.data);
+    } catch (error) {
+      console.error("Error in AI search:", error);
+      setError("Failed to get response. Please try again.");
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  return (
+    <div className="bg-white shadow-lg rounded-lg p-6 space-y-4">
+      <h2 className="text-xl font-semibold text-gray-800">AI-Powered Search</h2>
+      <div className="space-y-4">
+        <input
+          type="text"
+          placeholder={`Ask about ${bookTitle}'s character relationships...`}
+          value={query}
+          onChange={(e) => setQuery(e.target.value)}
+          className="w-full px-4 py-2 rounded-md border-gray-300
+                   focus:border-blue-500 focus:ring focus:ring-blue-200
+                   transition duration-200"
+          disabled={isLoading}
+        />
+        <button
+          onClick={handleSearch}
+          disabled={isLoading || !query.trim()}
+          className="w-full bg-blue-500 hover:bg-blue-600
+                   text-white font-semibold py-2 px-4 rounded-md
+                   transition duration-200 flex items-center justify-center
+                   disabled:bg-blue-300 space-x-2"
+        >
+          {isLoading ? <FaSpinner className="animate-spin" /> : <FaSearch />}
+          <span>{isLoading ? "Searching..." : "Search"}</span>
+        </button>
+      </div>
+
+      {error && (
+        <div className="p-4 bg-red-50 rounded-md text-red-600">{error}</div>
+      )}
+
+      {response && !error && (
+        <div className="mt-4 p-4 bg-gray-50 rounded-md">
+          <h3 className="font-semibold text-gray-800 mb-2">AI Response:</h3>
+          <p className="text-gray-600 whitespace-pre-wrap">{response}</p>
+        </div>
+      )}
+    </div>
+  );
+}

+ 132 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/pages/bookPage/components/CharacterGraph.jsx

@@ -0,0 +1,132 @@
+import { useState, useEffect, useRef } from "react";
+import ForceGraph2D from "react-force-graph-2d";
+// import * as d3 from 'd3';
+
+export default function CharacterGraph({ graphData }) {
+  const containerRef = useRef(null);
+  const [dimensions, setDimensions] = useState({ width: 600, height: 600 });
+  const [hoveredLink, setHoveredLink] = useState(null);
+
+  useEffect(() => {
+    const updateDimensions = () => {
+      if (containerRef.current) {
+        setDimensions({
+          width: containerRef.current.offsetWidth,
+          height: Math.max(300, containerRef.current.offsetHeight),
+        });
+      }
+    };
+
+    updateDimensions();
+    window.addEventListener("resize", updateDimensions);
+    return () => window.removeEventListener("resize", updateDimensions);
+  }, []);
+
+  return (
+    <div className="bg-white shadow-lg rounded-lg p-6">
+      <h2 className="text-xl font-semibold text-gray-800 mb-4">
+        Character Relationship Graph
+      </h2>
+      <div ref={containerRef} className="w-full h-[600px]">
+        <ForceGraph2D
+          graphData={graphData}
+          // d3Force={(engine) => {
+          //   engine.force('charge').strength(-300); // Increase repulsion (negative value)
+          //   engine.force('link').distance(200);    // Increase link distance
+          // }}
+          d3Force={(engine) => {
+            // Make sure the forces exist before modifying them
+            if (engine.force('charge')) engine.force('charge').strength(-500);
+            if (engine.force('link')) engine.force('link').distance(300);
+
+            // // Add a center force to keep nodes in view
+            // engine.force('center', d3.forceCenter(dimensions.width/2, dimensions.height/2));
+          }}
+          nodeCanvasObject={(node, ctx, globalScale) => {
+            // Draw node
+            ctx.beginPath();
+            ctx.arc(node.x, node.y, 5, 0, 2 * Math.PI);
+            ctx.fillStyle = "transparent";
+            ctx.fill();
+
+            // Always show node label
+            const label = node.name;
+            const fontSize = 12 / globalScale;
+            ctx.font = `${fontSize}px Arial`;
+            const textWidth = ctx.measureText(label).width;
+            const bckgDimensions = [textWidth, fontSize].map(
+              (n) => n + fontSize * 0.2
+            );
+
+            ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
+            ctx.fillRect(
+              node.x - bckgDimensions[0] / 2,
+              node.y - bckgDimensions[1] / 2,
+              ...bckgDimensions
+            );
+
+            ctx.textAlign = "center";
+            ctx.textBaseline = "middle";
+            ctx.fillStyle = "#3b82f6";
+            ctx.fillText(label, node.x, node.y);
+
+            node.__bckgDimensions = bckgDimensions;
+          }}
+          onLinkHover={(link) => setHoveredLink(link ? `${link.source.id}-${link.target.id}` : null)}
+          nodePointerAreaPaint={(node, color, ctx) => {
+            ctx.fillStyle = color;
+            const bckgDimensions = node.__bckgDimensions;
+            bckgDimensions &&
+              ctx.fillRect(
+                node.x - bckgDimensions[0] / 2,
+                node.y - bckgDimensions[1] / 2,
+                ...bckgDimensions
+              );
+          }}
+          linkCanvasObject={(link, ctx, globalScale) => {
+            const start = link.source;
+            const end = link.target;
+            ctx.beginPath();
+            ctx.moveTo(start.x, start.y);
+            ctx.lineTo(end.x, end.y);
+            ctx.strokeStyle = "#9ca3af";
+            ctx.lineWidth = 1;
+            ctx.stroke();
+
+            // Only draw label if link is hovered
+            const linkId = `${link.source.id}-${link.target.id}`;
+            if (hoveredLink === linkId && link.label) {
+              const textPos = {
+                x: start.x + (end.x - start.x) / 2,
+                y: start.y + (end.y - start.y) / 2
+              };
+
+              const fontSize = 3 + 1/globalScale;
+              ctx.font = `${fontSize}px Arial`;
+
+              const textWidth = ctx.measureText(link.label).width;
+              const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2);
+
+              ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
+              ctx.fillRect(
+                textPos.x - bckgDimensions[0] / 2,
+                textPos.y - bckgDimensions[1] / 2,
+                ...bckgDimensions
+              );
+
+              ctx.textAlign = 'center';
+              ctx.textBaseline = 'middle';
+              ctx.fillStyle = '#666';
+              ctx.fillText(link.label, textPos.x, textPos.y);
+            }
+          }}
+          linkColor={() => "#9ca3af"}
+          linkWidth={1}
+          backgroundColor="#ffffff"
+          width={dimensions.width}
+          height={dimensions.height}
+        />
+      </div>
+    </div>
+  );
+}

+ 29 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/pages/bookPage/components/ErrorBoundary.jsx

@@ -0,0 +1,29 @@
+'use client'
+
+import { Component } from 'react'
+
+class ErrorBoundary extends Component {
+  constructor(props) {
+    super(props)
+    this.state = { hasError: false }
+  }
+
+  static getDerivedStateFromError(error) {
+    return { hasError: true }
+  }
+
+  componentDidCatch(error, errorInfo) {
+    console.log('ErrorBoundary caught an error:', error, errorInfo)
+  }
+
+  render() {
+    if (this.state.hasError) {
+      return <h1>Something went wrong.</h1>
+    }
+
+    return this.props.children
+  }
+}
+
+export default ErrorBoundary
+

+ 364 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/pages/bookPage/index.jsx

@@ -0,0 +1,364 @@
+import React, { useState } from "react";
+import { FaSearch, FaBook, FaHome } from "react-icons/fa";
+import { Link } from "react-router-dom";
+import AISearch from "./components/AISearch";
+import CharacterGraph from "./components/CharacterGraph";
+import Together from "together-ai";
+
+// Change from SYSTEM_PROMPT = "..." to:
+const SYSTEM_PROMPT = `You are tasked with analyzing a text excerpt from a book to extract character relationships and present them in a structured JSON format. Your output must include the book's title, a summary of its content, character nodes, and labeled relationship links.
+
+Input:
+
+A text excerpt from a book.
+
+Output:
+
+A valid JSON object representing the characters and their relationships. Make sure only to include json format and no other text.
+
+⸻
+
+JSON Format Specification:
+
+1. title: The title of the book.
+
+2. summary: A brief summary of the book's content.
+
+3. Nodes: Each character should be a node with the following properties:
+	•	"id": A unique identifier for the character (e.g., "c1", "char_john").
+	•	"name": The full name of the character as it appears in the text.
+	•	"val": Always set to 10.
+
+4. Links: Each relationship should be a link with the following properties:
+	•	"source": The id of the source character.
+	•	"target": The id of the target character.
+	•	"label": A brief natural language explanation of the relationship between the characters (e.g., "mother of", "friends with", "enemy of").
+
+  ## Example JSON Structure:
+  JSON
+{
+  "title": "The Fellowship of the Ring",
+  "summary": "In the first part of the epic trilogy, Frodo Baggins inherits a powerful ring that must be destroyed to stop the rise of evil. He sets out on a perilous journey with a group of companions to reach Mount Doom. Along the way, they face temptation, betrayal, and battles that test their unity and resolve.",
+  "nodes": [
+    { "id": "c1", "name": "Frodo Baggins", "val": 10 },
+    { "id": "c2", "name": "Samwise Gamgee", "val": 10 }
+  ],
+  "links": [
+    { "source": "c2", "target": "c1", "label": "loyal companion of" }
+  ]
+}
+  Note: Replace id1, id2, etc., with unique identifiers for each character. Do not use these exact values.`;
+
+const together = new Together({
+  apiKey: process.env.REACT_APP_TOGETHER_API_KEY
+});
+
+export default function BookPage() {
+  const [filePath, setFilePath] = useState("");
+  const [fileObject, setFileObject] = useState(null);
+  const [isLoading, setIsLoading] = useState(false);
+  const [graphData, setGraphData] = useState(null);
+  const [bookData, setBookData] = useState(null);
+  const [searchComplete, setSearchComplete] = useState(false);
+
+  const readFileContent = async (file) => {
+    try {
+      const reader = new FileReader();
+      return new Promise((resolve, reject) => {
+        reader.onload = () => resolve(reader.result);
+        reader.onerror = () => reject("Error reading file");
+        reader.readAsText(file);
+      });
+    } catch (error) {
+      console.error("Error reading file content:", error);
+      return "Failed to read file content.";
+    }
+  };
+
+  const submitQuery = async (query) => {
+    try {
+      const response = await together.chat.completions.create({
+        // model: "Llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
+        model: "meta-llama/Llama-4-Scout-17B-16E-Instruct",
+        messages: [{
+          role: "system",
+          content: SYSTEM_PROMPT
+        },
+        {
+          role: "user",
+          content: query
+        }]
+      });
+      console.log("Response:", response.choices[0].message.content);
+      return response.choices[0].message.content;
+    } catch (error) {
+      console.error("Error submitting query:", error);
+      return "Sorry, I couldn't generate a response.";
+    }
+  };
+
+  const verificationGraphData = (graphData) => {
+    try {
+      // Create Set of valid node IDs
+      const nodeIds = new Set(graphData.nodes.map((node) => node.id));
+
+      // Filter links to only include valid node references
+      const validLinks = graphData.links.filter(
+        (link) => nodeIds.has(link.source) && nodeIds.has(link.target)
+      );
+
+      return {
+        nodes: graphData.nodes,
+        links: validLinks,
+      };
+    } catch (error) {
+      console.error("Error validating graph data:", error);
+      return graphData; // Return original data if validation fails
+    }
+  };
+
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+    if (!filePath.trim()) {
+      return;
+    }
+
+    setIsLoading(true);
+    try {
+      // Initialize memory and fetch book data in parallel
+      // const [memoryResponse, bookInfo] = await Promise.all([
+      //   initializeMemory(filePath),
+      //   fetchBookData(filePath),
+      // ]);
+      const fileContent = await readFileContent(fileObject);
+      console.log("fileContent", fileContent)
+      // const queryResult = await submitQuery(fileContent);
+      const queryResultString = await submitQuery(fileContent);
+      const queryResult = JSON.parse(queryResultString.replace(/^```json|```$/g, ""));
+      // const queryResult = JSON.parse(`{
+      //   "title": "The Reunion",
+      //   "summary": "A group of friends reunite in their hometown five years after high school, rekindling their old relationships and navigating changes in their lives.",
+      //   "nodes": [
+      //     { "id": "c1", "name": "Alex Chen", "val": 10 },
+      //     { "id": "c2", "name": "Emily Patel", "val": 10 },
+      //     { "id": "c3", "name": "Jake Lee", "val": 10 },
+      //     { "id": "c4", "name": "Sarah Kim", "val": 10 }
+      //   ],
+      //   "links": [
+      //     { "source": "c1", "target": "c2", "label": "best friends with" },
+      //     { "source": "c1", "target": "c3", "label": "friends with" },
+      //     { "source": "c1", "target": "c4", "label": "friends with" },
+      //     { "source": "c2", "target": "c3", "label": "friends with" },
+      //     { "source": "c2", "target": "c4", "label": "friends with" },
+      //     { "source": "c3", "target": "c4", "label": "dating" },
+      //     { "source": "c3", "target": "c1", "label": "friends with" },
+      //     { "source": "c4", "target": "c1", "label": "friends with" },
+      //     { "source": "c4", "target": "c2", "label": "friends with" }
+      //   ]
+      // }`);
+
+      // setBookData({
+      //   title: bookInfo.title,
+      //   subtitle: bookInfo.summary,
+      //   posterUrl: bookInfo.coverUrl,
+      //   author: bookInfo.author,
+      //   publishedDate: bookInfo.publishedDate,
+      //   pageCount: bookInfo.pageCount,
+      // });
+
+      setBookData({
+        title: queryResult.title,
+        subtitle: queryResult.summary,
+        posterUrl: "",
+        author: "",
+        publishedDate: "",
+        pageCount: "",
+      });
+
+      const graphData = verificationGraphData({
+        nodes: queryResult.nodes,
+        links: queryResult.links,
+      })
+
+      setGraphData(graphData);
+      setSearchComplete(true);
+    } catch (error) {
+      console.error("Search error:", error);
+    } finally {
+      setIsLoading(false);
+    }
+  };
+  // Add new function to fetch book cover
+  // const fetchBookData = async (bookTitle) => {
+  //   try {
+  //     const response = await axios.get(
+  //       `https://www.googleapis.com/books/v1/volumes`,
+  //       {
+  //         params: {
+  //           q: bookTitle,
+  //           key: process.env.REACT_APP_GOOGLE_BOOKS_API_KEY,
+  //         },
+  //       }
+  //     );
+
+  //     if (response.data.items && response.data.items[0]) {
+  //       const volumeInfo = response.data.items[0].volumeInfo;
+  //       const imageLinks = volumeInfo.imageLinks || {};
+
+  //       return {
+  //         coverUrl:
+  //           imageLinks.extraLarge ||
+  //           imageLinks.large ||
+  //           imageLinks.medium ||
+  //           imageLinks.thumbnail ||
+  //           "/placeholder.jpg",
+  //         summary: volumeInfo.description || "No summary available",
+  //         title: volumeInfo.title,
+  //         author: volumeInfo.authors?.[0] || "Unknown Author",
+  //         publishedDate: volumeInfo.publishedDate,
+  //         pageCount: volumeInfo.pageCount,
+  //       };
+  //     }
+
+  //     return {
+  //       coverUrl: "/placeholder.jpg",
+  //       summary: "No summary available",
+  //       title: bookTitle,
+  //       author: "Unknown Author",
+  //       publishedDate: "",
+  //       pageCount: 0,
+  //     };
+  //   } catch (error) {
+  //     console.error("Error fetching book data:", error);
+  //     return {
+  //       coverUrl: "/placeholder.jpg",
+  //       summary: "Failed to load book information",
+  //       title: bookTitle,
+  //       author: "Unknown Author",
+  //       publishedDate: "",
+  //       pageCount: 0,
+  //     };
+  //   }
+  // };
+
+  return (
+    <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 py-16 px-4 sm:px-6 lg:px-8">
+      <div className="max-w-7xl mx-auto">
+        {/* Home Button */}
+        <div className="flex justify-center mb-4">
+          <Link
+            to="/"
+            className="flex items-center text-blue-600 hover:text-blue-800 transition-colors"
+          >
+            <FaHome className="mr-2" />
+            Home
+          </Link>
+        </div>
+
+        {/* Search Section */}
+        <div className="max-w-md mx-auto mb-16">
+          <h1 className="text-4xl font-extrabold text-center mb-8 text-gray-800 tracking-tight">
+            <span className="bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-indigo-600">
+              Character Mind Map
+            </span>
+          </h1>
+          <div className="bg-white/80 backdrop-blur-sm shadow-xl rounded-xl p-8 space-y-6 transform transition-all duration-300 hover:scale-[1.02]">
+            <form onSubmit={handleSubmit} className="space-y-4">
+              <div className="relative">
+                <div className="w-full px-5 py-3 rounded-lg border-2 border-dashed border-gray-200
+                    focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-200
+                    transition-all duration-200 bg-white/90 text-center">
+                  <label className="flex flex-col items-center justify-center cursor-pointer">
+                    {filePath ? (
+                      <>
+                        <div className="flex items-center text-blue-500">
+                          <svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
+                          </svg>
+                          <span className="truncate max-w-xs">{filePath.split('/').pop()}</span>
+                        </div>
+                        <span className="text-xs text-gray-500 mt-1">Click to change file</span>
+                      </>
+                    ) : (
+                      <>
+                        <svg className="w-8 h-8 text-gray-400 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+                          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
+                        </svg>
+                        <span className="text-gray-500">Upload book file (.txt, .pdf, .doc, .docx)</span>
+                      </>
+                    )}
+                    <input
+                      type="file"
+                      className="hidden"
+                      accept=".txt,.pdf,.doc,.docx"
+                      onChange={(e) => {
+                        const file = e.target.files?.[0];
+                        if (file) {
+                          setFilePath(file.name);
+                          setFileObject(file);
+                        }
+                      }}
+                      disabled={isLoading}
+                    />
+                  </label>
+                </div>
+              </div>
+              <button
+                type="submit"
+                disabled={isLoading}
+                className="w-full bg-gradient-to-r from-blue-500 to-indigo-600
+                       text-white font-semibold py-3 px-6 rounded-lg
+                       transform transition-all duration-200
+                       hover:from-blue-600 hover:to-indigo-700
+                       focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
+                       disabled:opacity-50 disabled:cursor-not-allowed
+                       flex items-center justify-center space-x-2"
+              >
+                <FaSearch className={`${isLoading ? "animate-spin" : ""}`} />
+                <span>{isLoading ? "Generating..." : "Visualize"}</span>
+              </button>
+            </form>
+          </div>
+          <p className="mt-4 text-center text-sm text-gray-600">
+            Search for any book or movie to explore character relationships
+          </p>
+        </div>
+
+        {/* Info Section - Only show when search is complete */}
+        {searchComplete && bookData && (
+          <div className="space-y-8">
+            <div className="bg-white shadow-xl rounded-xl overflow-hidden">
+              <div className="md:flex">
+                <div className="md:flex-shrink-0">
+                  <img
+                    src={bookData.posterUrl}
+                    alt={bookData.title}
+                    className="h-48 w-full object-cover md:h-full md:w-48"
+                  />
+                </div>
+                <div className="p-8">
+                  <div className="flex items-center">
+                    <FaBook className="text-blue-500 mr-2" />
+                    <h1 className="text-3xl font-bold text-gray-800">
+                      {bookData.title}
+                    </h1>
+                  </div>
+                  <p className="mt-2 text-gray-600">{bookData.subtitle}</p>
+                </div>
+              </div>
+            </div>
+
+            <div className="grid md:grid-cols-2 gap-8">
+              <div className="bg-white/80 backdrop-blur-sm shadow-xl rounded-xl p-6">
+                <AISearch bookTitle={bookData.title} />
+              </div>
+              <div className="bg-white/80 backdrop-blur-sm shadow-xl rounded-xl p-6">
+                <CharacterGraph graphData={graphData} />
+              </div>
+            </div>
+          </div>
+        )}
+      </div>
+    </div>
+  );
+}

+ 78 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/pages/homePage/components/Features.jsx

@@ -0,0 +1,78 @@
+import { BookOpen, MessageSquare, FileText, Users } from "lucide-react";
+
+const features = [
+  {
+    icon: BookOpen,
+    title: "Interactive Mind Maps",
+    description:
+      "Visualize relationships between characters and plot elements.",
+  },
+  {
+    icon: MessageSquare,
+    title: "AI Chatbot",
+    description:
+      "Ask deep questions about the book and get insightful answers.",
+  },
+  {
+    icon: FileText,
+    title: "Book Summaries",
+    description: "Get concise overviews of plots and themes.",
+  },
+  {
+    icon: Users,
+    title: "Community Contributions",
+    description: "Add and refine maps with fellow book lovers.",
+  },
+];
+
+export default function Features() {
+  return (
+    <section className="py-24 bg-gradient-to-b from-white to-indigo-50">
+      <div className="container mx-auto px-4">
+        <div className="text-center max-w-3xl mx-auto mb-16">
+          <h2 className="text-4xl md:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-indigo-600 to-blue-500 mb-4">
+            Key Features
+          </h2>
+          <p className="text-lg text-indigo-600/80">
+            Discover the power of AI-driven book analysis
+          </p>
+        </div>
+
+        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
+          {features.map((feature, index) => (
+            <div
+              key={index}
+              className="group relative bg-white rounded-xl p-8 
+                         shadow-lg transition-all duration-300 
+                         hover:shadow-2xl hover:-translate-y-1
+                         hover:bg-gradient-to-br hover:from-indigo-50 hover:to-blue-50
+                         border border-indigo-100"
+            >
+              <div
+                className="absolute inset-0 bg-gradient-to-r from-indigo-500/5 to-blue-500/5 
+                            rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"
+              />
+              <feature.icon
+                className="w-12 h-12 text-indigo-600 mx-auto mb-6 
+                                    transition-transform duration-300 
+                                    group-hover:scale-110 group-hover:rotate-3"
+              />
+              <h3
+                className="text-xl font-bold text-gray-900 mb-3 
+                           group-hover:text-indigo-700 transition-colors duration-300"
+              >
+                {feature.title}
+              </h3>
+              <p
+                className="text-gray-600 group-hover:text-indigo-900/80 
+                          transition-colors duration-300 leading-relaxed"
+              >
+                {feature.description}
+              </p>
+            </div>
+          ))}
+        </div>
+      </div>
+    </section>
+  );
+}

+ 53 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/pages/homePage/components/Hero.jsx

@@ -0,0 +1,53 @@
+import { useNavigate } from "react-router-dom";
+import { useState, useEffect } from "react";
+import Lottie from "lottie-react";
+
+export default function Hero() {
+  const navigate = useNavigate();
+  const [animationData, setAnimationData] = useState(null);
+
+  useEffect(() => {
+    const fetchAnimationData = async () => {
+      try {
+        const response = await fetch(
+          "https://lottie.host/909e50db-34ef-47ac-8384-db31f6fc0654/e2xX6qZZ7i.json"
+        );
+        const data = await response.json();
+        setAnimationData(data);
+      } catch (error) {
+        console.error("Error fetching Lottie animation:", error);
+      }
+    };
+
+    fetchAnimationData();
+  }, []);
+
+  return (
+    <section className="relative h-screen flex items-center justify-center overflow-hidden">
+      <div className="absolute inset-0 z-0">
+        {animationData && (
+          <Lottie
+            animationData={animationData}
+            style={{ width: "100%", height: "100%" }}
+          />
+        )}
+      </div>
+      <div className="absolute inset-0 bg-gradient-to-b from-transparent to-indigo-900 opacity-75 z-0"></div>
+      <div className="relative z-10 text-center space-y-6 max-w-4xl mx-auto px-4">
+        <h1 className="text-5xl md:text-6xl font-bold text-white leading-tight">
+          Unravel Stories <br /> One Map at a Time
+        </h1>
+        <p className="text-xl md:text-2xl text-white">
+          Explore character relationships and storylines with AI-powered
+          visualizations.
+        </p>
+        <button
+          onClick={() => navigate("/search")}
+          className="bg-gradient-to-r from-blue-500 to-indigo-600 hover:from-blue-600 hover:to-indigo-700 text-white font-semibold py-3 px-8 rounded-full text-lg transition-all duration-300 transform hover:scale-105"
+        >
+          Get Started
+        </button>
+      </div>
+    </section>
+  );
+}

+ 89 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/pages/homePage/components/HowItWorks.jsx

@@ -0,0 +1,89 @@
+import { useState, useEffect } from "react";
+import { Brain, Search, MessageSquare } from "lucide-react";
+
+const steps = [
+  {
+    icon: Search,
+    title: "Search for a Book",
+    description: "Enter the title of the book you want to explore.",
+  },
+  {
+    icon: Brain,
+    title: "AI Analysis",
+    description: "The AI analyzes the book and generates a mind map.",
+  },
+  {
+    icon: MessageSquare,
+    title: "Explore Insights",
+    description:
+      "Ask questions and explore relationships, themes, and insights.",
+  },
+];
+
+export default function HowItWorks() {
+  const [activeStep, setActiveStep] = useState(0);
+
+  useEffect(() => {
+    const interval = setInterval(() => {
+      setActiveStep((prevStep) => (prevStep + 1) % steps.length);
+    }, 3000);
+    return () => clearInterval(interval);
+  }, []);
+
+  return (
+    <section className="py-24 bg-gradient-to-b from-indigo-50 to-white">
+      <div className="container mx-auto px-4">
+        <div className="text-center max-w-3xl mx-auto mb-16">
+          <h2 className="text-4xl md:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-indigo-600 to-blue-500 mb-4">
+            How It Works
+          </h2>
+          <p className="text-lg text-indigo-600/80">
+            Discover the power of AI-driven book analysis
+          </p>
+        </div>
+        <div className="flex flex-col md:flex-row justify-center items-center space-y-8 md:space-y-0 md:space-x-8">
+          {steps.map((step, index) => (
+            <div
+              key={index}
+              className={`group relative bg-white rounded-xl p-8 
+                         shadow-lg transition-all duration-300 
+                         hover:shadow-2xl hover:-translate-y-1
+                         hover:bg-gradient-to-br hover:from-indigo-50 hover:to-blue-50
+                         border border-indigo-100 ${
+                           index === activeStep
+                             ? "scale-110 shadow-xl"
+                             : "scale-100"
+                         }`}
+            >
+              <div
+                className="absolute inset-0 bg-gradient-to-r from-indigo-500/5 to-blue-500/5 
+                            rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"
+              />
+              <step.icon
+                className={`w-16 h-16 mx-auto mb-6 
+                            transition-transform duration-300 
+                            group-hover:scale-110 group-hover:rotate-3 ${
+                              index === activeStep
+                                ? "text-indigo-600"
+                                : "text-indigo-400"
+                            }`}
+              />
+              <h3
+                className="text-xl font-bold text-gray-900 mb-3 
+                           group-hover:text-indigo-700 transition-colors duration-300"
+              >
+                {step.title}
+              </h3>
+              <p
+                className="text-gray-600 group-hover:text-indigo-900/80 
+                          transition-colors duration-300 leading-relaxed"
+              >
+                {step.description}
+              </p>
+            </div>
+          ))}
+        </div>
+      </div>
+    </section>
+  );
+}

+ 13 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/pages/homePage/index.jsx

@@ -0,0 +1,13 @@
+import Hero from "./components/Hero";
+import Features from "./components/Features";
+import HowItWorks from "./components/HowItWorks";
+
+export default function Home() {
+  return (
+    <main className="min-h-screen bg-gradient-to-b from-indigo-100 to-white">
+      <Hero />
+      <Features />
+      <HowItWorks />
+    </main>
+  );
+}

+ 13 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/reportWebVitals.js

@@ -0,0 +1,13 @@
+const reportWebVitals = onPerfEntry => {
+  if (onPerfEntry && onPerfEntry instanceof Function) {
+    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+      getCLS(onPerfEntry);
+      getFID(onPerfEntry);
+      getFCP(onPerfEntry);
+      getLCP(onPerfEntry);
+      getTTFB(onPerfEntry);
+    });
+  }
+};
+
+export default reportWebVitals;

+ 5 - 0
end-to-end-use-cases/long_context/book-character-mindmap/src/setupTests.js

@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom';

+ 23 - 0
end-to-end-use-cases/long_context/book-character-mindmap/tailwind.config.js

@@ -0,0 +1,23 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+  content: ["./src/**/*.{js,jsx,ts,tsx}"],
+  theme: {
+    extend: {
+      keyframes: {
+        fadeInUp: {
+          "0%": { opacity: 0, transform: "translateY(20px)" },
+          "100%": { opacity: 1, transform: "translateY(0)" },
+        },
+        fadeOutDown: {
+          "0%": { opacity: 1, transform: "translateY(0)" },
+          "100%": { opacity: 0, transform: "translateY(-20px)" },
+        },
+      },
+      animation: {
+        fadeInUp: "fadeInUp 0.3s ease-out",
+        fadeOutDown: "fadeOutDown 0.3s ease-in",
+      },
+    },
+  },
+  plugins: [],
+};