From 5fb6d5107750e1ab6ae0ae8188c459aaeed70ae8 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 30 Sep 2024 16:55:17 +0300 Subject: [PATCH 001/115] commit --- app/components/chat.tsx | 39 ++++++++++------ app/components/code-graph.tsx | 85 ++++++++++++++++------------------- app/globals.css | 37 +++++++-------- app/page.tsx | 15 ++++--- public/falkordb-circle.svg | 21 +++++++++ public/falkordb-white.svg | 18 ++++++++ 6 files changed, 130 insertions(+), 85 deletions(-) create mode 100644 public/falkordb-circle.svg create mode 100644 public/falkordb-white.svg diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 04025737..f7cb87c0 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react"; import { QUESTIONS } from "../api/repo/questions"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import Image from "next/image"; +import { SendHorizonal } from "lucide-react"; enum MessageTypes { Query, @@ -74,6 +75,7 @@ export function Chat(props: { repo: string }) { } return [...messages, { text: data.result, type: MessageTypes.Response }]; }); + setQuery("") }).catch((error) => { setMessages(function (messages) { if (messages[messages.length - 1].type === MessageTypes.Pending) { @@ -82,6 +84,7 @@ export function Chat(props: { repo: string }) { } return messages }); + setQuery("") toast({ variant: "destructive", title: "Uh oh! Something went wrong.", @@ -109,23 +112,29 @@ export function Chat(props: { repo: string }) { return ( <> -
+
+ +

Set your query

+
+
{ messages.map((message, index) => { if (message.type === MessageTypes.Query) { - return (
-
-

{message.text}

+ return (
+
+
+

{message.text}

) } else if (message.type === MessageTypes.Response) { - return (
-
-

{message.text}

+ return (
+
+
+

{message.text}

) } else { - return (
+ return (
Waiting for response
@@ -134,10 +143,10 @@ export function Chat(props: { repo: string }) { }) }
-
+
{props.repo && -
- @@ -148,9 +157,11 @@ export function Chat(props: { repo: string }) { }) } - - - + */} + +
}
diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 35db7e49..28b69fea 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -1,4 +1,3 @@ -import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import CytoscapeComponent from 'react-cytoscapejs' import { useContext, useEffect, useRef, useState } from "react"; @@ -6,12 +5,11 @@ import { Category, Node } from "./model"; import { RESPOSITORIES } from "../api/repo/repositories"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { GraphContext } from "./provider"; - import cytoscape from 'cytoscape'; import fcose from 'cytoscape-fcose'; -import { Skeleton } from "@/components/ui/skeleton"; import { Toolbar } from "./toolbar"; import { Labels } from "./labels"; +import { GitFork, Search } from "lucide-react"; const LIMITED_MODE = process.env.NEXT_PUBLIC_MODE?.toLowerCase() === 'limited'; @@ -118,11 +116,11 @@ export function CodeGraph(parmas: { onFetchGraph: (url: string) => void, onFetch parmas.onFetchGraph(value) } - const defaultRepo = RESPOSITORIES[0]; - // Fetch the default graph on first render - useEffect(() => { - onRepoSelected(defaultRepo) - }, []); + // const defaultRepo = RESPOSITORIES[0]; + // // Fetch the default graph on first render + // useEffect(() => { + // onRepoSelected(defaultRepo) + // }, []); function onCategoryClick(category: Category) { let chart = chartRef.current @@ -140,37 +138,40 @@ export function CodeGraph(parmas: { onFetchGraph: (url: string) => void, onFetch } return ( - <> -
-
- - { - !LIMITED_MODE && - <> - - - - } -
+
+
+

Knowledge Graph

+
-
+
{graph.Id ? ( <> -
+
- + +
+ { + !LIMITED_MODE && +
+ + +
+ } +
void, onFetch ) : ( -
-
- Loading Repository Graph... -
-
- -
- - -
-
+
+ +

Select a repo to show its graph here

) }
- +
) } \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index 6a757250..00f62bf7 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,7 +1,7 @@ @tailwind base; @tailwind components; @tailwind utilities; - + @layer base { :root { --background: 0 0% 100%; @@ -9,67 +9,68 @@ --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; - + --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; - + --primary: 222.2 47.4% 11.2%; --primary-foreground: 210 40% 98%; - + --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; - + --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; - + --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; - + --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 222.2 84% 4.9%; - + --radius: 0.5rem; } - + .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; - + --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; - + --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; - + --primary: 210 40% 98%; --primary-foreground: 222.2 47.4% 11.2%; - + --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; - + --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; - + --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; - + --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; - + --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 212.7 26.8% 83.9%; } } - + @layer base { * { @apply border-border; } + body { @apply bg-background text-foreground; } diff --git a/app/page.tsx b/app/page.tsx index 3981a1cc..95c4b4b7 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -9,6 +9,7 @@ import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; import { CodeGraph } from './components/code-graph'; import { toast } from '@/components/ui/use-toast'; import { GraphContext } from './components/provider'; +import Image from 'next/image'; export default function Home() { @@ -70,28 +71,28 @@ export default function Home() { return (
-
+
- + FalkorDB

Code Graph by FalkorDB

- - + + - - + + diff --git a/public/falkordb-circle.svg b/public/falkordb-circle.svg new file mode 100644 index 00000000..cebff978 --- /dev/null +++ b/public/falkordb-circle.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/public/falkordb-white.svg b/public/falkordb-white.svg new file mode 100644 index 00000000..1afdce2a --- /dev/null +++ b/public/falkordb-white.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + From b652d8b11d19c13357efd059ac5c0a21c663730c Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 30 Sep 2024 17:02:46 +0300 Subject: [PATCH 002/115] remove header border --- app/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/page.tsx b/app/page.tsx index 95c4b4b7..b31b651c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -71,7 +71,7 @@ export default function Home() { return (
-
+
FalkorDB From a80b1742bd249b676f7e0805a6536a0f4f0dc331 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 28 Oct 2024 18:01:28 +0200 Subject: [PATCH 003/115] restyle --- app/api/repo/[graph]/[node]/route.ts | 57 ++- app/api/repo/[graph]/route.ts | 296 -------------- app/api/repo/[graph]/route.tsx | 89 +++++ app/api/repo/graph_ops.ts | 552 -------------------------- app/api/repo/questions.ts | 7 - app/api/repo/repositories.ts | 5 - app/api/repo/route.ts | 332 ++-------------- app/components/Input.tsx | 150 ++++++++ app/components/chat.tsx | 357 ++++++++++++++--- app/components/code-graph.tsx | 555 +++++++++++++++++++++------ app/components/combobox.tsx | 28 ++ app/components/dataPanel.tsx | 74 ++++ app/components/elementMenu.tsx | 92 +++++ app/components/elementTooltip.tsx | 30 ++ app/components/labels.tsx | 42 +- app/components/model.ts | 73 +++- app/components/toolbar.tsx | 54 ++- app/globals.css | 1 + app/layout.tsx | 3 +- app/page.tsx | 245 +++++++++--- components/ui/checkbox.tsx | 30 ++ components/ui/dialog.tsx | 122 ++++++ components/ui/dropdown-menu.tsx | 200 ++++++++++ components/ui/select.tsx | 2 +- package-lock.json | 463 ++++++++++++++++++++++ package.json | 3 + public/color-logo.svg | 23 ++ public/falkordb-white.svg | 18 - 28 files changed, 2396 insertions(+), 1507 deletions(-) delete mode 100644 app/api/repo/[graph]/route.ts create mode 100644 app/api/repo/[graph]/route.tsx delete mode 100644 app/api/repo/graph_ops.ts delete mode 100644 app/api/repo/questions.ts delete mode 100644 app/api/repo/repositories.ts create mode 100644 app/components/Input.tsx create mode 100644 app/components/combobox.tsx create mode 100644 app/components/dataPanel.tsx create mode 100644 app/components/elementMenu.tsx create mode 100644 app/components/elementTooltip.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 public/color-logo.svg delete mode 100644 public/falkordb-white.svg diff --git a/app/api/repo/[graph]/[node]/route.ts b/app/api/repo/[graph]/[node]/route.ts index 277e3945..fa22cfbf 100644 --- a/app/api/repo/[graph]/[node]/route.ts +++ b/app/api/repo/[graph]/[node]/route.ts @@ -1,24 +1,51 @@ -import { FalkorDB, Graph } from "falkordb"; import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest, { params }: { params: { graph: string, node: string } }) { - - const nodeId = parseInt(params.node); + + const nodeId = parseInt(params.node); const graphId = params.graph; + try { + + const result = await fetch(`http://localhost:5000/get_neighbors?repo=${graphId}&node_id=${nodeId}`, { + method: 'GET', + }) + + const json = await result.json() + + return NextResponse.json({ result: json }, { status: 200 }) + } catch (err) { + return NextResponse.json({ massage: (err as Error).message }, { status: 400 }) + } +} + +export async function POST(request: NextRequest, { params }: { params: { graph: string, node: string } }) { + + const nodeId = params.node; + const graphId = params.graph; + const targetId = request.nextUrl.searchParams.get('targetId') + + try { - const db = await FalkorDB.connect({url: process.env.FALKORDB_URL || 'falkor://localhost:6379',}); - const graph = db.selectGraph(graphId); + const result = await fetch(`http://localhost:5000/find_paths`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + repo: graphId, + src: Number(nodeId), + dest: Number(targetId!) + }) + }) - // Get node's neighbors - const q_params = {nodeId: nodeId}; - const query = `MATCH (src)-[e]-(n) - WHERE ID(src) = $nodeId - RETURN collect(distinct { label:labels(n)[0], id:ID(n), name: n.name } ) as nodes, - collect( { src: ID(startNode(e)), id: ID(e), dest: ID(endNode(e)), type: type(e) } ) as edges`; + if (!result.ok) { + throw new Error(await result.text()) + } - let res: any = await graph.query(query, { params: q_params }); - let nodes = res.data[0]['nodes']; - let edges = res.data[0]['edges']; + const json = await result.json() - return NextResponse.json({ id: graphId, nodes: nodes, edges: edges }, { status: 200 }) + return NextResponse.json({ result: json }, { status: 200 }) + } catch (err) { + return NextResponse.json({ massage: (err as Error).message }, { status: 200 }) + } } \ No newline at end of file diff --git a/app/api/repo/[graph]/route.ts b/app/api/repo/[graph]/route.ts deleted file mode 100644 index 1fe88b8b..00000000 --- a/app/api/repo/[graph]/route.ts +++ /dev/null @@ -1,296 +0,0 @@ -import OpenAI from "openai"; -import { QUESTIONS } from '../questions'; -import { graphSchema } from "../graph_ops"; -import { FalkorDB, Graph } from 'falkordb'; -import { NextRequest, NextResponse } from "next/server"; -import { ChatCompletionCreateParams, ChatCompletionMessageParam, ChatCompletionMessageToolCall, ChatCompletionTool } from 'openai/resources/chat/completions.mjs'; - -// convert a structured graph schema into a string representation -// used in a model prompt -async function GraphSchemaToPrompt( - graph: Graph, - graphId: string, - db: FalkorDB -) { - // Retrieve graph schema - let schema: any = await graphSchema(graphId, db); - - // Build a string description of graph schema - let desc: string = "The knowladge graph schema is as follows:\n"; - - //------------------------------------------------------------------------- - // Describe labels - //------------------------------------------------------------------------- - - // list labels - desc = desc + "The graph contains the following node labels:\n"; - for (const lbl in schema["labels"]) { - desc = desc + `${lbl}\n`; - } - - // specify attributes associated with each label - for (const lbl in schema["labels"]) { - let node_count = schema["labels"][lbl]['node_count']; - let attributes = schema["labels"][lbl]['attributes']; - let attr_count = Object.keys(attributes).length; - - if (attr_count == 0) { - desc = desc + `the ${lbl} label has ${node_count} nodes and has no attributes\n`; - } else { - desc = desc + `the ${lbl} label has ${node_count} nodes and is associated with the following attribute(s):\n`; - for (const attr in attributes) { - let type = attributes[attr]['type']; - desc = desc + `'${attr}' which is of type ${type}\n`; - } - } - } - - desc = desc + "The graph contains the following relationship types:\n" - - //------------------------------------------------------------------------- - // Describe relationships - //------------------------------------------------------------------------- - - // list relations - for (const relation in schema["relations"]) { - desc = desc + `${relation}\n`; - } - - // specify attributes associated with each relationship - for (const relation in schema["relations"]) { - let connect = schema["relations"][relation]['connect']; - let edge_count = schema["relations"][relation]['edge_count']; - let attributes = schema["relations"][relation]['attributes']; - let attr_count = Object.keys(attributes).length; - - if (attr_count == 0) { - desc = desc + `the ${relation} relationship has ${edge_count} edges and has no attributes\n`; - } else { - desc = desc + `the ${relation} relationship has ${edge_count} edges and is associated with the following attribute(s):\n`; - for (const attr in attributes) { - let type = attributes[attr]['type']; - desc = desc + `'${attr}' which is of type ${type}\n`; - } - } - - if (connect.length > 0) { - desc = desc + `the ${relation} relationship connects the following labels:\n` - for (let i = 0; i < connect.length; i += 2) { - let src = connect[i]; - let dest = connect[i + 1]; - desc = desc + `${src} is connected via ${relation} to ${dest}\n`; - } - } - } - - desc = desc + `This is the end of the knowladge graph schema description.\n` - - //------------------------------------------------------------------------- - // include graph indices - //------------------------------------------------------------------------- - - // vector indices - let query = `CALL db.indexes() YIELD label, properties, types, entitytype`; - let res = await graph.query(query); - - // process indexes - let indexes: any = res.data; - if (indexes.length > 0) { - let index_prompt = "The knowladge graph contains the following indexes:\n" - for (let i = 0; i < indexes.length; i++) { - const index = indexes[i]; - const label: string = index['label']; - const entityType: string = index['entitytype']; - const props = index['properties']; - const types = index['types']; - - for (const prop of props) { - const propTypes: string[] = types[prop]; - for (let j = 0; j < propTypes.length; j++) { - const idxType: string = propTypes[j]; - index_prompt += `${entityType} of type ${label} have a ${idxType} index indexing its ${prop} attribute\n`; - } - } - } - index_prompt += `This is the end of our indexes list - To use a Vector index use the following procedure: - CALL db.idx.vector.queryNodes(
- + ); } diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 28b69fea..2aa0902b 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -1,17 +1,31 @@ -import { Input } from "@/components/ui/input"; import CytoscapeComponent from 'react-cytoscapejs' -import { useContext, useEffect, useRef, useState } from "react"; -import { Category, Node } from "./model"; -import { RESPOSITORIES } from "../api/repo/repositories"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Dispatch, MutableRefObject, SetStateAction, useContext, useEffect, useRef, useState } from "react"; +import { Node } from "./model"; import { GraphContext } from "./provider"; -import cytoscape from 'cytoscape'; +import cytoscape, { ElementDefinition, EventObject, Position } from 'cytoscape'; import fcose from 'cytoscape-fcose'; import { Toolbar } from "./toolbar"; import { Labels } from "./labels"; -import { GitFork, Search } from "lucide-react"; +import { ChevronLeft, ChevronRight, GitFork, Search } from "lucide-react"; +import ElementMenu from "./elementMenu"; +import ElementTooltip from "./elementTooltip"; +import Combobox from "./combobox"; +import { toast } from '@/components/ui/use-toast'; +import { cn } from '@/lib/utils'; +import { Path } from '../page'; +import Input from './Input'; -const LIMITED_MODE = process.env.NEXT_PUBLIC_MODE?.toLowerCase() === 'limited'; +interface Props { + onFetchGraph: (graphName: string) => void, + onFetchNode: (node: Node) => Promise, + options: string[] + isShowPath: boolean + setPath: Dispatch> + chartRef: MutableRefObject + selectedValue: string + setSelectedPathId: (selectedPathId: string) => void + isPathResponse: boolean +} // The stylesheet for the graph const STYLESHEET: cytoscape.Stylesheet[] = [ @@ -35,16 +49,19 @@ const STYLESHEET: cytoscape.Stylesheet[] = [ selector: "node", style: { label: "data(name)", + "color": "black", "text-valign": "center", - "text-halign": "center", + "text-wrap": "ellipsis", + "text-max-width": "10rem", shape: "ellipse", - height: 10, - width: 10, - "border-width": 0.15, + height: "15rem", + width: "15rem", + "border-width": 0.3, + "border-color": "black", "border-opacity": 0.5, "background-color": "data(color)", - "font-size": "3", - "overlay-padding": "1px", + "font-size": "3rem", + "overlay-padding": "1rem", }, }, { @@ -53,6 +70,30 @@ const STYLESHEET: cytoscape.Stylesheet[] = [ "overlay-opacity": 0, // hide gray box around active node }, }, + { + selector: "node:selected", + style: { + 'border-width': 0.5, + 'border-color': 'black', + 'border-opacity': 1, + }, + }, + { + selector: "node[?isPath]", + style: { + 'border-width': 0.5, + 'border-color': 'pink', + 'border-opacity': 1, + }, + }, + { + selector: "node[?isPathStartEnd]", + style: { + 'border-width': 1, + 'border-color': 'pink', + 'border-opacity': 1, + }, + }, { selector: "edge", style: { @@ -60,6 +101,7 @@ const STYLESHEET: cytoscape.Stylesheet[] = [ "line-color": "#ccc", "arrow-scale": 0.3, "target-arrow-shape": "triangle", + "target-arrow-color": "#ccc", label: "data(label)", 'curve-style': 'straight', "text-background-color": "#ffffff", @@ -68,67 +110,132 @@ const STYLESHEET: cytoscape.Stylesheet[] = [ "overlay-padding": "2px", }, }, + { + selector: "edge:active", + style: { + "overlay-opacity": 0, // hide gray box around active node + }, + }, + { + selector: "edge[?isPath]", + style: { + "line-style": "dashed", + "line-color": "pink", + "target-arrow-color": "pink", + }, + } ] - cytoscape.use(fcose); -const LAYOUT = { +export const LAYOUT = { name: "fcose", fit: true, - padding: 30, + padding: 80, avoidOverlap: true, } -export function CodeGraph(parmas: { onFetchGraph: (url: string) => void, onFetchNode: (node: Node) => Promise }) { +const COMMIT_LIMIT = 7 - let graph = useContext(GraphContext) +export function CodeGraph({ + onFetchGraph, + onFetchNode, + options, + isShowPath, + setPath, + chartRef, + selectedValue, + setSelectedPathId, + isPathResponse +}: Props) { - // Holds the user input while typing - const [url, setURL] = useState(''); + let graph = useContext(GraphContext) - // A reference to the chart container to allowing zooming and editing - const chartRef = useRef(null) + const [url, setURL] = useState(""); + const [selectedObj, setSelectedObj] = useState(); + const [tooltipLabel, setTooltipLabel] = useState(); + const [position, setPosition] = useState(); + const [tooltipPosition, setTooltipPosition] = useState(); + const [graphName, setGraphName] = useState(""); + const [searchNodeName, setSearchNodeName] = useState(""); + const [commits, setCommits] = useState([]); + const [nodesCount, setNodesCount] = useState(0); + const [edgesCount, setEdgesCount] = useState(0); + const [commitIndex, setCommitIndex] = useState(0); + const [currentCommit, setCurrentCommit] = useState(0); + const containerRef = useRef(null); - // A function that handles the change event of the url input box - async function handleRepoInputChange(event: any) { + async function fetchCount() { + const result = await fetch(`/api/repo/${graphName}`, { + method: 'POST' + }) - // If the user pressed enter, submit the URL - if (event.key === "Enter") { - await handleSubmit(event); + if (!result.ok) { + toast({ + variant: "destructive", + title: "Uh oh! Something went wrong.", + description: await result.text(), + }) + return } - // Get the new value of the input box - let value: string = event.target.value; + const json = await result.json() - // Update the url state - setURL(value); + setNodesCount(json.result.info.node_count) + setEdgesCount(json.result.info.edge_count) + setURL(json.result.info.repo_url) } - // A function that handles the click event - async function handleSubmit(event: any) { - event.preventDefault(); - parmas.onFetchGraph(url); - } + useEffect(() => { + if (!selectedValue) return + handelSelectedValue(selectedValue) + }, [selectedValue]) - function onRepoSelected(value: string): void { - setURL(value) - parmas.onFetchGraph(value) - } + useEffect(() => { + if (!graphName) return + + const run = async () => { + fetchCount() + const result = await fetch(`/api/repo/${graphName}/?type=commit`, { + method: 'POST' + }) - // const defaultRepo = RESPOSITORIES[0]; - // // Fetch the default graph on first render - // useEffect(() => { - // onRepoSelected(defaultRepo) - // }, []); + if (!result.ok) { + toast({ + variant: "destructive", + title: "Uh oh! Something went wrong.", + description: await result.text(), + }) + return + } - function onCategoryClick(category: Category) { + const json = await result.json() + const commitsArr = json.result.commits + setCommits(commitsArr) + setCurrentCommit(commitsArr[commitsArr.length - 1].hash) + setCommitIndex(commitsArr.length) + } + + run() + }, [graphName]) + + function handelSelectedValue(value: string) { + setGraphName(value) + onFetchGraph(value) + } + + function onCategoryClick(name: string, show: boolean) { let chart = chartRef.current if (chart) { - let elements = chart.elements(`node[category = "${category.name}"]`) - category.show = !category.show + let elements = chart.elements(`node[category = "${name}"]`) + + graph.Categories.forEach((category) => { + if (category.name === name) { + category.show = show + } + }) - if (category.show) { + if (show) { elements.style({ display: 'element' }) } else { elements.style({ display: 'none' }) @@ -137,78 +244,292 @@ export function CodeGraph(parmas: { onFetchGraph: (url: string) => void, onFetch } } + const deleteNeighbors = (node: Node, chart: cytoscape.Core) => { + const neighbors = chart.elements(`#${node.id}`).outgoers() + neighbors.forEach((n) => { + const id = n.id() + const index = graph.Elements.findIndex(e => e.data.id === id); + const element = graph.Elements[index] + + if (index === -1 || !element.data.collapsed) return + + const type = "category" in element.data + + if (element.data.expand) { + deleteNeighbors(element.data, chart) + } + + graph.Elements.splice(index, 1); + + if (type) { + graph.NodesMap.delete(Number(id)) + } else { + graph.EdgesMap.delete(Number(id.split('')[1])) + } + + chart.remove(`#${id}`) + }) + + } + + const handleDoubleTap = async (evt?: EventObject) => { + + const chart = chartRef.current + + if (!chart) return + + let node: Node + let elements: ElementDefinition[] + + if (evt) { + const { target } = evt + target.unselect() + node = target.json().data; + } else { + node = selectedObj! + } + + const graphNode = graph.Elements.find(e => e.data.id === node.id); + + if (!graphNode) return + + if (!graphNode.data.expand) { + elements = await onFetchNode(node) + + if (elements.length === 0) { + toast({ + title: "No neighbors found", + }) + return + } + + chart.add(elements); + chart.elements().layout(LAYOUT).run(); + } else { + deleteNeighbors(node, chart) + } + + graphNode.data.expand = !graphNode.data.expand; + + setSelectedObj(undefined) + } + + const handelTap = (evt: EventObject) => { + const chart = chartRef.current + + if (!chart) return + + const { target } = evt + setTooltipLabel(undefined) + + if (isShowPath) { + setPath(prev => { + if (!prev?.start?.name || (prev.end?.name && prev.end?.name !== "")) { + return ({ start: { id: Number(target.id()), name: target.data().name as string } }) + } else { + return ({ end: { id: Number(target.id()), name: target.data().name as string }, start: prev.start }) + } + }) + return + } + + const position = target.renderedPosition() + setPosition(() => position ? { x: position.x, y: position.y + chart.zoom() * 8 } : { x: 0, y: 0 }); + setSelectedObj(target.json().data) + } + + const handelSearchSubmit = (node: any) => { + const chart = chartRef.current + + if (!chart) return + + let chartNode = chart.elements(`node[name = "${node.properties.name}"]`) + + if (chartNode.length === 0) { + const [newNode] = graph.extend({ nodes: [node], edges: [] }) + chartNode = chart.add(newNode) + } + + chartNode.select() + const layout = { ...LAYOUT, padding: 250 } + chartNode.layout(layout).run() + } + return ( -
+
-

Knowledge Graph

- +
-
- {graph.Id ? - ( - <> -
- - -
- { - !LIMITED_MODE && -
- - -
- } -
-
+
+ +
+ { + graph.Id ? +
+
+ setSearchNodeName(node.name!)} + icon={} + handelSubmit={handelSearchSubmit} + /> + +
+
+
+

{nodesCount} Nodes

+

{edgesCount} Edges

+
+ +
+ + + { + chartRef.current = cy + + // Make sure no previous listeners are attached + cy.removeAllListeners(); + + // Listen to the click event on nodes for expanding the node + cy.on('dbltap', 'node', handleDoubleTap); + + cy.on('mousedown', (evt) => { + setTooltipLabel(undefined) + const { target } = evt + + if (target !== cy && !target.isEdge()) return; + + setSelectedObj(undefined) + }) + + cy.on('mouseout', (evt) => { + const { target } = evt + + if (target === cy || target.isEdge()) { + setTooltipLabel(undefined) + return + } + + setTooltipLabel(undefined) + + if (selectedObj) return - { - chartRef.current = cy - - // Make sure no previous listeners are attached - cy.removeAllListeners(); - - // Listen to the click event on nodes for expanding the node - cy.on('dbltap', 'node', async function (evt) { - var node: Node = evt.target.json().data; - let elements = await parmas.onFetchNode(node); - //cy.add(elements).layout(LAYOUT).run() - - // adjust entire graph. - if (elements.length > 0) { - cy.add(elements); - cy.elements().layout(LAYOUT).run(); - } - }); - }} - stylesheet={STYLESHEET} - elements={graph.Elements} - layout={LAYOUT} - className="w-full h-full" - /> - - ) : - ( -
- -

Select a repo to show its graph here

-
- ) + target.unselect() + }) + + cy.on('scrollzoom', () => { + setSelectedObj(undefined) + setTooltipLabel(undefined) + }); + + cy.on('mouseover', 'node', (evt) => { + const { target } = evt + target.select() + + if (selectedObj) return + + const position = target.renderedPosition() + + setTooltipPosition(() => ({ x: position.x, y: position.y + cy.zoom() * 8 })); + setTooltipLabel(() => target.json().data.name); + }) + + cy.on('tap', 'node', handelTap); + + cy.on('drag', 'node', () => { + setTooltipLabel(undefined) + setSelectedObj(undefined) + }); + + cy.on('tap', 'edge', (evt) => { + const { target } = evt + + if (!isPathResponse) return + + setSelectedPathId(target.id()) + }); + }} + stylesheet={STYLESHEET} + elements={graph.Elements} + layout={LAYOUT} + className="h-full w-full" + /> +
+ :
+ +

