Skip to content

Commit 5bf503e

Browse files
authored
Merge pull request #389 from FalkorDB/mobile-version
Fix #375 Mobile version
2 parents 42fe6c3 + 344f98d commit 5bf503e

30 files changed

Lines changed: 2776 additions & 1983 deletions

app/components/Input.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { toast } from "@/components/ui/use-toast"
22
import { getCategoryColorName, getCategoryColorValue, Graph } from "./model"
33
import { useEffect, useRef, useState } from "react"
4-
import { PathNode } from "../page"
4+
import { PathNode } from "@/lib/utils"
55
import { cn } from "@/lib/utils"
66
import { prepareArg } from "../utils"
77

@@ -131,7 +131,7 @@ export default function Input({ onValueChange, handleSubmit, graph, icon, node,
131131

132132
return (
133133
<div
134-
className={cn("w-[20dvw] relative pointer-events-none rounded-md gap-4", parentClassName)}
134+
className={cn("w-full md:w-[20dvw] relative pointer-events-none rounded-md gap-4", parentClassName)}
135135
data-name='search-bar'
136136
>
137137
<input
@@ -142,6 +142,7 @@ export default function Input({ onValueChange, handleSubmit, graph, icon, node,
142142
}}
143143
onKeyDown={handleKeyDown}
144144
className={cn("w-full border p-2 rounded-md pointer-events-auto", className)}
145+
placeholder="Search for nodes in the graph"
145146
value={node?.name || ""}
146147
onChange={(e) => {
147148
const newVal = e.target.value
@@ -166,7 +167,7 @@ export default function Input({ onValueChange, handleSubmit, graph, icon, node,
166167
open &&
167168
<div
168169
ref={containerRef}
169-
className="z-10 w-full bg-white absolute flex flex-col pointer-events-auto border rounded-md max-h-[50dvh] overflow-y-auto overflow-x-hidden p-2 gap-2"
170+
className="z-10 w-full bg-white absolute flex flex-col pointer-events-auto border rounded-md md:max-h-[50dvh] h-[25dvh] overflow-y-auto overflow-x-hidden p-2 gap-2"
170171
data-name='search-bar-list'
171172
style={{
172173
top: inputHeight + 16

app/components/chat.tsx

Lines changed: 73 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,15 @@
11
import { toast } from "@/components/ui/use-toast";
2-
import { Dispatch, FormEvent, SetStateAction, useEffect, useRef, useState } from "react";
2+
import { Dispatch, FormEvent, MutableRefObject, SetStateAction, useEffect, useRef, useState } from "react";
33
import Image from "next/image";
4-
import { AlignLeft, ArrowDown, ArrowRight, ChevronDown, Lightbulb, Undo2 } from "lucide-react";
5-
import { Path } from "../page";
4+
import { AlignLeft, ArrowRight, ChevronDown, Lightbulb, Undo2 } from "lucide-react";
5+
import { Message, MessageTypes, Path, PathData } from "@/lib/utils";
66
import Input from "./Input";
7-
import { Graph, GraphData, Link } from "./model";
8-
import { cn } from "@/lib/utils";
7+
import { Graph, GraphData, Link, Node } from "./model";
8+
import { cn, GraphRef } from "@/lib/utils";
99
import { TypeAnimation } from "react-type-animation";
1010
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
1111
import { prepareArg } from "../utils";
12-
import { NodeObject, ForceGraphMethods } from "react-force-graph-2d";
13-
14-
type PathData = {
15-
nodes: any[]
16-
links: any[]
17-
}
18-
19-
enum MessageTypes {
20-
Query,
21-
Response,
22-
Path,
23-
PathResponse,
24-
Pending,
25-
Text,
26-
}
27-
28-
interface Message {
29-
type: MessageTypes;
30-
text?: string;
31-
paths?: { nodes: any[], links: any[] }[];
32-
graphName?: string;
33-
}
12+
import { ForceGraphMethods, NodeObject } from "react-force-graph-2d";
3413

3514
interface Props {
3615
repo: string
@@ -41,7 +20,16 @@ interface Props {
4120
isPathResponse: boolean | undefined
4221
setIsPathResponse: (isPathResponse: boolean | undefined) => void
4322
setData: Dispatch<SetStateAction<GraphData>>
44-
chartRef: React.MutableRefObject<ForceGraphMethods<Node, Link>>
23+
chartRef: GraphRef
24+
messages: Message[]
25+
setMessages: Dispatch<SetStateAction<Message[]>>
26+
query: string
27+
setQuery: Dispatch<SetStateAction<string>>
28+
selectedPath: PathData | undefined
29+
setSelectedPath: Dispatch<SetStateAction<PathData | undefined>>
30+
setChatOpen?: Dispatch<SetStateAction<boolean>>
31+
paths: PathData[]
32+
setPaths: Dispatch<SetStateAction<PathData[]>>
4533
}
4634

4735
const SUGGESTIONS = [
@@ -63,20 +51,7 @@ const RemoveLastPath = (messages: Message[]) => {
6351
return messages
6452
}
6553

66-
export function Chat({ repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setData, chartRef }: Props) {
67-
68-
// Holds the messages in the chat
69-
const [messages, setMessages] = useState<Message[]>([]);
70-
71-
// Holds the messages in the chat
72-
const [paths, setPaths] = useState<PathData[]>([]);
73-
74-
const [selectedPath, setSelectedPath] = useState<PathData>();
75-
76-
// Holds the user input while typing
77-
const [query, setQuery] = useState('');
78-
79-
const [tipOpen, setTipOpen] = useState(false);
54+
export function Chat({ messages, setMessages, query, setQuery, selectedPath, setSelectedPath, setChatOpen, repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setData, chartRef, paths, setPaths }: Props) {
8055

8156
const [sugOpen, setSugOpen] = useState(false);
8257

@@ -94,9 +69,14 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
9469

9570
// Scroll to the bottom of the chat on new message
9671
useEffect(() => {
97-
setTimeout(() => {
72+
if (messages.length === 0) return
73+
const timeout = setTimeout(() => {
9874
containerRef.current?.scrollTo(0, containerRef.current?.scrollHeight);
9975
}, 300)
76+
77+
return () => {
78+
clearTimeout(timeout)
79+
}
10080
}, [messages]);
10181

10282
useEffect(() => {
@@ -112,7 +92,7 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
11292

11393
const handleSetSelectedPath = (p: PathData) => {
11494
const chart = chartRef.current
115-
95+
11696
if (!chart) return
11797
setSelectedPath(prev => {
11898
if (prev) {
@@ -169,6 +149,7 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
169149
setTimeout(() => {
170150
chart.zoomToFit(1000, 150, (n: NodeObject<Node>) => p.nodes.some(node => node.id === n.id));
171151
}, 0)
152+
setChatOpen && setChatOpen(false)
172153
}
173154

174155
// A function that handles the change event of the url input box
@@ -233,6 +214,8 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
233214

234215
if (!path?.start?.id || !path.end?.id) return
235216

217+
setPath(undefined)
218+
236219
const result = await fetch(`/api/repo/${prepareArg(repo)}/${prepareArg(String(path.start.id))}/?targetId=${prepareArg(String(path.end.id))}`, {
237220
method: 'POST'
238221
})
@@ -261,21 +244,20 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
261244

262245
setPaths(formattedPaths)
263246
setMessages((prev) => [...RemoveLastPath(prev), { type: MessageTypes.PathResponse, paths: formattedPaths, graphName: graph.Id }]);
264-
setPath(undefined)
265247
setIsPathResponse(true)
266248
setData({ ...graph.Elements })
267249
setTimeout(() => {
268250
chart.zoomToFit(1000, 150, (n: NodeObject<Node>) => formattedPaths.some(p => p.nodes.some(node => node.id === n.id)));
269251
}, 0)
270252
}
271253

272-
const getTip = (disabled = false) =>
254+
const getTip = (className?: string) =>
273255
<>
274256
<button
275-
disabled={disabled}
276-
className="Tip"
257+
disabled={isSendMessage}
258+
className={cn("Tip", className)}
277259
onClick={() => {
278-
setTipOpen(false)
260+
setSugOpen(false)
279261
setMessages(prev => [
280262
...RemoveLastPath(prev),
281263
{ type: MessageTypes.Query, text: "Create a path" },
@@ -299,12 +281,24 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
299281
}, 4000)
300282
}}
301283
>
302-
<Lightbulb />
303-
<div>
304-
<h1 className="label">Show the path</h1>
305-
<p className="text">Fetch, update, batch, and navigate data efficiently</p>
306-
</div>
284+
<p className="text-center w-full">Show the path</p>
307285
</button>
286+
{
287+
SUGGESTIONS.map((s, i) => (
288+
<button
289+
disabled={isSendMessage}
290+
type="submit"
291+
key={i}
292+
className={cn("Tip", className)}
293+
onClick={() => {
294+
sendQuery(undefined, s)
295+
setSugOpen(false)
296+
}}
297+
>
298+
<p className="text-center w-full">{s}</p>
299+
</button>
300+
))
301+
}
308302
</>
309303

310304
const getMessage = (message: Message, index?: number) => {
@@ -390,10 +384,10 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
390384
}
391385

392386
if (selectedPath?.nodes.every(node => p?.nodes.some((n) => n.id === node.id)) && selectedPath.nodes.length === p.nodes.length) return
393-
387+
394388
if (!isPathResponse) {
395389
setIsPathResponse(undefined)
396-
390+
397391
}
398392
handleSetSelectedPath(p)
399393
}}
@@ -420,73 +414,41 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
420414
}
421415

422416
return (
423-
<div className="relative h-full flex flex-col justify-between px-6 pt-10 pb-4 gap-4">
417+
<div className="relative h-1 grow md:h-full flex flex-col justify-between px-6 pt-10 pb-4 gap-4">
424418
<main data-name="main-chat" ref={containerRef} className="grow flex flex-col overflow-y-auto gap-6 px-4">
425419
{
426420
messages.length === 0 &&
427421
<>
428-
<h1 className="font-oswald text-[20px] font-semibold leading-[32px] text-left text-[#13343B]">WELCOME TO OUR ASSISTANCE SERVICE</h1>
429-
<span className="text-base font-normal leading-5 text-left text-[#7D7D7D]">
430-
We can help you access and update only the needed
431-
data via paths, optimizing network requests with
432-
batching and catching for better performance.
433-
</span>
434-
{getTip()}
422+
<h1 className="text-center text-2xl">What would you like to analyze?</h1>
423+
<div className="flex flex-row flex-wrap gap-2 justify-center">
424+
{getTip()}
425+
</div>
435426
</>
436427
}
437428
{
438429
messages.map((message, index) => {
439430
return getMessage(message, index)
440431
})
441432
}
442-
{
443-
tipOpen &&
444-
<div ref={ref => ref?.focus()} className="bg-white absolute bottom-24 border rounded-md flex flex-col gap-3 p-2 overflow-y-auto" tabIndex={-1} onMouseDown={(e) => e.preventDefault()} onBlur={() => setTipOpen(false)}>
445-
{getTip(isSendMessage)}
446-
</div>
447-
}
448433
</main>
449-
<DropdownMenu open={sugOpen} onOpenChange={setSugOpen}>
450-
<footer>
451-
{
452-
repo &&
453-
<div className="flex gap-4 px-4">
454-
<button data-name="lightbulb" onClick={() => setTipOpen(true)} className="p-4 border rounded-md hover:border-[#FF66B3] hover:bg-[#FFF0F7]">
455-
<Lightbulb />
456-
</button>
457-
<form className="grow flex items-center border rounded-md px-2" onSubmit={sendQuery}>
458-
<DropdownMenuTrigger asChild>
459-
<button data-name="questionOptionsMenu" className="bg-gray-200 p-2 rounded-md hover:bg-gray-300">
460-
<ArrowDown color="white" />
461-
</button>
462-
</DropdownMenuTrigger>
463-
<input className="grow p-4 rounded-md focus-visible:outline-none" placeholder="Ask your question" onChange={handleQueryInputChange} value={query} />
464-
<button disabled={isSendMessage} className={`bg-gray-200 p-2 rounded-md ${!isSendMessage && 'hover:bg-gray-300'}`}>
465-
<ArrowRight color="white" />
466-
</button>
467-
</form>
468-
</div>
469-
}
470-
</footer>
471-
<DropdownMenuContent className="flex flex-col mb-4 w-[20dvw]" side="top">
472-
{
473-
SUGGESTIONS.map((s, i) => (
474-
<button
475-
disabled={isSendMessage}
476-
type="submit"
477-
key={i}
478-
className="p-2 text-left hover:bg-gray-200"
479-
onClick={() => {
480-
sendQuery(undefined, s)
481-
setSugOpen(false)
482-
}}
483-
>
484-
{s}
485-
</button>
486-
))
487-
}
488-
</DropdownMenuContent>
489-
</DropdownMenu>
434+
<footer className="flex gap-4 px-4 overflow-hidden min-h-fit">
435+
<DropdownMenu open={sugOpen} onOpenChange={setSugOpen}>
436+
<DropdownMenuTrigger asChild>
437+
<button data-name="lightbulb" className="p-4 border rounded-md hover:border-[#FF66B3] hover:bg-[#FFF0F7]">
438+
<Lightbulb />
439+
</button>
440+
</DropdownMenuTrigger>
441+
<DropdownMenuContent align="start" className="flex flex-col gap-2 mb-4 w-[81.51dvw] md:w-[20dvw] overflow-y-auto" side="top">
442+
{getTip("!w-full")}
443+
</DropdownMenuContent>
444+
</DropdownMenu>
445+
<form className="grow flex items-center border rounded-md px-2" onSubmit={sendQuery}>
446+
<input className="w-1 grow p-4 rounded-md focus-visible:outline-none" placeholder="Ask your question" onChange={handleQueryInputChange} value={query} />
447+
<button disabled={isSendMessage} className={`bg-gray-200 p-2 rounded-md ${!isSendMessage && 'hover:bg-gray-300'}`}>
448+
<ArrowRight color="white" />
449+
</button>
450+
</form>
451+
</footer>
490452
</div>
491453
);
492454
}

0 commit comments

Comments
 (0)