Select a repo to show its graph here

+
+ } +
+ { + graph.Id && +
+ +
    + { + commits.slice(commitIndex - COMMIT_LIMIT, commitIndex).map((commit: any) => { + const date = new Date(commit.date * 1000) + const month = `${date.getDate()} ${date.toLocaleString('default', { month: 'long' })}` + const hour = `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}` + return ( +
  • + +
  • + ) + }) + } +
+ +
} -
+
) } \ No newline at end of file diff --git a/app/components/combobox.tsx b/app/components/combobox.tsx new file mode 100644 index 00000000..59020d6c --- /dev/null +++ b/app/components/combobox.tsx @@ -0,0 +1,28 @@ +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; + +interface Props { + options: string[] + selectedValue: string + onSelectedValue: (value: string) => void + +} + +export default function Combobox({ options, selectedValue, onSelectedValue }: Props) { + return ( + + ) +} \ No newline at end of file diff --git a/app/components/dataPanel.tsx b/app/components/dataPanel.tsx new file mode 100644 index 00000000..611a4aa8 --- /dev/null +++ b/app/components/dataPanel.tsx @@ -0,0 +1,74 @@ +import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; +import { Node } from "./model"; +import { ChevronLeft, ChevronRight, Copy, SquareArrowOutUpRight, X } from "lucide-react"; + +interface Props { + obj: Node | undefined; + setObj: Dispatch>; + url: string; +} + +const excludedProperties = [ + "category", + "color", + "expand", + "collapsed", + "isPath", +] + +export default function DataPanel({ obj, setObj, url }: Props) { + + if (!obj) return null; + + const label = `${obj.category}: ${obj.name}` + const object = Object.fromEntries(Object.entries(obj).filter(([k]) => !excludedProperties.includes(k))) + + return ( +
+
+
+

{label.toUpperCase()}

+
+ +
+
+
+                    {JSON.stringify(object, null, 1)
+                        .replace(/({|}|\[|\]|,)/g, match => match === '}' || match === "]" ? `\n${match}` : `${match}\n`).slice(1, -1)
+                    }
+                
+
+ +
+ ) +} \ No newline at end of file diff --git a/app/components/elementMenu.tsx b/app/components/elementMenu.tsx new file mode 100644 index 00000000..7feac1b3 --- /dev/null +++ b/app/components/elementMenu.tsx @@ -0,0 +1,92 @@ +"use client" + +import { useEffect, useState } from "react"; +import { Node } from "./model"; +import { ChevronLeft, ChevronRight, ChevronsLeftRight, ChevronsRightLeft, Copy, Globe, Maximize2 } from "lucide-react"; +import DataPanel from "./dataPanel"; +import { Position } from "cytoscape"; + +interface Props { + obj: Node | undefined; + position: Position | undefined; + url: string; + handelMaximize: () => void; + parentWidth: number; +} + + +export default function ElementMenu({ obj, position, url, handelMaximize, parentWidth }: Props) { + const [currentObj, setCurrentObj] = useState(); + const [containerWidth, setContainerWidth] = useState(0); + + useEffect(() => { + setCurrentObj(undefined) + }, [obj]) + + if (!obj || !position) return null + + const objURL = obj.category === "File" + ? `${url}/tree/master/${obj.path}/${obj.name}` + : `${url}/tree/master/${obj.path}#L${obj.src_start}-L${obj.src_end + 1}` + + return ( + <> +
{ + if (!ref) return + setContainerWidth(ref.clientWidth) + }} + className="absolute z-10 bg-black rounded-lg shadow-lg flex" + style={{ + left: position.x - containerWidth / 2, + top: position.y + 5 + }} + > + + { + const newTab = window.open(objURL, '_blank'); + + if (!obj.src_start || !obj.src_end || !newTab) return + + newTab.scroll({ + top: obj.src_start, + left: obj.src_end, + behavior: 'smooth' + }) + }} + > + + + + +
+ + + ) +} \ No newline at end of file diff --git a/app/components/elementTooltip.tsx b/app/components/elementTooltip.tsx new file mode 100644 index 00000000..4fcd1390 --- /dev/null +++ b/app/components/elementTooltip.tsx @@ -0,0 +1,30 @@ +import { Position } from "cytoscape"; +import { useState } from "react"; + +interface Props { + label: string | undefined; + position: Position | undefined; + parentWidth: number; +} + +export default function ElementTooltip({ label, position }: Props) { + const [containerWidth, setContainerWidth] = useState(0); + + if (!label || !position) return null + + return ( +
{ + if (!ref) return + setContainerWidth(ref.clientWidth) + }} + className="absolute z-10 bg-white rounded-lg shadow-lg p-3" + style={{ + left: position.x - containerWidth / 2, + top: position.y + }} + > + {label} +
+ ) +} \ No newline at end of file diff --git a/app/components/labels.tsx b/app/components/labels.tsx index 605b538d..716c2a62 100644 --- a/app/components/labels.tsx +++ b/app/components/labels.tsx @@ -1,32 +1,28 @@ -import { Category, getCategoryColorName} from "./model"; +import { Category, getCategoryColorName } from "./model"; import { cn } from "@/lib/utils"; -import { Minus, Plus } from "lucide-react"; +import { Checkbox } from "@/components/ui/checkbox"; import { useState } from "react"; -import { Button } from "@/components/ui/button"; -export function Labels(params: { categories: Category[], className?: string, onClick: (category: Category) => void }) { +export function Labels(params: { categories: Category[], className?: string, onClick: (name: string, show: boolean) => void }) { - // fake stae to force reload const [reload, setReload] = useState(false) return ( -
- {params.categories.map((category) => { - return ( -
- -

{category.name}

-
- ) - })} -
+
+ {params.categories.map((category) => +
+ { + params.onClick(category.name, checked as boolean) + setReload(!reload) + }} + checked={category.show} + /> +

{category.name}

+
+ ) + } +
) } \ No newline at end of file diff --git a/app/components/model.ts b/app/components/model.ts index 0afda744..984a633e 100644 --- a/app/components/model.ts +++ b/app/components/model.ts @@ -1,4 +1,5 @@ import twcolors from 'tailwindcss/colors' +import { Path } from '../page' export interface Category { name: string, @@ -11,12 +12,14 @@ export interface Node { name: string, category: string, color: string, + [key: string]: any, } export interface Edge { source: number, target: number, label: string, + [key: string]: any, } const COLORS_ORDER = [ @@ -34,17 +37,23 @@ const COLORS_ORDER = [ "pink", ] -export function getCategoryColorName(index: number): string { - index = index; + private categoriesMap: Map; private nodesMap: Map; private edgesMap: Map; private constructor(id: string, categories: Category[], elements: any[], - categoriesMap: Map, nodesMap: Map, edgesMap: Map) { + categoriesMap: Map, nodesMap: Map, edgesMap: Map) { this.id = id; this.categories = categories; this.elements = elements; @@ -76,25 +85,38 @@ export class Graph { return this.categories; } + get CategoriesMap(): Map { + return this.categoriesMap; + } + get Elements(): any[] { return this.elements; } + get EdgesMap(): Map { + return this.edgesMap; + } + + get NodesMap(): Map { + return this.nodesMap; + } + public static empty(): Graph { - return new Graph("", [], [], new Map(), new Map(), new Map()) + return new Graph("", [], [], new Map(), new Map(), new Map()) } - public static create(results: any): Graph { + public static create(results: any, graphName: string): Graph { let graph = Graph.empty() graph.extend(results) - graph.id = results.id + graph.id = graphName return graph } - public extend(results: any): any[] { + public extend(results: any, collapsed = false, path?: Path): any[] { let newElements: any[] = [] + results.nodes.forEach((nodeData: any) => { - let label = nodeData.label; + let label = nodeData.labels[0]; // check if category already exists in categories let category = this.categoriesMap.get(label) if (!category) { @@ -106,6 +128,11 @@ export class Graph { // check if node already exists in nodes let node = this.nodesMap.get(nodeData.id) if (node) { + node.isPath = !!path + if (path?.start?.id == nodeData.id || path?.end?.id == nodeData.id) { + node.isPathStartEnd = true + } + node.isPath = !!path return } @@ -114,7 +141,16 @@ export class Graph { name: nodeData.name, color: getCategoryColorValue(category.index), category: category.name, + expand: false, + collapsed, + isPath: !!path, + } + if (path?.start?.id == nodeData.id || path?.end?.id == nodeData.id) { + node.isPathStartEnd = true } + Object.entries(nodeData.properties).forEach(([key, value]) => { + node[key] = value + }) this.nodesMap.set(nodeData.id, node) this.elements.push({ data: node }) newElements.push({ data: node }) @@ -123,16 +159,21 @@ export class Graph { results.edges.forEach((edgeData: any) => { let edge = this.edgesMap.get(edgeData.id) if (edge) { + edge.isPath = !!path return } - let sourceId = edgeData.src.toString(); - let destinationId = edgeData.dest.toString() + let sourceId = edgeData.src_node.toString(); + let destinationId = edgeData.dest_node.toString() edge = { + id: `_${edgeData.id}`, source: sourceId, target: destinationId, - label: edgeData.type, + label: edgeData.relation, + expand: false, + collapsed, + isPath: !!path, } this.edgesMap.set(edgeData.id, edge) this.elements.push({ data: edge }) diff --git a/app/components/toolbar.tsx b/app/components/toolbar.tsx index c497039d..7c948797 100644 --- a/app/components/toolbar.tsx +++ b/app/components/toolbar.tsx @@ -1,5 +1,4 @@ -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; -import { CircleDot, XCircle, ZoomIn, ZoomOut } from "lucide-react"; +import { CircleDot, Minus, Plus } from "lucide-react"; import { cn } from "@/lib/utils" export function Toolbar(params: { @@ -16,39 +15,34 @@ export function Toolbar(params: { function handleCenterClick() { let chart = params.chartRef.current if (chart) { - chart.fit() + chart.fit(undefined, 80) chart.center() } } return ( -
- - - handleZoomClick(1.1)}> - - - -

Zoom In

-
-
- - handleZoomClick(0.9)}> - - - -

Zoom Out

-
-
- - - - - -

Center

-
-
-
+
+ + +
) } \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index 00f62bf7..e22d9a6b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -63,6 +63,7 @@ --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 212.7 26.8% 83.9%; + --radius: 0.5rem; } } diff --git a/app/layout.tsx b/app/layout.tsx index 6afa69ae..7d2475e9 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,6 +3,7 @@ import { Inter } from 'next/font/google' import './globals.css' import { Toaster } from '@/components/ui/toaster' import GoogleAnalytics from './components/GoogleAnalytics' +import { cn } from '@/lib/utils' const inter = Inter({ subsets: ['latin'] }) @@ -18,7 +19,7 @@ export default function RootLayout({ }) { return ( - + {process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS ? ( diff --git a/app/page.tsx b/app/page.tsx index b31b651c..4ccf65f9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,99 +1,232 @@ 'use client' -import { createContext, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Chat } from './components/chat'; import { Graph, Node } from './components/model'; -import { Github, HomeIcon } from 'lucide-react'; +import { BookOpen, Github, HomeIcon } from 'lucide-react'; import Link from 'next/link'; import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; import { CodeGraph } from './components/code-graph'; import { toast } from '@/components/ui/use-toast'; import { GraphContext } from './components/provider'; import Image from 'next/image'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; + +export type PathNode = { + id?: number + name?: string +} + +export type Path = { + start?: PathNode, + end?: PathNode +} export default function Home() { const [graph, setGraph] = useState(Graph.empty()); + const [selectedValue, setSelectedValue] = useState(""); + const [selectedPathId, setSelectedPathId] = useState(); + const [isPathResponse, setIsPathResponse] = useState(false); + const [createURL, setCreateURL] = useState("https://github.com/FalkorDB/GraphRAG-SDK") + const [createOpen, setCreateOpen] = useState(false) + const [options, setOptions] = useState([]); + const [path, setPath] = useState(); + const chartRef = useRef(null) + + useEffect(() => { + const run = async () => { + const result = await fetch(`/api/repo`, { + method: 'GET', + }) - function onFetchGraph(url: string) { - let value = url; - if (!value || value.length === 0) { - value = 'https://github.com/falkorDB/falkordb-py'; + if (!result.ok) { + toast({ + variant: "destructive", + title: "Uh oh! Something went wrong.", + description: await result.text(), + }) + return + } + + const json = await result.json() + setOptions(json.result) } - setGraph(Graph.empty()) + run() + }, []) - // Send the user query to the server to fetch a repo graph - fetch('/api/repo', { + async function onCreateRepo(e: React.FormEvent) { + e.preventDefault() + + if (!createURL) { + toast({ + variant: "destructive", + title: "Uh oh! Something went wrong.", + description: "Please enter a URL.", + }) + return + } + + const result = await fetch(`/api/repo/?url=${createURL}`, { method: 'POST', - body: JSON.stringify({ - url: value + }) + + if (!result.ok) { + toast({ + variant: "destructive", + title: "Uh oh! Something went wrong.", + description: await result.text(), }) - }).then(async (result) => { - if (result.status >= 300) { - throw Error(await result.text()) - } - return result.json() - }).then(data => { - let graph = Graph.create(data); - setGraph(graph); - }).catch((error) => { + return + } + + const graphName = createURL.split('/').pop()! + + setOptions(prev => [...prev, graphName]) + setSelectedValue(graphName) + setCreateURL("") + setCreateOpen(false) + + toast({ + title: "Success", + description: `Project ${graphName} created successfully`, + }) + } + + async function onFetchGraph(graphName: string) { + + setGraph(Graph.empty()) + + const result = await fetch(`/api/repo/${graphName}`, { + method: 'GET' + }) + + if (!result.ok) { toast({ variant: "destructive", title: "Uh oh! Something went wrong.", - description: error.message, - }); - }); + description: await result.text(), + }) + return + } + + const json = await result.json() + setGraph(Graph.create(json.result.entities, graphName)) } // Send the user query to the server to expand a node async function onFetchNode(node: Node) { - return fetch(`/api/repo/${graph.Id}/${node.id}`, { + const result = await fetch(`/api/repo/${graph.Id}/${node.id}`, { method: 'GET' - }).then(async (result) => { - if (result.status >= 300) { - throw Error(await result.text()) - } - return result.json() - }).then(data => { - let newElements = graph.extend(data) - setGraph(graph) - return newElements - }).catch((error) => { + }) + + if (!result.ok) { toast({ variant: "destructive", title: "Uh oh! Something went wrong.", - description: error.message, + description: await result.text(), }) - return [] as any[] - }) + return [] + } + + const json = await result.json() + + return graph.extend(json.result.neighbors, true) } return (
-
- - FalkorDB - -

- Code Graph by FalkorDB -

- +
+
+
+ + FalkorDB + +

+ CODE GRAPH BY FALKORDB +

+
+
    + + +

    Home

    + + + +

    Github

    + + + +

    Tip

    + + + + + + + + CREATE A NEW PROJECT + + + Please provide the URL of the model to connect and start querying data + +
    + setCreateURL(e.target.value)} + placeholder="Type URL" + /> +
    + +
    +
    +
    +
    +
+
+
- - - + + - + - - - + + +
diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx new file mode 100644 index 00000000..df61a138 --- /dev/null +++ b/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 00000000..01ff19c7 --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..6373043b --- /dev/null +++ b/components/ui/dropdown-menu.tsx @@ -0,0 +1,200 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/components/ui/select.tsx b/components/ui/select.tsx index cbe5a36b..de308396 100644 --- a/components/ui/select.tsx +++ b/components/ui/select.tsx @@ -26,7 +26,7 @@ const SelectTrigger = React.forwardRef< > {children} - + )) diff --git a/package-lock.json b/package-lock.json index 5bd6d4fd..46ee6bfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "code-graph", "version": "0.1.0", "dependencies": { + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", @@ -513,6 +516,72 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz", + "integrity": "sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", @@ -569,6 +638,165 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -611,6 +839,48 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz", + "integrity": "sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-menu": "2.1.2", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", @@ -669,6 +939,169 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", + "integrity": "sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", @@ -772,6 +1205,36 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", + "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", diff --git a/package.json b/package.json index 8b9c360d..7d85c1bb 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "lint": "next lint" }, "dependencies": { + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", diff --git a/public/color-logo.svg b/public/color-logo.svg new file mode 100644 index 00000000..3632ebf0 --- /dev/null +++ b/public/color-logo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/falkordb-white.svg b/public/falkordb-white.svg deleted file mode 100644 index 1afdce2a..00000000 --- a/public/falkordb-white.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - From ec11a2a357809b6b6b56e68507eab8fc40804bbe Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 29 Oct 2024 15:54:19 +0200 Subject: [PATCH 004/115] add hover card and fix show path and search --- app/api/chat/[graph]/route.ts | 27 ++++++++ app/components/Input.tsx | 17 +++-- app/components/chat.tsx | 86 +++++++++++++------------ app/components/code-graph.tsx | 82 +++++++++++------------- components/ui/hover-card.tsx | 29 +++++++++ package-lock.json | 117 ++++++++++++++++++++++++++++++++++ package.json | 1 + 7 files changed, 267 insertions(+), 92 deletions(-) create mode 100644 app/api/chat/[graph]/route.ts create mode 100644 components/ui/hover-card.tsx diff --git a/app/api/chat/[graph]/route.ts b/app/api/chat/[graph]/route.ts new file mode 100644 index 00000000..a69ba2ad --- /dev/null +++ b/app/api/chat/[graph]/route.ts @@ -0,0 +1,27 @@ +import { NextRequest, NextResponse } from "next/server" + +export async function POST(request: NextRequest, { params }: { params: { graph: string } }) { + + const graphName = params.graph + const msg = request.nextUrl.searchParams.get('msg') + + try { + const result = await fetch(`http://localhost:5000/chat`, { + method: 'POST', + body: JSON.stringify({ repo: graphName, msg}), + headers: { + "Content-Type": 'application/json' + } + }) + + if (!result.ok) { + throw new Error(await result.text()) + } + + const json = await result.json() + + return NextResponse.json({ result: json }, { status: 200 }) + } catch (err) { + return NextResponse.json({ message: (err as Error).message }, { status: 400 }) + } +} \ No newline at end of file diff --git a/app/components/Input.tsx b/app/components/Input.tsx index 314a1c16..bfe03c3b 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -11,9 +11,10 @@ interface Props extends React.InputHTMLAttributes { handelSubmit?: (node: any) => void icon?: React.ReactNode node?: PathNode + parentClassName?: string } -export default function Input({ value, onValueChange, handelSubmit, graph, icon, node, ...props }: Props) { +export default function Input({ value, onValueChange, handelSubmit, graph, icon, node, className, parentClassName, ...props }: Props) { const [open, setOpen] = useState(false) const [options, setOptions] = useState([]) @@ -60,8 +61,9 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, case "Enter": { e.preventDefault() const option = options.find((o, i) => i === selectedOption) - onValueChange(option) + onValueChange({ name: option.properties.name, id: option.id }) handelSubmit && handelSubmit(option) + setOpen(false) return } case "ArrowUp": { @@ -78,7 +80,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, case "Space": { if (e.ctrlKey) { e.preventDefault() - setOpen(prev => true) + setOpen(true) } return } @@ -91,12 +93,12 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, return (
{ const newVal = e.target.value @@ -107,7 +109,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, { open &&
setSelectedOption(index)} onMouseLeave={() => setSelectedOption(-1)} onClick={() => { - onValueChange(option) + onValueChange({ name: option.properties.name, id: option.id }) handelSubmit && handelSubmit(option) + setOpen(false) }} key={option.id} > diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 16d044c0..06b9d9d7 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -123,50 +123,35 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set title: "Uh oh! Something went wrong.", description: "Please enter a question.", }) - setQuery("") return } - setMessages((messages) => [...messages, { text: q, type: MessageTypes.Query }, { text: "", type: MessageTypes.Pending }]); + setQuery("") - return fetch(`/api/repo/${repo}?q=${encodeURIComponent(q)}&type=text`, { - method: 'GET' - }).then(async (result) => { - if (result.status >= 300) { - throw Error(await result.text()) + setMessages((messages) => [...messages, { text: q, type: MessageTypes.Query }, { text: "", type: MessageTypes.Pending }]); - } + const result = await fetch(`/api/chat/${repo}?msg=${encodeURIComponent(q)}`, { + method: 'POST' + }) - return result.json() - }).then(data => { - // Create an array of messages from the current messages remove the last pending message and add the new response - setMessages(function (messages) { - if (messages[messages.length - 1].type === MessageTypes.Pending) { - // Remove the last pending message if exists - messages = messages.slice(0, -1); - } - return [...messages, { text: data.result, type: MessageTypes.Response }]; - }); - setQuery("") - }).catch((error) => { - setMessages(function (messages) { - if (messages[messages.length - 1].type === MessageTypes.Pending) { - // Remove the last pending message if exists - return messages.slice(0, -1); - } - return messages - }); - setQuery("") - toast({ - variant: "destructive", - title: "Uh oh! Something went wrong.", - description: error.message, + if (!result.ok) { + setMessages((prev) => { + prev = [...prev.slice(0, -1)]; + return [...prev, { type: MessageTypes.Response, text: "Sorry but I couldn't answer your question, please try rephrasing." }]; }); + return + } + + const json = await result.json() + + setMessages((prev) => { + prev = prev.slice(0, -1); + return [...prev, { text: json.result.response, type: MessageTypes.Response }]; }); } // A function that handles the click event - async function handleQueryClick(event: any) { + const handleQueryClick = async (event: any) => { event.preventDefault(); return sendQuery(query.trim()); } @@ -200,12 +185,36 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set const addedElements: ElementDefinition[] = [] formattedPaths.forEach((p: any) => addedElements.push(...graph.extend(p, false, path))) formattedPaths.forEach(p => p.edges.forEach(e => e.id = `_${e.id}`)) - console.log(formattedPaths); + console.log(formattedPaths.flatMap(p => p.edges.map(e => e.id))); + chart.add(addedElements) graph.Elements.forEach((element: any) => { const { id } = element.data - if (!formattedPaths.some((p: any) => [...p.nodes, ...p.edges].some((el: any) => el.id == id))) { - const e = chart.elements().filter(el => el.id() == id) + const e = chart.elements().filter(el => el.id() == id) + console.log(id); + if (id == path.start?.id || id == path.end?.id) { + e.style({ + 'border-width': 1, + 'border-color': 'pink', + 'border-opacity': 1, + }); + } else if (formattedPaths.some((p: any) => [...p.nodes, ...p.edges].some((el: any) => el.id == id))) { + if (e.isNode()) { + e.style({ + 'border-width': 0.5, + 'border-color': 'pink', + 'border-opacity': 1, + }); + } + if (e.isEdge()) { + e.style({ + "line-style": "dashed", + "line-color": "pink", + "target-arrow-color": "pink", + "opacity": 1 + }); + } + } else { if (e.isNode()) { e.style({ "color": "gray", @@ -223,10 +232,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set } } }) - chart.add(addedElements) - console.log(formattedPaths.flatMap(p => [...p.nodes, ...p.edges].map(e => e.id))); const elements = chart.elements().filter((element) => { - console.log(element.id()); return formattedPaths.some(p => [...p.nodes, ...p.edges].some((node) => node.id == element.id())) }); elements.layout(LAYOUT).run() @@ -263,6 +269,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set return (
setPath(prev => ({ start: { name, id }, end: prev?.end }))} value={path?.start?.name} @@ -272,6 +279,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set node={path?.start} /> setPath(prev => ({ end: { name, id }, start: prev?.start }))} diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 2aa0902b..dcbc3837 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -14,6 +14,7 @@ import { toast } from '@/components/ui/use-toast'; import { cn } from '@/lib/utils'; import { Path } from '../page'; import Input from './Input'; +import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'; interface Props { onFetchGraph: (graphName: string) => void, @@ -78,22 +79,6 @@ const STYLESHEET: cytoscape.Stylesheet[] = [ 'border-opacity': 1, }, }, - { - selector: "node[?isPath]", - style: { - 'border-width': 0.5, - 'border-color': 'pink', - 'border-opacity': 1, - }, - }, - { - selector: "node[?isPathStartEnd]", - style: { - 'border-width': 1, - 'border-color': 'pink', - 'border-opacity': 1, - }, - }, { selector: "edge", style: { @@ -115,14 +100,6 @@ const STYLESHEET: cytoscape.Stylesheet[] = [ style: { "overlay-opacity": 0, // hide gray box around active node }, - }, - { - selector: "edge[?isPath]", - style: { - "line-style": "dashed", - "line-color": "pink", - "target-arrow-color": "pink", - }, } ] @@ -264,7 +241,7 @@ export function CodeGraph({ if (type) { graph.NodesMap.delete(Number(id)) } else { - graph.EdgesMap.delete(Number(id.split('')[1])) + graph.EdgesMap.delete(Number(id.split('_')[1])) } chart.remove(`#${id}`) @@ -295,7 +272,7 @@ export function CodeGraph({ if (!graphNode.data.expand) { elements = await onFetchNode(node) - + console.log(elements); if (elements.length === 0) { toast({ title: "No neighbors found", @@ -304,14 +281,13 @@ export function CodeGraph({ } chart.add(elements); - chart.elements().layout(LAYOUT).run(); } else { deleteNeighbors(node, chart) } graphNode.data.expand = !graphNode.data.expand; - setSelectedObj(undefined) + chart.elements().layout(LAYOUT).run(); } const handelTap = (evt: EventObject) => { @@ -481,49 +457,63 @@ export function CodeGraph({ graph.Id &&
-
    +
      { commits.slice(commitIndex - COMMIT_LIMIT, commitIndex).map((commit: any) => { const date = new Date(commit.date * 1000) const month = `${date.getDate()} ${date.toLocaleString('default', { month: 'long' })}` const hour = `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}` return ( -
    • - -
    • + +
    • + +
    • +
      + +
      +

      {commit.author}:

      +

      {commit.message}

      +

      {commit.hash}

      +
      +
      + ) }) }
    diff --git a/components/ui/hover-card.tsx b/components/ui/hover-card.tsx new file mode 100644 index 00000000..e54d91cf --- /dev/null +++ b/components/ui/hover-card.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as HoverCardPrimitive from "@radix-ui/react-hover-card" + +import { cn } from "@/lib/utils" + +const HoverCard = HoverCardPrimitive.Root + +const HoverCardTrigger = HoverCardPrimitive.Trigger + +const HoverCardContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + +)) +HoverCardContent.displayName = HoverCardPrimitive.Content.displayName + +export { HoverCard, HoverCardTrigger, HoverCardContent } diff --git a/package-lock.json b/package-lock.json index 46ee6bfb..9cc07005 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-hover-card": "^1.1.2", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", @@ -921,6 +922,122 @@ } } }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.2.tgz", + "integrity": "sha512-Y5w0qGhysvmqsIy6nQxaPa6mXNKznfoGjOfBgzOjocLxr2XlSjqBMYQQL+FfyogsMuX+m8cZyQGYhJxvxUzO4w==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", diff --git a/package.json b/package.json index 7d85c1bb..cf38f37d 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-hover-card": "^1.1.2", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", From ecbef2a238031e8b479f70c3c2ae3b5d22b089dc Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Fri, 1 Nov 2024 06:43:48 -0700 Subject: [PATCH 005/115] Fix Typo in Chat Component --- app/components/chat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 06b9d9d7..ba1796b2 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -322,7 +322,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set >
    -

    Show unreadable code

    +

    Show unreachable code

    Remove it if unnecessary or fix logic issues.

    From e7c8d76a7cbecb893f2f7e04490aa43e1433b0bd Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 5 Nov 2024 14:25:07 +0200 Subject: [PATCH 006/115] replace localhost in ip --- app/api/chat/[graph]/route.ts | 2 +- app/api/repo/[graph]/[node]/route.ts | 4 +- app/api/repo/[graph]/route.tsx | 171 ++++++++++++++++++++++++++- app/api/repo/route.ts | 4 +- 4 files changed, 171 insertions(+), 10 deletions(-) diff --git a/app/api/chat/[graph]/route.ts b/app/api/chat/[graph]/route.ts index a69ba2ad..6431fe56 100644 --- a/app/api/chat/[graph]/route.ts +++ b/app/api/chat/[graph]/route.ts @@ -6,7 +6,7 @@ export async function POST(request: NextRequest, { params }: { params: { graph: const msg = request.nextUrl.searchParams.get('msg') try { - const result = await fetch(`http://localhost:5000/chat`, { + const result = await fetch(`http://127.0.0.1:5000/chat`, { method: 'POST', body: JSON.stringify({ repo: graphName, msg}), headers: { diff --git a/app/api/repo/[graph]/[node]/route.ts b/app/api/repo/[graph]/[node]/route.ts index fa22cfbf..9c18d64e 100644 --- a/app/api/repo/[graph]/[node]/route.ts +++ b/app/api/repo/[graph]/[node]/route.ts @@ -6,7 +6,7 @@ export async function GET(request: NextRequest, { params }: { params: { graph: s const graphId = params.graph; try { - const result = await fetch(`http://localhost:5000/get_neighbors?repo=${graphId}&node_id=${nodeId}`, { + const result = await fetch(`http://127.0.0.1:5000/get_neighbors?repo=${graphId}&node_id=${nodeId}`, { method: 'GET', }) @@ -26,7 +26,7 @@ export async function POST(request: NextRequest, { params }: { params: { graph: try { - const result = await fetch(`http://localhost:5000/find_paths`, { + const result = await fetch(`http://127.0.0.1:5000/find_paths`, { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/app/api/repo/[graph]/route.tsx b/app/api/repo/[graph]/route.tsx index b6487228..ebab0af4 100644 --- a/app/api/repo/[graph]/route.tsx +++ b/app/api/repo/[graph]/route.tsx @@ -5,7 +5,7 @@ export async function GET(request: NextRequest, { params }: { params: { graph: s const graphName = params.graph try { - const result = await fetch(`http://localhost:5000/graph_entities?repo=${graphName}`, { + const result = await fetch(`http://127.0.0.1:5000/graph_entities?repo=${graphName}`, { method: 'GET', }) @@ -29,7 +29,7 @@ export async function POST(request: NextRequest, { params }: { params: { graph: try { switch (type) { case "commit": { - const result = await fetch(`http://localhost:5000/list_commits`, { + const result = await fetch(`http://127.0.0.1:5000/list_commits`, { method: 'POST', body: JSON.stringify({ repo: graphName }), headers: { @@ -45,11 +45,172 @@ export async function POST(request: NextRequest, { params }: { params: { graph: return NextResponse.json({ result: json }, { status: 200 }) } + case "switchCommit": { + return NextResponse.json({ + result: { + deletions: { + 'nodes': [ + { + "alias": "", + "id": 2, + "labels": [ + "Function" + ], + "properties": { + "args": [ + [ + "cls", + "Unknown" + ] + ], + "name": "setUpClass", + "path": "tests/test_kg_gemini.py", + "src": "def setUpClass(cls):\n\n cls.ontology = Ontology([], [])\n\n cls.ontology.add_entity(\n Entity(\n label=\"Actor\",\n attributes=[\n Attribute(\n name=\"name\",\n attr_type=AttributeType.STRING,\n unique=True,\n required=True,\n ),\n ],\n )\n )\n cls.ontology.add_entity(\n Entity(\n label=\"Movie\",\n attributes=[\n Attribute(\n name=\"title\",\n attr_type=AttributeType.STRING,\n unique=True,\n required=True,\n ),\n ],\n )\n )\n cls.ontology.add_relation(\n Relation(\n label=\"ACTED_IN\",\n source=\"Actor\",\n target=\"Movie\",\n attributes=[\n Attribute(\n name=\"role\",\n attr_type=AttributeType.STRING,\n unique=False,\n required=False,\n ),\n ],\n )\n )\n\n cls.graph_name = \"IMDB_gemini\"\n\n model = GeminiGenerativeModel(model_name=\"gemini-1.5-flash-001\")\n cls.kg = KnowledgeGraph(\n name=cls.graph_name,\n ontology=cls.ontology,\n model_config=KnowledgeGraphModelConfig.with_model(model),\n )", + "src_end": 82, + "src_start": 29 + } + }, + { + "alias": "", + "id": 13, + "labels": [ + "Function" + ], + "properties": { + "args": [ + [ + "self", + "Unknown" + ], + [ + "restaurants_kg", + "KnowledgeGraph" + ], + [ + "attractions_kg", + "KnowledgeGraph" + ] + ], + "name": "import_data", + "path": "tests/test_multi_agent.py", + "src": "def import_data(\n self,\n restaurants_kg: KnowledgeGraph,\n attractions_kg: KnowledgeGraph,\n ):\n with open(\"tests/data/cities.json\") as f:\n cities = loads(f.read())\n with open(\"tests/data/restaurants.json\") as f:\n restaurants = loads(f.read())\n with open(\"tests/data/attractions.json\") as f:\n attractions = loads(f.read())\n\n for city in cities:\n restaurants_kg.add_node(\n \"City\",\n {\n \"name\": city[\"name\"],\n \"weather\": city[\"weather\"],\n \"population\": city[\"population\"],\n },\n )\n restaurants_kg.add_node(\"Country\", {\"name\": city[\"country\"]})\n restaurants_kg.add_edge(\n \"IN_COUNTRY\",\n \"City\",\n \"Country\",\n {\"name\": city[\"name\"]},\n {\"name\": city[\"country\"]},\n )\n\n attractions_kg.add_node(\n \"City\",\n {\n \"name\": city[\"name\"],\n \"weather\": city[\"weather\"],\n \"population\": city[\"population\"],\n },\n )\n attractions_kg.add_node(\"Country\", {\"name\": city[\"country\"]})\n attractions_kg.add_edge(\n \"IN_COUNTRY\",\n \"City\",\n \"Country\",\n {\"name\": city[\"name\"]},\n {\"name\": city[\"country\"]},\n )\n\n for restaurant in restaurants:\n restaurants_kg.add_node(\n \"Restaurant\",\n {\n \"name\": restaurant[\"name\"],\n \"description\": restaurant[\"description\"],\n \"rating\": restaurant[\"rating\"],\n \"food_type\": restaurant[\"food_type\"],\n },\n )\n restaurants_kg.add_edge(\n \"IN_CITY\",\n \"Restaurant\",\n \"City\",\n {\"name\": restaurant[\"name\"]},\n {\"name\": restaurant[\"city\"]},\n )\n\n for attraction in attractions:\n attractions_kg.add_node(\n \"Attraction\",\n {\n \"name\": attraction[\"name\"],\n \"description\": attraction[\"description\"],\n \"type\": attraction[\"type\"],\n },\n )\n attractions_kg.add_edge(\n \"IN_CITY\",\n \"Attraction\",\n \"City\",\n {\"name\": attraction[\"name\"]},\n {\"name\": attraction[\"city\"]},\n )", + "src_end": 310, + "src_start": 230 + } + }, + ], + 'edges': [ + { + "alias": "", + "dest_node": 13, + "id": 460, + "properties": {}, + "relation": "CALLS", + "src_node": 2 + }, + ] + }, + additions: { + 'nodes': [ + { + "alias": "", + "id": 13, + "labels": [ + "File" + ], + "properties": { + "ext": ".py", + "name": "test_kg_gemini.py", + "path": "tests" + } + }, + { + "alias": "", + "id": 2, + "labels": [ + "Class" + ], + "properties": { + "doc": "\"\"\"\n Test Knowledge Graph\n \"\"\"", + "name": "TestKGGemini", + "path": "tests/test_kg_gemini.py", + "src_end": 106, + "src_start": 23 + } + }, + ], + 'edges': [ + { + "alias": "", + "dest_node": 13, + "id": 460, + "properties": {}, + "relation": "DEFINES", + "src_node": 2 + }, + ], + }, + modifications: { + 'nodes': [ + { + "alias": "", + "id": 3, + "labels": [ + "Function" + ], + "properties": { + "args": [ + [] + ], + "name": "", + "path": "", + "src": "", + "src_end": 0, + "src_start": 0 + } + }, + { + "alias": "", + "id": 61, + "labels": [ + "Function" + ], + "properties": { + "args": [ + [], + [] + ], + "doc": "", + "name": "", + "path": "", + "ret_type": "", + "src": "", + "src_end": 0, + "src_start": 0 + } + }, + ], + 'edges': [ + { + "alias": "", + "dest_node": 61, + "id": 439, + "properties": { + name: "Source" + }, + "relation": "CALLS", + "src_node": 3 + }, + ] + } + } + }, { status: 200 }) + } case "autoComplete": { const prefix = request.nextUrl.searchParams.get('prefix')! - const result = await fetch(`http://localhost:5000/auto_complete`, { + const result = await fetch(`http://127.0.0.1:5000/auto_complete`, { method: 'POST', - body: JSON.stringify({ repo: graphName, prefix}), + body: JSON.stringify({ repo: graphName, prefix }), headers: { "Content-Type": 'application/json' } @@ -64,7 +225,7 @@ export async function POST(request: NextRequest, { params }: { params: { graph: return NextResponse.json({ result: json }, { status: 200 }) } default: { - const result = await fetch(`http://localhost:5000/repo_info`, { + const result = await fetch(`http://127.0.0.1:5000/repo_info`, { method: 'POST', body: JSON.stringify({ repo: graphName }), headers: { diff --git a/app/api/repo/route.ts b/app/api/repo/route.ts index 1544626d..83f339e4 100644 --- a/app/api/repo/route.ts +++ b/app/api/repo/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; export async function GET() { try { - const result = await fetch(`http://localhost:5000/list_repos`, { + const result = await fetch(`http://127.0.0.1:5000/list_repos`, { method: 'GET', }) @@ -23,7 +23,7 @@ export async function POST(request: NextRequest) { const url = request.nextUrl.searchParams.get('url'); try { - const result = await fetch(`http://localhost:5000/process_repo`, { + const result = await fetch(`http://127.0.0.1:5000/process_repo`, { method: 'POST', body: JSON.stringify({ repo_url: url, ignore: ["./.github", "./sbin", "./.git", "./deps", "./bin", "./build"] }), headers: { From fbe93b20501069f7c0b8636118af3cb5f2b1f408 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 5 Nov 2024 14:34:46 +0200 Subject: [PATCH 007/115] improve styling and add multi paths messages --- .env.local.template | 6 - app/components/Input.tsx | 41 ++++-- app/components/chat.tsx | 237 +++++++++++++++++++++++---------- app/components/code-graph.tsx | 140 +++++++++---------- app/components/commitList.tsx | 144 ++++++++++++++++++++ app/components/dataPanel.tsx | 32 +++-- app/components/elementMenu.tsx | 24 ++-- app/components/model.ts | 2 +- app/globals.css | 31 +++++ app/page.tsx | 32 ++--- public/color-logo.svg | 23 ---- public/logo_02.svg | 11 ++ 12 files changed, 484 insertions(+), 239 deletions(-) delete mode 100644 .env.local.template create mode 100644 app/components/commitList.tsx delete mode 100644 public/color-logo.svg create mode 100644 public/logo_02.svg diff --git a/.env.local.template b/.env.local.template deleted file mode 100644 index ab2504cc..00000000 --- a/.env.local.template +++ /dev/null @@ -1,6 +0,0 @@ -NEXT_PUBLIC_MODE=UNLIMITED # Optional values LIMITED/UNLIMITED - -FALKORDB_URL=falkordb://localhost:6379 # FALKORDB_URL - -OPENAI_API_KEY=[API_KEY] # Set you openai api key here - diff --git a/app/components/Input.tsx b/app/components/Input.tsx index bfe03c3b..2e3282a7 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -1,8 +1,11 @@ import { toast } from "@/components/ui/use-toast" -import { getCategoryColorName, Graph } from "./model" +import { getCategoryColorName, getCategoryColorValue, Graph } from "./model" import { useEffect, useRef, useState } from "react" import { PathNode } from "../page" import { cn } from "@/lib/utils" +import twcolors from 'tailwindcss/colors' + +let colors = twcolors as any interface Props extends React.InputHTMLAttributes { value?: string @@ -12,9 +15,10 @@ interface Props extends React.InputHTMLAttributes { icon?: React.ReactNode node?: PathNode parentClassName?: string + scrollToBottom?: () => void } -export default function Input({ value, onValueChange, handelSubmit, graph, icon, node, className, parentClassName, ...props }: Props) { +export default function Input({ value, onValueChange, handelSubmit, graph, icon, node, className, parentClassName, scrollToBottom, ...props }: Props) { const [open, setOpen] = useState(false) const [options, setOptions] = useState([]) @@ -23,6 +27,10 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, useEffect(() => { setSelectedOption(0) + + if (open) { + scrollToBottom && scrollToBottom() + } }, [open]) useEffect(() => { @@ -48,9 +56,6 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, const { completions } = json.result setOptions(completions) setOpen(true) - setTimeout(() => { - inputRef?.current?.focus(); - }, 0); }, 500) return () => clearTimeout(timeout) @@ -60,7 +65,9 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, switch (e.code) { case "Enter": { e.preventDefault() + if (!open) return const option = options.find((o, i) => i === selectedOption) + if (!option) return onValueChange({ name: option.properties.name, id: option.id }) handelSubmit && handelSubmit(option) setOpen(false) @@ -93,7 +100,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, return (
    setOpen(false)} {...props} /> { open &&
    { const label = option.labels[0] - const color = getCategoryColorName(graph.CategoriesMap.get(label)?.index) + const name = option.properties.name + const path = option.properties.path + const colorName = getCategoryColorName(graph.CategoriesMap.get(label)?.index) return ( ) }) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index ba1796b2..f37f8254 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -1,12 +1,11 @@ import { toast } from "@/components/ui/use-toast"; -import { Dispatch, MutableRefObject, SetStateAction, use, useEffect, useRef, useState } from "react"; +import { Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState } from "react"; import Image from "next/image"; -import { AlignLeft, ArrowRight, ChevronDown, Lightbulb, Redo2, SendHorizonal, Undo2 } from "lucide-react"; +import { AlignLeft, ArrowRight, ChevronDown, Lightbulb, Undo2 } from "lucide-react"; import { Path } from "../page"; import Input from "./Input"; import { Graph } from "./model"; import { cn } from "@/lib/utils"; -import { ElementDefinition } from "cytoscape"; import { LAYOUT } from "./code-graph"; enum MessageTypes { @@ -22,6 +21,7 @@ enum MessageTypes { interface Message { type: MessageTypes; text?: string; + paths?: { nodes: any[], edges: any[] }[]; } interface Props { @@ -31,13 +31,25 @@ interface Props { graph: Graph chartRef: MutableRefObject selectedPathId: string | undefined - setIsPathResponse: (isPathResponse: boolean) => void + isPath: boolean + setIsPath: (isPathResponse: boolean) => void } -export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, setIsPathResponse }: Props) { +const RemoveLastPath = (messages: Message[]) => { + const index = messages.findIndex((m) => m.type === MessageTypes.Path) + + if (index !== -1) { + messages = [...messages.slice(0, index - 2), ...messages.slice(index + 1)]; + messages = RemoveLastPath(messages) + } + + return messages +} + +export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isPath, setIsPath }: Props) { // Holds the messages in the chat - const [messages, setMessages] = useState([]); + const [messages, setMessages] = useState([{ type: MessageTypes.Tip }]); // Holds the messages in the chat const [paths, setPaths] = useState<{ nodes: any[], edges: any[] }[]>([]); @@ -47,58 +59,124 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set // Holds the user input while typing const [query, setQuery] = useState(''); + const [isPathResponse, setIsPathResponse] = useState(false); + // A reference to the chat container to allow scrolling to the bottom const containerRef: React.RefObject = useRef(null); useEffect(() => { - const path = paths.find((p) => [...p.edges, ...p.nodes].some((e: any) => e.id === selectedPathId)) + const p = paths.find((path) => [...path.edges, ...path.nodes].some((e: any) => e.id === selectedPathId)) - if (!path) return + if (!p) return - handelSetSelectedPath(path) + handelSetSelectedPath(p) }, [selectedPathId]) useEffect(() => { handelSubmit() }, [path]) + useEffect(() => { + if (isPath) return + setIsPathResponse(false) + setSelectedPath(undefined) + setPaths([]) + }, [isPath]) + + useEffect(() => { + setIsPath(isPathResponse) + }, [isPathResponse]) + const handelSetSelectedPath = (p: { nodes: any[], edges: any[] }) => { const chart = chartRef.current if (!chart) return setSelectedPath(prev => { - prev?.edges.forEach(element => { - const e = chart.elements().filter(el => el.id() == element.id) - if (!p.edges.some(e => e.id === element.id)) { - e.style({ - width: 0.5, - "line-style": "dashed", - "line-color": "pink", - "arrow-scale": 0.3, - "target-arrow-color": "pink", + if (prev) { + if (isPathResponse && paths.some((path) => [...path.nodes, ...path.edges].every((e: any) => [...prev.nodes, ...prev.edges].some((el: any) => el.id === e.id)))) { + chart.edges().forEach(e => { + const id = e.id() + + if (prev.edges.some(el => el.id == id) && !p.edges.some(el => el.id == id)) { + e.style({ + width: 0.5, + "line-style": "dashed", + "line-color": "#FF66B3", + "arrow-scale": 0.3, + "target-arrow-color": "#FF66B3", + }) + } }) + } else { + const elements = chart.elements().filter(e => [...prev.edges, ...prev.nodes].some(el => el.id == e.id())).removeStyle() + if (isPathResponse) { + elements.forEach(e => { + if (e.isNode()) { + e.style({ + "border-width": 0.5, + "color": "gray", + "border-color": "black", + "background-color": "gray", + "opacity": 0.5 + }); + } + + if (e.isEdge()) { + e.style({ + "line-color": "gray", + "target-arrow-color": "gray", + "opacity": 0.5, + }); + } + }) + } } - }) - + } return p - }); - - p.edges.forEach(element => { - const e = chart.elements().filter(el => el.id() == element.id) - e.style({ - width: 1, - "line-style": "solid", - "line-color": "pink", - "arrow-scale": 0.6, - "target-arrow-color": "pink", - }) }) - const elementsInPath = chart.elements().filter((element) => { - return [...p.nodes, ...p.edges].some((node) => node.id == element.id()) - }); - elementsInPath.layout(LAYOUT).run(); + if (isPathResponse && paths.some((path) => [...path.nodes, ...path.edges].every((e: any) => [...p.nodes, ...p.edges].some((el: any) => el.id === e.id)))) { + chart.edges().forEach(e => { + const id = e.id() + + if (p.edges.some(el => el.id == id)) { + e.style({ + width: 1, + "line-style": "solid", + "line-color": "#FF66B3", + "arrow-scale": 0.6, + "target-arrow-color": "#FF66B3", + }) + } + }) + chart.elements().filter(el => [...p.nodes, ...p.edges].some(e => e.id == el.id())).layout(LAYOUT).run(); + } else { + chart.elements().filter(el => [...p.nodes, ...p.edges].some(e => e.id == el.id())).forEach(el => { + if (el.id() == p.nodes[0].id || el.id() == p.nodes[p.nodes.length - 1].id) { + el.removeStyle().style({ + "border-width": 1, + "border-color": "#FF66B3", + "border-opacity": 1, + }); + } else if (el.isNode()) { + el.removeStyle().style({ + "border-width": 0.5, + "border-color": "#FF66B3", + "border-opacity": 1, + }); + } + if (el.isEdge()) { + el.removeStyle().style({ + width: 1, + "line-style": "solid", + "line-color": "#FF66B3", + "arrow-scale": 0.6, + "target-arrow-color": "#FF66B3", + }) + } + }).layout(LAYOUT).run(); + } } // A function that handles the change event of the url input box @@ -127,9 +205,10 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set } setQuery("") - setMessages((messages) => [...messages, { text: q, type: MessageTypes.Query }, { text: "", type: MessageTypes.Pending }]); + containerRef.current?.scrollTo(0, containerRef.current?.scrollHeight); + const result = await fetch(`/api/chat/${repo}?msg=${encodeURIComponent(q)}`, { method: 'POST' }) @@ -162,6 +241,8 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set }, [messages]); const handelSubmit = async () => { + setSelectedPath(undefined) + const chart = chartRef?.current if (!chart || !path?.start?.id || !path.end?.id) return @@ -181,27 +262,31 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set const json = await result.json() + if (json.result.paths.length === 0) { + toast({ + title: `No path found`, + description: `no path found between node ${path.start.name} - ${path.end.name}`, + }) + return + } + const formattedPaths: { nodes: any[], edges: any[] }[] = json.result.paths.map((p: any) => ({ nodes: p.filter((node: any, i: number) => i % 2 === 0), edges: p.filter((edge: any, i: number) => i % 2 !== 0) })) - const addedElements: ElementDefinition[] = [] - formattedPaths.forEach((p: any) => addedElements.push(...graph.extend(p, false, path))) + chart.add(formattedPaths.flatMap((p: any) => graph.extend(p, false, path))) formattedPaths.forEach(p => p.edges.forEach(e => e.id = `_${e.id}`)) - console.log(formattedPaths.flatMap(p => p.edges.map(e => e.id))); - chart.add(addedElements) graph.Elements.forEach((element: any) => { const { id } = element.data const e = chart.elements().filter(el => el.id() == id) - console.log(id); if (id == path.start?.id || id == path.end?.id) { e.style({ 'border-width': 1, - 'border-color': 'pink', + 'border-color': '#FF66B3', 'border-opacity': 1, }); } else if (formattedPaths.some((p: any) => [...p.nodes, ...p.edges].some((el: any) => el.id == id))) { if (e.isNode()) { e.style({ 'border-width': 0.5, - 'border-color': 'pink', + 'border-color': '#FF66B3', 'border-opacity': 1, }); } @@ -209,15 +294,17 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set if (e.isEdge()) { e.style({ "line-style": "dashed", - "line-color": "pink", - "target-arrow-color": "pink", + "line-color": "#FF66B3", + "target-arrow-color": "#FF66B3", "opacity": 1 }); } } else { if (e.isNode()) { e.style({ + "border-width": 0.5, "color": "gray", + "border-color": "black", "background-color": "gray", "opacity": 0.5 }); @@ -227,7 +314,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set e.style({ "line-color": "gray", "target-arrow-color": "gray", - "opacity": 0.5 + "opacity": 0.5, }); } } @@ -237,7 +324,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set }); elements.layout(LAYOUT).run() setPaths(formattedPaths) - setMessages(prev => [...prev.slice(0, -2), { type: MessageTypes.PathResponse }]) + setMessages(prev => [...prev.slice(0, -2), { type: MessageTypes.PathResponse, paths: formattedPaths }]) setPath(undefined) setIsPathResponse(true) } @@ -250,7 +337,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set

    You

    -

    {message.text}

    +

    {message.text}

    ) case MessageTypes.Response: return ( @@ -259,7 +346,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set

    Answer

-

{message.text}

+

{message.text?.replaceAll('"', "")}

) case MessageTypes.Text: return ( @@ -277,6 +364,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set type="text" icon={} node={path?.start} + scrollToBottom={() => containerRef.current?.scrollTo(0, containerRef.current?.scrollHeight)} /> } node={path?.end} + scrollToBottom={() => containerRef.current?.scrollTo(0, containerRef.current?.scrollHeight)} />
) @@ -294,14 +383,18 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, set case MessageTypes.PathResponse: return (
{ - paths.map((p, i: number) => ( + message.paths && + message.paths.map((p, i: number) => ( )) }
) case MessageTypes.Tip: return ( -
+
diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index dcbc3837..a3baff4d 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -6,15 +6,15 @@ import cytoscape, { ElementDefinition, EventObject, Position } from 'cytoscape'; import fcose from 'cytoscape-fcose'; import { Toolbar } from "./toolbar"; import { Labels } from "./labels"; -import { ChevronLeft, ChevronRight, GitFork, Search } from "lucide-react"; +import { GitFork, Search, X } from "lucide-react"; import ElementMenu from "./elementMenu"; import ElementTooltip from "./elementTooltip"; import Combobox from "./combobox"; import { toast } from '@/components/ui/use-toast'; -import { cn } from '@/lib/utils'; import { Path } from '../page'; import Input from './Input'; -import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'; +import CommitList from './commitList'; +import { Checkbox } from '@/components/ui/checkbox'; interface Props { onFetchGraph: (graphName: string) => void, @@ -26,6 +26,7 @@ interface Props { selectedValue: string setSelectedPathId: (selectedPathId: string) => void isPathResponse: boolean + setIsPathResponse: Dispatch> } // The stylesheet for the graph @@ -112,8 +113,6 @@ export const LAYOUT = { avoidOverlap: true, } -const COMMIT_LIMIT = 7 - export function CodeGraph({ onFetchGraph, onFetchNode, @@ -123,7 +122,8 @@ export function CodeGraph({ chartRef, selectedValue, setSelectedPathId, - isPathResponse + isPathResponse, + setIsPathResponse }: Props) { let graph = useContext(GraphContext) @@ -329,6 +329,7 @@ export function CodeGraph({ chartNode.select() const layout = { ...LAYOUT, padding: 250 } chartNode.layout(layout).run() + setSearchNodeName("") } return ( @@ -346,22 +347,55 @@ export function CodeGraph({ { graph.Id ?
-
- setSearchNodeName(node.name!)} - icon={} - handelSubmit={handelSearchSubmit} - /> - +
+
+ setSearchNodeName(node.name!)} + icon={} + handelSubmit={handelSearchSubmit} + /> + +
+ { + isPathResponse && + + }
-
+

{nodesCount} Nodes

{edgesCount} Edges

- +
+ {/*
+
+ +

Display Changes

+
+
+
+

Were added

+
+
+
+

Were edited

+
+
*/} + +
{ graph.Id && -
- -
    - { - commits.slice(commitIndex - COMMIT_LIMIT, commitIndex).map((commit: any) => { - const date = new Date(commit.date * 1000) - const month = `${date.getDate()} ${date.toLocaleString('default', { month: 'long' })}` - const hour = `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}` - return ( - - -
  • - -
  • -
    - -
    -

    {commit.author}:

    -

    {commit.message}

    -

    {commit.hash}

    -
    -
    -
    - ) - }) - } -
- -
+ }
diff --git a/app/components/commitList.tsx b/app/components/commitList.tsx new file mode 100644 index 00000000..43d46140 --- /dev/null +++ b/app/components/commitList.tsx @@ -0,0 +1,144 @@ +import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card" +import { toast } from "@/components/ui/use-toast" +import { cn } from "@/lib/utils" +import cytoscape from "cytoscape" +import { ChevronLeft, ChevronRight } from "lucide-react" +import { Dispatch, MutableRefObject, SetStateAction, useState } from "react" +import { Graph } from "./model" +import { LAYOUT } from "./code-graph" + +interface Props { + commits: any[] + currentCommit: number + setCurrentCommit: Dispatch> + commitIndex: number + setCommitIndex: Dispatch> + graph: Graph, + chartRef: MutableRefObject +} + +const COMMIT_LIMIT = 7 + +export default function CommitList({ commitIndex, commits, currentCommit, setCommitIndex, setCurrentCommit, graph, chartRef }: Props) { + + const [commitChanges, setCommitChanges] = useState() + + const handelCommitChange = async (commit: any) => { + const chart = chartRef.current + + if (!chart) return + + const result = await fetch(`api/repo/${graph.Id}/?type=switchCommit`, { + method: 'POST', + }) + + if (!result.ok) { + toast({ + title: "Uh oh! Something went wrong", + description: (await result.text()), + }) + return + } + + const json = await result.json() + + json.result.deletions.nodes.forEach((e: any) => { + chart.remove(`#${e.id}`) + graph.NodesMap.delete(e.id) + graph.Elements.splice(graph.Elements.findIndex((el) => el.data.id === e.id), 1) + }) + + json.result.deletions.edges.forEach((e: any) => { + chart.remove(`#_${e.id}`) + graph.EdgesMap.delete(e.id) + graph.Elements.splice(graph.Elements.findIndex((el) => el.data.id === e.id), 1) + }) + + const additionsIds = chart.add(graph.extend(json.result.additions)) + .filter((e) => e.isNode()).style({ "border-color": "pink", "border-width": 2, "border-opacity": 1 }) + .map((e) => e.id())! + + const g = Graph.empty() + g.extend(json.result.modifications) + + const modifiedIds = g.Elements.map((e) => { + const graphElement = graph.Elements.find((el) => el.data.id === e.data.id) + graphElement.data = e.data + + if ("category" in e.data) { + chart.$(`#${e.data.id}`).data(e.data).style({ "border-color": "blue", "border-width": 2, "border-opacity": 1 }) + } + + return e.data.id + }) + + chart.layout(LAYOUT).run() + + setCommitChanges({ additionsIds, modifiedIds }) + setCurrentCommit(commit.hash) + } + + return ( +
+ +
    + { + commits.slice(commitIndex - COMMIT_LIMIT, commitIndex).map((commit: any) => { + const date = new Date(commit.date * 1000) + const month = `${date.getDate()} ${date.toLocaleString('default', { month: 'short' })}` + const hour = `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}` + return ( + + +
  • + +
  • +
    + +

    {commit.author}

    +

    {commit.message}

    +

    {commit.hash}

    +
    +
    + ) + }) + } +
+ +
+ ) +} \ No newline at end of file diff --git a/app/components/dataPanel.tsx b/app/components/dataPanel.tsx index 611a4aa8..7aefeb7d 100644 --- a/app/components/dataPanel.tsx +++ b/app/components/dataPanel.tsx @@ -1,6 +1,6 @@ -import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; +import { Dispatch, SetStateAction } from "react"; import { Node } from "./model"; -import { ChevronLeft, ChevronRight, Copy, SquareArrowOutUpRight, X } from "lucide-react"; +import { Copy, SquareArrowOutUpRight, X } from "lucide-react"; interface Props { obj: Node | undefined; @@ -14,6 +14,7 @@ const excludedProperties = [ "expand", "collapsed", "isPath", + "isPathStartEnd" ] export default function DataPanel({ obj, setObj, url }: Props) { @@ -21,11 +22,11 @@ export default function DataPanel({ obj, setObj, url }: Props) { if (!obj) return null; const label = `${obj.category}: ${obj.name}` - const object = Object.fromEntries(Object.entries(obj).filter(([k]) => !excludedProperties.includes(k))) + const object = Object.entries(obj).filter(([k]) => !excludedProperties.includes(k)) return ( -
-
+
+

{label.toUpperCase()}

@@ -33,18 +34,21 @@ export default function DataPanel({ obj, setObj, url }: Props) {
-
-
-                    {JSON.stringify(object, null, 1)
-                        .replace(/({|}|\[|\]|,)/g, match => match === '}' || match === "]" ? `\n${match}` : `${match}\n`).slice(1, -1)
-                    }
-                
+
+ { + object.map(([key, value]) => ( +
+

{key}:

+

{value}

+
+ )) + }
-
+
- + CREATE A NEW PROJECT @@ -178,7 +178,7 @@ export default function Home() {
setCreateURL(e.target.value)} @@ -186,7 +186,7 @@ export default function Home() { />
-
@@ -464,8 +464,8 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP return (
-

WELCOME TO OUR ASSISTANCE SERVICE

- +

WELCOME TO OUR ASSISTANCE SERVICE

+ We can help you access and update only the needed data via paths, optimizing network requests with batching and catching for better performance. diff --git a/app/components/labels.tsx b/app/components/labels.tsx index 716c2a62..132ab856 100644 --- a/app/components/labels.tsx +++ b/app/components/labels.tsx @@ -12,7 +12,7 @@ export function Labels(params: { categories: Category[], className?: string, onC {params.categories.map((category) =>
{ params.onClick(category.name, checked as boolean) setReload(!reload) diff --git a/app/components/model.ts b/app/components/model.ts index aa144950..a2f713a5 100644 --- a/app/components/model.ts +++ b/app/components/model.ts @@ -22,39 +22,24 @@ export interface Edge { [key: string]: any, } -const COLORS_ORDER = [ - "rose", +const COLORS_ORDER_NAME = [ + "pink", "yellow", - "teal", - "fuchsia", "blue", - "violet", - "slate", - "cyan", - "orange", - "red", - "green", - "pink", ] -export function getCategoryColorName(index = -1): string { - if (index === -1) return "gray" - - index = index < COLORS_ORDER.length ? index : 0 +const COLORS_ORDER = [ + "#F43F5F", + "#E9B306", + "#15B8A6", +] - return COLORS_ORDER[index] +export function getCategoryColorValue(index: number): string { + return COLORS_ORDER[index % COLORS_ORDER.length] } -export function getCategoryColorValue(index = -1): string { - let colors = twcolors as any - - if (index === -1) return colors["gray"] - - index = index < COLORS_ORDER.length ? index : 0 - let colorName = COLORS_ORDER[index] - let color = colors[colorName] - - return color["500"] +export function getCategoryColorName(index: number): string { + return COLORS_ORDER_NAME[index % COLORS_ORDER.length] } export class Graph { diff --git a/app/globals.css b/app/globals.css index a3c89fab..7a32f2b4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -71,11 +71,32 @@ * { @apply border-border; } - + body { - @apply bg-background text-foreground; + @apply bg-background text-foreground font-roberto; } - + +} + +.Tip { + @apply flex gap-2 p-4 border border-[#0000001A] bg-[#F9F9F9] hover:bg-[#ECECEC] rounded-md text-left; + .label { + @apply text-[16px] font-bold leading-[14px] + } + .text { + @apply text-[16px] font-normal leading-[20px] text-[#7D7D7D] + } + div { + @apply flex flex-col gap-2 text-start + } +} + +.font-roberto { + font-family: 'Roboto', sans-serif; +} + +.font-oswald { + font-family: 'Oswald', sans-serif; } ::-webkit-scrollbar { @@ -95,15 +116,5 @@ ::-webkit-scrollbar-thumb:hover { background-color: #a8bbbf; - -} - -.font-roberto { - font-family: 'Roboto', sans-serif; - font-style: italic; -} -.title { - font-weight: 700; - font-size: 22px; } \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 0946cae0..a5061d72 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -141,9 +141,9 @@ export default function Home() {
- FalkorDB + FalkorDB -

+

CODE GRAPH

diff --git a/tailwind.config.js b/tailwind.config.js index a14e83b2..f0c4cb6f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -23,6 +23,9 @@ module.exports = { }, extend: { colors: { + pink: "#F43F5F", + yellow: "#E9B306", + blue: "#15B8A6", border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", From 98f3cb690f25e91e223d50f9a4e2a0896461e4e0 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 5 Nov 2024 15:19:42 +0200 Subject: [PATCH 009/115] fix type err --- app/components/Input.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Input.tsx b/app/components/Input.tsx index 2e3282a7..4d36afa5 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -127,7 +127,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, const label = option.labels[0] const name = option.properties.name const path = option.properties.path - const colorName = getCategoryColorName(graph.CategoriesMap.get(label)?.index) + const colorName = getCategoryColorName(graph.CategoriesMap.get(label)!.index) return ( diff --git a/e2e/config/constants.ts b/e2e/config/constants.ts new file mode 100644 index 00000000..e873f0d7 --- /dev/null +++ b/e2e/config/constants.ts @@ -0,0 +1,5 @@ +export const GRAPH_ID = "1"; +export const PROJECT_NAME = "GraphRAG-SDK"; +export const CHAT_OPTTIONS_COUNT = 3; +export const Node_Question = "how many nodes do we have?"; +export const Edge_Question = "how many edges do we have?"; \ No newline at end of file diff --git a/e2e/config/urls.json b/e2e/config/urls.json index 4bae1f98..69a3e328 100644 --- a/e2e/config/urls.json +++ b/e2e/config/urls.json @@ -1,3 +1,4 @@ { - "baseUrl": "http://localhost:3000/" + "baseUrl": "http://localhost:3000/", + "graphRAGuRL": "https://github.com/FalkorDB/GraphRAG-SDK" } \ No newline at end of file diff --git a/e2e/infra/api/apiRequests.ts b/e2e/infra/api/apiRequests.ts index f0322ef0..256330e9 100644 --- a/e2e/infra/api/apiRequests.ts +++ b/e2e/infra/api/apiRequests.ts @@ -1,7 +1,7 @@ import { APIRequestContext, request } from "@playwright/test" -const getRequest = async (url: string, body: any, availableRequest?: APIRequestContext, headers?: Record) => { +const getRequest = async (url: string, body?: any, availableRequest?: APIRequestContext, headers?: Record) => { const requestOptions = { data: body, headers: headers || undefined, @@ -11,7 +11,7 @@ const getRequest = async (url: string, body: any, availableRequest?: APIRequestC return await requestContext.get(url, requestOptions); }; -const postRequest = async (url: string, body: any, availableRequest?: APIRequestContext, headers?: Record) => { +const postRequest = async (url: string, body?: any, availableRequest?: APIRequestContext, headers?: Record) => { const requestOptions = { data: body, headers: headers || undefined, diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts new file mode 100644 index 00000000..01886ce0 --- /dev/null +++ b/e2e/logic/POM/codeGraph.ts @@ -0,0 +1,106 @@ +import { Locator } from "playwright"; +import BasePage from "../../infra/ui/basePage"; + +export default class CodeGraph extends BasePage { + /* NavBar Locators*/ + private get homeButton(): Locator { + return this.page.locator("//a[p[text() = 'Home']]") + } + + /* CodeGraph Locators*/ + private get comboBoxbtn(): Locator { + return this.page.locator("//button[@role='combobox']") + } + + private get selectGraphInComboBox(): (graph: string) => Locator { + return (graph: string) => this.page.locator(`//div[@role='presentation']//div[@role='option'][${graph}]`); + } + + private get lastElementInChat(): Locator { + return this.page.locator("//main[@data-name='main-chat']/*[last()]/p"); + } + + /* Chat Locators */ + private get showPathBtn(): Locator { + return this.page.locator("//button[contains(@class, 'Tip')]//div//h1[text()='Show the path']"); + } + + private get askquestionInput(): Locator { + return this.page.locator("//input[contains(@placeholder, 'Ask your question')]"); + } + + private get askquestionBtn(): Locator { + return this.page.locator("//input[contains(@placeholder, 'Ask your question')]/following::button[1]"); + } + + private get lightbulbBtn(): Locator { + return this.page.locator("//button[@data-name='lightbulb']"); + } + + private get lastChatElementButtonCount(): Locator { + return this.page.locator("//main[@data-name='main-chat']/*[last()]/button"); + } + + private get chatContainer(): Locator { + return this.page.locator("//main[@data-name='main-chat']"); + } + + private get previousQuestionLoadingImage(): Locator { + return this.page.locator("//main[@data-name='main-chat']/*[last()-2]//img[@alt='Waiting for response']") + } + + /* NavBar functionality */ + async clickOnHomeBtn(): Promise { + await this.homeButton.click() + } + + /* Chat functionality */ + async clickOnshowPathBtn(): Promise { + await this.showPathBtn.click(); + } + + async sendMessage(message: string) { + await this.askquestionInput.fill(message); + await this.askquestionBtn.click(); + } + + async clickOnLightBulbBtn(): Promise { + await this.lightbulbBtn.click(); + } + + async getTextInLastChatElement(): Promise{ + return await this.lastElementInChat.textContent(); + } + + async getLastChatElementButtonCount(): Promise{ + return await this.lastChatElementButtonCount.count(); + } + + async scrollToTop() { + await this.chatContainer.evaluate((chat) => { + chat.scrollTop = 0; + }); + } + + async getScrollMetrics() { + const scrollTop = await this.chatContainer.evaluate((el) => el.scrollTop); + const scrollHeight = await this.chatContainer.evaluate((el) => el.scrollHeight); + const clientHeight = await this.chatContainer.evaluate((el) => el.clientHeight); + return { scrollTop, scrollHeight, clientHeight }; + } + + async isAtBottom(): Promise { + const { scrollTop, scrollHeight, clientHeight } = await this.getScrollMetrics(); + return Math.abs(scrollTop + clientHeight - scrollHeight) < 1; + } + + async getpreviousQuestionLoadingImage(): Promise { + return this.previousQuestionLoadingImage.isVisible(); + } + + /* CodeGraph functionality */ + async selectGraph(graph: string): Promise { + await this.comboBoxbtn.click(); + await this.selectGraphInComboBox(graph).click(); + } +} \ No newline at end of file diff --git a/e2e/logic/POM/navBarComponent.ts b/e2e/logic/POM/navBarComponent.ts deleted file mode 100644 index 62326f55..00000000 --- a/e2e/logic/POM/navBarComponent.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Locator } from "playwright"; -import BasePage from "../../infra/ui/basePage"; - -export default class NavBarComponent extends BasePage { - - private get homeButton(): Locator { - return this.page.locator("//a[p[text() = 'Home']]") - } - - async clickOnHomeBtn(): Promise { - await this.homeButton.click() - } -} \ No newline at end of file diff --git a/e2e/logic/api/apiCalls.ts b/e2e/logic/api/apiCalls.ts new file mode 100644 index 00000000..371191d2 --- /dev/null +++ b/e2e/logic/api/apiCalls.ts @@ -0,0 +1,32 @@ +import { getRequest, postRequest } from "../../infra/api/apiRequests"; +import urls from '../../config/urls.json' +import { askQuestionResponse, createProjectResponse, fetchLatestRepoInfo, getProjectResponse, showPathResponse } from "./apiResponse"; + +export class ApiCalls { + + async createProject(projectname: string): Promise{ + const result = await postRequest(urls.baseUrl + "api/repo?url=" + projectname); + return await result.json(); + } + + async getProject(projectName: string): Promise{ + const result = await getRequest(urls.baseUrl + "api/repo/" + projectName); + return await result.json(); + } + + async fetchLatestRepoInfo(projectName: string): Promise{ + const result = await postRequest(urls.baseUrl + "api/repo/" + projectName); + return await result.json(); + } + + async showPath(projectName: string, sourceId: string, tragetId: string): Promise{ + const result = await postRequest(urls.baseUrl + "api/repo/" + projectName + "/" + sourceId + "?targetId=" + tragetId); + return await result.json() + } + + async askQuestion(projectName: string, question: string): Promise{ + const result = await postRequest(urls.baseUrl + "api/chat/" + projectName + "?msg=" + question); + return await result.json() + } + +} \ No newline at end of file diff --git a/e2e/logic/api/apiResponse.ts b/e2e/logic/api/apiResponse.ts new file mode 100644 index 00000000..2782237d --- /dev/null +++ b/e2e/logic/api/apiResponse.ts @@ -0,0 +1,75 @@ +export interface createProjectResponse { + message: string; +} + +export interface getProjectResponse { + result: { + entities: { + edges: { + alias: string; + dest_node: number; + id: number; + properties: Record; + relation: string; + src_node: number; + }[]; + nodes: { + alias: string; + id: number; + labels: string[]; + properties: { + ext?: string; + name: string; + path: string; + doc?: string; + src_end?: number; + src_start?: number; + }; + }[]; + }; + status: string; + }; +} + +export interface fetchLatestRepoInfo { + result: { + info: { + commit: string; + edge_count: number; + node_count: number; + repo_url: string; + }; + status: string; + }; +} + + +export interface showPathResponse { + result: { + paths: Array>; + status: string; + }; +} + +export interface askQuestionResponse{ + result: { + response: string; + status: string; + } +} + diff --git a/e2e/tests/api.spec.ts b/e2e/tests/api.spec.ts new file mode 100644 index 00000000..a9f05185 --- /dev/null +++ b/e2e/tests/api.spec.ts @@ -0,0 +1,70 @@ +import { test, expect } from "@playwright/test"; +import BrowserWrapper from "../infra/ui/browserWrapper"; +import urls from "../config/urls.json"; +import { ApiCalls } from "../logic/api/apiCalls"; +import { Node_Question, PROJECT_NAME } from "../config/constants"; + +test.describe("Api tests", () => { + let api: ApiCalls; + + test.beforeAll(async () => { + api = new ApiCalls(); + await api.createProject(urls.graphRAGuRL); + }); + + test("Validates API response structure after project creation and retrieval", async () => { + const result = await api.getProject(PROJECT_NAME); + expect(result).toEqual( + expect.objectContaining({ + result: expect.objectContaining({ + entities: expect.objectContaining({ + edges: expect.any(Array), + nodes: expect.any(Array), + }), + status: "success", + }), + }) + ); + }); + + test("Validate structure of API response for latest repository info", async () => { + const response = await api.fetchLatestRepoInfo(PROJECT_NAME); + expect(response).toEqual( + expect.objectContaining({ + result: expect.objectContaining({ + info: expect.objectContaining({ + commit: expect.any(String), + edge_count: expect.any(Number), + node_count: expect.any(Number), + repo_url: expect.any(String), + }), + status: "success", + }), + }) + ); + }); + + test("Validate API response for paths between nodes", async () => { + const response = await api.showPath(PROJECT_NAME, "4", "485"); + expect(response).toEqual( + expect.objectContaining({ + result: expect.objectContaining({ + paths: expect.any(Array), + status: "success", + }), + }) + ); + }); + + test("Validate API response for asking a graph-related question", async () => { + const response = await api.askQuestion(PROJECT_NAME, Node_Question); + expect(response).toEqual( + expect.objectContaining({ + result: expect.objectContaining({ + response: expect.any(String), + status: "success", + }), + }) + ); + }); +}); diff --git a/e2e/tests/navBar.spec.ts b/e2e/tests/navBar.spec.ts index 8d8b80b7..4f6ce741 100644 --- a/e2e/tests/navBar.spec.ts +++ b/e2e/tests/navBar.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from '@playwright/test'; import BrowserWrapper from '../infra/ui/browserWrapper'; -import NavBarComponent from '../logic/POM/navBarComponent'; +import CodeGraph from '../logic/POM/codeGraph'; import urls from '../config/urls.json'; test.describe(' Navbar tests', () => { @@ -15,7 +15,7 @@ test.describe(' Navbar tests', () => { }); test("Verify clicking on Home redirects to specified URL", async () => { - const navBar = await browser.createNewPage(NavBarComponent, urls.baseUrl) + const navBar = await browser.createNewPage(CodeGraph, urls.baseUrl) const page = await navBar.clickOnHomeBtn() expect(navBar.getCurrentURL()).toBe("http://localhost:3000/") }) From ffec1fe21218487ad4a76a7f6b2199c6813f9e29 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:50:29 +0200 Subject: [PATCH 027/115] Fix backend setup to properly run Flask --- .github/workflows/playwright.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 734c5177..3f15d03e 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -29,6 +29,7 @@ jobs: run: npx playwright install --with-deps - name: Set up Python environment run: | + cd code-graph-backend python3 -m venv myproject_env source myproject_env/bin/activate pip install Flask @@ -38,12 +39,10 @@ jobs: export PATH="$HOME/.local/bin:$PATH" - name: Set up Flask and install backend dependencies run: | - cd code-graph-backend poetry install - name: Run Flask backend run: | - cd code-graph-backend - nohup flask --app code_graph run --debug & + flask --app code_graph run --debug - name: Run Playwright tests with detailed reporting run: | npm install From 0180c14a15522f8d90df49bf23561a1f36096782 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:06:01 +0200 Subject: [PATCH 028/115] Update playwright.yml --- .github/workflows/playwright.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 3f15d03e..63db32b9 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -39,6 +39,7 @@ jobs: export PATH="$HOME/.local/bin:$PATH" - name: Set up Flask and install backend dependencies run: | + cd code-graph-backend poetry install - name: Run Flask backend run: | From 74fff0ab921d6e24ca84cfd9faade599899f32a4 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:09:38 +0200 Subject: [PATCH 029/115] Update playwright.yml --- .github/workflows/playwright.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 63db32b9..cad18b52 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -43,6 +43,7 @@ jobs: poetry install - name: Run Flask backend run: | + cd code-graph-backend flask --app code_graph run --debug - name: Run Playwright tests with detailed reporting run: | From 5ccad750c6e979faa81246fd8512a1a211bd22b0 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:17:09 +0200 Subject: [PATCH 030/115] Update playwright.yml --- .github/workflows/playwright.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index cad18b52..f7510403 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -27,23 +27,24 @@ jobs: run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps - - name: Set up Python environment + - name: Set up Python environment, install dependencies, and run Flask run: | + # Install Poetry first + curl -sSL https://install.python-poetry.org | python3 - + export PATH="$HOME/.local/bin:$PATH" + + # Set up Python virtual environment cd code-graph-backend python3 -m venv myproject_env source myproject_env/bin/activate + + # Install Flask pip install Flask - - name: Install Poetry - run: | - curl -sSL https://install.python-poetry.org | python3 - - export PATH="$HOME/.local/bin:$PATH" - - name: Set up Flask and install backend dependencies - run: | - cd code-graph-backend + + # Install dependencies using Poetry poetry install - - name: Run Flask backend - run: | - cd code-graph-backend + + # Run Flask backend flask --app code_graph run --debug - name: Run Playwright tests with detailed reporting run: | From ff67b5d9159111a5ef57a1060072d65d7207b018 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:25:19 +0200 Subject: [PATCH 031/115] fix: run flask server in the background --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f7510403..4bcd2938 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -45,7 +45,7 @@ jobs: poetry install # Run Flask backend - flask --app code_graph run --debug + flask --app code_graph run --debug & - name: Run Playwright tests with detailed reporting run: | npm install From 8f85cdd8ed54a45c68df3e3932fc63b906ead781 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 11 Nov 2024 20:51:40 +0200 Subject: [PATCH 032/115] remove commit list and change the chat style --- app/components/chat.tsx | 179 ++++++++++++++++++++++------------ app/components/code-graph.tsx | 4 +- package-lock.json | 11 +++ package.json | 1 + repositories/GraphRAG-SDK | 1 + 5 files changed, 132 insertions(+), 64 deletions(-) create mode 160000 repositories/GraphRAG-SDK diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 5f9bdca0..308d96b2 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -7,11 +7,11 @@ import Input from "./Input"; import { Graph } from "./model"; import { cn } from "@/lib/utils"; import { LAYOUT } from "./code-graph"; +import { TypeAnimation } from "react-type-animation"; enum MessageTypes { Query, Response, - Tip, Path, PathResponse, Pending, @@ -49,7 +49,7 @@ const RemoveLastPath = (messages: Message[]) => { export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isPath, setIsPath }: Props) { // Holds the messages in the chat - const [messages, setMessages] = useState([{ type: MessageTypes.Tip }]); + const [messages, setMessages] = useState([]); // Holds the messages in the chat const [paths, setPaths] = useState<{ nodes: any[], edges: any[] }[]>([]); @@ -61,6 +61,8 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP const [isPathResponse, setIsPathResponse] = useState(false); + const [tipOpen, setTipOpen] = useState(false); + // A reference to the chat container to allow scrolling to the bottom const containerRef: React.RefObject = useRef(null); @@ -337,16 +339,21 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP

You

-

{message.text}

+

{message.text}

) case MessageTypes.Response: return (
-

Answer

+

Answer

-

{message.text?.replaceAll('"', "")}

+
) case MessageTypes.Text: return ( @@ -408,51 +415,6 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP }
) - case MessageTypes.Tip: return ( -
- - - -
- ) default: return (
Waiting for response @@ -463,28 +425,121 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP return (
-
-

WELCOME TO OUR ASSISTANCE SERVICE

- - We can help you access and update only the needed - data via paths, optimizing network requests with - batching and catching for better performance. - +
+ { + messages.length === 0 && + <> +

WELCOME TO OUR ASSISTANCE SERVICE

+ + We can help you access and update only the needed + data via paths, optimizing network requests with + batching and catching for better performance. + + + + + + } { messages.map((message, index) => { return getMessage(message, index) }) } + { + tipOpen && +
setTipOpen(false)}> + + + +
+ }
- {repo && + { + repo &&
- - - - diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index b626fd10..bc49a25c 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -487,7 +487,7 @@ export function CodeGraph({
}
- { + {/* { graph.Id && - } + } */}
) diff --git a/package-lock.json b/package-lock.json index 9cc07005..3f4547f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "react-cytoscapejs": "^2.0.0", "react-dom": "^18", "react-resizable-panels": "^2.0.20", + "react-type-animation": "^3.2.0", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7", "web-tree-sitter": "^0.22.6" @@ -5925,6 +5926,16 @@ } } }, + "node_modules/react-type-animation": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/react-type-animation/-/react-type-animation-3.2.0.tgz", + "integrity": "sha512-WXTe0i3rRNKjmggPvT5ntye1QBt0ATGbijeW6V3cQe2W0jaMABXXlPPEdtofnS9tM7wSRHchEvI9SUw+0kUohw==", + "peerDependencies": { + "prop-types": "^15.5.4", + "react": ">= 15.0.0", + "react-dom": ">= 15.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index cf38f37d..e466c31d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "react-cytoscapejs": "^2.0.0", "react-dom": "^18", "react-resizable-panels": "^2.0.20", + "react-type-animation": "^3.2.0", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7", "web-tree-sitter": "^0.22.6" diff --git a/repositories/GraphRAG-SDK b/repositories/GraphRAG-SDK new file mode 160000 index 00000000..52e3042b --- /dev/null +++ b/repositories/GraphRAG-SDK @@ -0,0 +1 @@ +Subproject commit 52e3042ba79d83489d4ceff6108973f0f2e43442 From 24d9943b0b0798deef92e2ae779ae3da35cab259 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 12 Nov 2024 10:14:19 +0200 Subject: [PATCH 033/115] add env variables for api keys --- .github/workflows/playwright.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 4bcd2938..b4b5999e 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -28,6 +28,9 @@ jobs: - name: Install Playwright Browsers run: npx playwright install --with-deps - name: Set up Python environment, install dependencies, and run Flask + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} run: | # Install Poetry first curl -sSL https://install.python-poetry.org | python3 - From 1426b64387204401248f67e0b04abb83cb5288a5 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 12 Nov 2024 12:15:58 +0200 Subject: [PATCH 034/115] final fix --- app/components/Input.tsx | 3 +- app/components/chat.tsx | 191 ++++++++++++++++------------------ app/components/code-graph.tsx | 6 +- app/page.tsx | 1 + 4 files changed, 96 insertions(+), 105 deletions(-) diff --git a/app/components/Input.tsx b/app/components/Input.tsx index 1fa3d960..078141c3 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -133,6 +133,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, const name = option.properties.name const path = option.properties.path const colorName = getCategoryColorName(graph.CategoriesMap.get(label)!.index) + const color = getCategoryColorValue(graph.CategoriesMap.get(label)!.index) return ( + + + + const getMessage = (message: Message, index?: number) => { switch (message.type) { case MessageTypes.Query: return ( @@ -349,8 +414,9 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP

Answer

@@ -394,8 +460,9 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP message.paths.map((p, i: number) => ( - - + {getTip()} } { @@ -485,48 +512,8 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP } { tipOpen && -
setTipOpen(false)}> - - - +
setTipOpen(false)}> + {getTip()}
}
diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index bc49a25c..7394ab7d 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -24,6 +24,7 @@ interface Props { setPath: Dispatch> chartRef: MutableRefObject selectedValue: string + selectedPathId: string | undefined setSelectedPathId: (selectedPathId: string) => void isPathResponse: boolean setIsPathResponse: Dispatch> @@ -123,7 +124,8 @@ export function CodeGraph({ selectedValue, setSelectedPathId, isPathResponse, - setIsPathResponse + setIsPathResponse, + selectedPathId }: Props) { let graph = useContext(GraphContext) @@ -470,7 +472,7 @@ export function CodeGraph({ cy.on('tap', 'edge', (evt) => { const { target } = evt - if (!isPathResponse) return + if (!isPathResponse || selectedPathId === target.id()) return setSelectedPathId(target.id()) }); diff --git a/app/page.tsx b/app/page.tsx index a5061d72..a9aac303 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -211,6 +211,7 @@ export default function Home() { setPath={setPath} isShowPath={!!path} selectedValue={selectedValue} + selectedPathId={selectedPathId} setSelectedPathId={setSelectedPathId} isPathResponse={isPathResponse} setIsPathResponse={setIsPathResponse} From 6c72394d89a439aab1b0ab7cb63617799d87c58f Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 12 Nov 2024 14:09:03 +0200 Subject: [PATCH 035/115] commit --- app/page.tsx | 57 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index a9aac303..a4a81851 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -11,6 +11,7 @@ import { toast } from '@/components/ui/use-toast'; import { GraphContext } from './components/provider'; import Image from 'next/image'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Progress } from '@/components/ui/progress'; export type PathNode = { id?: number @@ -28,10 +29,11 @@ export default function Home() { const [selectedValue, setSelectedValue] = useState(""); const [selectedPathId, setSelectedPathId] = useState(); const [isPathResponse, setIsPathResponse] = useState(false); - const [createURL, setCreateURL] = useState("https://github.com/FalkorDB/GraphRAG-SDK") + const [createURL, setCreateURL] = useState("") const [createOpen, setCreateOpen] = useState(false) const [options, setOptions] = useState([]); const [path, setPath] = useState(); + const [isSubmit, setIsSubmit] = useState(false); const chartRef = useRef(null) useEffect(() => { @@ -59,6 +61,8 @@ export default function Home() { async function onCreateRepo(e: React.FormEvent) { e.preventDefault() + setIsSubmit(true) + if (!createURL) { toast({ variant: "destructive", @@ -87,6 +91,7 @@ export default function Home() { setSelectedValue(graphName) setCreateURL("") setCreateOpen(false) + setIsSubmit(false) toast({ title: "Success", @@ -171,29 +176,37 @@ export default function Home() { - CREATE A NEW PROJECT + {!isSubmit ? "CREATE A NEW PROJECT" : "THANK YOU FOR A NEW REQUEST"} - Please provide the URL of the model to connect and start querying data + { + !isSubmit + ? "Please provide the URL of the model to connect and start querying data" + : "Processing your graph, this could take a while. We appreciate your patience" + } -
- setCreateURL(e.target.value)} - placeholder="Type URL" - /> -
- -
-
+ { + !isSubmit ? +
+ setCreateURL(e.target.value)} + placeholder="Type URL" + /> +
+ +
+
+ : + }
@@ -215,7 +228,7 @@ export default function Home() { setSelectedPathId={setSelectedPathId} isPathResponse={isPathResponse} setIsPathResponse={setIsPathResponse} - /> + /> From 96e29aa786264d6004f901145ac3fb59d95ed3c3 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 12 Nov 2024 14:09:51 +0200 Subject: [PATCH 036/115] commit --- app/components/Input.tsx | 12 ++++++++---- app/components/chat.tsx | 36 +++++++---------------------------- app/components/code-graph.tsx | 15 +++++++++------ components/ui/progress.tsx | 28 +++++++++++++++++++++++++++ package-lock.json | 24 +++++++++++++++++++++++ package.json | 1 + 6 files changed, 77 insertions(+), 39 deletions(-) create mode 100644 components/ui/progress.tsx diff --git a/app/components/Input.tsx b/app/components/Input.tsx index 078141c3..42df1fc1 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -36,7 +36,11 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, useEffect(() => { const timeout = setTimeout(async () => { - if (!value || node?.id) return + if (!value || node?.id) { + setOptions([]) + setOpen(false) + return + } const result = await fetch(`/api/repo/${graph.Id}/?prefix=${value}&type=autoComplete`, { method: 'POST' @@ -71,10 +75,10 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, if (!option) return if (handelSubmit) { handelSubmit(option) + } else { + if (!open) return onValueChange({ name: option.properties.name, id: option.id }) - } - if (!open) return - onValueChange({ name: option.properties.name, id: option.id }) + } setOpen(false) return } diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 2c550325..069ed4cf 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -344,18 +344,6 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP const getTip = () => <> - - const getMessage = (message: Message, index?: number) => { diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 7394ab7d..9e32a3e7 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -11,7 +11,7 @@ import ElementMenu from "./elementMenu"; import ElementTooltip from "./elementTooltip"; import Combobox from "./combobox"; import { toast } from '@/components/ui/use-toast'; -import { Path } from '../page'; +import { Path, PathNode } from '../page'; import Input from './Input'; import CommitList from './commitList'; import { Checkbox } from '@/components/ui/checkbox'; @@ -136,7 +136,7 @@ export function CodeGraph({ const [position, setPosition] = useState(); const [tooltipPosition, setTooltipPosition] = useState(); const [graphName, setGraphName] = useState(""); - const [searchNodeName, setSearchNodeName] = useState(""); + const [searchNode, setSearchNode] = useState({}); const [commits, setCommits] = useState([]); const [nodesCount, setNodesCount] = useState(0); const [edgesCount, setEdgesCount] = useState(0); @@ -321,7 +321,9 @@ export function CodeGraph({ if (!chart) return - let chartNode = chart.elements(`node[name = "${node.properties.name}"]`) + const n = { name: node.properties.name, id: node.id } + + let chartNode = chart.elements(`node[name = "${n.name}"]`) if (chartNode.length === 0) { const [newNode] = graph.extend({ nodes: [node], edges: [] }) @@ -331,7 +333,7 @@ export function CodeGraph({ chartNode.select() const layout = { ...LAYOUT, padding: 250 } chartNode.layout(layout).run() - setSearchNodeName("") + setSearchNode(n) } return ( @@ -353,10 +355,11 @@ export function CodeGraph({
setSearchNodeName(node.name!)} + value={searchNode.name} + onValueChange={({ name }) => setSearchNode({ name })} icon={} handelSubmit={handelSearchSubmit} + node={searchNode} />
diff --git a/components/ui/progress.tsx b/components/ui/progress.tsx new file mode 100644 index 00000000..5c87ea48 --- /dev/null +++ b/components/ui/progress.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" + +import { cn } from "@/lib/utils" + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)) +Progress.displayName = ProgressPrimitive.Root.displayName + +export { Progress } diff --git a/package-lock.json b/package-lock.json index 3f4547f9..6f8bab75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-hover-card": "^1.1.2", + "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", @@ -1323,6 +1324,29 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz", + "integrity": "sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==", + "dependencies": { + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", diff --git a/package.json b/package.json index e466c31d..26bc32a5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-hover-card": "^1.1.2", + "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", From e4470541e32cd4ef8ddb0a33fd5232f5f5d6e64b Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 12 Nov 2024 15:43:45 +0200 Subject: [PATCH 037/115] fix search input --- app/components/Input.tsx | 6 ++++-- app/components/code-graph.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/components/Input.tsx b/app/components/Input.tsx index 42df1fc1..fd8ddc8b 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -37,7 +37,9 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, const timeout = setTimeout(async () => { if (!value || node?.id) { - setOptions([]) + if (!value) { + setOptions([]) + } setOpen(false) return } @@ -120,7 +122,6 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, const newVal = e.target.value onValueChange({ name: newVal }) }} - onBlur={() => setOpen(false)} {...props} /> { @@ -147,6 +148,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, onMouseEnter={() => setSelectedOption(index)} onMouseLeave={() => setSelectedOption(-1)} onClick={() => { + debugger onValueChange({ name: option.properties.name, id: option.id }) handelSubmit && handelSubmit(option) setOpen(false) diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 9e32a3e7..025395a3 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -323,7 +323,7 @@ export function CodeGraph({ const n = { name: node.properties.name, id: node.id } - let chartNode = chart.elements(`node[name = "${n.name}"]`) + let chartNode = chart.elements(`node[id = "${n.id}"]`) if (chartNode.length === 0) { const [newNode] = graph.extend({ nodes: [node], edges: [] }) From d35231f0bd24878dcd7d6d904f72edfb62945f71 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:06:51 +0200 Subject: [PATCH 038/115] chat tests (#189, #190, #191) --- e2e/config/constants.ts | 2 +- e2e/tests/chat.spec.ts | 60 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 e2e/tests/chat.spec.ts diff --git a/e2e/config/constants.ts b/e2e/config/constants.ts index e873f0d7..a04076ba 100644 --- a/e2e/config/constants.ts +++ b/e2e/config/constants.ts @@ -1,5 +1,5 @@ export const GRAPH_ID = "1"; export const PROJECT_NAME = "GraphRAG-SDK"; -export const CHAT_OPTTIONS_COUNT = 3; +export const CHAT_OPTTIONS_COUNT = 1; export const Node_Question = "how many nodes do we have?"; export const Edge_Question = "how many edges do we have?"; \ No newline at end of file diff --git a/e2e/tests/chat.spec.ts b/e2e/tests/chat.spec.ts new file mode 100644 index 00000000..eca9a06e --- /dev/null +++ b/e2e/tests/chat.spec.ts @@ -0,0 +1,60 @@ +import { test, expect } from "@playwright/test"; +import BrowserWrapper from "../infra/ui/browserWrapper"; +import urls from "../config/urls.json"; +import { ApiCalls } from "../logic/api/apiCalls"; +import CodeGraph from "../logic/POM/codeGraph"; +import { CHAT_OPTTIONS_COUNT, GRAPH_ID, Node_Question } from "../config/constants"; + +test.describe("Chat tests", () => { + let browser: BrowserWrapper; + let api: ApiCalls; + + test.beforeAll(async () => { + browser = new BrowserWrapper(); + api = new ApiCalls(); + await api.createProject(urls.graphRAGuRL); + }); + + test.afterAll(async () => { + await browser.closeBrowser(); + }); + + test(`Validate clicking the lightbulb button displays the correct options at the end of the chat`, async () => { + const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); + await chat.selectGraph(GRAPH_ID); + await chat.clickOnLightBulbBtn(); + const count = await chat.getLastChatElementButtonCount(); + expect(count).toBe(CHAT_OPTTIONS_COUNT); + }); + + test(`Validate that multiple consecutive questions receive individual answers`, async () => { + const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); + await chat.selectGraph(GRAPH_ID); + const isLoadingArray: boolean[] = []; + + for (let i = 0; i < 10; i++) { + await chat.sendMessage(Node_Question); + const isLoading: boolean = await chat.getpreviousQuestionLoadingImage(); + isLoadingArray.push(isLoading); + if (i > 0) { + const prevIsLoading = isLoadingArray[i - 1]; + expect(prevIsLoading).toBe(false); + } + } + }); + + test("Verify auto-scroll and manual scroll in chat", async () => { + const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); + await chat.selectGraph(GRAPH_ID); + for (let i = 0; i < 10; i++) { + await chat.sendMessage(Node_Question); + } + await new Promise(resolve => setTimeout(resolve, 500)); + await chat.scrollToTop(); + const { scrollTop } = await chat.getScrollMetrics(); + expect(scrollTop).toBeLessThanOrEqual(1); + await chat.sendMessage("Latest Message"); + await new Promise(resolve => setTimeout(resolve, 500)); + expect(await chat.isAtBottom()).toBe(true); + }); +}); From 6d966cfb7b02a669188a4154bdc10447c3b5aceb Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:55:53 +0200 Subject: [PATCH 039/115] chat tests (#195, #196) path between nodes --- app/components/Input.tsx | 1 - e2e/config/constants.ts | 4 +++- e2e/config/urls.json | 4 +++- e2e/logic/POM/codeGraph.ts | 31 ++++++++++++++++++++++++++++++- e2e/logic/utils.ts | 2 ++ e2e/tests/chat.spec.ts | 28 +++++++++++++++++++++++++--- 6 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 e2e/logic/utils.ts diff --git a/app/components/Input.tsx b/app/components/Input.tsx index fd8ddc8b..65ee6e1e 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -148,7 +148,6 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, onMouseEnter={() => setSelectedOption(index)} onMouseLeave={() => setSelectedOption(-1)} onClick={() => { - debugger onValueChange({ name: option.properties.name, id: option.id }) handelSubmit && handelSubmit(option) setOpen(false) diff --git a/e2e/config/constants.ts b/e2e/config/constants.ts index a04076ba..0c38c15a 100644 --- a/e2e/config/constants.ts +++ b/e2e/config/constants.ts @@ -2,4 +2,6 @@ export const GRAPH_ID = "1"; export const PROJECT_NAME = "GraphRAG-SDK"; export const CHAT_OPTTIONS_COUNT = 1; export const Node_Question = "how many nodes do we have?"; -export const Edge_Question = "how many edges do we have?"; \ No newline at end of file +export const Edge_Question = "how many edges do we have?"; +export const Node_Import_Data = "import_data"; +export const Node_Add_Edge = "add_edge"; \ No newline at end of file diff --git a/e2e/config/urls.json b/e2e/config/urls.json index 69a3e328..c8f8a232 100644 --- a/e2e/config/urls.json +++ b/e2e/config/urls.json @@ -1,4 +1,6 @@ { "baseUrl": "http://localhost:3000/", - "graphRAGuRL": "https://github.com/FalkorDB/GraphRAG-SDK" + "graphRAGuRL": "https://github.com/FalkorDB/GraphRAG-SDK", + "falkorDBUrl": "https://www.falkordb.com/", + "falkorDbGithubUrl": "https://github.com/FalkorDB/code-graph" } \ No newline at end of file diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 01886ce0..0e207cd2 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -22,7 +22,7 @@ export default class CodeGraph extends BasePage { /* Chat Locators */ private get showPathBtn(): Locator { - return this.page.locator("//button[contains(@class, 'Tip')]//div//h1[text()='Show the path']"); + return this.page.locator("//button[contains(@class, 'Tip')]"); } private get askquestionInput(): Locator { @@ -48,6 +48,22 @@ export default class CodeGraph extends BasePage { private get previousQuestionLoadingImage(): Locator { return this.page.locator("//main[@data-name='main-chat']/*[last()-2]//img[@alt='Waiting for response']") } + + private get selectInputForShowPath(): (inputNum: string) => Locator { + return (inputNum: string) => this.page.locator(`(//main[@data-name='main-chat']//input)[${inputNum}]`); + } + + private get locateNodeInLastChatPath(): (node: string) => Locator { + return (node: string) => this.page.locator(`//main[@data-name='main-chat']/*[last()]//span[contains(text(), '${node}')]`); + } + + private get selectFirstPathOption(): (inputNum: string) => Locator { + return (inputNum: string) => this.page.locator(`(//main[@data-name='main-chat']//input)[1]/following::div[${inputNum}]//button[1]`); + } + + private get notificationNoPathFound(): Locator { + return this.page.locator("//div//ol/li"); + } /* NavBar functionality */ async clickOnHomeBtn(): Promise { @@ -98,6 +114,19 @@ export default class CodeGraph extends BasePage { return this.previousQuestionLoadingImage.isVisible(); } + async insertInputForShowPath(inputNum: string, node: string): Promise { + await this.selectInputForShowPath(inputNum).fill(node); + await this.selectFirstPathOption(inputNum).click(); + } + + async isNodeVisibleInLastChatPath(node: string): Promise { + return await this.locateNodeInLastChatPath(node).isVisible(); + } + + async isNotificationNoPathFound(): Promise { + return await this.notificationNoPathFound.isVisible(); + } + /* CodeGraph functionality */ async selectGraph(graph: string): Promise { await this.comboBoxbtn.click(); diff --git a/e2e/logic/utils.ts b/e2e/logic/utils.ts new file mode 100644 index 00000000..091fadac --- /dev/null +++ b/e2e/logic/utils.ts @@ -0,0 +1,2 @@ + +export const delay = (ms: number): Promise => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/e2e/tests/chat.spec.ts b/e2e/tests/chat.spec.ts index eca9a06e..fd45e616 100644 --- a/e2e/tests/chat.spec.ts +++ b/e2e/tests/chat.spec.ts @@ -3,7 +3,8 @@ import BrowserWrapper from "../infra/ui/browserWrapper"; import urls from "../config/urls.json"; import { ApiCalls } from "../logic/api/apiCalls"; import CodeGraph from "../logic/POM/codeGraph"; -import { CHAT_OPTTIONS_COUNT, GRAPH_ID, Node_Question } from "../config/constants"; +import { CHAT_OPTTIONS_COUNT, GRAPH_ID, Node_Add_Edge, Node_Import_Data, Node_Question } from "../config/constants"; +import { delay } from "../logic/utils"; test.describe("Chat tests", () => { let browser: BrowserWrapper; @@ -49,12 +50,33 @@ test.describe("Chat tests", () => { for (let i = 0; i < 10; i++) { await chat.sendMessage(Node_Question); } - await new Promise(resolve => setTimeout(resolve, 500)); + await delay(500); await chat.scrollToTop(); const { scrollTop } = await chat.getScrollMetrics(); expect(scrollTop).toBeLessThanOrEqual(1); await chat.sendMessage("Latest Message"); - await new Promise(resolve => setTimeout(resolve, 500)); + await delay(500); expect(await chat.isAtBottom()).toBe(true); }); + + test("Verify successful node path connection between two nodes in chat", async () => { + const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); + await chat.selectGraph(GRAPH_ID); + await chat.clickOnshowPathBtn(); + await chat.insertInputForShowPath("1", Node_Import_Data); + await chat.insertInputForShowPath("2", Node_Add_Edge); + await delay(200); + expect(await chat.isNodeVisibleInLastChatPath(Node_Import_Data)).toBe(true); + expect(await chat.isNodeVisibleInLastChatPath(Node_Add_Edge)).toBe(true); + }); + + test("Verify unsuccessful node path connection between two nodes in chat", async () => { + const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); + await chat.selectGraph(GRAPH_ID); + await chat.clickOnshowPathBtn(); + await chat.insertInputForShowPath("1", Node_Add_Edge); + await chat.insertInputForShowPath("2", Node_Import_Data); + await delay(200); + expect(await chat.isNotificationNoPathFound()).toBe(true); + }); }); From 416558c41cb38dac282d02f499496c029c69219e Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:57:11 +0200 Subject: [PATCH 040/115] navBar test (#197, #198, 199) --- e2e/logic/POM/codeGraph.ts | 44 +++++++++++++++++++++++++++++++++----- e2e/tests/navBar.spec.ts | 28 ++++++++++++++++++++---- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 0e207cd2..325a763f 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -1,10 +1,22 @@ -import { Locator } from "playwright"; +import { Locator, Page } from "playwright"; import BasePage from "../../infra/ui/basePage"; export default class CodeGraph extends BasePage { /* NavBar Locators*/ - private get homeButton(): Locator { - return this.page.locator("//a[p[text() = 'Home']]") + private get falkorDBLogo(): Locator { + return this.page.locator("//*[img[@alt='FalkorDB']]") + } + + private get navBaritem(): (navItem: string) => Locator { + return (navItem: string) => this.page.locator(`//a[p[text() = '${navItem}']]`); + } + + private get createNewProjectBtn(): Locator { + return this.page.getByRole('button', { name: 'Create new project' }); + } + + private get createNewProjectDialog(): Locator { + return this.page.locator("//div[@role='dialog']") } /* CodeGraph Locators*/ @@ -66,8 +78,30 @@ export default class CodeGraph extends BasePage { } /* NavBar functionality */ - async clickOnHomeBtn(): Promise { - await this.homeButton.click() + async clickOnFalkorDbLogo(): Promise { + await this.page.waitForLoadState('networkidle'); + const [newPage] = await Promise.all([ + this.page.waitForEvent('popup'), + this.falkorDBLogo.click(), + ]); + return newPage + } + + async getNavBarItem(navItem : string): Promise { + await this.page.waitForLoadState('networkidle'); + const [newPage] = await Promise.all([ + this.page.waitForEvent('popup'), + this.navBaritem(navItem).click(), + ]); + return newPage + } + + async clickCreateNewProjectBtn(): Promise { + await this.createNewProjectBtn.click(); + } + + async isCreateNewProjectDialog(): Promise { + return await this.createNewProjectDialog.isVisible(); } /* Chat functionality */ diff --git a/e2e/tests/navBar.spec.ts b/e2e/tests/navBar.spec.ts index 4f6ce741..bfff2bfd 100644 --- a/e2e/tests/navBar.spec.ts +++ b/e2e/tests/navBar.spec.ts @@ -14,9 +14,29 @@ test.describe(' Navbar tests', () => { await browser.closeBrowser(); }); - test("Verify clicking on Home redirects to specified URL", async () => { - const navBar = await browser.createNewPage(CodeGraph, urls.baseUrl) - const page = await navBar.clickOnHomeBtn() - expect(navBar.getCurrentURL()).toBe("http://localhost:3000/") + test("Verify clicking on falkordb logo redirects to specified URL", async () => { + const navBar = await browser.createNewPage(CodeGraph, urls.baseUrl) + const page = await navBar.clickOnFalkorDbLogo(); + expect(page.url()).toBe(urls.falkorDBUrl) + }) + + const navitems: { navItem: string; expectedRes: string }[] = [ + { navItem: "Home", expectedRes: urls.falkorDBUrl }, + { navItem: "Github", expectedRes: urls.falkorDbGithubUrl }, + { navItem: "Tip", expectedRes: urls.falkorDbGithubUrl } + ]; + + navitems.forEach(({navItem, expectedRes}) => { + test(`Verify clicking on ${navItem} redirects to specified URL`, async () => { + const navBar = await browser.createNewPage(CodeGraph, urls.baseUrl) + const page = await navBar.getNavBarItem(navItem); + expect(page.url()).toBe(expectedRes) + }) + }) + + test("Verify that clicking the Create New Project button displays the correct dialog", async () => { + const navBar = await browser.createNewPage(CodeGraph, urls.baseUrl) + await navBar.clickCreateNewProjectBtn(); + expect(await navBar.isCreateNewProjectDialog()).toBe(true) }) }); From 938dec2d6d0eb5a387f47829eeb6c6ac4c5315ac Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 14 Nov 2024 13:07:43 +0200 Subject: [PATCH 041/115] commit --- app/api/chat/[graph]/route.ts | 6 ++- app/api/repo/[graph]/[node]/route.ts | 10 ++-- app/api/repo/[graph]/{route.tsx => route.ts} | 14 +++-- app/api/repo/route.ts | 10 ++-- app/components/Input.tsx | 8 +-- app/components/chat.tsx | 54 +++++++++----------- app/components/code-graph.tsx | 6 +-- 7 files changed, 61 insertions(+), 47 deletions(-) rename app/api/repo/[graph]/{route.tsx => route.ts} (95%) diff --git a/app/api/chat/[graph]/route.ts b/app/api/chat/[graph]/route.ts index 6431fe56..d6ef73a6 100644 --- a/app/api/chat/[graph]/route.ts +++ b/app/api/chat/[graph]/route.ts @@ -1,15 +1,17 @@ import { NextRequest, NextResponse } from "next/server" + export async function POST(request: NextRequest, { params }: { params: { graph: string } }) { const graphName = params.graph const msg = request.nextUrl.searchParams.get('msg') try { - const result = await fetch(`http://127.0.0.1:5000/chat`, { + const result = await fetch(`${process.env.BEAKEND_URL}/chat`, { method: 'POST', - body: JSON.stringify({ repo: graphName, msg}), + body: JSON.stringify({ repo: graphName, msg }), headers: { + "Authorization": process.env.SECRET_TOKEN!, "Content-Type": 'application/json' } }) diff --git a/app/api/repo/[graph]/[node]/route.ts b/app/api/repo/[graph]/[node]/route.ts index 9c18d64e..4b4f0143 100644 --- a/app/api/repo/[graph]/[node]/route.ts +++ b/app/api/repo/[graph]/[node]/route.ts @@ -6,8 +6,11 @@ export async function GET(request: NextRequest, { params }: { params: { graph: s const graphId = params.graph; try { - const result = await fetch(`http://127.0.0.1:5000/get_neighbors?repo=${graphId}&node_id=${nodeId}`, { + const result = await fetch(`${process.env.BEAKEND_URL}/get_neighbors?repo=${graphId}&node_id=${nodeId}`, { method: 'GET', + headers: { + "Authorization": process.env.SECRET_TOKEN!, + } }) const json = await result.json() @@ -23,12 +26,13 @@ export async function POST(request: NextRequest, { params }: { params: { graph: const nodeId = params.node; const graphId = params.graph; const targetId = request.nextUrl.searchParams.get('targetId') - + try { - const result = await fetch(`http://127.0.0.1:5000/find_paths`, { + const result = await fetch(`${process.env.BEAKEND_URL}/find_paths`, { method: 'POST', headers: { + "Authorization": process.env.SECRET_TOKEN!, 'Content-Type': 'application/json' }, body: JSON.stringify({ diff --git a/app/api/repo/[graph]/route.tsx b/app/api/repo/[graph]/route.ts similarity index 95% rename from app/api/repo/[graph]/route.tsx rename to app/api/repo/[graph]/route.ts index ebab0af4..a4a2dd7e 100644 --- a/app/api/repo/[graph]/route.tsx +++ b/app/api/repo/[graph]/route.ts @@ -5,8 +5,11 @@ export async function GET(request: NextRequest, { params }: { params: { graph: s const graphName = params.graph try { - const result = await fetch(`http://127.0.0.1:5000/graph_entities?repo=${graphName}`, { + const result = await fetch(`${process.env.BEAKEND_URL}/graph_entities?repo=${graphName}`, { method: 'GET', + headers: { + "Authorization": process.env.SECRET_TOKEN!, + } }) if (!result.ok) { @@ -29,10 +32,11 @@ export async function POST(request: NextRequest, { params }: { params: { graph: try { switch (type) { case "commit": { - const result = await fetch(`http://127.0.0.1:5000/list_commits`, { + const result = await fetch(`${process.env.BEAKEND_URL}/list_commits`, { method: 'POST', body: JSON.stringify({ repo: graphName }), headers: { + "Authorization": process.env.SECRET_TOKEN!, "Content-Type": 'application/json' } }) @@ -208,10 +212,11 @@ export async function POST(request: NextRequest, { params }: { params: { graph: } case "autoComplete": { const prefix = request.nextUrl.searchParams.get('prefix')! - const result = await fetch(`http://127.0.0.1:5000/auto_complete`, { + const result = await fetch(`${process.env.BEAKEND_URL}/auto_complete`, { method: 'POST', body: JSON.stringify({ repo: graphName, prefix }), headers: { + "Authorization": process.env.SECRET_TOKEN!, "Content-Type": 'application/json' } }) @@ -225,10 +230,11 @@ export async function POST(request: NextRequest, { params }: { params: { graph: return NextResponse.json({ result: json }, { status: 200 }) } default: { - const result = await fetch(`http://127.0.0.1:5000/repo_info`, { + const result = await fetch(`${process.env.BEAKEND_URL}/repo_info`, { method: 'POST', body: JSON.stringify({ repo: graphName }), headers: { + "Authorization": process.env.SECRET_TOKEN!, "Content-Type": 'application/json' } }) diff --git a/app/api/repo/route.ts b/app/api/repo/route.ts index 83f339e4..06d6199c 100644 --- a/app/api/repo/route.ts +++ b/app/api/repo/route.ts @@ -2,8 +2,11 @@ import { NextRequest, NextResponse } from "next/server"; export async function GET() { try { - const result = await fetch(`http://127.0.0.1:5000/list_repos`, { + const result = await fetch(`${process.env.BEAKEND_URL}/list_repos`, { method: 'GET', + headers: { + "Authorization": process.env.SECRET_TOKEN!, + } }) if (!result.ok) { @@ -21,12 +24,13 @@ export async function GET() { export async function POST(request: NextRequest) { const url = request.nextUrl.searchParams.get('url'); - + try { - const result = await fetch(`http://127.0.0.1:5000/process_repo`, { + const result = await fetch(`${process.env.BEAKEND_URL}/process_repo`, { method: 'POST', body: JSON.stringify({ repo_url: url, ignore: ["./.github", "./sbin", "./.git", "./deps", "./bin", "./build"] }), headers: { + "Authorization": process.env.SECRET_TOKEN!, 'Content-Type': 'application/json' } }) diff --git a/app/components/Input.tsx b/app/components/Input.tsx index fd8ddc8b..51b0c981 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -58,11 +58,14 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, } const json = await result.json() - const { completions } = json.result - setOptions(completions) + + setOptions(completions || []) + if (completions?.length > 0) { setOpen(true) + } else { + setOpen(false) } }, 500) @@ -86,7 +89,6 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, } case "ArrowUp": { e.preventDefault() - console.log(selectedOption <= 0 ? selectedOption : selectedOption - 1); setSelectedOption(prev => prev <= 0 ? options.length - 1 : prev - 1) return } diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 069ed4cf..29aa3674 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -1,5 +1,5 @@ import { toast } from "@/components/ui/use-toast"; -import { Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState } from "react"; +import { Dispatch, FormEvent, MutableRefObject, SetStateAction, useEffect, useRef, useState } from "react"; import Image from "next/image"; import { AlignLeft, ArrowRight, ChevronDown, Lightbulb, Undo2 } from "lucide-react"; import { Path } from "../page"; @@ -68,6 +68,8 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP const tipRef: React.RefObject = useRef(null); + const isSendMessage = messages.some(m => m.type === MessageTypes.Pending) || (messages.some(m => m.text === "Please select a starting point and the end point. Select or press relevant item on the graph") && !messages.some(m => m.type === MessageTypes.Path)) + useEffect(() => { if (tipOpen) { tipRef.current?.focus() @@ -192,10 +194,6 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP // A function that handles the change event of the url input box async function handleQueryInputChange(event: any) { - if (event.key === "Enter") { - await handleQueryClick(event); - } - // Get the new value of the input box const value = event.target.value; @@ -204,7 +202,25 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP } // Send the user query to the server - async function sendQuery(q: string) { + async function sendQuery(event: FormEvent) { + + event.preventDefault(); + + const q = query.trim() + + if (!q) { + toast({ + variant: "destructive", + title: "Uh oh! Something went wrong.", + description: "Please enter a question.", + }) + return + } + + setQuery("") + + setMessages((messages) => [...messages, { text: q, type: MessageTypes.Query }, { type: MessageTypes.Pending }]); + const result = await fetch(`/api/chat/${repo}?msg=${encodeURIComponent(q)}`, { method: 'POST' }) @@ -223,27 +239,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP prev = prev.slice(0, -1); return [...prev, { text: json.result.response, type: MessageTypes.Response }]; }); - } - - // A function that handles the click event - const handleQueryClick = async (event: any) => { - event.preventDefault(); - - const q = query.trim() - - if (!q) { - toast({ - variant: "destructive", - title: "Uh oh! Something went wrong.", - description: "Please enter a question.", - }) - return - } - - setQuery("") - setMessages((messages) => [...messages, { text: q, type: MessageTypes.Query }, { text: "", type: MessageTypes.Pending }]); - return await sendQuery(q); } // Scroll to the bottom of the chat on new message @@ -502,9 +498,9 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP -
- -
diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 025395a3..df796dfe 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -274,7 +274,7 @@ export function CodeGraph({ if (!graphNode.data.expand) { elements = await onFetchNode(node) - console.log(elements); + if (elements.length === 0) { toast({ title: "No neighbors found", @@ -492,7 +492,7 @@ export function CodeGraph({ } - {/* { + { graph.Id && - } */} + } ) From 679b236378272e9da4926bfffc9dd3863ddd1cc8 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 14 Nov 2024 14:45:02 +0200 Subject: [PATCH 042/115] fix commit list --- app/components/code-graph.tsx | 42 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index df796dfe..63a402da 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -191,8 +191,11 @@ export function CodeGraph({ const json = await result.json() const commitsArr = json.result.commits setCommits(commitsArr) - setCurrentCommit(commitsArr[commitsArr.length - 1].hash) - setCommitIndex(commitsArr.length) + + if (commitsArr.length > 0) { + setCurrentCommit(commitsArr[commitsArr.length - 1].hash) + setCommitIndex(commitsArr.length) + } } run() @@ -383,22 +386,25 @@ export function CodeGraph({

{edgesCount} Edges

- {/*
-
- -

Display Changes

-
-
-
-

Were added

-
-
-
-

Were edited

+ { + commitIndex !== commits.length && +
+
+ +

Display Changes

+
+
+
+

Were added

+
+
+
+

Were edited

+
-
*/} + }
@@ -493,7 +499,7 @@ export function CodeGraph({ } { - graph.Id && + graph.Id && commits.length > 0 && Date: Thu, 14 Nov 2024 15:02:08 +0200 Subject: [PATCH 043/115] commit --- app/components/Input.tsx | 21 +++++++++++++++++---- app/components/chat.tsx | 10 +++++----- repositories/GraphRAG-SDK | 1 - 3 files changed, 22 insertions(+), 10 deletions(-) delete mode 160000 repositories/GraphRAG-SDK diff --git a/app/components/Input.tsx b/app/components/Input.tsx index 51b0c981..d314fe8e 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -24,6 +24,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, const [options, setOptions] = useState([]) const [selectedOption, setSelectedOption] = useState(0) const inputRef = useRef(null) + const containerRef = useRef(null) useEffect(() => { setSelectedOption(0) @@ -37,7 +38,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, const timeout = setTimeout(async () => { if (!value || node?.id) { - if (!value) { + if (!value) { setOptions([]) } setOpen(false) @@ -59,7 +60,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, const json = await result.json() const { completions } = json.result - + setOptions(completions || []) if (completions?.length > 0) { @@ -89,12 +90,18 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, } case "ArrowUp": { e.preventDefault() - setSelectedOption(prev => prev <= 0 ? options.length - 1 : prev - 1) + setSelectedOption(prev => { + containerRef.current?.scrollTo({ behavior: 'smooth', top: (prev <= 0 ? options.length - 1 : prev - 1) * 64 }) + return prev <= 0 ? options.length - 1 : prev - 1 + }) return } case "ArrowDown": { e.preventDefault() - setSelectedOption(prev => (prev + 1) % options.length) + setSelectedOption(prev => { + containerRef.current?.scrollTo({ behavior: 'smooth', top: ((prev + 1) % options.length) * 64 }) + return (prev + 1) % options.length + }) return } case "Space": { @@ -125,10 +132,16 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon, onValueChange({ name: newVal }) }} {...props} + onBlur={(e) => { + if (!containerRef.current?.contains(e.relatedTarget as Node)) { + setOpen(false) + } + }} /> { open &&
[...messages, { text: q, type: MessageTypes.Query }, { type: MessageTypes.Pending }]); - + const result = await fetch(`/api/chat/${repo}?msg=${encodeURIComponent(q)}`, { method: 'POST' }) @@ -495,7 +495,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP { repo &&
-
diff --git a/repositories/GraphRAG-SDK b/repositories/GraphRAG-SDK deleted file mode 160000 index 52e3042b..00000000 --- a/repositories/GraphRAG-SDK +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 52e3042ba79d83489d4ceff6108973f0f2e43442 From f03f71a9a5065d04f5557b0ea148a9c4d13df00f Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 14 Nov 2024 15:34:52 +0200 Subject: [PATCH 044/115] fix env variable --- app/api/chat/[graph]/route.ts | 2 +- app/api/repo/[graph]/[node]/route.ts | 4 ++-- app/api/repo/[graph]/route.ts | 8 ++++---- app/api/repo/route.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/api/chat/[graph]/route.ts b/app/api/chat/[graph]/route.ts index d6ef73a6..18d4acf6 100644 --- a/app/api/chat/[graph]/route.ts +++ b/app/api/chat/[graph]/route.ts @@ -7,7 +7,7 @@ export async function POST(request: NextRequest, { params }: { params: { graph: const msg = request.nextUrl.searchParams.get('msg') try { - const result = await fetch(`${process.env.BEAKEND_URL}/chat`, { + const result = await fetch(`${process.env.BACKEND_URL}/chat`, { method: 'POST', body: JSON.stringify({ repo: graphName, msg }), headers: { diff --git a/app/api/repo/[graph]/[node]/route.ts b/app/api/repo/[graph]/[node]/route.ts index 4b4f0143..d88397a2 100644 --- a/app/api/repo/[graph]/[node]/route.ts +++ b/app/api/repo/[graph]/[node]/route.ts @@ -6,7 +6,7 @@ export async function GET(request: NextRequest, { params }: { params: { graph: s const graphId = params.graph; try { - const result = await fetch(`${process.env.BEAKEND_URL}/get_neighbors?repo=${graphId}&node_id=${nodeId}`, { + const result = await fetch(`${process.env.BACKEND_URL}/get_neighbors?repo=${graphId}&node_id=${nodeId}`, { method: 'GET', headers: { "Authorization": process.env.SECRET_TOKEN!, @@ -29,7 +29,7 @@ export async function POST(request: NextRequest, { params }: { params: { graph: try { - const result = await fetch(`${process.env.BEAKEND_URL}/find_paths`, { + const result = await fetch(`${process.env.BACKEND_URL}/find_paths`, { method: 'POST', headers: { "Authorization": process.env.SECRET_TOKEN!, diff --git a/app/api/repo/[graph]/route.ts b/app/api/repo/[graph]/route.ts index a4a2dd7e..bceef1a6 100644 --- a/app/api/repo/[graph]/route.ts +++ b/app/api/repo/[graph]/route.ts @@ -5,7 +5,7 @@ export async function GET(request: NextRequest, { params }: { params: { graph: s const graphName = params.graph try { - const result = await fetch(`${process.env.BEAKEND_URL}/graph_entities?repo=${graphName}`, { + const result = await fetch(`${process.env.BACKEND_URL}/graph_entities?repo=${graphName}`, { method: 'GET', headers: { "Authorization": process.env.SECRET_TOKEN!, @@ -32,7 +32,7 @@ export async function POST(request: NextRequest, { params }: { params: { graph: try { switch (type) { case "commit": { - const result = await fetch(`${process.env.BEAKEND_URL}/list_commits`, { + const result = await fetch(`${process.env.BACKEND_URL}/list_commits`, { method: 'POST', body: JSON.stringify({ repo: graphName }), headers: { @@ -212,7 +212,7 @@ export async function POST(request: NextRequest, { params }: { params: { graph: } case "autoComplete": { const prefix = request.nextUrl.searchParams.get('prefix')! - const result = await fetch(`${process.env.BEAKEND_URL}/auto_complete`, { + const result = await fetch(`${process.env.BACKEND_URL}/auto_complete`, { method: 'POST', body: JSON.stringify({ repo: graphName, prefix }), headers: { @@ -230,7 +230,7 @@ export async function POST(request: NextRequest, { params }: { params: { graph: return NextResponse.json({ result: json }, { status: 200 }) } default: { - const result = await fetch(`${process.env.BEAKEND_URL}/repo_info`, { + const result = await fetch(`${process.env.BACKEND_URL}/repo_info`, { method: 'POST', body: JSON.stringify({ repo: graphName }), headers: { diff --git a/app/api/repo/route.ts b/app/api/repo/route.ts index 06d6199c..cf27cd82 100644 --- a/app/api/repo/route.ts +++ b/app/api/repo/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; export async function GET() { try { - const result = await fetch(`${process.env.BEAKEND_URL}/list_repos`, { + const result = await fetch(`${process.env.BACKEND_URL}/list_repos`, { method: 'GET', headers: { "Authorization": process.env.SECRET_TOKEN!, @@ -26,7 +26,7 @@ export async function POST(request: NextRequest) { const url = request.nextUrl.searchParams.get('url'); try { - const result = await fetch(`${process.env.BEAKEND_URL}/process_repo`, { + const result = await fetch(`${process.env.BACKEND_URL}/process_repo`, { method: 'POST', body: JSON.stringify({ repo_url: url, ignore: ["./.github", "./sbin", "./.git", "./deps", "./bin", "./build"] }), headers: { From 5ed94e9602a809e1e5a51044a28f4438bd97f4f5 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 14 Nov 2024 15:36:35 +0200 Subject: [PATCH 045/115] remove unneccery buttons --- app/components/code-graph.tsx | 4 ++-- app/page.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 63a402da..6d84b253 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -498,7 +498,7 @@ export function CodeGraph({
} - { + {/* { graph.Id && commits.length > 0 && - } + } */}
) diff --git a/app/page.tsx b/app/page.tsx index a4a81851..f1d222db 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -161,7 +161,7 @@ export default function Home() {

Github

- + {/*

Tip

@@ -208,7 +208,7 @@ export default function Home() { : } - + */}
From 26597942384c7afdba8f6c5baeeadfb050e30219 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 14 Nov 2024 15:40:11 +0200 Subject: [PATCH 046/115] remove apis --- app/api/repo/[graph]/route.ts | 352 +++++++++++++++++----------------- app/api/repo/route.ts | 46 ++--- 2 files changed, 199 insertions(+), 199 deletions(-) diff --git a/app/api/repo/[graph]/route.ts b/app/api/repo/[graph]/route.ts index a4a2dd7e..2b22eb29 100644 --- a/app/api/repo/[graph]/route.ts +++ b/app/api/repo/[graph]/route.ts @@ -31,185 +31,185 @@ export async function POST(request: NextRequest, { params }: { params: { graph: try { switch (type) { - case "commit": { - const result = await fetch(`${process.env.BEAKEND_URL}/list_commits`, { - method: 'POST', - body: JSON.stringify({ repo: graphName }), - headers: { - "Authorization": process.env.SECRET_TOKEN!, - "Content-Type": 'application/json' - } - }) + // case "commit": { + // const result = await fetch(`${process.env.BEAKEND_URL}/list_commits`, { + // method: 'POST', + // body: JSON.stringify({ repo: graphName }), + // headers: { + // "Authorization": process.env.SECRET_TOKEN!, + // "Content-Type": 'application/json' + // } + // }) - if (!result.ok) { - throw new Error(await result.text()) - } + // if (!result.ok) { + // throw new Error(await result.text()) + // } - const json = await result.json() + // const json = await result.json() - return NextResponse.json({ result: json }, { status: 200 }) - } - case "switchCommit": { - return NextResponse.json({ - result: { - deletions: { - 'nodes': [ - { - "alias": "", - "id": 2, - "labels": [ - "Function" - ], - "properties": { - "args": [ - [ - "cls", - "Unknown" - ] - ], - "name": "setUpClass", - "path": "tests/test_kg_gemini.py", - "src": "def setUpClass(cls):\n\n cls.ontology = Ontology([], [])\n\n cls.ontology.add_entity(\n Entity(\n label=\"Actor\",\n attributes=[\n Attribute(\n name=\"name\",\n attr_type=AttributeType.STRING,\n unique=True,\n required=True,\n ),\n ],\n )\n )\n cls.ontology.add_entity(\n Entity(\n label=\"Movie\",\n attributes=[\n Attribute(\n name=\"title\",\n attr_type=AttributeType.STRING,\n unique=True,\n required=True,\n ),\n ],\n )\n )\n cls.ontology.add_relation(\n Relation(\n label=\"ACTED_IN\",\n source=\"Actor\",\n target=\"Movie\",\n attributes=[\n Attribute(\n name=\"role\",\n attr_type=AttributeType.STRING,\n unique=False,\n required=False,\n ),\n ],\n )\n )\n\n cls.graph_name = \"IMDB_gemini\"\n\n model = GeminiGenerativeModel(model_name=\"gemini-1.5-flash-001\")\n cls.kg = KnowledgeGraph(\n name=cls.graph_name,\n ontology=cls.ontology,\n model_config=KnowledgeGraphModelConfig.with_model(model),\n )", - "src_end": 82, - "src_start": 29 - } - }, - { - "alias": "", - "id": 13, - "labels": [ - "Function" - ], - "properties": { - "args": [ - [ - "self", - "Unknown" - ], - [ - "restaurants_kg", - "KnowledgeGraph" - ], - [ - "attractions_kg", - "KnowledgeGraph" - ] - ], - "name": "import_data", - "path": "tests/test_multi_agent.py", - "src": "def import_data(\n self,\n restaurants_kg: KnowledgeGraph,\n attractions_kg: KnowledgeGraph,\n ):\n with open(\"tests/data/cities.json\") as f:\n cities = loads(f.read())\n with open(\"tests/data/restaurants.json\") as f:\n restaurants = loads(f.read())\n with open(\"tests/data/attractions.json\") as f:\n attractions = loads(f.read())\n\n for city in cities:\n restaurants_kg.add_node(\n \"City\",\n {\n \"name\": city[\"name\"],\n \"weather\": city[\"weather\"],\n \"population\": city[\"population\"],\n },\n )\n restaurants_kg.add_node(\"Country\", {\"name\": city[\"country\"]})\n restaurants_kg.add_edge(\n \"IN_COUNTRY\",\n \"City\",\n \"Country\",\n {\"name\": city[\"name\"]},\n {\"name\": city[\"country\"]},\n )\n\n attractions_kg.add_node(\n \"City\",\n {\n \"name\": city[\"name\"],\n \"weather\": city[\"weather\"],\n \"population\": city[\"population\"],\n },\n )\n attractions_kg.add_node(\"Country\", {\"name\": city[\"country\"]})\n attractions_kg.add_edge(\n \"IN_COUNTRY\",\n \"City\",\n \"Country\",\n {\"name\": city[\"name\"]},\n {\"name\": city[\"country\"]},\n )\n\n for restaurant in restaurants:\n restaurants_kg.add_node(\n \"Restaurant\",\n {\n \"name\": restaurant[\"name\"],\n \"description\": restaurant[\"description\"],\n \"rating\": restaurant[\"rating\"],\n \"food_type\": restaurant[\"food_type\"],\n },\n )\n restaurants_kg.add_edge(\n \"IN_CITY\",\n \"Restaurant\",\n \"City\",\n {\"name\": restaurant[\"name\"]},\n {\"name\": restaurant[\"city\"]},\n )\n\n for attraction in attractions:\n attractions_kg.add_node(\n \"Attraction\",\n {\n \"name\": attraction[\"name\"],\n \"description\": attraction[\"description\"],\n \"type\": attraction[\"type\"],\n },\n )\n attractions_kg.add_edge(\n \"IN_CITY\",\n \"Attraction\",\n \"City\",\n {\"name\": attraction[\"name\"]},\n {\"name\": attraction[\"city\"]},\n )", - "src_end": 310, - "src_start": 230 - } - }, - ], - 'edges': [ - { - "alias": "", - "dest_node": 13, - "id": 460, - "properties": {}, - "relation": "CALLS", - "src_node": 2 - }, - ] - }, - additions: { - 'nodes': [ - { - "alias": "", - "id": 13, - "labels": [ - "File" - ], - "properties": { - "ext": ".py", - "name": "test_kg_gemini.py", - "path": "tests" - } - }, - { - "alias": "", - "id": 2, - "labels": [ - "Class" - ], - "properties": { - "doc": "\"\"\"\n Test Knowledge Graph\n \"\"\"", - "name": "TestKGGemini", - "path": "tests/test_kg_gemini.py", - "src_end": 106, - "src_start": 23 - } - }, - ], - 'edges': [ - { - "alias": "", - "dest_node": 13, - "id": 460, - "properties": {}, - "relation": "DEFINES", - "src_node": 2 - }, - ], - }, - modifications: { - 'nodes': [ - { - "alias": "", - "id": 3, - "labels": [ - "Function" - ], - "properties": { - "args": [ - [] - ], - "name": "", - "path": "", - "src": "", - "src_end": 0, - "src_start": 0 - } - }, - { - "alias": "", - "id": 61, - "labels": [ - "Function" - ], - "properties": { - "args": [ - [], - [] - ], - "doc": "", - "name": "", - "path": "", - "ret_type": "", - "src": "", - "src_end": 0, - "src_start": 0 - } - }, - ], - 'edges': [ - { - "alias": "", - "dest_node": 61, - "id": 439, - "properties": { - name: "Source" - }, - "relation": "CALLS", - "src_node": 3 - }, - ] - } - } - }, { status: 200 }) - } + // return NextResponse.json({ result: json }, { status: 200 }) + // } + // case "switchCommit": { + // return NextResponse.json({ + // result: { + // deletions: { + // 'nodes': [ + // { + // "alias": "", + // "id": 2, + // "labels": [ + // "Function" + // ], + // "properties": { + // "args": [ + // [ + // "cls", + // "Unknown" + // ] + // ], + // "name": "setUpClass", + // "path": "tests/test_kg_gemini.py", + // "src": "def setUpClass(cls):\n\n cls.ontology = Ontology([], [])\n\n cls.ontology.add_entity(\n Entity(\n label=\"Actor\",\n attributes=[\n Attribute(\n name=\"name\",\n attr_type=AttributeType.STRING,\n unique=True,\n required=True,\n ),\n ],\n )\n )\n cls.ontology.add_entity(\n Entity(\n label=\"Movie\",\n attributes=[\n Attribute(\n name=\"title\",\n attr_type=AttributeType.STRING,\n unique=True,\n required=True,\n ),\n ],\n )\n )\n cls.ontology.add_relation(\n Relation(\n label=\"ACTED_IN\",\n source=\"Actor\",\n target=\"Movie\",\n attributes=[\n Attribute(\n name=\"role\",\n attr_type=AttributeType.STRING,\n unique=False,\n required=False,\n ),\n ],\n )\n )\n\n cls.graph_name = \"IMDB_gemini\"\n\n model = GeminiGenerativeModel(model_name=\"gemini-1.5-flash-001\")\n cls.kg = KnowledgeGraph(\n name=cls.graph_name,\n ontology=cls.ontology,\n model_config=KnowledgeGraphModelConfig.with_model(model),\n )", + // "src_end": 82, + // "src_start": 29 + // } + // }, + // { + // "alias": "", + // "id": 13, + // "labels": [ + // "Function" + // ], + // "properties": { + // "args": [ + // [ + // "self", + // "Unknown" + // ], + // [ + // "restaurants_kg", + // "KnowledgeGraph" + // ], + // [ + // "attractions_kg", + // "KnowledgeGraph" + // ] + // ], + // "name": "import_data", + // "path": "tests/test_multi_agent.py", + // "src": "def import_data(\n self,\n restaurants_kg: KnowledgeGraph,\n attractions_kg: KnowledgeGraph,\n ):\n with open(\"tests/data/cities.json\") as f:\n cities = loads(f.read())\n with open(\"tests/data/restaurants.json\") as f:\n restaurants = loads(f.read())\n with open(\"tests/data/attractions.json\") as f:\n attractions = loads(f.read())\n\n for city in cities:\n restaurants_kg.add_node(\n \"City\",\n {\n \"name\": city[\"name\"],\n \"weather\": city[\"weather\"],\n \"population\": city[\"population\"],\n },\n )\n restaurants_kg.add_node(\"Country\", {\"name\": city[\"country\"]})\n restaurants_kg.add_edge(\n \"IN_COUNTRY\",\n \"City\",\n \"Country\",\n {\"name\": city[\"name\"]},\n {\"name\": city[\"country\"]},\n )\n\n attractions_kg.add_node(\n \"City\",\n {\n \"name\": city[\"name\"],\n \"weather\": city[\"weather\"],\n \"population\": city[\"population\"],\n },\n )\n attractions_kg.add_node(\"Country\", {\"name\": city[\"country\"]})\n attractions_kg.add_edge(\n \"IN_COUNTRY\",\n \"City\",\n \"Country\",\n {\"name\": city[\"name\"]},\n {\"name\": city[\"country\"]},\n )\n\n for restaurant in restaurants:\n restaurants_kg.add_node(\n \"Restaurant\",\n {\n \"name\": restaurant[\"name\"],\n \"description\": restaurant[\"description\"],\n \"rating\": restaurant[\"rating\"],\n \"food_type\": restaurant[\"food_type\"],\n },\n )\n restaurants_kg.add_edge(\n \"IN_CITY\",\n \"Restaurant\",\n \"City\",\n {\"name\": restaurant[\"name\"]},\n {\"name\": restaurant[\"city\"]},\n )\n\n for attraction in attractions:\n attractions_kg.add_node(\n \"Attraction\",\n {\n \"name\": attraction[\"name\"],\n \"description\": attraction[\"description\"],\n \"type\": attraction[\"type\"],\n },\n )\n attractions_kg.add_edge(\n \"IN_CITY\",\n \"Attraction\",\n \"City\",\n {\"name\": attraction[\"name\"]},\n {\"name\": attraction[\"city\"]},\n )", + // "src_end": 310, + // "src_start": 230 + // } + // }, + // ], + // 'edges': [ + // { + // "alias": "", + // "dest_node": 13, + // "id": 460, + // "properties": {}, + // "relation": "CALLS", + // "src_node": 2 + // }, + // ] + // }, + // additions: { + // 'nodes': [ + // { + // "alias": "", + // "id": 13, + // "labels": [ + // "File" + // ], + // "properties": { + // "ext": ".py", + // "name": "test_kg_gemini.py", + // "path": "tests" + // } + // }, + // { + // "alias": "", + // "id": 2, + // "labels": [ + // "Class" + // ], + // "properties": { + // "doc": "\"\"\"\n Test Knowledge Graph\n \"\"\"", + // "name": "TestKGGemini", + // "path": "tests/test_kg_gemini.py", + // "src_end": 106, + // "src_start": 23 + // } + // }, + // ], + // 'edges': [ + // { + // "alias": "", + // "dest_node": 13, + // "id": 460, + // "properties": {}, + // "relation": "DEFINES", + // "src_node": 2 + // }, + // ], + // }, + // modifications: { + // 'nodes': [ + // { + // "alias": "", + // "id": 3, + // "labels": [ + // "Function" + // ], + // "properties": { + // "args": [ + // [] + // ], + // "name": "", + // "path": "", + // "src": "", + // "src_end": 0, + // "src_start": 0 + // } + // }, + // { + // "alias": "", + // "id": 61, + // "labels": [ + // "Function" + // ], + // "properties": { + // "args": [ + // [], + // [] + // ], + // "doc": "", + // "name": "", + // "path": "", + // "ret_type": "", + // "src": "", + // "src_end": 0, + // "src_start": 0 + // } + // }, + // ], + // 'edges': [ + // { + // "alias": "", + // "dest_node": 61, + // "id": 439, + // "properties": { + // name: "Source" + // }, + // "relation": "CALLS", + // "src_node": 3 + // }, + // ] + // } + // } + // }, { status: 200 }) + // } case "autoComplete": { const prefix = request.nextUrl.searchParams.get('prefix')! const result = await fetch(`${process.env.BEAKEND_URL}/auto_complete`, { diff --git a/app/api/repo/route.ts b/app/api/repo/route.ts index 06d6199c..26cdd6d5 100644 --- a/app/api/repo/route.ts +++ b/app/api/repo/route.ts @@ -21,26 +21,26 @@ export async function GET() { } } -export async function POST(request: NextRequest) { - - const url = request.nextUrl.searchParams.get('url'); - - try { - const result = await fetch(`${process.env.BEAKEND_URL}/process_repo`, { - method: 'POST', - body: JSON.stringify({ repo_url: url, ignore: ["./.github", "./sbin", "./.git", "./deps", "./bin", "./build"] }), - headers: { - "Authorization": process.env.SECRET_TOKEN!, - 'Content-Type': 'application/json' - } - }) - - if (!result.ok) { - throw new Error(await result.text()) - } - - return NextResponse.json({ message: "success" }, { status: 200 }) - } catch (err) { - return NextResponse.json({ message: (err as Error).message }, { status: 400 }) - } -} \ No newline at end of file +// export async function POST(request: NextRequest) { + +// const url = request.nextUrl.searchParams.get('url'); + +// try { +// const result = await fetch(`${process.env.BEAKEND_URL}/process_repo`, { +// method: 'POST', +// body: JSON.stringify({ repo_url: url, ignore: ["./.github", "./sbin", "./.git", "./deps", "./bin", "./build"] }), +// headers: { +// "Authorization": process.env.SECRET_TOKEN!, +// 'Content-Type': 'application/json' +// } +// }) + +// if (!result.ok) { +// throw new Error(await result.text()) +// } + +// return NextResponse.json({ message: "success" }, { status: 200 }) +// } catch (err) { +// return NextResponse.json({ message: (err as Error).message }, { status: 400 }) +// } +// } \ No newline at end of file From a041e6b2cbdcbd8950dd6b80cf16d41325425c94 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Sun, 17 Nov 2024 09:39:15 +0200 Subject: [PATCH 047/115] udpate nextjs and eslint --- package-lock.json | 511 +++++++++++++++++++++++----------------------- package.json | 4 +- 2 files changed, 262 insertions(+), 253 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b27ecb4..14004f6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "falkordb": "^6.0.2", "isomorphic-git": "^1.25.7", "lucide-react": "^0.441.0", - "next": "14.2.8", + "next": "14.2.18", "openai": "^4.60.0", "react": "^18", "react-cytoscapejs": "^2.0.0", @@ -42,7 +42,7 @@ "@types/react-cytoscapejs": "^1.2.5", "@types/react-dom": "^18", "autoprefixer": "^10.4.19", - "eslint": "^8", + "eslint": "^9", "eslint-config-next": "15.0.3", "postcss": "^8", "tailwindcss": "^3.4.3", @@ -79,26 +79,51 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", + "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", + "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -106,20 +131,43 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", + "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@floating-ui/core": { @@ -160,20 +208,42 @@ "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==", "license": "MIT" }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/module-importer": { @@ -190,13 +260,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -291,9 +367,10 @@ } }, "node_modules/@next/env": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.8.tgz", - "integrity": "sha512-L44a+ynqkolyNBnYfF8VoCiSrjSZWgEHYKkKLGcs/a80qh7AkfVUD/MduVPgdsWZ31tgROR+yJRA0PZjSVBXWQ==" + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.18.tgz", + "integrity": "sha512-2vWLOUwIPgoqMJKG6dt35fVXVhgM09tw4tK3/Q34GFXDrfiHlG7iS33VA4ggnjWxjiz9KV5xzfsQzJX6vGAekA==", + "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { "version": "15.0.3", @@ -333,12 +410,13 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.8.tgz", - "integrity": "sha512-1VrQlG8OzdyvvGZhGJFnaNE2P10Jjy/2FopnqbY0nSa/gr8If3iINxvOEW3cmVeoAYkmW0RsBazQecA2dBFOSw==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.18.tgz", + "integrity": "sha512-tOBlDHCjGdyLf0ube/rDUs6VtwNOajaWV+5FV/ajPgrvHeisllEdymY/oDgv2cx561+gJksfMUtqf8crug7sbA==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -348,12 +426,13 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.8.tgz", - "integrity": "sha512-87t3I86rNRSOJB1gXIUzaQWWSWrkWPDyZGsR0Z7JAPtLeX3uUOW2fHxl7dNWD2BZvbvftctTQjgtfpp7nMtmWg==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.18.tgz", + "integrity": "sha512-uJCEjutt5VeJ30jjrHV1VIHCsbMYnEqytQgvREx+DjURd/fmKy15NaVK4aR/u98S1LGTnjq35lRTnRyygglxoA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -363,12 +442,13 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.8.tgz", - "integrity": "sha512-ta2sfVzbOpTbgBrF9HM5m+U58dv6QPuwU4n5EX4LLyCJGKc433Z0D9h9gay/HSOjLEXJ2fJYrMP5JYYbHdxhtw==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.18.tgz", + "integrity": "sha512-IL6rU8vnBB+BAm6YSWZewc+qvdL1EaA+VhLQ6tlUc0xp+kkdxQrVqAnh8Zek1ccKHlTDFRyAft0e60gteYmQ4A==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -378,12 +458,13 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.8.tgz", - "integrity": "sha512-+IoLTPK6Z5uIgDhgeWnQF5/o5GBN7+zyUNrs4Bes1W3g9++YELb8y0unFybS8s87ntAKMDl6jeQ+mD7oNwp/Ng==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.18.tgz", + "integrity": "sha512-RCaENbIZqKKqTlL8KNd+AZV/yAdCsovblOpYFp0OJ7ZxgLNbV5w23CUU1G5On+0fgafrsGcW+GdMKdFjaRwyYA==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -393,12 +474,13 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.8.tgz", - "integrity": "sha512-pO+hVXC+mvzUOQJJRG4RX4wJsRJ5BkURSf6dD6EjUXAX4Ml9es1WsEfkaZ4lcpmFzFvY47IkDaffks/GdCn9ag==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.18.tgz", + "integrity": "sha512-3kmv8DlyhPRCEBM1Vavn8NjyXtMeQ49ID0Olr/Sut7pgzaQTo4h01S7Z8YNE0VtbowyuAL26ibcz0ka6xCTH5g==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -408,12 +490,13 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.8.tgz", - "integrity": "sha512-bCat9izctychCtf3uL1nqHq31N5e1VxvdyNcBQflkudPMLbxVnlrw45Vi87K+lt1CwrtVayHqzo4ie0Szcpwzg==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.18.tgz", + "integrity": "sha512-mliTfa8seVSpTbVEcKEXGjC18+TDII8ykW4a36au97spm9XMPqQTpdGPNBJ9RySSFw9/hLuaCMByluQIAnkzlw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -423,12 +506,13 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.8.tgz", - "integrity": "sha512-gbxfUaSPV7EyUobpavida2Hwi62GhSJaSg7iBjmBWoxkxlmETOD7U4tWt763cGIsyE6jM7IoNavq0BXqwdW2QA==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.18.tgz", + "integrity": "sha512-J5g0UFPbAjKYmqS3Cy7l2fetFmWMY9Oao32eUsBPYohts26BdrMUyfCJnZFQkX9npYaHNDOWqZ6uV9hSDPw9NA==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -438,12 +522,13 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.8.tgz", - "integrity": "sha512-PUXzEzjTTlUh3b5VAn1nlpwvujTnuCMMwbiCnaTazoVlN1nA3kWjlmp42IfURA2N/nyrlVEw7pURa/o4Qxj1cw==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.18.tgz", + "integrity": "sha512-Ynxuk4ZgIpdcN7d16ivJdjsDG1+3hTvK24Pp8DiDmIa2+A4CfhJSEHHVndCHok6rnLUzAZD+/UOKESQgTsAZGg==", "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -453,12 +538,13 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.8.tgz", - "integrity": "sha512-EnPKv0ttq02E9/1KZ/8Dn7kuutv6hy1CKc0HlNcvzOQcm4/SQtvfws5gY0zrG9tuupd3HfC2L/zcTrnBhpjTuQ==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.18.tgz", + "integrity": "sha512-dtRGMhiU9TN5nyhwzce+7c/4CCeykYS+ipY/4mIrGzJ71+7zNo55ZxCB7cAVuNqdwtYniFNR2c9OFQ6UdFIMcg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1785,6 +1871,20 @@ "@types/cytoscape": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -2065,13 +2165,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true, - "license": "ISC" - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -2085,9 +2178,9 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", "bin": { @@ -2776,9 +2869,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2997,19 +3090,6 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3231,59 +3311,63 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", + "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.15.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.5", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-next": { @@ -3570,9 +3654,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3580,7 +3664,7 @@ "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3599,19 +3683,45 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3744,16 +3854,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -3786,18 +3896,17 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -3880,13 +3989,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4066,16 +4168,13 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4252,18 +4351,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -4538,16 +4625,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -5082,11 +5159,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "14.2.8", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.8.tgz", - "integrity": "sha512-EyEyJZ89r8C5FPlS/401AiF3O8jeMtHIE+bLom9MwcdWJJFBgRl+MR/2VgO0v5bI6tQORNY0a0DR5sjpFNrjbg==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.18.tgz", + "integrity": "sha512-H9qbjDuGivUDEnK6wa+p2XKO+iMzgVgyr9Zp/4Iv29lKa+DYaxJGjOeEA+5VOvJh/M7HLiskehInSa0cWxVXUw==", + "license": "MIT", "dependencies": { - "@next/env": "14.2.8", + "@next/env": "14.2.18", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -5101,15 +5179,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.8", - "@next/swc-darwin-x64": "14.2.8", - "@next/swc-linux-arm64-gnu": "14.2.8", - "@next/swc-linux-arm64-musl": "14.2.8", - "@next/swc-linux-x64-gnu": "14.2.8", - "@next/swc-linux-x64-musl": "14.2.8", - "@next/swc-win32-arm64-msvc": "14.2.8", - "@next/swc-win32-ia32-msvc": "14.2.8", - "@next/swc-win32-x64-msvc": "14.2.8" + "@next/swc-darwin-arm64": "14.2.18", + "@next/swc-darwin-x64": "14.2.18", + "@next/swc-linux-arm64-gnu": "14.2.18", + "@next/swc-linux-arm64-musl": "14.2.18", + "@next/swc-linux-x64-gnu": "14.2.18", + "@next/swc-linux-x64-musl": "14.2.18", + "@next/swc-win32-arm64-msvc": "14.2.18", + "@next/swc-win32-ia32-msvc": "14.2.18", + "@next/swc-win32-x64-msvc": "14.2.18" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -5473,16 +5551,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6063,45 +6131,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6724,13 +6753,6 @@ "node": ">=6" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6832,19 +6854,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", diff --git a/package.json b/package.json index 8c7e0f13..c4f1db33 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "falkordb": "^6.0.2", "isomorphic-git": "^1.25.7", "lucide-react": "^0.441.0", - "next": "14.2.8", + "next": "14.2.18", "openai": "^4.60.0", "react": "^18", "react-cytoscapejs": "^2.0.0", @@ -43,7 +43,7 @@ "@types/react-cytoscapejs": "^1.2.5", "@types/react-dom": "^18", "autoprefixer": "^10.4.19", - "eslint": "^8", + "eslint": "^9", "eslint-config-next": "15.0.3", "postcss": "^8", "tailwindcss": "^3.4.3", From 0876b0e6f7a6f3c6f3cf11cc705769868493f4e6 Mon Sep 17 00:00:00 2001 From: Roi Lipman Date: Sun, 17 Nov 2024 11:20:47 +0200 Subject: [PATCH 048/115] Update code-graph.tsx do not bring commit data --- app/components/code-graph.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 6d84b253..c70e9b18 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -175,6 +175,7 @@ export function CodeGraph({ const run = async () => { fetchCount() + /* const result = await fetch(`/api/repo/${graphName}/?type=commit`, { method: 'POST' }) @@ -196,6 +197,7 @@ export function CodeGraph({ setCurrentCommit(commitsArr[commitsArr.length - 1].hash) setCommitIndex(commitsArr.length) } + */ } run() @@ -513,4 +515,4 @@ export function CodeGraph({
) -} \ No newline at end of file +} From e20d811e748c2e148a190efe33a67842924307c6 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Sun, 17 Nov 2024 11:21:35 +0200 Subject: [PATCH 049/115] add tip dropdown --- app/page.tsx | 80 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index f1d222db..7c5a995b 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -3,15 +3,14 @@ import { useEffect, useRef, useState } from 'react'; import { Chat } from './components/chat'; import { Graph, Node } from './components/model'; -import { BookOpen, Github, HomeIcon } from 'lucide-react'; +import { BookOpen, Github, HomeIcon, X } from 'lucide-react'; import Link from 'next/link'; import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; import { CodeGraph } from './components/code-graph'; import { toast } from '@/components/ui/use-toast'; import { GraphContext } from './components/provider'; import Image from 'next/image'; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; -import { Progress } from '@/components/ui/progress'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuLabel, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; export type PathNode = { id?: number @@ -23,6 +22,40 @@ export type Path = { end?: PathNode } +type Tip = { + title: string + description: string + keyboardCommand: string +} + +const TIPS: Tip[] = [ + { + title: "Zoom In", + description: "Use this command to zoom into the graph.", + keyboardCommand: "Ctrl + Plus" + }, + { + title: "Zoom Out", + description: "Use this command to zoom out of the graph.", + keyboardCommand: "Ctrl + Minus" + }, + { + title: "Pan", + description: "Click and drag to pan around the graph.", + keyboardCommand: "Mouse Drag" + }, + { + title: "Select Node", + description: "Click on a node to select it.", + keyboardCommand: "Mouse Click" + }, + { + title: "Expand Node", + description: "Use this command to expand node.", + keyboardCommand: "Mouse Double Click" + } +] + export default function Home() { const [graph, setGraph] = useState(Graph.empty()); @@ -31,6 +64,7 @@ export default function Home() { const [isPathResponse, setIsPathResponse] = useState(false); const [createURL, setCreateURL] = useState("") const [createOpen, setCreateOpen] = useState(false) + const [tipOpen, setTipOpen] = useState(false) const [options, setOptions] = useState([]); const [path, setPath] = useState(); const [isSubmit, setIsSubmit] = useState(false); @@ -153,19 +187,45 @@ export default function Home() {